//RotatorTranslatorToSuper superimposes the set of cartesian coordinates given as the rows of the matrix test on the gnOnes of the rows //of the matrix templa. Returns the transformed matrix, the rotation matrix, 2 translation row vectors //For the superposition plus an error. In order to perform the superposition, without using the transformed //the first translation vector has to be added first to the moving matrix, then the rotation must be performed //and finally the second translation has to be added. //This is a low level function, although one can use it directly since it returns the transformed matrix. //The math for this function is by Prof. Veronica Jimenez-Curihual, University of Concepcion, Chile. func RotatorTranslatorToSuper(test, templa *v3.Matrix) (*v3.Matrix, *v3.Matrix, *v3.Matrix, *v3.Matrix, error) { tmr, tmc := templa.Dims() tsr, tsc := test.Dims() if tmr != tsr || tmc != 3 || tsc != 3 { return nil, nil, nil, nil, CError{"goChem: Ill-formed matrices", []string{"RotatorTranslatorToSuper"}} } var Scal float64 Scal = float64(1.0) / float64(tmr) j := gnOnes(tmr, 1) //Mass is not important for this matter so we'll just use this. ctest, distest, err := MassCenter(test, test, j) if err != nil { return nil, nil, nil, nil, errDecorate(err, "RotatorTranslatorToSuper") } ctempla, distempla, err := MassCenter(templa, templa, j) if err != nil { return nil, nil, nil, nil, errDecorate(err, "RotatorTranslatorToSuper") } Mid := gnEye(tmr) jT := gnT(j) ScaledjProd := gnMul(j, jT) ScaledjProd.Scale(Scal, ScaledjProd) aux2 := gnMul(gnT(ctempla), Mid) r, _ := aux2.Dims() Maux := v3.Zeros(r) Maux.Mul(aux2, ctest) Maux.Tr() //Dont understand why this is needed factors := mat64.SVD(v3.Matrix2Dense(Maux), appzero, math.SmallestNonzeroFloat64, true, true) U := factors.U V := factors.V // if err != nil { // return nil, nil, nil, nil, err //I'm not sure what err is this one // } U.Scale(-1, U) V.Scale(-1, V) //SVD gives different results here than in numpy. U and V are multiplide by -1 in one of them //and gomatrix gives as V the transpose of the matrix given as V by numpy. I guess is not an //error, but don't know for sure. vtr, _ := V.Dims() Rotation := v3.Zeros(vtr) Rotation.Mul(V, gnT(U)) Rotation.Tr() //Don't know why does this work :( RightHand := gnEye(3) if det(Rotation) < 0 { RightHand.Set(2, 2, -1) Rotation.Mul(V, RightHand) Rotation.Mul(Rotation, gnT(U)) //If I get this to work Ill arrange so gnT(U) is calculated once, not twice as now. Rotation.Tr() //TransposeTMP contains the transpose of the original Rotation //Same, no ide why I need this //return nil, nil, nil, nil, fmt.Errorf("Got a reflection instead of a translations. The objects may be specular images of each others") } jT.Scale(Scal, jT) subtempla := v3.Zeros(tmr) subtempla.Copy(ctempla) sub := v3.Zeros(ctest.NVecs()) sub.Mul(ctest, Rotation) subtempla.Sub(subtempla, sub) jtr, _ := jT.Dims() Translation := v3.Zeros(jtr) Translation.Mul(jT, subtempla) Translation.Add(Translation, distempla) //This alings the transformed with the original template, not the mean centrate one transformed := v3.Zeros(ctest.NVecs()) transformed.Mul(ctest, Rotation) transformed.AddVec(transformed, Translation) //end transformed distest.Scale(-1, distest) return transformed, Rotation, distest, Translation, nil }
//Next Reads the next frame in a XTCObj that has been initialized for read //With initread. If keep is true, returns a pointer to matrix.DenseMatrix //With the coordinates read, otherwiser, it discards the coordinates and //returns nil. func (X *XTCObj) Next(output *v3.Matrix) error { if !X.Readable() { return Error{TrajUnIni, X.filename, []string{"Next"}, true} } cnatoms := C.int(X.natoms) worked := C.get_coords(X.fp, &X.cCoords[0], cnatoms) if worked == 11 { X.readable = false return newlastFrameError(X.filename, "Next") //This is not really an error and should be catched in the calling function } if worked != 0 { X.readable = false return Error{ReadError, X.filename, []string{"Next"}, true} } if output != nil { //col the frame r, c := output.Dims() if r < (X.natoms) { panic("Buffer v3.Matrix too small to hold trajectory frame") } for j := 0; j < r; j++ { for k := 0; k < c; k++ { l := k + (3 * j) output.Set(j, k, (10 * float64(X.cCoords[l]))) //nm to Angstroms } } return nil } return nil //Just drop the frame }
//BestPlane returns a row vector that is normal to the plane that best contains the molecule //if passed a nil Masser, it will simply set all masses to 1. func BestPlane(coords *v3.Matrix, mol Masser) (*v3.Matrix, error) { var err error var Mmass []float64 cr, _ := coords.Dims() if mol != nil { Mmass, err = mol.Masses() if err != nil { return nil, errDecorate(err, "BestPlane") } if len(Mmass) != cr { return nil, CError{fmt.Sprintf("Inconsistent coordinates(%d)/atoms(%d)", len(Mmass), cr), []string{"BestPlane"}} } } moment, err := MomentTensor(coords, Mmass) if err != nil { return nil, errDecorate(err, "BestPlane") } evecs, _, err := v3.EigenWrap(moment, appzero) if err != nil { return nil, errDecorate(err, "BestPlane") } normal, err := BestPlaneP(evecs) if err != nil { return nil, errDecorate(err, "BestPlane") } //MomentTensor(, mass) return normal, err }
//MomentTensor returns the moment tensor for a matrix A of coordinates and a column //vector mass with the respective massess. func MomentTensor(A *v3.Matrix, massslice []float64) (*v3.Matrix, error) { ar, ac := A.Dims() var err error var mass *mat64.Dense if massslice == nil { mass = gnOnes(ar, 1) } else { mass = mat64.NewDense(ar, 1, massslice) // if err != nil { // return nil, err // } } center, _, err := MassCenter(A, v3.Dense2Matrix(gnCopy(A)), mass) if err != nil { return nil, errDecorate(err, "MomentTensor") } sqrmass := gnZeros(ar, 1) // sqrmass.Pow(mass,0.5) pow(mass, sqrmass, 0.5) //the result is stored in sqrmass // fmt.Println(center,sqrmass) //////////////////////// center.ScaleByCol(center, sqrmass) // fmt.Println(center,"scaled center") centerT := gnZeros(ac, ar) centerT.Copy(center.T()) moment := gnMul(centerT, center) return v3.Dense2Matrix(moment), nil }
//MassCenter centers in in the center of mass of oref. Mass must be //A column vector. Returns the centered matrix and the displacement matrix. func MassCenter(in, oref *v3.Matrix, mass *mat64.Dense) (*v3.Matrix, *v3.Matrix, error) { or, _ := oref.Dims() ir, _ := in.Dims() if mass == nil { //just obtain the geometric center tmp := ones(or) mass = mat64.NewDense(or, 1, tmp) //gnOnes(or, 1) } ref := v3.Zeros(or) ref.Copy(oref) gnOnesvector := gnOnes(1, or) f := func() { ref.ScaleByCol(ref, mass) } if err := gnMaybe(gnPanicker(f)); err != nil { return nil, nil, CError{err.Error(), []string{"v3.Matrix.ScaleByCol", "MassCenter"}} } ref2 := v3.Zeros(1) g := func() { ref2.Mul(gnOnesvector, ref) } if err := gnMaybe(gnPanicker(g)); err != nil { return nil, nil, CError{err.Error(), []string{"v3.gOnesVector", "MassCenter"}} } ref2.Scale(1.0/mass.Sum(), ref2) returned := v3.Zeros(ir) returned.Copy(in) returned.SubVec(returned, ref2) /* for i := 0; i < ir; i++ { if err := returned.GetRowVector(i).Subtract(ref2); err != nil { return nil, nil, err } } */ return returned, ref2, nil }
//PDBStringWrite writes a string in PDB format for a given reference, coordinate set and bfactor set, which must match each other //returns the written string and error or nil. func PDBStringWrite(coords *v3.Matrix, mol Atomer, bfact []float64) (string, error) { if bfact == nil { bfact = make([]float64, mol.Len()) } cr, _ := coords.Dims() br := len(bfact) if cr != mol.Len() || cr != br { return "", CError{"Ref and Coords and/or Bfactors dont have the same number of atoms", []string{"PDBStringWrite"}} } chainprev := mol.Atom(0).Chain //this is to know when the chain changes. var outline string var outstring string var err error for i := 0; i < mol.Len(); i++ { // r,c:=coords.Dims() // fmt.Println("IIIIIIIIIIIi", i,coords,r,c, "lllllll") writecoord := coords.VecView(i) outline, chainprev, err = writePDBLine(mol.Atom(i), writecoord, bfact[i], chainprev) if err != nil { return "", errDecorate(err, "PDBStringWrite "+fmt.Sprintf("Could not print PDB line: %d", i)) } outstring = strings.Join([]string{outstring, outline}, "") } outstring = strings.Join([]string{outstring, "END\n"}, "") return outstring, nil }
func pdbWrite(out io.Writer, coords *v3.Matrix, mol Atomer, bfact []float64) error { if bfact == nil { bfact = make([]float64, mol.Len()) } cr, _ := coords.Dims() br := len(bfact) if cr != mol.Len() || cr != br { return CError{"Ref and Coords and/or Bfactors dont have the same number of atoms", []string{"pdbWrite"}} } chainprev := mol.Atom(0).Chain //this is to know when the chain changes. var outline string var err error iowriteError := func(err error) error { return CError{"Failed to write in io.Writer" + err.Error(), []string{"io.Write.Write", "pdbWrite"}} } for i := 0; i < mol.Len(); i++ { // r,c:=coords.Dims() // fmt.Println("IIIIIIIIIIIi", i,coords,r,c, "lllllll") writecoord := coords.VecView(i) outline, chainprev, err = writePDBLine(mol.Atom(i), writecoord, bfact[i], chainprev) if err != nil { return errDecorate(err, "pdbWrite "+fmt.Sprintf("Could not print PDB line: %d", i)) } _, err := out.Write([]byte(outline)) if err != nil { return iowriteError(err) } } _, err = out.Write([]byte("END")) //no newline, this is in case the write is part of a PDB and one needs to write "ENDMODEL". if err != nil { return iowriteError(err) } return nil }
//ScaleBond takes a C-H bond and moves the H (in place) so the distance between them is the one given (bond). //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. func ScaleBond(C, H *v3.Matrix, bond float64) { Odist := v3.Zeros(1) Odist.Sub(H, C) distance := Odist.Norm(0) Odist.Scale((distance-bond)/distance, Odist) H.Sub(H, Odist) }
//Projection returns the projection of test in ref. func Projection(test, ref *v3.Matrix) *v3.Matrix { rr, _ := ref.Dims() Uref := v3.Zeros(rr) Uref.Unit(ref) scalar := test.Dot(Uref) //math.Abs(la)*math.Cos(angle) Uref.Scale(scalar, Uref) return Uref }
//BestPlaneP takes sorted evecs, according to the eval,s and returns a row vector that is normal to the //Plane that best contains the molecule. Notice that the function can't possibly check //that the vectors are sorted. The P at the end of the name is for Performance. If //That is not an issue it is safer to use the BestPlane function that wraps this one. func BestPlaneP(evecs *v3.Matrix) (*v3.Matrix, error) { evr, evc := evecs.Dims() if evr != 3 || evc != 3 { return evecs, CError{"goChem: Eigenvectors matrix must be 3x3", []string{"BestPlaneP"}} //maybe this should be a panic } v1 := evecs.VecView(2) v2 := evecs.VecView(1) normal := v3.Zeros(1) normal.Cross(v1, v2) return normal, nil }
func EncodeCoords(coords *v3.Matrix, enc *json.Encoder) *Error { c := new(Coords) t := make([]float64, 3, 3) for i := 0; i < coords.NVecs(); i++ { c.Coords = coords.Row(t, i) if err := enc.Encode(c); err != nil { return NewError("postprocess", "chemjson.EncodeCoords", err) } } return nil }
//SelCone, Given a set of cartesian points in sellist, obtains a vector "plane" normal to the best plane passing through the points. //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, //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. //If one starts from one given point, 2 cgnOnes, one in each direction, are possible. If whatcone is 0, both cgnOnes are considered. //if whatcone<0, only the cone opposite to the plane vector direction. If whatcone>0, only the cone in the plane vector direction. //the 'initial' argument allows the construction of a truncate cone with a radius of initial. func SelCone(B, selection *v3.Matrix, angle, distance, thickness, initial float64, whatcone int) []int { A := v3.Zeros(B.NVecs()) A.Copy(B) //We will be altering the input so its better to work with a copy. ar, _ := A.Dims() selected := make([]int, 0, 3) neverselected := make([]int, 0, 30000) //waters that are too far to ever be selected nevercutoff := distance / math.Cos(angle) //cutoff to be added to neverselected A, _, err := MassCenter(A, selection, nil) //Centrate A in the geometric center of the selection, Its easier for the following calculations if err != nil { panic(PanicMsg(err.Error())) } selection, _, _ = MassCenter(selection, selection, nil) //Centrate the selection as well plane, err := BestPlane(selection, nil) //I have NO idea which direction will this vector point. We might need its negative. if err != nil { panic(PanicMsg(err.Error())) } for i := thickness / 2; i <= distance; i += thickness { maxdist := math.Tan(angle)*i + initial //this should give me the radius of the cone at this point for j := 0; j < ar; j++ { if isInInt(selected, j) || isInInt(neverselected, j) { //we dont scan things that we have already selected, or are too far continue } atom := A.VecView(j) proj := Projection(atom, plane) norm := proj.Norm(0) //Now at what side of the plane is the atom? angle := Angle(atom, plane) if whatcone > 0 { if angle > math.Pi/2 { continue } } else if whatcone < 0 { if angle < math.Pi/2 { continue } } if norm > i+(thickness/2.0) || norm < (i-thickness/2.0) { continue } proj.Sub(proj, atom) projnorm := proj.Norm(0) if projnorm <= maxdist { selected = append(selected, j) } if projnorm >= nevercutoff { neverselected = append(neverselected, j) } } } return selected }
//ScaleBonds scales all bonds between atoms in the same residue with names n1, n2 to a final lenght finallengt, by moving the atoms n2. //the operation is executed in place. func ScaleBonds(coords *v3.Matrix, mol Atomer, n1, n2 string, finallenght float64) { for i := 0; i < mol.Len(); i++ { c1 := mol.Atom(i) if c1.Name != n1 { continue } for j := 0; j < mol.Len(); j++ { c2 := mol.Atom(j) if c1.MolID == c2.MolID && c1.Name == n1 && c2.Name == n2 { A := coords.VecView(i) B := coords.VecView(j) ScaleBond(A, B, finallenght) } } } }
//XYZStringWrite writes the mol Ref and the Coord coordinates in an XYZ-formatted string. func XYZStringWrite(Coords *v3.Matrix, mol Atomer) (string, error) { var out string if mol.Len() != Coords.NVecs() { return "", CError{"Ref and Coords dont have the same number of atoms", []string{"XYZStringWrite"}} } c := make([]float64, 3, 3) out = fmt.Sprintf("%-4d\n\n", mol.Len()) //towrite := Coords.Arrays() //An array of array with the data in the matrix for i := 0; i < mol.Len(); i++ { //c := towrite[i] //coordinates for the corresponding atoms c = Coords.Row(c, i) temp := fmt.Sprintf("%-2s %12.6f%12.6f%12.6f \n", mol.Atom(i).Symbol, c[0], c[1], c[2]) out = strings.Join([]string{out, temp}, "") } return out, nil }
//CenterOfMass returns the center of mass the atoms represented by the coordinates in geometry //and the masses in mass, and an error. If mass is nil, it calculates the geometric center func CenterOfMass(geometry *v3.Matrix, mass *mat64.Dense) (*v3.Matrix, error) { if geometry == nil { return nil, CError{"goChem: nil matrix to get the center of mass", []string{"CenterOfMass"}} } gr, _ := geometry.Dims() if mass == nil { //just obtain the geometric center tmp := ones(gr) mass = mat64.NewDense(gr, 1, tmp) //gnOnes(gr, 1) } tmp2 := ones(gr) gnOnesvector := mat64.NewDense(1, gr, tmp2) //gnOnes(1, gr) ref := v3.Zeros(gr) ref.ScaleByCol(geometry, mass) ref2 := v3.Zeros(1) ref2.Mul(gnOnesvector, ref) ref2.Scale(1.0/mass.Sum(), ref2) return ref2, nil }
//Super determines the best rotation and translations to superimpose the coords in test //listed in testlst on te atoms of molecule templa, frame frametempla, listed in templalst. //It applies those rotation and translations to the whole frame frametest of molecule test, in palce. //testlst and templalst must have the same number of elements. If any of the two slices, or both, are //nil or have a zero lenght, they will be replaced by a list containing the number of all atoms in the //corresponding molecule. func Super(test, templa *v3.Matrix, testlst, templalst []int) (*v3.Matrix, error) { //_, testcols := test.Dims() //_, templacols := templa.Dims() structs := []*v3.Matrix{test, templa} lists := [][]int{testlst, templalst} //In case one or both lists are nil or have lenght zero. for k, v := range lists { if v == nil || len(v) == 0 { lists[k] = make([]int, structs[k].NVecs(), structs[k].NVecs()) for k2, _ := range lists[k] { lists[k][k2] = k2 } } } //fmt.Println(lists[0]) if len(lists[0]) != len(lists[1]) { return nil, CError{fmt.Sprintf("Mismatched template and test atom numbers: %d, %d", len(lists[1]), len(lists[0])), []string{"Super"}} } ctest := v3.Zeros(len(lists[0])) ctest.SomeVecs(test, lists[0]) ctempla := v3.Zeros(len(lists[1])) ctempla.SomeVecs(templa, lists[1]) _, rotation, trans1, trans2, err1 := RotatorTranslatorToSuper(ctest, ctempla) if err1 != nil { return nil, errDecorate(err1, "Super") } test.AddVec(test, trans1) // fmt.Println("test1",test, rotation) /////////////77 test.Mul(test, rotation) // fmt.Println("test2",test) /////////// test.AddVec(test, trans2) // fmt.Println("test3",test) /////// return test, nil }
//writePDBLine writes a line in PDB format from the data passed as a parameters. It takes the chain of the previous atom //and returns the written line, the chain of the just-written atom, and error or nil. func writePDBLine(atom *Atom, coord *v3.Matrix, bfact float64, chainprev string) (string, string, error) { var ter string var out string if atom.Chain != chainprev { ter = fmt.Sprint(out, "TER\n") chainprev = atom.Chain } first := "ATOM" if atom.Het { first = "HETATM" } formatstring := "%-6s%5d %-3s %-4s%1s%4d %8.3f%8.3f%8.3f%6.2f%6.2f %2s \n" //4 chars for the atom name are used when hydrogens are included. //This has not been tested if len(atom.Name) == 4 { formatstring = strings.Replace(formatstring, "%-3s ", "%-4s", 1) } else if len(atom.Name) > 4 { return "", chainprev, CError{"Cant print PDB line", []string{"writePDBLine"}} } //"%-6s%5d %-3s %3s %1c%4d %8.3f%8.3f%8.3f%6.2f%6.2f %2s \n" out = fmt.Sprintf(formatstring, first, atom.ID, atom.Name, atom.Molname, atom.Chain, atom.MolID, coord.At(0, 0), coord.At(0, 1), coord.At(0, 2), atom.Occupancy, bfact, atom.Symbol) out = strings.Join([]string{ter, out}, "") return out, chainprev, nil }
//EulerRotateAbout uses Euler angles to rotate the coordinates in coordsorig around by angle //radians around the axis given by the vector axis. It returns the rotated coordsorig, //since the original is not affected. It seems more clunky than the RotateAbout, which uses Clifford algebra. //I leave it for benchmark, mostly, and might remove it later. There is no test for this function! func EulerRotateAbout(coordsorig, ax1, ax2 *v3.Matrix, angle float64) (*v3.Matrix, error) { r, _ := coordsorig.Dims() coords := v3.Zeros(r) translation := v3.Zeros(ax1.NVecs()) translation.Copy(ax1) axis := v3.Zeros(ax2.NVecs()) axis.Sub(ax2, ax1) //now it became the rotation axis f := func() { coords.SubVec(coordsorig, translation) } if err := gnMaybe(gnPanicker(f)); err != nil { return nil, CError{err.Error(), []string{"v3.Matrix.Subvec", "EulerRotateAbout"}} } Zswitch := RotatorToNewZ(axis) coords.Mul(coords, Zswitch) //rotated Zrot, err := RotatorAroundZ(angle) if err != nil { return nil, errDecorate(err, "EulerRotateAbout") } // Zsr, _ := Zswitch.Dims() // RevZ := v3.Zeros(Zsr) RevZ, err := gnInverse(Zswitch) if err != nil { return nil, errDecorate(err, "EulerRotateAbout") } coords.Mul(coords, Zrot) //rotated coords.Mul(coords, RevZ) coords.AddVec(coords, translation) return coords, nil }
//XYZStringWrite writes the mol Ref and the Coord coordinates in an XYZ-formatted string. func XYZWrite(out io.Writer, Coords *v3.Matrix, mol Atomer) error { iowriterError := func(err error) error { return CError{"Failed to write in io.Writer" + err.Error(), []string{"io.Writer.Write", "XYZWrite"}} } if mol.Len() != Coords.NVecs() { return CError{"Ref and Coords dont have the same number of atoms", []string{"XYZWrite"}} } c := make([]float64, 3, 3) _, err := out.Write([]byte(fmt.Sprintf("%-4d\n\n", mol.Len()))) if err != nil { return iowriterError(err) } //towrite := Coords.Arrays() //An array of array with the data in the matrix for i := 0; i < mol.Len(); i++ { //c := towrite[i] //coordinates for the corresponding atoms c = Coords.Row(c, i) temp := fmt.Sprintf("%-2s %12.6f%12.6f%12.6f \n", mol.Atom(i).Symbol, c[0], c[1], c[2]) _, err := out.Write([]byte(temp)) if err != nil { return iowriterError(err) } } return nil }
//AntiProjection returns a vector in the direction of ref with the magnitude of //a vector A would have if |test| was the magnitude of its projection //in the direction of test. func AntiProjection(test, ref *v3.Matrix) *v3.Matrix { rr, _ := ref.Dims() testnorm := test.Norm(0) Uref := v3.Zeros(rr) Uref.Unit(ref) scalar := test.Dot(Uref) scalar = (testnorm * testnorm) / scalar Uref.Scale(scalar, Uref) return Uref }
//Angle takes 2 vectors and calculate the angle in radians between them //It does not check for correctness or return errors! func Angle(v1, v2 *v3.Matrix) float64 { normproduct := v1.Norm(0) * v2.Norm(0) dotprod := v1.Dot(v2) argument := dotprod / normproduct //Take care of floating point math errors if math.Abs(argument-1) <= appzero { argument = 1 } else if math.Abs(argument+1) <= appzero { argument = -1 } //fmt.Println(dotprod/normproduct,argument) //dotprod/normproduct, dotprod, normproduct,v1.TwoNorm(),v2.TwoNorm()) angle := math.Acos(argument) if math.Abs(angle) <= appzero { return 0.00 } return angle }
//RMSD returns the RSMD (root of the mean square deviation) for the sets of cartesian //coordinates in test and template. func RMSD(test, template *v3.Matrix) (float64, error) { //This is a VERY naive implementation. tmr, tmc := template.Dims() tsr, tsc := test.Dims() if tmr != tsr || tmc != 3 || tsc != 3 { return 0, fmt.Errorf("Ill formed matrices for RMSD calculation") } tr := tmr ctempla := v3.Zeros(template.NVecs()) ctempla.Copy(template) //the maybe thing might not be needed since we check the dimensions before. f := func() { ctempla.Sub(ctempla, test) } if err := gnMaybe(gnPanicker(f)); err != nil { return 0, CError{err.Error(), []string{"v3.Matrix.Sub", "RMSD"}} } var RMSD float64 for i := 0; i < template.NVecs(); i++ { temp := ctempla.VecView(i) RMSD += math.Pow(temp.Norm(0), 2) } RMSD = RMSD / float64(tr) RMSD = math.Sqrt(RMSD) return RMSD, nil }
//RotatorToNewY takes a set of coordinates (mol) and a vector (y). It returns //a rotation matrix that, when applied to mol, will rotate it around the Z axis //in such a way that the projection of newy in the XY plane will be aligned with //the Y axis. func RotatorAroundZToNewY(newy *v3.Matrix) (*v3.Matrix, error) { nr, nc := newy.Dims() if nc != 3 || nr != 1 { return nil, CError{"Wrong newy vector", []string{"RotatorAroundZtoNewY"}} } if nc != 3 { return nil, CError{"Wrong mol vector", []string{"RotatorAroundZtoNewY"}} //this one doesn't seem reachable } gamma := math.Atan2(newy.At(0, 0), newy.At(0, 1)) singamma := math.Sin(gamma) cosgamma := math.Cos(gamma) operator := []float64{cosgamma, singamma, 0, -singamma, cosgamma, 0, 0, 0, 1} return v3.NewMatrix(operator) }
//RotateAbout about rotates the coordinates in coordsorig around by angle radians around the axis //given by the vector axis. It returns the rotated coordsorig, since the original is not affected. //Uses Clifford algebra. func RotateAbout(coordsorig, ax1, ax2 *v3.Matrix, angle float64) (*v3.Matrix, error) { coordsLen := coordsorig.NVecs() coords := v3.Zeros(coordsLen) translation := v3.Zeros(ax1.NVecs()) translation.Copy(ax1) axis := v3.Zeros(ax2.NVecs()) axis.Sub(ax2, ax1) // the rotation axis f := func() { coords.SubVec(coordsorig, translation) } if err := gnMaybe(gnPanicker(f)); err != nil { return nil, CError{err.Error(), []string{"v3.Matrix.SubVec", "RotateAbout"}} } Rot := v3.Zeros(coordsLen) Rot = Rotate(coords, Rot, axis, angle) g := func() { Rot.AddVec(Rot, translation) } if err := gnMaybe(gnPanicker(g)); err != nil { return nil, CError{err.Error(), []string{"v3.Matrix.AddVec", "RotateAbout"}} } return Rot, nil }
// RamaCalc Obtains the values for the phi and psi dihedrals indicated in []Ramaset, for the // structure M. It returns a slice of 2-element slices, one for the phi the next for the psi // dihedral, a and an error or nil. func RamaCalc(M *v3.Matrix, dihedrals []RamaSet) ([][]float64, error) { if M == nil || dihedrals == nil { return nil, Error{ErrNilData, "", "RamaCalc", "", true} } r, _ := M.Dims() Rama := make([][]float64, 0, len(dihedrals)) for _, j := range dihedrals { if j.Npost >= r { return nil, Error{ErrOutOfRange, "", "RamaCalc", "", true} } Cprev := M.VecView(j.Cprev) N := M.VecView(j.N) Ca := M.VecView(j.Ca) C := M.VecView(j.C) Npost := M.VecView(j.Npost) phi := chem.Dihedral(Cprev, N, Ca, C) psi := chem.Dihedral(N, Ca, C, Npost) temp := []float64{phi * (180 / math.Pi), psi * (180 / math.Pi)} Rama = append(Rama, temp) } return Rama, nil }
func main() { //This is the part that collects all the data from PyMOL, with all the proper error checking. stdin := bufio.NewReader(os.Stdin) options, err := chemjson.DecodeOptions(stdin) if err != nil { fmt.Fprint(os.Stderr, err.Marshal()) log.Fatal(err) } mainName := options.SelNames[0] if len(options.AtomsPerSel) > 1 { for _, v := range options.SelNames[1:] { mainName = mainName + "__" + v //inefficient but there should never be THAT many selections. } } dielectric := options.FloatOptions[0][0] charge := options.IntOptions[0][0] multi := options.IntOptions[0][1] qmprogram := options.StringOptions[0][0] method := options.StringOptions[0][1] calctype := options.StringOptions[0][2] var osidemol *chem.Topology var osidecoords, sidecoords *v3.Matrix var sidelist, sidefrozen []int selindex := 0 total := 0 selections := len(options.AtomsPerSel) if options.BoolOptions[0][0] { //sidechain selections exist sidecoords, osidecoords, osidemol, sidelist, sidefrozen = SideChains(stdin, options) selections-- total += osidemol.Len() selindex++ } fmt.Fprint(os.Stderr, selections) obbmol := make([]*chem.Topology, selections, selections) obbcoords := make([]*v3.Matrix, selections, selections) bbcoords := make([]*v3.Matrix, selections, selections) bblist := make([][]int, selections, selections) bbfrozen := make([][]int, selections, selections) for i := 0; i < selections; i++ { bbcoords[i], obbcoords[i], obbmol[i], bblist[i], bbfrozen[i] = BackBone(stdin, options, selindex) total += obbmol[i].Len() selindex++ fmt.Fprint(os.Stderr, "chetumanga") } //Now we put the juit together bigC := v3.Zeros(total) bigA := chem.NewTopology([]*chem.Atom{}, 0, 0) bigFroz := make([]int, 0, total) setoffset := 0 if options.BoolOptions[0][0] { bigC.SetMatrix(0, 0, osidecoords) setoffset += osidecoords.NVecs() bigA = chem.MergeAtomers(bigA, osidemol) // bigA = osidemol bigFroz = append(bigFroz, sidefrozen...) } for k, v := range obbcoords { bigC.SetMatrix(setoffset, 0, v) bigA = chem.MergeAtomers(bigA, obbmol[k]) tmpfroz := SliceOffset(bbfrozen[k], setoffset) bigFroz = append(bigFroz, tmpfroz...) setoffset += v.NVecs() } bigA.SetCharge(charge) bigA.SetMulti(multi) chem.PDBFileWrite(mainName+"toOPT.pdb", bigC, bigA, nil) ///////////////////////////////////// chem.XYZFileWrite(mainName+"toOPT.xyz", bigC, bigA) ///////////////////////////////////// //Ok, we have now one big matrix and one big atom set, now the optimization calc := new(qm.Calc) if calctype == "Optimization" { calc.Optimize = true } calc.RI = true //some options, including this one, are meaningless for MOPAC calc.CConstraints = bigFroz calc.Dielectric = dielectric calc.SCFTightness = 1 calc.Dispersion = "D3" calc.Method = "TPSS" if method == "Cheap" { calc.BSSE = "gcp" if qmprogram == "ORCA" { calc.Method = "HF-3c" calc.RI = false } else if qmprogram == "MOPAC2012" { calc.Method = "PM6-D3H4 NOMM MOZYME" } else { calc.Basis = "def2-SVP" } } else { calc.Basis = "def2-TZVP" } //We will use the default methods and basis sets of each program. In the case of MOPAC, that is currently PM6-D3H4. var QM qm.Handle switch qmprogram { case "ORCA": orca := qm.NewOrcaHandle() orca.SetnCPU(runtime.NumCPU()) QM = qm.Handle(orca) case "TURBOMOLE": QM = qm.Handle(qm.NewTMHandle()) case "NWCHEM": QM = qm.Handle(qm.NewNWChemHandle()) calc.SCFConvHelp = 1 default: QM = qm.Handle(qm.NewMopacHandle()) } QM.SetName(mainName) QM.BuildInput(bigC, bigA, calc) fmt.Fprint(os.Stderr, options.BoolOptions) if options.BoolOptions[0][2] { return //Dry run } if err2 := QM.Run(true); err != nil { log.Fatal(err2.Error()) } //Now we ran the calculation, we must retrive the geometry and divide the coordinates among the original selections. var newBigC *v3.Matrix info := new(chemjson.Info) //Contains the return info var err2 error if calc.Optimize { newBigC, err2 = QM.OptimizedGeometry(bigA) if err2 != nil { log.Fatal(err2.Error()) } if qmprogram == "NWCHEM" { //NWchem translates/rotates the system before optimizing so we need to superimpose with the original geometry in order for them to match. newBigC, err2 = chem.Super(newBigC, bigC, bigFroz, bigFroz) if err2 != nil { log.Fatal(err2.Error()) } } info.Molecules = len(options.AtomsPerSel) geooffset := 0 if options.BoolOptions[0][0] { tmp := newBigC.View(geooffset, 0, len(sidelist), 3) //This is likely to change when we agree on a change for the gonum API!!!! sidecoords.SetVecs(tmp, sidelist) info.FramesPerMolecule = []int{1} info.AtomsPerMolecule = []int{sidecoords.NVecs()} //I DO NOT understand why the next line is += len(sidelist)-1 instead of len(sidelist), but it works. If a bug appears //take a look at this line, and the equivalent in the for loop that follows. geooffset += (len(sidelist) - 1) } for k, v := range bbcoords { //Take a look here in case of bugs. tmp := newBigC.View(geooffset, 0, len(bblist[k]), 3) //This is likely to change when we agree on a change for the gonum API!!!! v.SetVecs(tmp, bblist[k]) info.FramesPerMolecule = append(info.FramesPerMolecule, 1) info.AtomsPerMolecule = append(info.AtomsPerMolecule, v.NVecs()) geooffset += (len(bblist[k]) - 1) } // for k,v:=range(bbcoords){ // chem.XYZWrite(fmt.Sprintf("opti%d.xyz",k), , newcoords) // } } else { //nothing here, the else part will get deleted after tests } energy, err2 := QM.Energy() if err2 != nil { log.Fatal(err2.Error()) } //Start transfering data back info.Energies = []float64{energy} if err2 := info.Send(os.Stdout); err2 != nil { fmt.Fprint(os.Stderr, err2) log.Fatal(err2) } // fmt.Fprint(os.Stdout,mar) // fmt.Fprint(os.Stdout,"\n") // A loop again to transmit the info. if options.BoolOptions[0][0] { if err := chemjson.SendMolecule(nil, []*v3.Matrix{sidecoords}, nil, nil, os.Stdout); err2 != nil { fmt.Fprint(os.Stderr, err) log.Fatal(err) } } for _, v := range bbcoords { fmt.Fprintln(os.Stderr, "BB transmit!", v.NVecs()) if err := chemjson.SendMolecule(nil, []*v3.Matrix{v}, nil, nil, os.Stdout); err2 != nil { fmt.Fprint(os.Stderr, err) log.Fatal(err) } } }
//BuildInput builds an input for NWChem based int the data in atoms, coords and C. //returns only error. func (O *NWChemHandle) BuildInput(coords *v3.Matrix, atoms chem.AtomMultiCharger, Q *Calc) error { //Only error so far if atoms == nil || coords == nil { return Error{ErrMissingCharges, NWChem, O.inputname, "", []string{"BuildInput"}, true} } if Q.Basis == "" { log.Printf("no basis set assigned for NWChem calculation, will use the default %s, \n", O.defbasis) Q.Basis = O.defbasis } if Q.Method == "" { log.Printf("no method assigned for NWChem calculation, will use the default %s, \n", O.defmethod) Q.Method = O.defmethod Q.RI = true } if O.inputname == "" { O.inputname = "gochem" } //The initial guess vectors := fmt.Sprintf("output %s.movecs", O.inputname) //The initial guess switch Q.Guess { case "": case "hcore": //This option is not a great idea, apparently. vectors = fmt.Sprintf("input hcore %s", vectors) default: if !Q.OldMO { //If the user gives something in Q.Guess but DOES NOT want an old MO to be used, I assume he/she wants to put whatever //is in Q.Guess directly in the vector keyword. If you want the default put an empty string in Q.Guess. vectors = fmt.Sprintf("%s %s", Q.Guess, vectors) break } //I assume the user gave a basis set name in Q.Guess which I can use to project vectors from a previous run. moname := getOldMO(O.previousMO) if moname == "" { break } if strings.ToLower(Q.Guess) == strings.ToLower(Q.Basis) { //Useful if you only change functionals. vectors = fmt.Sprintf("input %s %s", moname, vectors) } else { //This will NOT work if one assigns different basis sets to different atoms. vectors = fmt.Sprintf("input project %s %s %s", strings.ToLower(Q.Guess), moname, vectors) } } vectors = "vectors " + vectors disp, ok := nwchemDisp[Q.Dispersion] if !ok { disp = "vdw 3" } tightness := "" switch Q.SCFTightness { case 1: tightness = "convergence energy 5.000000E-08\n convergence density 5.000000E-09\n convergence gradient 1E-05" case 2: //NO idea if this will work, or the criteria will be stronger than the criteria for the intergral evaluation //and thus the SCF will never converge. Delete when properly tested. tightness = "convergence energy 1.000000E-10\n convergence density 5.000000E-11\n convergence gradient 1E-07" } //For now, the only convergence help I trust is to run a little HF calculation before and use the orbitals as a guess. //It works quite nicely. When the NWChem people get their shit together and fix the bugs with cgmin and RI and cgmin and //COSMO, cgmin will be a great option also. scfiters := "iterations 60" prevscf := "" if Q.SCFConvHelp > 0 { scfiters = "iterations 200" if Q.Guess == "" { prevscf = fmt.Sprintf("\nbasis \"3-21g\"\n * library 3-21g\nend\nset \"ao basis\" 3-21g\nscf\n maxiter 200\n vectors output hf.movecs\n %s\nend\ntask scf energy\n\n", strings.ToLower(mopacMultiplicity[atoms.Multi()])) vectors = fmt.Sprintf("vectors input project \"3-21g\" hf.movecs output %s.movecs", O.inputname) } } grid, ok := nwchemGrid[Q.Grid] if !ok { grid = "medium" } if Q.SCFTightness > 0 { //We need this if we want the SCF to converge. grid = "xfine" } grid = fmt.Sprintf("grid %s", grid) var err error //Only cartesian constraints supported by now. constraints := "" if len(Q.CConstraints) > 0 { constraints = "constraints\n fix atom" for _, v := range Q.CConstraints { constraints = fmt.Sprintf("%s %d", constraints, v+1) //goChem numbering starts from 0, apparently NWChem starts from 1, hence the v+1 } constraints = constraints + "\nend" } cosmo := "" if Q.Dielectric > 0 { //SmartCosmo in a single-point means that do_gasphase False is used, nothing fancy. if Q.Job.Opti || O.smartCosmo { cosmo = fmt.Sprintf("cosmo\n dielec %4.1f\n do_gasphase False\nend", Q.Dielectric) } else { cosmo = fmt.Sprintf("cosmo\n dielec %4.1f\n do_gasphase True\nend", Q.Dielectric) } } memory := "" if Q.Memory != 0 { memory = fmt.Sprintf("memory total %d mb", Q.Memory) } m := strings.ToLower(Q.Method) method, ok := nwchemMethods[m] if !ok { method = "xtpss03 ctpss03" } method = fmt.Sprintf("xc %s", method) task := "dft energy" driver := "" preopt := "" jc := jobChoose{} jc.opti = func() { eprec := "" //The available presition is set to default except if tighter SCF convergene criteria are being used. if Q.SCFTightness > 0 { eprec = " eprec 1E-7\n" } if Q.Dielectric > 0 && O.smartCosmo { //If COSMO is used, and O.SmartCosmo is enabled, we start the optimization with a rather loose SCF (the default). //and use that denisty as a starting point for the next calculation. The idea is to //avoid the gas phase calculation in COSMO. //This procedure doesn't seem to help at all, and just using do_gasphase False appears to be good enough in my tests. preopt = fmt.Sprintf("cosmo\n dielec %4.1f\n do_gasphase True\nend\n", Q.Dielectric) preopt = fmt.Sprintf("%sdft\n iterations 100\n %s\n %s\n print low\nend\ntask dft energy\n", preopt, vectors, method) vectors = fmt.Sprintf("vectors input %s.movecs output %s.movecs", O.inputname, O.inputname) //We must modify the initial guess so we use the vectors we have just generated } //The NWCHem optimizer is horrible. To try to get something out of it we use this 3-step optimization scheme where we try to compensate for the lack of //variable trust radius in nwchem. task = "dft optimize" //First an optimization with very loose convergency and the standard trust radius. driver = fmt.Sprintf("driver\n maxiter 200\n%s trust 0.3\n gmax 0.0500\n grms 0.0300\n xmax 0.1800\n xrms 0.1200\n xyz %s_prev\nend\ntask dft optimize", eprec, O.inputname) //Then a second optimization with a looser convergency and a 0.1 trust radius driver = fmt.Sprintf("%s\ndriver\n maxiter 200\n%s trust 0.1\n gmax 0.009\n grms 0.001\n xmax 0.04 \n xrms 0.02\n xyz %s_prev2\nend\ntask dft optimize", driver, eprec, O.inputname) //Then the final optimization with a small trust radius and the NWChem default convergence criteria. driver = fmt.Sprintf("%s\ndriver\n maxiter 200\n%s trust 0.05\n xyz %s\nend\n", driver, eprec, O.inputname) //Old criteria (ORCA): gmax 0.003\n grms 0.0001\n xmax 0.004 \n xrms 0.002\n } Q.Job.Do(jc) ////////////////////////////////////////////////////////////// //Now lets write the thing. Ill process/write the basis later ////////////////////////////////////////////////////////////// file, err := os.Create(fmt.Sprintf("%s.nw", O.inputname)) if err != nil { return Error{err.Error(), NWChem, O.inputname, "", []string{"os.Create", "BuildInput"}, true} } defer file.Close() start := "start" if O.restart { start = "restart" } _, err = fmt.Fprintf(file, "%s %s\n", start, O.inputname) //after this check its assumed that the file is ok. if err != nil { return Error{err.Error(), NWChem, O.inputname, "", []string{"fmt.Fprintf", "BuildInput"}, true} } fmt.Fprint(file, "echo\n") //echo input in the output. fmt.Fprintf(file, "charge %d\n", atoms.Charge()) if memory != "" { fmt.Fprintf(file, "%s\n", memory) //the memory } //Now the geometry: //If we have cartesian constraints we give the directive noautoz to optimize in cartesian coordinates. autoz := "" if len(Q.CConstraints) > 0 { autoz = "noautoz" } fmt.Fprintf(file, "geometry units angstroms noautosym %s\n", autoz) elements := make([]string, 0, 5) //I will collect the different elements that are in the molecule using the same loop as the geometry. for i := 0; i < atoms.Len(); i++ { symbol := atoms.Atom(i).Symbol //In the following if/else I try to set up basis for specific atoms. Not SO sure it works. if isInInt(Q.HBAtoms, i) { symbol = symbol + "1" } else if isInInt(Q.LBAtoms, i) { symbol = symbol + "2" } fmt.Fprintf(file, " %-2s %8.3f%8.3f%8.3f \n", symbol, coords.At(i, 0), coords.At(i, 1), coords.At(i, 2)) if !isInString(elements, symbol) { elements = append(elements, symbol) } } fmt.Fprintf(file, "end\n") fmt.Fprintf(file, prevscf) //The preeliminar SCF if exists. //The basis. First the ao basis (required) decap := strings.ToLower //hoping to make the next for loop less ugly basis := make([]string, 1, 2) basis[0] = "\"ao basis\"" fmt.Fprintf(file, "basis \"large\" spherical\n") //According to the manual this fails with COSMO. The calculations dont crash. Need to compare energies and geometries with Turbomole in order to be sure. for _, el := range elements { if isInString(Q.HBElements, el) || strings.HasSuffix(el, "1") { fmt.Fprintf(file, " %-2s library %s\n", el, decap(Q.HighBasis)) } else if isInString(Q.LBElements, el) || strings.HasSuffix(el, "2") { fmt.Fprintf(file, " %-2s library %s\n", el, decap(Q.LowBasis)) } else { fmt.Fprintf(file, " %-2s library %s\n", el, decap(Q.Basis)) } } fmt.Fprintf(file, "end\n") fmt.Fprintf(file, "set \"ao basis\" large\n") //Only Ahlrichs basis are supported for RI. USE AHLRICHS BASIS, PERKELE! :-) //The only Ahlrichs J basis in NWchem appear to be equivalent to def2-TZVPP/J (orca nomenclature). I suppose that they are still faster //than not using RI if the main basis is SVP. One can also hope that they are good enough if the main basis is QZVPP or something. //(about the last point, it appears that in Turbomole, the aux basis also go up to TZVPP). //This comment is based on the H, Be and C basis. if Q.RI { fmt.Fprint(file, "basis \"cd basis\"\n * library \"Ahlrichs Coulomb Fitting\"\nend\n") } //Now the geometry constraints. I kind of assume they are if constraints != "" { fmt.Fprintf(file, "%s\n", constraints) } fmt.Fprintf(file, preopt) if cosmo != "" { fmt.Fprintf(file, "%s\n", cosmo) } //The DFT block fmt.Fprint(file, "dft\n") fmt.Fprintf(file, " %s\n", vectors) fmt.Fprintf(file, " %s\n", scfiters) if tightness != "" { fmt.Fprintf(file, " %s\n", tightness) } fmt.Fprintf(file, " %s\n", grid) fmt.Fprintf(file, " %s\n", method) if disp != "" { fmt.Fprintf(file, " disp %s\n", disp) } if Q.Job.Opti { fmt.Fprintf(file, " print convergence\n") } //task part fmt.Fprintf(file, " mult %d\n", atoms.Multi()) fmt.Fprint(file, "end\n") fmt.Fprintf(file, "%s", driver) fmt.Fprintf(file, "task %s\n", task) return nil }
//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. //Notice that the exact position of the water is not well defined when angle is not zero. One can always use the RotateAbout //function to move the molecule to the desired location. If oxygen is true, the oxygen will be pointing to a2. Otherwise, //one of the hydrogens will. func MakeWater(a1, a2 *v3.Matrix, distance, angle float64, oxygen bool) *v3.Matrix { water := v3.Zeros(3) const WaterOHDist = 0.96 const WaterAngle = 52.25 const deg2rad = 0.0174533 w := water.VecView(0) //we first set the O coordinates w.Copy(a2) w.Sub(w, a1) w.Unit(w) dist := v3.Zeros(1) dist.Sub(a1, a2) a1a2dist := dist.Norm(0) fmt.Println("ala2dist", a1a2dist, distance) ////////////////7777 w.Scale(distance+a1a2dist, w) w.Add(w, a1) for i := 0; i <= 1; i++ { o := water.VecView(0) w = water.VecView(i + 1) w.Copy(o) fmt.Println("w1", w) //////// w.Sub(w, a2) fmt.Println("w12", w) /////////////// w.Unit(w) fmt.Println("w4", w) w.Scale(WaterOHDist+distance, w) fmt.Println("w3", w, WaterOHDist, distance) o.Sub(o, a2) t, _ := v3.NewMatrix([]float64{0, 0, 1}) upp := v3.Zeros(1) upp.Cross(w, t) fmt.Println("upp", upp, w, t) upp.Add(upp, o) upp.Add(upp, a2) //water.SetMatrix(3,0,upp) w.Add(w, a2) o.Add(o, a2) sign := 1.0 if i == 1 { sign = -1.0 } temp, _ := RotateAbout(w, o, upp, deg2rad*WaterAngle*sign) w.SetMatrix(0, 0, temp) } var v1, v2 *v3.Matrix if angle != 0 { v1 = v3.Zeros(1) v2 = v3.Zeros(1) v1.Sub(a2, a1) v2.Copy(v1) v2.Set(0, 2, v2.At(0, 2)+1) //a "random" modification. The idea is that its not colinear with v1 v3 := cross(v1, v2) v3.Add(v3, a2) water, _ = RotateAbout(water, a2, v3, angle) } if oxygen { return water } //we move things so an hydrogen points to a2 and modify the distance acordingly. e1 := water.VecView(0) e2 := water.VecView(1) e3 := water.VecView(2) if v1 == nil { v1 = v3.Zeros(1) } if v2 == nil { v2 = v3.Zeros(1) } v1.Sub(e2, e1) v2.Sub(e3, e1) axis := cross(v1, v2) axis.Add(axis, e1) water, _ = RotateAbout(water, e1, axis, deg2rad*(180-WaterAngle)) v1.Sub(e1, a2) v1.Unit(v1) v1.Scale(WaterOHDist, v1) water.AddVec(water, v1) return water }
//RotatorToNewZ takes a matrix a row vector (newz). //It returns a linear operator such that, when applied to a matrix mol ( with the operator on the right side) //it will rotate mol such that the z axis is aligned with newz. func RotatorToNewZ(newz *v3.Matrix) *v3.Matrix { r, c := newz.Dims() if c != 3 || r != 1 { panic("Wrong newz vector") } normxy := math.Sqrt(math.Pow(newz.At(0, 0), 2) + math.Pow(newz.At(0, 1), 2)) theta := math.Atan2(normxy, newz.At(0, 2)) //Around the new y phi := math.Atan2(newz.At(0, 1), newz.At(0, 0)) //First around z psi := 0.000000000000 // second around z sinphi := math.Sin(phi) cosphi := math.Cos(phi) sintheta := math.Sin(theta) costheta := math.Cos(theta) sinpsi := math.Sin(psi) cospsi := math.Cos(psi) operator := []float64{cosphi*costheta*cospsi - sinphi*sinpsi, -sinphi*cospsi - cosphi*costheta*sinpsi, cosphi * sintheta, sinphi*costheta*cospsi + cosphi*sinpsi, -sinphi*costheta*sinpsi + cosphi*cospsi, sintheta * sinphi, -sintheta * cospsi, sintheta * sinpsi, costheta} finalop, _ := v3.NewMatrix(operator) //we are hardcoding opperator so it must have the right dimensions. return finalop }
//BuildInput builds an input for Fermions++ based int the data in atoms, coords and C. //returns only error. func (O *FermionsHandle) BuildInput(coords *v3.Matrix, atoms chem.AtomMultiCharger, Q *Calc) error { //Only error so far if atoms == nil || coords == nil { return Error{ErrMissingCharges, Fermions, O.inputname, "", []string{"BuildInput"}, true} } if Q.Basis == "" { log.Printf("no basis set assigned for Fermions++ calculation, will used the default %s, \n", O.defbasis) Q.Basis = O.defbasis } if Q.Method == "" { log.Printf("no method assigned for Fermions++ calculation, will used the default %s, \n", O.defmethod) Q.Method = O.defmethod } disp, ok := fermionsDisp[strings.ToLower(Q.Dispersion)] if !ok { disp = "disp_corr D3" } grid, ok := fermionsGrid[Q.Grid] if !ok { grid = "M3" } grid = fmt.Sprintf("GRID_RAD_TYPE %s", grid) var err error m := strings.ToLower(Q.Method) method, ok := fermionsMethods[m] if !ok { method = "EXC XC_GGA_X_PBE_R\n ECORR XC_GGA_C_PBE" } task := "SinglePoint" dloptions := "" jc := jobChoose{} jc.opti = func() { task = "DLF_OPTIMIZE" dloptions = fmt.Sprintf("*start::dlfind\n JOB std\n method l-bfgs\n trust_radius energy\n dcd %s.dcd\n maxcycle 300\n maxene 200\n coord_type cartesian\n*end\n", O.inputname) //Only cartesian constraints supported by now. if len(Q.CConstraints) > 0 { dloptions = fmt.Sprintf("%s\n*start::dlf_constraints\n", dloptions) for _, v := range Q.CConstraints { dloptions = fmt.Sprintf("%s cart %d\n", dloptions, v+1) //fortran numbering, starts with 1. } dloptions = fmt.Sprintf("%s*end\n", dloptions) } } Q.Job.Do(jc) cosmo := "" if Q.Dielectric > 0 { cosmo = fmt.Sprintf("*start::solvate\n pcm_model cpcm\n epsilon %f\n cavity_model bondi\n*end\n", Q.Dielectric) } ////////////////////////////////////////////////////////////// //Now lets write the thing. ////////////////////////////////////////////////////////////// file, err := os.Create(fmt.Sprintf("%s.in", O.inputname)) if err != nil { return Error{ErrCantInput, Fermions, O.inputname, err.Error(), []string{"os.Create", "BuildInput"}, true} } defer file.Close() //Start with the geometry part (coords, charge and multiplicity) fmt.Fprintf(file, "*start::geo\n") fmt.Fprintf(file, "%d %d\n", atoms.Charge(), atoms.Multi()) for i := 0; i < atoms.Len(); i++ { fmt.Fprintf(file, "%-2s %8.3f%8.3f%8.3f\n", atoms.Atom(i).Symbol, coords.At(i, 0), coords.At(i, 1), coords.At(i, 2)) } fmt.Fprintf(file, "*end\n\n") fmt.Fprintf(file, "*start::sys\n") fmt.Fprintf(file, " TODO %s\n", task) fmt.Fprintf(file, " BASIS %s\n PC pure\n", strings.ToLower(Q.Basis)) fmt.Fprintf(file, " %s\n", method) fmt.Fprintf(file, " %s\n", grid) fmt.Fprintf(file, " %s\n", disp) fmt.Fprintf(file, " %s\n", O.gpu) if !Q.Job.Opti { fmt.Fprintf(file, " INFO 2\n") } fmt.Fprintf(file, "*end\n\n") fmt.Fprintf(file, "%s\n", cosmo) fmt.Fprintf(file, "%s\n", dloptions) return nil }