//This program will align the best plane passing through a set of atoms in a molecule with the XY-plane. //Usage: func main() { if len(os.Args) < 2 { fmt.Printf("Usage:\n%s file.xyz [indexes.dat]\nindexes.dat is a file containing one single line, with all the atoms defining the plane separated by spaces. If it is not given, all the atoms of the molecule will be taken to define the plane.\n", os.Args[0]) os.Exit(1) } mol, err := chem.XYZFileRead(os.Args[1]) if err != nil { panic(err.Error()) } var indexes []int //if no file with indexes given, will just use all the atoms. if len(os.Args) < 3 { indexes = make([]int, mol.Len()) for k, _ := range indexes { indexes[k] = k } } else { indexes, err = scu.IndexFileParse(os.Args[2]) if err != nil { panic(err.Error()) } } some := v3.Zeros(len(indexes)) //will contain the atoms selected to define the plane. some.SomeVecs(mol.Coords[0], indexes) //for most rotation things it is good to have the molecule centered on its mean. mol.Coords[0], _, _ = chem.MassCenter(mol.Coords[0], some, nil) //As we changed the atomic positions, must extract the plane-defining atoms again. some.SomeVecs(mol.Coords[0], indexes) //The strategy is: Take the normal to the plane of the molecule (os molecular subset), and rotate it until it matches the Z-axis //This will mean that the plane of the molecule will now match the XY-plane. best, err := chem.BestPlane(some, nil) if err != nil { panic(err.Error()) } z, _ := v3.NewMatrix([]float64{0, 0, 1}) zero, _ := v3.NewMatrix([]float64{0, 0, 0}) fmt.Fprintln(os.Stderr, "Best Plane", best, z, indexes) axis := v3.Zeros(1) axis.Cross(best, z) fmt.Fprintln(os.Stderr, "axis", axis) //The main part of the program, where the rotation actually happens. Note that we rotate the whole //molecule, not just the planar subset, this is only used to calculate the rotation angle. mol.Coords[0], err = chem.RotateAbout(mol.Coords[0], zero, axis, chem.Angle(best, z)) if err != nil { panic(err.Error()) } //Now we write the rotated result. final, err := chem.XYZStringWrite(mol.Coords[0], mol) fmt.Print(final) fmt.Fprintln(os.Stderr, err) }
//RotatorAroundZ returns an operator that will rotate a set of //coordinates by gamma radians around the z axis. func RotatorAroundZ(gamma float64) (*v3.Matrix, error) { singamma := math.Sin(gamma) cosgamma := math.Cos(gamma) operator := []float64{cosgamma, singamma, 0, -singamma, cosgamma, 0, 0, 0, 1} return v3.NewMatrix(operator) }
//xyzReadSnap reads an xyz file snapshot from a bufio.Reader, returns a slice of Atom objects, which will be nil if ReadTopol is false, // a slice of matrix.DenseMatrix and an error or nil. func xyzReadSnap(xyz *bufio.Reader, ReadTopol bool) (*v3.Matrix, []*Atom, error) { line, err := xyz.ReadString('\n') if err != nil { return nil, nil, CError{fmt.Sprintf("Empty XYZ File: %s", err.Error()), []string{"bufio.Reader.ReadString", "xyzReadSnap"}} } natoms, err := strconv.Atoi(strings.TrimSpace(line)) if err != nil { return nil, nil, CError{fmt.Sprintf("Wrong header for an XYZ file %s", err.Error()), []string{"strconv.Atoi", "xyzReadSnap"}} } var molecule []*Atom if ReadTopol { molecule = make([]*Atom, natoms, natoms) } coords := make([]float64, natoms*3, natoms*3) _, err = xyz.ReadString('\n') //We dont care about this line if err != nil { return nil, nil, CError{fmt.Sprintf("Ill formatted XYZ file: %s", err.Error()), []string{"bufio.Reader.ReadString", "xyzReadSnap"}} } errs := make([]error, 3, 3) for i := 0; i < natoms; i++ { line, errs[0] = xyz.ReadString('\n') if errs[0] != nil { //inefficient, (errs[1] can be checked once before), but clearer. if strings.Contains(errs[0].Error(), "EOF") && i == natoms-1 { //This allows that an XYZ ends without a newline errs[0] = nil } else { break } } fields := strings.Fields(line) if len(fields) < 4 { errs[0] = fmt.Errorf("Line number %d ill formed", i) break } if ReadTopol { molecule[i] = new(Atom) molecule[i].Symbol = strings.Title(fields[0]) molecule[i].Mass = symbolMass[molecule[i].Symbol] molecule[i].Molname = "UNK" molecule[i].Name = molecule[i].Symbol } coords[i*3], errs[0] = strconv.ParseFloat(fields[1], 64) coords[i*3+1], errs[1] = strconv.ParseFloat(fields[2], 64) coords[i*3+2], errs[2] = strconv.ParseFloat(fields[3], 64) } //This could be done faster if done in the same loop where the coords are read //Instead of having another loop just for them. for k, i := range errs { if i != nil { fmt.Println("line", line, k) return nil, nil, CError{i.Error(), []string{"strconv.ParseFloat", "xyzReadSnap"}} } } mcoords, err := v3.NewMatrix(coords) return mcoords, molecule, errDecorate(err, "xyzReadSnap") }
func TestProjectionAndAntiProjection(Te *testing.T) { A := v3.Zeros(1) A.Set(0, 0, 2.0) B, _ := v3.NewMatrix([]float64{1, 1, 0}) C := AntiProjection(A, B) D := Projection(B, A) fmt.Println("Projection of B on A (D)", D) fmt.Println("Anti-projection of A on B (C):", C) fmt.Println("Norm of C: ", C.Norm(0), " Norm of A,B: ", A.Norm(0), B.Norm(0), "Norm of D:", D.Norm(0)) }
//Aligns the main plane of a molecule with the XY-plane. //Here XYZRead and XYZWrite are tested func TestPutInXYPlane(Te *testing.T) { myxyz, _ := os.Open("test/sample_plane.xyz") mol, err := XYZRead(myxyz) if err != nil { Te.Error(err) } indexes := []int{0, 1, 2, 3, 23, 22, 21, 20, 25, 44, 39, 40, 41, 42, 61, 60, 59, 58, 63, 5} some := v3.Zeros(len(indexes)) some.SomeVecs(mol.Coords[0], indexes) //for most rotation things it is good to have the molecule centered on its mean. mol.Coords[0], _, _ = MassCenter(mol.Coords[0], some, nil) //The test molecule is not completely planar so we use a subset of atoms that are contained in a plane //These are the atoms given in the indexes slice. some.SomeVecs(mol.Coords[0], indexes) //The strategy is: Take the normal to the plane of the molecule (os molecular subset), and rotate it until it matches the Z-axis //This will mean that the plane of the molecule will now match the XY-plane. best, err := BestPlane(some, nil) if err != nil { err2 := err.(Error) fmt.Println(err2.Decorate("")) Te.Fatal(err) // panic(err.Error()) } z, _ := v3.NewMatrix([]float64{0, 0, 1}) zero, _ := v3.NewMatrix([]float64{0, 0, 0}) fmt.Println("Best Plane", best, z) axis := v3.Zeros(1) axis.Cross(best, z) fmt.Println("axis", axis) //The main part of the program, where the rotation actually happens. Note that we rotate the whole //molecule, not just the planar subset, this is only used to calculate the rotation angle. // fmt.Println("DATA", mol.Coords[0], zero, axis, Angle(best, z)) mol.Coords[0], err = RotateAbout(mol.Coords[0], zero, axis, Angle(best, z)) if err != nil { Te.Error(err) } // fmt.Println("after!", mol.Coords[0], err) //Now we write the rotated result. outxyz, _ := os.Create("test/Rotated.xyz") //This is the XYZWrite written file XYZWrite(outxyz, mol.Coords[0], mol) }
//DecodeMolecule Decodes a JSON molecule into a gochem molecule. Can handle several frames (all of which need to have the same amount of atoms). It does //not collect the b-factors. func DecodeMolecule(stream *bufio.Reader, atomnumber, frames int) (*chem.Topology, []*v3.Matrix, *Error) { const funcname = "DecodeMolecule" //for the error atoms := make([]*chem.Atom, 0, atomnumber) coordset := make([]*v3.Matrix, 0, frames) rawcoords := make([]float64, 0, 3*atomnumber) for i := 0; i < atomnumber; i++ { line, err := stream.ReadBytes('\n') //Using this function allocates a lot without need. There is no function that takes a []bytes AND a limit. I might write one at some point. if err != nil { break } at := new(chem.Atom) err = json.Unmarshal(line, at) if err != nil { return nil, nil, NewError("selection", funcname, err) } atoms = append(atoms, at) line, err = stream.ReadBytes('\n') //See previous comment. if err != nil { break } ctemp := new(Coords) if err = json.Unmarshal(line, ctemp); err != nil { return nil, nil, NewError("selection", funcname, err) } rawcoords = append(rawcoords, ctemp.Coords...) } mol := chem.NewTopology(atoms, -1, 99999) //no idea of the charge or multiplicity coords, err := v3.NewMatrix(rawcoords) if err != nil { return nil, nil, NewError("selection", funcname, err) } coordset = append(coordset, coords) if frames == 1 { return mol, coordset, nil } for i := 0; i < (frames - 1); i++ { coords, err := DecodeCoords(stream, atomnumber) if err != nil { return mol, coordset, NewError("selection", funcname, fmt.Errorf("Error reading the %d th frame: %s", i+2, err.Error())) } coordset = append(coordset, coords) } return mol, coordset, 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) }
//TestChangeAxis reads the PDB 2c9v.pdb from the test directory, collects //The CA and CB of residue D124 of the chain A, and uses Clifford algebra to rotate the //whole molecule such as the vector defined by these 2 atoms is //aligned with the Z axis. The new molecule is written //as 2c9v_aligned.pdb to the test folder. func TestChangeAxis(Te *testing.T) { //runtime.GOMAXPROCS(2) /////////////////////////// mol, err := PDBFileRead("test/2c9v.pdb", true) if err != nil { Te.Error(err) } PDBFileWrite("test/2c9v-Readtest.pdb", mol.Coords[0], mol, nil) //The selection thing orient_atoms := [2]int{0, 0} for index := 0; index < mol.Len(); index++ { atom := mol.Atom(index) if atom.Chain == "A" && atom.MolID == 124 { if atom.Name == "CA" { orient_atoms[0] = index } else if atom.Name == "CB" { orient_atoms[1] = index } } } //Get the axis of rotation //ov1:=mol.Coord(orient_atoms[0], 0) ov2 := mol.Coord(orient_atoms[1], 0) //now we center the thing in the beta carbon of D124 mol.Coords[0].SubVec(mol.Coords[0], ov2) PDBFileWrite("test/2c9v-translated.pdb", mol.Coords[0], mol, nil) //Now the rotation ov1 := mol.Coord(orient_atoms[0], 0) //make sure we have the correct versions ov2 = mol.Coord(orient_atoms[1], 0) //same orient := v3.Zeros(ov2.NVecs()) orient.Sub(ov2, ov1) // PDBWrite(mol,"test/2c9v-124centered.pdb") Z, _ := v3.NewMatrix([]float64{0, 0, 1}) axis := cross(orient, Z) angle := Angle(orient, Z) oldcoords := v3.Zeros(mol.Coords[0].NVecs()) oldcoords.Copy(mol.Coords[0]) mol.Coords[0] = Rotate(oldcoords, mol.Coords[0], axis, angle) if err != nil { Te.Error(err) } PDBFileWrite("test/2c9v-aligned.pdb", mol.Coords[0], mol, nil) fmt.Println("bench1") }
//Decodecoords decodes streams from a bufio.Reader containing 3*atomnumber JSON floats into a v3.Matrix with atomnumber rows. func DecodeCoords(stream *bufio.Reader, atomnumber int) (*v3.Matrix, *Error) { const funcname = "DecodeCoords" rawcoords := make([]float64, 0, 3*atomnumber) for i := 0; i < atomnumber; i++ { line, err := stream.ReadBytes('\n') //Using this function allocates a lot without need. There is no function that takes a []bytes AND a limit. I might write one at some point. if err != nil { break } ctemp := new(Coords) if err = json.Unmarshal(line, ctemp); err != nil { return nil, NewError("selection", funcname, err) } rawcoords = append(rawcoords, ctemp.Coords...) } coords, err := v3.NewMatrix(rawcoords) if err != nil { return nil, NewError("selection", funcname, err) } return coords, nil }
//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 }
//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 }
//pdbBufIORead reads the atomic entries for a PDB bufio.IO, returns a bunch of without coordinates, // and the coordinates in a separate array of arrays. If there is one frame in the PDB // the coordinates array will be of lenght 1. It also returns an error which is not // really well set up right now. func pdbBufIORead(pdb *bufio.Reader, read_additional bool) (*Molecule, error) { molecule := make([]*Atom, 0) modelnumber := 0 //This is the number of frames read coords := make([][]float64, 1, 1) coords[0] = make([]float64, 0) bfactors := make([][]float64, 1, 1) bfactors[0] = make([]float64, 0) first_model := true //are we reading the first model? if not we only save coordinates contlines := 1 //count the lines read to better report errors for { line, err := pdb.ReadString('\n') if err != nil { //fmt.Println("PDB reading complete") /***change this to stderr************/ break contlines++ //count all the lines even if empty. } if len(line) < 4 { continue } //here we start actually reading /*There might be a bug for not copying the string (symbol, name, etc) but just assigning the slice * which is a reference. Check!*/ var c = make([]float64, 3, 3) var bfactemp float64 //temporary bfactor var atomtmp *Atom // var foo string // not really needed if strings.HasPrefix(line, "ATOM") || strings.HasPrefix(line, "HETATM") { if !first_model { c, bfactemp, err = read_onlycoords_pdb_line(line, contlines) if err != nil { return nil, errDecorate(err, "pdbBufIORead") } } else { atomtmp = new(Atom) atomtmp, c, bfactemp, err = read_full_pdb_line(line, read_additional, contlines) if err != nil { return nil, errDecorate(err, "pdbBufIORead") } //atom data other than coords is the same in all models so just read for the first. molecule = append(molecule, atomtmp) } //coords are appended for all the models //we add the coords to the latest frame of coordinaates coords[len(coords)-1] = append(coords[len(coords)-1], c[0], c[1], c[2]) bfactors[len(bfactors)-1] = append(bfactors[len(bfactors)-1], bfactemp) } else if strings.HasPrefix(line, "MODEL") { modelnumber++ //,_=strconv.Atoi(strings.TrimSpace(line[6:])) if modelnumber > 1 { //will be one for the first model, 2 for the second. first_model = false coords = append(coords, make([]float64, 0)) //new bunch of coords for a new frame bfactors = append(bfactors, make([]float64, 0)) } } } //This could be done faster if done in the same loop where the coords are read //Instead of having another loop just for them. top := NewTopology(molecule, 0, 1) var err error frames := len(coords) mcoords := make([]*v3.Matrix, frames, frames) //Final thing to return for i := 0; i < frames; i++ { mcoords[i], err = v3.NewMatrix(coords[i]) if err != nil { return nil, errDecorate(err, "pdbBufIORead") } } //if something happened during the process if err != nil { return nil, errDecorate(err, "pdbBufIORead") } returned, err := NewMolecule(mcoords, top, bfactors) return returned, errDecorate(err, "pdbBufIORead") }
/*Get Geometry reads the optimized geometry from a MOPAC2009/2012 output. Return error if fail. Returns Error ("Probable problem in calculation") if there is a geometry but the calculation didnt end properly*/ func (O *MopacHandle) OptimizedGeometry(atoms chem.Atomer) (*v3.Matrix, error) { var err error natoms := atoms.Len() coords := make([]float64, natoms*3, natoms*3) //will be used for return file, err := os.Open(fmt.Sprintf("%s.out", O.inputname)) if err != nil { return nil, Error{ErrNoGeometry, Mopac, O.inputname, "", []string{"os.Open", "OptimizedGeometry"}, true} } defer file.Close() out := bufio.NewReader(file) err = Error{ErrNoGeometry, Mopac, O.inputname, "", []string{"OptimizedGeometry"}, true} //some variables that will be changed/increased during the next for loop final_point := false //to see if we got to the right part of the file reading := false //start reading i := 0 errsl := make([]error, 3, 3) trust_radius_warning := false for { var line string line, err = out.ReadString('\n') if err != nil { break } if (!reading) && strings.Contains(line, "TRUST RADIUS NOW LESS THAN 0.00010 OPTIMIZATION TERMINATING") { trust_radius_warning = true continue } //MOPAC output is a pleasure to parse. IF YOU ARE A F*** PERKELEN CTM M*******T!!!!!!!!!!!!!!!!!!! if !reading && (strings.Contains(line, "FINAL POINT AND DERIVATIVES") || strings.Contains(line, "GEOMETRY OPTIMISED")) || strings.Contains(line, "GRADIENTS WERE INITIALLY ACCEPTABLY SMALL") { final_point = true continue } if strings.Contains(line, "(ANGSTROMS) (ANGSTROMS) (ANGSTROMS)") && final_point { _, err = out.ReadString('\n') if err != nil { break } reading = true continue } if reading { //So far we dont check that there are not too many atoms in the mopac output. if i >= natoms { err = nil break } coords[i*3], errsl[0] = strconv.ParseFloat(strings.TrimSpace(line[22:35]), 64) coords[i*3+1], errsl[1] = strconv.ParseFloat(strings.TrimSpace(line[38:51]), 64) coords[i*3+2], errsl[2] = strconv.ParseFloat(strings.TrimSpace(line[54:67]), 64) i++ err = parseErrorSlice(errsl) if err != nil { break } } } if err != nil { return nil, Error{ErrNoGeometry, Mopac, O.inputname, err.Error(), []string{"OptimizedGeometry"}, true} } mcoords, err := v3.NewMatrix(coords) if trust_radius_warning { return mcoords, Error{ErrProbableProblem, Mopac, O.inputname, "", []string{"OptimizedGeometry"}, false} } return mcoords, err }