def __init__(self, natoms, minimiser, composition="default", labels=("X", ), pop_size=10, max_generation=10, selector=TournamentSelector(3), offspring=8, mutant_rate=0.1, remove_duplicates=False, mutator=MutateReplace(), crossover=DeavenHo()): #Parameters self.max_generation = max_generation self.mutant_rate = mutant_rate self.offspring = offspring self.pop_size = pop_size self.remove_duplicates = remove_duplicates self.selector = selector self.mutator = mutator self.crossover = crossover #Factory self.factory = ClusterFactory(natoms, minimiser, composition, labels) #PopulationList self.population = PopulationList(natoms, self.factory, pop_size) #Evolutionary progress self.mean_energy_series = [] self.min_energy_series = [] self.db = Database(db="mydatabase.sqlite") self.storage = self.db.minimum_adder()
def main(): parser = argparse.ArgumentParser(description="print information about the database") parser.add_argument("database", type=str, help="Database file name") parser.add_argument("--write-pathsample-db", dest="write_pathsample", action="store_true", help="generate a pathsample database by writing files min.data, ts.data, points.min, and points.ts") parser.add_argument("-m", dest="writeMinima", action="store_true", help="dump minima to screen") parser.add_argument("-t", dest="writeTS", action="store_true", help="dump transition states to screen") parser.add_argument("-p", dest="properties", action="store_true", help="print system properties") parser.add_argument("-s", dest="summary", action="store_true", help="print summary") parser.add_argument("-S", dest="summary_long", action="store_true", help="print long summary") args = parser.parse_args() if args.summary_long: args.summary = True db = Database(db=args.database, createdb=False) if args.properties or args.summary: print_system_properties(db) if args.summary: print "number of minima:", db.number_of_minima() print "number of transition states:", db.number_of_transition_states() if args.summary_long: long_summary(db) if args.writeMinima: print "List of minima: energy id fvib pgorder" print "---------------" for m in db.minima(): print "%f\t\tid %d %s %s" % (m.energy, m._id, str(m.fvib), str(m.pgorder)) print "END\n" if args.writeTS: print "List of transition states:" print "--------------------------" for ts in db.transition_states(): print "%d\t<->\t%d\tid %d\tenergies %f %f %f" % \ (ts.minimum1._id, ts.minimum2._id, ts._id, ts.minimum1.energy, ts.energy, ts.minimum2.energy) print "END\n" if args.write_pathsample: write_pathsample_db(db)
def main(): # add some program options parser = OptionParser(usage = "usage: %prog [options] storage") parser.add_option("--write-disconnect", dest="writeDPS", action="store_true", help="generate min.dat and ts.dat to use with disconnectDPS") parser.add_option("-m", dest="writeMinima", action="store_true", help="dump minima to screen") parser.add_option("-t", dest="writeTS", action="store_true", help="dump transition states to screen") parser.add_option("--cif", dest="writeCIF", action="store_true", help="export cif files") parser.add_option("--cif-dir", dest="cifDir", default=".", action="store",type="string", help="directory to write cifs to") (options, args) = parser.parse_args() # print help if no input file is given if(len(args) != 1): parser.print_help() exit(-1) db = Database(db=args[0]) if(options.writeMinima): print "List of minima:" print "---------------" for m in db.minima(): print "%f\t\tid %d"%(m.energy, m._id) print "END\n" if(options.writeTS): print "List of transition states:" print "--------------------------" for ts in db.transition_states(): print "%d\t<->\t%d\tid %d\tenergies %f %f %f"%\ (ts.minimum1._id, ts.minimum2._id, ts._id, ts.minimum1.energy, ts.energy, ts.minimum2.energy) print "END\n" if(options.writeDPS): writeDPS(db) if(options.writeCIF): GMIN.initialize() i=0 for m in db.minima(): i+=1 filename = options.cifDir+"/lowest%03d.cif"%(i) print "minimum",i, "energy",m.energy,"to",filename GMIN.writeCIF(filename, m.coords, "E"+str(m.energy))
def __init__(self,natoms,minimiser, composition="default",labels=("X",), pop_size=10,max_generation=10, selector=TournamentSelector(3), offspring=8,mutant_rate=0.1,remove_duplicates=False, mutator=MutateReplace(), crossover=DeavenHo()): #Parameters self.max_generation = max_generation self.mutant_rate = mutant_rate self.offspring=offspring self.pop_size=pop_size self.remove_duplicates=remove_duplicates self.selector=selector self.mutator = mutator self.crossover = crossover #Factory self.factory=ClusterFactory(natoms,minimiser,composition,labels) #PopulationList self.population = PopulationList(natoms,self.factory,pop_size) #Evolutionary progress self.mean_energy_series=[] self.min_energy_series=[] self.db = Database(db="mydatabase.sqlite") self.storage = self.db.minimum_adder()
def main(): parser = argparse.ArgumentParser(description="print information about the database") parser.add_argument("database", type=str, help="Database file name") parser.add_argument("--write-disconnect", dest="writeDPS", action="store_true", help="generate min.dat and ts.dat to use with disconnectDPS") parser.add_argument("-m", dest="writeMinima", action="store_true", help="dump minima to screen") parser.add_argument("-t", dest="writeTS", action="store_true", help="dump transition states to screen") parser.add_argument("-p", dest="properties", action="store_true", help="print system properties") parser.add_argument("-s", dest="summary", action="store_true", help="print summary") parser.add_argument("-S", dest="summary_long", action="store_true", help="print long summary") args = parser.parse_args() if args.summary_long: args.summary = True db = Database(db=args.database, createdb=False) if args.properties or args.summary: print_system_properties(db) if args.summary: print "number of minima:", db.number_of_minima() print "number of transition states:", db.number_of_transition_states() if args.summary_long: long_summary(db) if args.writeMinima: print "List of minima: energy id fvib pgorder" print "---------------" for m in db.minima(): print "%f\t\tid %d %s %s" % (m.energy, m._id, str(m.fvib), str(m.pgorder)) print "END\n" if args.writeTS: print "List of transition states:" print "--------------------------" for ts in db.transition_states(): print "%d\t<->\t%d\tid %d\tenergies %f %f %f" % \ (ts.minimum1._id, ts.minimum2._id, ts._id, ts.minimum1.energy, ts.energy, ts.minimum2.energy) print "END\n" if args.writeDPS: writeDPS(db)
class BatchGeneticAlgorithm(object): '''The Birmingham Cluster Genetic Algorithm. A new parallel version of the BCGA. The population is stored in a pele database and can be accessed by several processes simultaneously. Parameters: natoms- Number of atoms in cluster minimiser- See bcga.gpaw_interface Optional parameters: composition- A list containing the number of atoms of each type labels- A tuple orblist containing the names of each atom type (e.g. ["Au","Ag"] pop_size- Number of clusters in population max_generation- Number of generations to run GA selector- Selection method for choosing parents (see bcga.selector) offspring- Number of crossover operations in each generation mutant_rate- Probability of any cluster producing a mutant remove_duplicates- Remove identical clusters from population to prevent stagnation restart- Read population from restart.xyz and continue a search ''' def __init__(self,natoms,minimiser, composition="default",labels=("X",), pop_size=10,max_generation=10, selector=TournamentSelector(3), offspring=8,mutant_rate=0.1,remove_duplicates=False, mutator=MutateReplace(), crossover=DeavenHo()): #Parameters self.max_generation = max_generation self.mutant_rate = mutant_rate self.offspring=offspring self.pop_size=pop_size self.remove_duplicates=remove_duplicates self.selector=selector self.mutator = mutator self.crossover = crossover #Factory self.factory=ClusterFactory(natoms,minimiser,composition,labels) #PopulationList self.population = PopulationList(natoms,self.factory,pop_size) #Evolutionary progress self.mean_energy_series=[] self.min_energy_series=[] self.db = Database(db="mydatabase.sqlite") self.storage = self.db.minimum_adder() def write_xyz(self,file_name="cluster.xyz"): '''Open an xyz file and write the current population to it (non-blocking).''' try: with open(file_name,'w') as xyz_file: self.population.write_xyz(xyz_file) except IOError as err: print("File error: "+str(err)) def read_xyz(self,file_name="restart.xyz"): '''Read population from an xyz file (non-blocking for now).''' self.population.mass_extinction(0) try: with open(file_name) as xyz_file: self.population.read_xyz(xyz_file) except: print("No restart file available.") def run(self): '''Run the GA.''' for generation in range(1,self.max_generation+1): print ("Generation "+str(generation)) if self.db.number_of_minima() < self.population.max_size: cluster=self.factory.get_random_cluster() print("Filling population with random structure.") else: self.population.read_db(self.db) if np.random < self.mutant_rate: index=np.random.randint(0,len(self.population)) cluster=self.mutator.get_mutant(self.population[index]) print("Generating mutant of cluster "+str(index)) else: indices = self.selector.select(self.population) cluster=self.crossover.get_offspring(self.population[indices[0]], self.population[indices[1]]) print("Generating offpsring of clusters "+str(indices[0])+" and "+str(indices[1])) cluster.minimise() #self.read_xyz("restart.xyz") #self.population.append(cluster) self.storage(cluster.energy,cluster._coords.flatten())
# print help if no input file is given if len(args) != 1: parser.print_help() exit(-1) infile = args[0] outfile = infile # initialize GMIN GMIN.initialize() pot = gminpotential.GMINPotential(GMIN) crystals.GMIN = GMIN # open the storage class db_in = Database(db=infile) if options.out != None: outfile = options.out db_out = Database(db=outfile) print "Start to reoptimize minima" for m in db_in.minima(): print "optimizing minima", m._id, ", energy", m.energy ret = dmagmin.quenchCrystal(m.coords, pot.getEnergyGradient, tol=options.tol, maxErise=options.maxErise) print "new energy", ret[1], "(difference %f)" % (ret[1] - m.energy) print if options.out != None: db_out.addMinimum(ret[1], ret[0]) else: m.energy = ret[1] m.coords = ret[0]
def main(): # add some program options parser = OptionParser(usage="usage: %prog [options] storage") parser.add_option( "--write-disconnect", dest="writeDPS", action="store_true", help="generate min.dat and ts.dat to use with disconnectDPS") parser.add_option("-m", dest="writeMinima", action="store_true", help="dump minima to screen") parser.add_option("-t", dest="writeTS", action="store_true", help="dump transition states to screen") parser.add_option("--coords", dest="writeCoords", action="store_true", help="export coordinates files") parser.add_option("--xyz", dest="writeXYZ", action="store_true", help="export xyz files") (options, args) = parser.parse_args() # print help if no input file is given if (len(args) != 1): parser.print_help() exit(-1) db = Database(db=args[0]) if (options.writeMinima): print "List of minima:" print "---------------" for m in db.minima(): print "%f\t\tid %d" % (m.energy, m._id) print "END\n" if (options.writeTS): print "List of transition states:" print "--------------------------" for ts in db.transition_states(): print "%d\t<->\t%d\tid %d\tenergies %f %f %f"%\ (ts.minimum1._id, ts.minimum2._id, ts._id, ts.minimum1.energy, ts.energy, ts.minimum2.energy) print "END\n" if (options.writeDPS): writeDPS(db) if (options.writeCoords): GMIN.initialize() i = 0 for m in db.minima(): i += 1 filename = "lowest/lowest%03d.cif" % (i) print "minimum", i, "energy", m.energy, "to", filename GMIN.userpot_dump(filename, m.coords) if (not TO_PDB is None): os.system(TO_PDB % filename) np.savetxt("lowest/coords_%03d.txt" % (i), m.coords) if (options.writeXYZ): traj = open("lowest/traj.xyz", "w") i = 0 for m in db.minima(): i += 1 filename = "lowest/lowest%03d.xyz" % (i) print "minimum", i, "energy", m.energy, "to", filename export_xyz(open(filename, "w"), m.coords) export_xyz(traj, m.coords) traj.close()
coords=potential.getCoords() coords=np.random.random(coords.shape) # create takestep routine # we combine a normal step taking group = takestep.BlockMoves() step1 = takestep.AdaptiveStepsize(OXDNATakestep(displace=parameters.displace, rotate=0.), frequency=50) step2 = takestep.AdaptiveStepsize(OXDNATakestep(displace=0., rotate=parameters.rotate), frequency=50) group.addBlock(100, step1) group.addBlock(100, step2) # with a generate random configuration genrandom = OXDNAReseed() # in a reseeding takestep procedure reseed = takestep.Reseeding(group, genrandom, maxnoimprove=parameters.reseed) # store all minima in a database db = Database(db="storage.sqlite", accuracy=1e-2) # create Basinhopping object opt = BasinHopping(coords, potential, reseed, db.minimum_adder(), temperature=parameters.temperature) # run for 100 steps opt.run(parameters.nsteps) # now dump all the minima i=0 for m in db.minima(): i+=1 GMIN.userpot_dump("lowest_%03d.dat"%(i), m.coords)
# print help if no input file is given if(len(args) != 1): parser.print_help() exit(-1) infile = args[0] outfile = infile # initialize GMIN GMIN.initialize() pot = gminpotential.GMINPotential(GMIN) crystals.GMIN = GMIN # open the storage class db_in = Database(db=infile) if(options.out!=None): outfile = options.out db_out = Database(db=outfile) print("Start to reoptimize minima") for m in db_in.minima(): print("optimizing minima",m._id,", energy", m.energy) ret = dmagmin.quenchCrystal(m.coords, pot.getEnergyGradient, tol=options.tol, maxErise=options.maxErise) print("new energy",ret[1],"(difference %f)"%(ret[1] - m.energy)) print() if(options.out!=None): db_out.addMinimum(ret[1], ret[0]) else: m.energy = ret[1] m.coords = ret[0]
def main(): # add some program options parser = OptionParser(usage = "usage: %prog [options] storage") parser.add_option("--write-disconnect", dest="writeDPS", action="store_true", help="generate min.dat and ts.dat to use with disconnectDPS") parser.add_option("-m", dest="writeMinima", action="store_true", help="dump minima to screen") parser.add_option("-t", dest="writeTS", action="store_true", help="dump transition states to screen") parser.add_option("--coords", dest="writeCoords", action="store_true", help="export coordinates files") parser.add_option("--xyz", dest="writeXYZ", action="store_true", help="export xyz files") (options, args) = parser.parse_args() # print help if no input file is given if(len(args) != 1): parser.print_help() exit(-1) db = Database(db=args[0]) if(options.writeMinima): print "List of minima:" print "---------------" for m in db.minima(): print "%f\t\tid %d"%(m.energy, m._id) print "END\n" if(options.writeTS): print "List of transition states:" print "--------------------------" for ts in db.transition_states(): print "%d\t<->\t%d\tid %d\tenergies %f %f %f"%\ (ts.minimum1._id, ts.minimum2._id, ts._id, ts.minimum1.energy, ts.energy, ts.minimum2.energy) print "END\n" if(options.writeDPS): writeDPS(db) if(options.writeCoords): GMIN.initialize() i=0 for m in db.minima(): i+=1 filename = "lowest/lowest%03d.cif"%(i) print "minimum",i, "energy",m.energy,"to",filename GMIN.userpot_dump(filename, m.coords) if(not TO_PDB is None): os.system(TO_PDB%filename) np.savetxt("lowest/coords_%03d.txt"%(i), m.coords) if(options.writeXYZ): traj=open("lowest/traj.xyz", "w") i=0 for m in db.minima(): i+=1 filename = "lowest/lowest%03d.xyz"%(i) print "minimum",i, "energy",m.energy,"to",filename export_xyz(open(filename, "w"), m.coords) export_xyz(traj, m.coords) traj.close()
def main(): parser = argparse.ArgumentParser(description="print information about the database") parser.add_argument("database", type=str, help="Database file name") parser.add_argument("--write-pathsample-db", dest="write_pathsample", action="store_true", help="generate a pathsample database by writing files min.data, ts.data, points.min, and points.ts") parser.add_argument("--write-dummy-db", dest="write_pathsample_dummy", action="store_true", help="generate a pathsample database without the points files - min.data and ts.data only") parser.add_argument("-m", dest="writeMinima", action="store_true", help="dump minima to screen") parser.add_argument("--minimum", type=int, default=0, help="print the coordinates of a particular minimum to the screen") parser.add_argument("-t", dest="writeTS", action="store_true", help="dump transition states to screen") parser.add_argument("-p", dest="properties", action="store_true", help="print system properties") parser.add_argument("-s", dest="summary", action="store_true", help="print summary") parser.add_argument("-S", dest="summary_long", action="store_true", help="print long summary") parser.add_argument("-l", dest="long_output", action="store_true", help="Allow long output to be printed") args = parser.parse_args() if args.summary_long: args.summary = True suppress = not args.long_output db = Database(db=args.database, createdb=False) if args.properties or args.summary: print_system_properties(db,suppress_long=suppress) if args.summary: print "number of minima:", db.number_of_minima() print "number of transition states:", db.number_of_transition_states() if args.summary_long: long_summary(db) if args.minimum > 0: m = db.getMinimum(args.minimum) print m.energy, m._id x = m.coords.reshape(-1,3) print x if args.writeMinima: print "List of minima: energy id fvib pgorder" print "---------------" for m in db.minima(): print "%f\t\tid %d %s %s" % (m.energy, m._id, str(m.fvib), str(m.pgorder)) print "END\n" if args.writeTS: print "List of transition states:" print "--------------------------" for ts in db.transition_states(): print "%d\t<->\t%d\tid %d\tenergies %f %f %f" % \ (ts.minimum1._id, ts.minimum2._id, ts._id, ts.minimum1.energy, ts.energy, ts.minimum2.energy) print "END\n" if args.write_pathsample: write_pathsample_db(db) elif args.write_pathsample_dummy: write_pathsample_db(db, False)
''' Read structures from the database generated by the batch GA. Run the run_batch.py script to generate a database. @author: Mark Oakley ''' import numpy as np from pele.storage.database import Database db = Database(db="mydatabase.sqlite") minima=db.minima() print(db.number_of_minima()) for i in minima: print(i.energy) print(np.reshape(i.coords,(-1,3)))
import random import pickle from pele.storage.database import Database #from math import pi from pele.systems.oxdna import OXDNATakestep, export_xyz, OXDNAScrewStep # number of trial configurations to try nconf = 1000 # generate these from the n lowest minima from_nlowest = 100 # open the database with minima db = Database(db = "storage.sqlite") minima = db.minima() # make sure from_nlowest is not larger than # of minima from_nlowest = max(from_nlowest, len(minima)) # you can try a different step routine step = OXDNATakestep(displace=0.0, rotate=0.8, rotate_around_backbone=False) trial_configurations = [] for i in xrange(nconf): x = random.choice(minima[0:nconf]) coords = x.coords.copy() step.takeStep(coords) trial_configurations.append(coords) pickle.dump(trial_configurations, open("quench_benchmark.dat", "w")) fl = open("quench_benchmark.xyz", "w")
class BatchGeneticAlgorithm(object): '''The Birmingham Cluster Genetic Algorithm. A new parallel version of the BCGA. The population is stored in a pele database and can be accessed by several processes simultaneously. Parameters: natoms- Number of atoms in cluster minimiser- See bcga.gpaw_interface Optional parameters: composition- A list containing the number of atoms of each type labels- A tuple orblist containing the names of each atom type (e.g. ["Au","Ag"] pop_size- Number of clusters in population max_generation- Number of generations to run GA selector- Selection method for choosing parents (see bcga.selector) offspring- Number of crossover operations in each generation mutant_rate- Probability of any cluster producing a mutant remove_duplicates- Remove identical clusters from population to prevent stagnation restart- Read population from restart.xyz and continue a search ''' def __init__(self, natoms, minimiser, composition="default", labels=("X", ), pop_size=10, max_generation=10, selector=TournamentSelector(3), offspring=8, mutant_rate=0.1, remove_duplicates=False, mutator=MutateReplace(), crossover=DeavenHo()): #Parameters self.max_generation = max_generation self.mutant_rate = mutant_rate self.offspring = offspring self.pop_size = pop_size self.remove_duplicates = remove_duplicates self.selector = selector self.mutator = mutator self.crossover = crossover #Factory self.factory = ClusterFactory(natoms, minimiser, composition, labels) #PopulationList self.population = PopulationList(natoms, self.factory, pop_size) #Evolutionary progress self.mean_energy_series = [] self.min_energy_series = [] self.db = Database(db="mydatabase.sqlite") self.storage = self.db.minimum_adder() def write_xyz(self, file_name="cluster.xyz"): '''Open an xyz file and write the current population to it (non-blocking).''' try: with open(file_name, 'w') as xyz_file: self.population.write_xyz(xyz_file) except IOError as err: print("File error: " + str(err)) def read_xyz(self, file_name="restart.xyz"): '''Read population from an xyz file (non-blocking for now).''' self.population.mass_extinction(0) try: with open(file_name) as xyz_file: self.population.read_xyz(xyz_file) except: print("No restart file available.") def run(self): '''Run the GA.''' for generation in range(1, self.max_generation + 1): print("Generation " + str(generation)) if self.db.number_of_minima() < self.population.max_size: cluster = self.factory.get_random_cluster() print("Filling population with random structure.") else: self.population.read_db(self.db) if np.random < self.mutant_rate: index = np.random.randint(0, len(self.population)) cluster = self.mutator.get_mutant(self.population[index]) print("Generating mutant of cluster " + str(index)) else: indices = self.selector.select(self.population) cluster = self.crossover.get_offspring( self.population[indices[0]], self.population[indices[1]]) print("Generating offpsring of clusters " + str(indices[0]) + " and " + str(indices[1])) cluster.minimise() #self.read_xyz("restart.xyz") #self.population.append(cluster) self.storage(cluster.energy, cluster._coords.flatten())
step1 = takestep.AdaptiveStepsize(OXDNATakestep(displace=parameters.displace, rotate=0.), frequency=50) step2 = takestep.AdaptiveStepsize(OXDNATakestep(displace=0., rotate=parameters.rotate), frequency=50) group.addBlock(100, step1) group.addBlock(100, step2) # with a generate random configuration genrandom = OXDNAReseed() # in a reseeding takestep procedure reseed = takestep.Reseeding(group, genrandom, maxnoimprove=parameters.reseed) # store all minima in a database db = Database(db="storage.sqlite", accuracy=1e-2) # create Basinhopping object opt = BasinHopping(coords, potential, reseed, db.minimum_adder(), temperature=parameters.temperature) # run for 100 steps opt.run(parameters.nsteps) # now dump all the minima i = 0 for m in db.minima(): i += 1
import numpy as np import oxdnagmin_ as GMIN from pele.potentials.gminpotential import GMINPotential import pele.basinhopping as bh from pele.takestep import displace from pele.storage.database import Database # initialize GMIN GMIN.initialize() # create a potential which calls GMIN potential = GMINPotential(GMIN) # get the initial coorinates coords = potential.getCoords() coords = np.random.random(coords.shape) # create takestep routine step = displace.RandomDisplacement(stepsize=1.) # store all minima in a database db = Database(db="storage.sqlite", accuracy=1e-2) # create Basinhopping object opt = bh.BasinHopping(coords, potential, step, db.minimum_adder()) # run for 100 steps opt.run(1000) # now dump all the minima i = 0 for m in db.minima(): i += 1 GMIN.userpot_dump("lowest_%03d.dat" % (i), m.coords)
from builtins import range import random import pickle from pele.storage.database import Database #from math import pi from pele.systems.oxdna import OXDNATakestep, export_xyz, OXDNAScrewStep # number of trial configurations to try nconf = 1000 # generate these from the n lowest minima from_nlowest = 100 # open the database with minima db = Database(db="storage.sqlite") minima = db.minima() # make sure from_nlowest is not larger than # of minima from_nlowest = max(from_nlowest, len(minima)) # you can try a different step routine step = OXDNATakestep(displace=0.0, rotate=0.8, rotate_around_backbone=False) trial_configurations = [] for i in range(nconf): x = random.choice(minima[0:nconf]) coords = x.coords.copy() step.takeStep(coords) trial_configurations.append(coords) pickle.dump(trial_configurations, open("quench_benchmark.dat", "w"))
Input: coords.prmtop ( for number of atoms ) min.data, ts.data, extractedmin, extractedts Output: storage.sqlite """ # determine number of atoms from prmtop prmtop = AmberPrmtopFile( 'coords.prmtop' ) # TOSET natoms = prmtop.topology._numAtoms # open database db = Database(db="storage.sqlite") # TOSET def read_coords(filee): coords = np.zeros(3*natoms) for i in range(natoms): x = filee.readline().split() coords[i*3:i*3+3] = [float(y) for y in x] return coords # counter to keep track of added minima mini=1 minima={} # for timing tt = t0 = time.time()
''' Read structures from the database generated by the batch GA. Run the run_batch.py script to generate a database. @author: Mark Oakley ''' import numpy as np from pele.storage.database import Database db = Database(db="mydatabase.sqlite") minima = db.minima() print(db.number_of_minima()) for i in minima: print(i.energy) print(np.reshape(i.coords, (-1, 3)))