-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcoreClasses.py
More file actions
1688 lines (1397 loc) · 76 KB
/
Copy pathcoreClasses.py
File metadata and controls
1688 lines (1397 loc) · 76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#Core classes to act as interfaces between programs and perform simple manipulations
import numpy as np
class XYZFile:
''' Some of these may be better being staticmethods and some as instances
Beware of things where there may be lines with a character and then unit cells '''
def __init__(self,
fileName = None,
stringData = None,
maxStructures = None,
linesPerStructure = None):
# nStructures = None):
self.fileName = fileName
self.stringData = stringData
self.maxStructures = maxStructures
self.linesPerStructure = linesPerStructure
@staticmethod
def returnString(inString, swapDict = {}):
''' Quick way to rewrite files with dummy labels (use swapDict) '''
outLines = []
for line in inString.split("\n"):
if len(line) == 0:
continue
splitLine = line.split()
if splitLine[0] in swapDict.keys():
splitLine[0] = swapDict[splitLine[0]]
outLines.append(" ".join(splitLine))
return "\n".join(outLines)
def setLinesPerStructure(self):
''' The offset will depend on whether there are unit cell vectors etc '''
counter = 0
header = True
for line in open(self.fileName, 'r'):
if header and self.standardSpeciesLine(line):
header = False
elif not header and not self.standardSpeciesLine(line):
break
counter += 1
self.linesPerStructure = counter
return self.linesPerStructure
@staticmethod
def standardSpeciesLine(testLine):
''' A standard species line has 4 things [element] [x] [y] [z]
The element must begin with a character- the x,y,z are numbers '''
splitLine = testLine.split()
if len(splitLine) != 4:
return False
# Deal with cases where atom has a numerical index at another time
# print 'MASSIVE HACK IN CORECLASSES.XYZ';return True
if splitLine[0][0].isalpha() and all([x.replace('.', '', 1).replace('e', '', 1).replace('-', '', 1).replace('E', '', 1).isdigit() for x in splitLine[1:]]):
return True
return False
@staticmethod
def nStructures(fileName,
linesPerStructure):
''' calculate linesPerStructure manually or with quick subroutine
watch for blank lines '''
num_lines = float( sum(1 for line in open(fileName, 'r')) )
nStructures = num_lines / float(linesPerStructure)
try:
assert(nStructures.is_integer())
except:
raise Exception('Error XYZFile.nStructures')
return int(nStructures)
@staticmethod
def returnXYZStrings(fileName,
linesPerStructure,
maxNStructures = None,
selectList = None,
returnIndex = False):
''' Not fully tested or commented '''
counter = 0
yieldCounter = 0
structureCounter = 0
lines = []
# define max structures if selectList to make this quicker
if maxNStructures is None and selectList is not None:
maxNStructures = len(selectList)
for line in open(fileName, 'r'):
counter += 1
lines.append(line)
if len(lines) == linesPerStructure:
#do not allow blank lines at end of string
lines[-1].replace('\n', '')
if not selectList or structureCounter in selectList:
if returnIndex:
yield (yieldCounter, "".join(lines))
else:
yield "".join(lines)
yieldCounter += 1
lines = []
structureCounter += 1
# test this!!!! GeneratorExit ???
if maxNStructures and yieldCounter >= maxNStructures:
raise StopIteration
@staticmethod
def xyzStringToCellVectors(xyzString):
''' Cell info on second line-- assume orthorhombic for now- easy to add if need '''
cellInfo = xyzString.split("\n")[1].split()
#change this if need to go beyond orthorhombic - assume second line is only this cell info
# assert(len(cellInfo) == 3)
if len(cellInfo) == 3:
return np.diag(map(float, cellInfo))
elif len(cellInfo) == 6:
print 'warning, better to calculate angles etc and turn to cell'
return np.array(map(float, [cellInfo[0], cellInfo[3], cellInfo[4], 0., cellInfo[1], cellInfo[5], 0., 0., cellInfo[2]])).reshape((3,3))
@staticmethod
def xyzStringToSpeciesList(xyzString, speciesDict=None, offset = 0):
''' Clean all this up at some point
Atom Dict is in case of labelling atoms 1,2,3 not Fe, S, O etc - also can add other info if with in the atomDict
atm the species dict should have str for keys '''
from copy import deepcopy
speciesList = []
for l in xyzString.split("\n")[offset:]:
if XYZFile.standardSpeciesLine(l):
if speciesDict:
# tempAtom = deepcopy(speciesDict[int(l.split()[0])])
tempAtom = deepcopy(speciesDict[l.split()[0]])
tempAtom.cartCoord = np.array(l.split()[1:], dtype='float64')
else:
tempAtom = Species(element = l.split()[0],
cartCoord = np.array(l.split()[1:], dtype='float64') )
speciesList.append(tempAtom)
return speciesList
####### THIS IS NOT READY !!!!!!!!!!!!!!!#########
@staticmethod
def xyzList(self, inFile, returnObjects = 'xyzString'):#, containsUnitCell = True):
''' Read file and break into list of xyzStrings, PMGStructures, or Structures
Either give a string, or dummy instance of either structure type-- do ASE if ever needed'''
print "XYZ may or may not have unit cell - should work it out automatically"
structureList = []
topBufferLines = 2
counter = 0
lines = []
for line in open(historyFileName):
counter += 1
if counter <= topBufferLines:
nAtoms = line.split()[-1]
continue
exit()
timestepBufferLines = int(nAtoms) * 2 + 4
lines.append(line)
if len(lines) == timestepBufferLines:
# this should just be the quickest thing to break up the file
if isInstance(returnObjects, str):
structureList.append("\n".join(lines))
if maxStructures and len(structureList)>= maxStructures:
return structureList
lines = []
continue
latticeVectors = np.array([x.split() for x in lines[1:4]], dtype='float64')
species, coords = [], []
offset = 4
for ix, x in enumerate(lines[offset:]):
if ix%2==0:
if x == '' or (ignoreShells and 'shl' in x.split()[0]):
continue
elif selectOnlySpecie and x.split()[0].replace('_', '') != selectOnlySpecie:
continue
else:
species.append(x.split()[0].replace('_', ''))
coords.append( np.array(lines[ix+1+offset].split(), dtype='float64') )
if selectOnlySpecie and addOrigin:
species.append('X')
coords.append(np.zeros(3))
if isInstance(returnObject, PMGS ):
structureList.append( PMGS(PMGL(latticeVectors),
species,
coords,
coords_are_cartesian = True)
)
elif isInstance(returnObject, Structure):
structureList.append(Structure(unitCell = UnitCell(vectors = latticeVectors),
speciesList = [Species(element = species[i],
cartCoord = coords[i]) for i in xrange(species)])
)
if maxStructures and len(structureList)>= maxStructures:
return structureList
lines = []
return structureList
class Species:
def __init__(self,
label = None,
potentialLabel = None,
element = None,
mass = None,
charge = None,
fracCoord = None,
cartCoord = None,
cartVelocity = None,
cartForce = None,
core = 'core'
):
self.label = label
self.potentialLabel = potentialLabel
self.element = element
self.mass = mass
self.charge = charge
self.fracCoord = fracCoord
self.cartCoord = cartCoord
self.core = core
self.cartVelocity = cartVelocity
self.cartForce = cartForce
def toASEAtom(self):
from ase import Atom
return Atom(str(self.element), self.cartCoord)
@classmethod
def initFromASEAtom(cls, aseAtom):
return cls(cartCoord = aseAtom.position,
element = aseAtom.symbol)
@classmethod
def initFromPMGSite(cls, pmgSite):
''' Strictly PMG has _sites and so on ...
_species sits on _site '''
return cls(cartCoord = pmgSite._coords,
fracCoord = pmgSite._fcoords,
element = pmgSite._species._data.keys()[0].symbol)
@staticmethod
def elementFromPMGSpecies(pmgSpecies):
''' Should return element string from an instance of a PMG _species '''
return pmgSpecies._data.keys()[0].symbol
def dlpolyLabelStandard(self):
# for the moment try to use just this standard labelling scheme
outString = self.element
if len(self.element) < 2:
outString += "_"
if self.core[:4] == 'shel':
outString += "-shl"
return outString
def setThermalVelocity(self, temp):
''' Velocity is normal with mu=0, sigma = (kT/m)**0.5
because of shells, atm the mass is standard for core and v is 0 for shell '''
if self.core[:4] == 'shel':
self.cartVelocity = np.zeros(3)
return self.cartVelocity
import random
from scipy.stats import norm
from ase.data import atomic_masses, chemical_symbols
# per mol
sigma = (6.0221367e23 * 1.380658e-23 * temp / atomic_masses[chemical_symbols.index(self.element)]) ** 0.5
self.cartVelocity = np.array([norm.ppf(random.random(), scale=sigma) for _ in xrange(3)])
return self.cartVelocity
def atomicValenceElectrons(self):
from hardcode import atomicValenceElectrons
return atomicValenceElectrons[self.element]
def defaultCharge(self):
''' Use this v. sparingly
Do not assume charges are standard- always specify except for quick plots etc '''
from hardcode import defaultCharges
return defaultCharges[self.element]
class Defect():#Species):
# Do super classes later- python 2 vs 3 issues (and I did it wrong the first time)
def __init__(self,
defectType = None,
species = None):
# super(Species, self).__init__(species)
self.species = species
self.defectType = defectType
def charge(self):
# minus the charge....
return None
class UnitCell:
# add vectors and stuff as needed
def __init__(self,
angles = None,
lengths = None,
vectors = None):
self.angles = angles
self.lengths = lengths
self.vectors = vectors
self.invVectors = None
if self.vectors is None and self.angles is not None and self.lengths is not None:
self.vectors = self.calculateVectors(self.lengths, self.angles)
if self.vectors is not None and self.angles is None and self.lengths is None:
self.lengths = np.array(map(np.linalg.norm, [self.vectors[i] for i in xrange(3)]))
self.angles = (180. / np.pi) * np.array(map(np.arccos,[np.dot(self.vectors[1], self.vectors[2]) / (self.lengths[1] * self.lengths[2]),
np.dot(self.vectors[0], self.vectors[2]) / (self.lengths[0] * self.lengths[2]),
np.dot(self.vectors[0], self.vectors[1]) / (self.lengths[0] * self.lengths[1])]))
@staticmethod
def generalLengthsAnglesMatrix(matrix, anglesDegrees = True):
''' If have matrix in unusual form, can get angles and lengths
Useful e.g. with LAMMPS cells
matrix is numpy array with indices (fractional, cartesian)
'''
if anglesDegrees:
convFactor = 180./np.pi
else:
convFactor = 1.
lengths = np.array([np.linalg.norm(matrix[x, :]) for x in xrange(3)])
angles = convFactor * np.array(map(np.arccos, [np.dot(matrix[1], matrix[2]) / (lengths[1] * lengths[2]),
np.dot(matrix[0], matrix[2]) / (lengths[0] * lengths[2]),
np.dot(matrix[0], matrix[1]) / (lengths[0] * lengths[1])]))
return lengths, angles
def calculateVectors(self, lengths, angles):
"""
stolen from pymatgen- will possibly use their code in future
"""
a = lengths[0]
b = lengths[1]
c = lengths[2]
alpha_r = angles[0] * np.pi / 180.
beta_r = angles[1] * np.pi / 180.
gamma_r = angles[2] * np.pi / 180.
val = (np.cos(alpha_r) * np.cos(beta_r) - np.cos(gamma_r))\
/ (np.sin(alpha_r) * np.sin(beta_r))
#Sometimes rounding errors result in values slightly > 1.
val = min([max([-1., val]), 1.])
gamma_star = np.arccos(val)
vector_a = [a * np.sin(beta_r), 0.0, a * np.cos(beta_r)]
vector_b = [-b * np.sin(alpha_r) * np.cos(gamma_star),
b * np.sin(alpha_r) * np.sin(gamma_star),
b * np.cos(alpha_r)]
vector_c = [0.0, 0.0, float(c)]
self.vectors = np.array([vector_a, vector_b, vector_c])
return self.vectors
def setInvVectors(self):
self.invVectors = np.linalg.inv(self.vectors)
def createGrid(self, targetSeparation = 1., includeExtraPoint = True, returnMGrid = False, returnInfo = False, columnMajor = False):
''' Return a grid with roughly targetSeparation along axes (use meshgrid for certain things)
Normal use is (Npts,3) numpy array
Alternatively, return 3 (nx,ny,nz) arrays- see mgrid vectorization '''
if includeExtraPoint:
extraPt = 1
else:
extraPt = 0
nPts = np.floor(self.lengths / targetSeparation)
nPtsI = np.array(np.floor(self.lengths / targetSeparation), dtype=int)
if returnMGrid:
print 'not implemented yet';exit()
if includeExtraPoint:
return np.mgrid[0:1:nPtsI[0]*1j,
0:1:nPtsI[1]*1j,
0:1:nPtsI[2]*1j]
#mgrid must be orthogonal??
# return [np.dot(x, self.vectors) for x in np.mgrid[0:1:nPtsI[0]*1j,
# 0:1:nPtsI[1]*1j,
# 0:1:nPtsI[2]*1j]]
#delete this
if returnInfo:
print "Grid = (%s, %s, %s)"%(nPtsI[0] + extraPt,
nPtsI[1] + extraPt,
nPtsI[2] + extraPt)
if columnMajor:
outpoints = np.dot([(i,j,k)/nPts for k in xrange(nPtsI[2] + extraPt)
for j in xrange(nPtsI[1] + extraPt)
for i in xrange(nPtsI[0] + extraPt)], self.vectors)
else:
outpoints = np.dot([(i,j,k)/nPts for i in xrange(nPtsI[0] + extraPt)
for j in xrange(nPtsI[1] + extraPt)
for k in xrange(nPtsI[2] + extraPt)], self.vectors)
if returnInfo:
return (nPtsI + np.array([extraPt, extraPt, extraPt]), outpoints)
return outpoints
@classmethod
def fromXYZFile(cls, xyzFile, transposeVectors = False):
if transposeVectors:
return cls(vectors = XYZFile.xyzStringToCellVectors(open(xyzFile.fileName, 'r').read()).T)
else:
return cls(vectors = XYZFile.xyzStringToCellVectors(open(xyzFile.fileName, 'r').read()))
def vertices(self, fractional = True):
''' vertices of the unit cell - property?? '''
fracVertices = np.array([(i,j,k) for i in xrange(2) for j in xrange(2) for k in xrange(2)])
if not fractional:
return np.dot(fracVertices, self.vectors)
else:
return fracVertices
@classmethod
def randomCell(cls,
angles = np.array([90., 90., 90]),
lengths = None,
angleRandomNumbers = None,
lengthRandomNumbers = None,
targetVolume = None):
''' Generate a random cell
see organic structure generation (ask Dave) for details '''
if angleRandomNumbers is not None:
asin_val = np.arcsin(2.0 * angleRandomNumbers - 1.0 ) / np.pi
minAngle, delta = 60., 120. - 60.
_angles = (0.5 + asin_val)*delta + minAngle
else:
_angles = angles
if lengths is not None:
_lengths = lengths
else:
d2r = np.pi/180.
from scipy.stats import norm
vStar = (1.0+ 2.0 * np.cos(_angles[0]*d2r) * np.cos(_angles[1]*d2r) * np.cos(_angles[2]*d2r) - np.cos(_angles[0]*d2r)**2 - np.cos(_angles[1]*d2r)**2 - np.cos(_angles[2]*d2r)**2 )**0.5
_sigma = 2.
_lengths = np.array([norm.ppf(x) * _sigma + (targetVolume/vStar)**(1./3.) for x in np.clip(lengthRandomNumbers, 0.01, 0.99)])
# if rand_vec.unit_cell_lengths[0] > 0.99:
# temp_lengths [ 0 ] = norm.ppf(0.99) * sd_param * self.length_bounds[0][0] + mean0
# elif rand_vec.unit_cell_lengths[0]<0.01:
# temp_lengths [ 0 ] = norm.ppf(0.01) * sd_param * self.length_bounds[0][0] + mean0
# else:
# temp_lengths [ 0 ] = norm.ppf(rand_vec.unit_cell_lengths[0]) * sd_param * self.length_bounds[0][0] + mean0 _sigma = 0.1
# _lengths =
# _lengths = np.random.normal(targetVolume ** (1./3.),
# _sigma,
# 3)
return cls(angles = np.array(_angles),
lengths = np.array(_lengths))
class Potential:
''' At the moment, just a holder '''
# no need to __init__ ???
@staticmethod
def adaptRemoveShells(potsIn):
''' Input a list of pots, change VBuckingham species.core -> core '''
from copy import deepcopy
outPots = []
for x in potsIn:
if isinstance(x, VBuckingham):
x.species1.core = 'core'
x.species2.core = 'core'
outPots.append(x)
return outPots
class VSpring:
''' Assume that spring between species1 core and shel '''
def __init__(self,
species1 = None,
K = None):
self.species1 = species1
self.K = K
def stringForm(self):
if self.species1.potentialLabel:
l1 = self.species1.potentialLabel
else:
l1 = self.species1.element
return "spring\n%s %s"%(l1, self.K)
class VBuckingham:
''' Follow 1.4.3 GULP manual (rho is length (angstrom))
fitA etc are flags (ignore for now) '''
def __init__(self,
species1 = None,
species2 = None,
A = None,
rho = None,
C6 = None,
name = 'buck',
toBeFitted = False,
cutMin = 0.,
cutMax = 12.,
fitA = 0,
fitRho = 0,
fitC6 = 0):
self.species1 = species1
self.species2 = species2
self.A = A
self.fitA = fitA
self.rho = rho
self.fitRho = fitRho
self.C6 = C6
self.fitC = fitC6
self.cutMin = cutMin
self.cutMax = cutMax
def energy(self, r, chargeProduct = None):
''' Units are same as A or C6, r is rho^{-1} (what does this mean???)
Set chargeProduct to 'auto' if want to use defaults (check before using, e.g. Fe 2/3+ and so on) '''
from hardcode import hartrees2eV, bohr2angstrom
if type(chargeProduct) == float:
return self.A * np.exp(-r / self.rho) - self.C6 * r **-6
elif type(chargeProduct) == str and chargeProduct.lower() == 'auto':
return self.species1.defaultCharge() * self.species2.defaultCharge() * hartrees2eV * (r / bohr2angstrom)**-1 +\
self.A * np.exp(-r / self.rho) - self.C6 * r **-6
else:
return self.A * np.exp(-r / self.rho) - self.C6 * r **-6
def stringForm(self):
''' specify potential labels if you want different types of same atom
N.B. THIS IS GULP STRING FORM '''
if self.species1.potentialLabel:
l1 = self.species1.potentialLabel
else:
l1 = self.species1.element
if self.species2.potentialLabel:
l2 = self.species2.potentialLabel
else:
l2 = self.species2.element
return "buck\n" + " ".join([l1] + [self.species1.core] +
[l2] + [self.species2.core] +
[str(self.A)] + [str(self.rho)] + [str(self.C6)] +
[str(self.cutMin)] + [str(self.cutMax)])
def dlpolyString(self):
return " ".join([self.species1.dlpolyLabelStandard(),
self.species2.dlpolyLabelStandard(),
"buck",
str(self.A),
str(self.rho),
str(self.C6)])
@staticmethod
def removeSpeciesFromListBucks(listPots, species, attributeList = ['element']):
''' listPots can be non buckingham, but just remove elements which are
buckingham and contain species '''
from setTools import sameElementByAttributes
return [x for x in listPots if x.__class__.__name__ != 'VBuckingham'
or (not sameElementByAttributes(x.species1, species, attributeList)
and not sameElementByAttributes(x.species2, species, attributeList))]
@staticmethod
def latexTableFromListBucks(listPots, fileName = None):
''' From a list of potentials, make a latex table from the Buckingham pots
if fileName, write to this file, otherwise return a string '''
stringOut = r'\begin{tabular}{lrrr}' + "\n"
stringOut += r'\toprule' + "\n"
stringOut += r'Species & A & $\rho$ & C$_{6}$ \\' + "\n"
stringOut += r'\midrule' + "\n"
for p in listPots:
if p.__class__.__name__ == 'VBuckingham':
a= " & ".join(map(str, [p.species1.element + "-" + p.species2.element,
p.A,
p.rho,
p.C6]))
stringOut += a + r' \\' + "\n"
stringOut += r'\bottomrule' + "\n"
stringOut += r'\end{tabular}' + "\n"
if fileName is not None:
with open(fileName, 'w') as outf:
outf.write(stringOut)
return stringOut
@staticmethod
def plotListBuckinghams(listPots, nPts = 100, xBounds = [1., 8.], figsize=(10,10),
speciesList = None, matchAttributes = ['element']):
''' Make a matplotlib output for each VBuckingham '''
import matplotlib.pyplot as plt
from setTools import subsetByAttributes
listBucks = [x for x in listPots if x.__class__.__name__ == 'VBuckingham' and
subsetByAttributes([x.species1], speciesList, matchAttributes) and
subsetByAttributes([x.species2], speciesList, matchAttributes)]
xPts = [min(xBounds) + (max(xBounds) - min(xBounds)) * x/nPts for x in xrange(nPts)]
plt.subplots(figsize=figsize)
for i, lb in enumerate(listBucks):
plt.subplot(len(listBucks), 1, i)
plt.plot(xPts, [lb.energy(x, chargeProduct='auto') for x in xPts])
plt.title(lb.species1.element + "_" + lb.species2.element)
plt.show()
# return listBucks
def fitToLJ(self,
fitBounds = np.array([1., 2.5]),
fitPoints = 100,
initialGuess = np.array([10., 0.])):
''' return an LJ potential with fitted parameters '''
from scipy.optimize import fmin
# outLJPot =
from scipy.optimize import fmin
testPoints = np.array([np.min(fitBounds) + (np.max(fitBounds) - np.min(fitBounds)) * x / float(fitPoints) for x in xrange(fitPoints)])
def squareDifference(constants):
''' constants are np.array([c12, c6]) '''
if constants[0] < 0. or constants[1] > 0.:
penalty=1000.
else:
penalty = 0.
return sum([(self.energy(r) - VLennardJones.r6r12Energy(r, constants[0], constants[1]))**2 for r in testPoints]) + penalty
# return sum([(self.energy(r) - VLennardJones.r6r12Energy(r, constants[0], constants[1]) / self.energy(r))**2 for r in testPoints])
print testPoints
outputMinimization = fmin(squareDifference, initialGuess)
print self.energy(2.), VLennardJones.r6r12Energy(2., outputMinimization[0], outputMinimization[1])
print outputMinimization, "c12 c6"
# print self.energy(1.), VLennardJones.r6r12(1.,
# outLJPot.c6 =
return VLennardJones(species1 = self.species1,
species2 = self.species2,
c6 = outputMinimization[1],
c12 = outputMinimization[0])
class VLennardJones:
# be clear about conventions- note LAMMPS may be different (and note signs etc)
def __init__(self,
species1 = None,
species2 = None,
c6 = None,
c12 = None):
self.species1 = species1
self.species2 = species2
self.c6 = c6
self.c12 = c12
def energy(self, r):
return self.c12 * r**(-12) - self.c6 * r**(-6)
@staticmethod
def r6r12Energy(r, c12, c6):
return c12 * r**(-12) - c6 * r**(-6)
def returnEpsilonSigma(self):
print self.c6, self.c12
epsilon = self.c6 **2 / (4. * self.c12)
sigma = (self.c12 / self.c6)**(1./6.)
return np.array([epsilon, sigma])
class VThreeBody:
''' Follow 1.4.3 GULP manual (rho is inverse length)
fitA etc are flags (ignore for now) '''
def __init__(self,
species1 = None,
species2 = None,
species3 = None,
K = None,
theta0 = None,
cut12 = None,
cut13 = None,
cut23 = None):
self.species1 = species1
self.species2 = species2
self.species3 = species3
self.K = K
self.theta0 = theta0
self.cut12 = cut12
self.cut13 = cut13
self.cut23 = cut23
def stringForm(self):
''' specify potential labels if you want different types of same atom '''
if self.species1.potentialLabel:
l1 = self.species1.potentialLabel
else:
l1 = self.species1.element
if self.species2.potentialLabel:
l2 = self.species2.potentialLabel
else:
l2 = self.species2.element
if self.species3.potentialLabel:
l3 = self.species3.potentialLabel
else:
l3 = self.species3.element
return "three\n" + " ".join([l1] + [self.species1.core] +
[l2] + [self.species2.core] +
[l3] + [self.species3.core] +
[str(self.K)] + [str(self.theta0)] +
[str(self.cut12)] + [str(self.cut13)] + [str(self.cut23)])
# buck coulomb.. inherit buckingham and coulomb
class SymmetryGroup:
''' Most likely a space group (point groups are for molecules)
elementList is going to be in pmg form (so use element.affine_matrix)
self.elementList[0].__class__ = <class 'pymatgen.core.operations.SymmOp'> '''
def __init__(self,
labelHM = None,
number = None,
elementList = []):
self.labelHM = labelHM
self.number = number
self.elementList = elementList
@classmethod
def fromCif(cls, fileName):
''' wrapper- could be useful to use pmg stuff '''
from pymatgen.core import Structure as PMGS
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
analyzer = SpacegroupAnalyzer(PMGS.from_file(fileName))
return cls(labelHM = analyzer.get_space_group_symbol(),
number = analyzer.get_space_group_number(),
elementList = analyzer.get_space_group())
def generateUniquePoints(self, testPts, cut = 1.e-5, boundUnitCell = False):
outList = []
for t in testPts:
for e in self.elementList:
# for x in e.operate_multi(testPts):
x = e.operate(t)
if boundUnitCell:
testPoint = x - np.floor(x)
else:
testPoint = x
if not any([np.linalg.norm(testPoint - y) < cut for y in outList]):
outList.append(testPoint)
return np.array(outList)
class Structure:
def __init__(self,
unitCell = None,
speciesList = [],
symmetryGroup = None):
self.unitCell = unitCell
self.speciesList = speciesList
self.symmetryGroup = symmetryGroup
def cartCoords(self):
''' List of cart coords - remove shells later if needed'''
if self.speciesList[0].cartCoord is None:
self.setCartCoord()
return np.array([x.cartCoord for x in self.speciesList])
def fracCoords(self):
''' List of frac coords - remove shells later if needed'''
if self.speciesList[0].fracCoord is None:
self.setFracCoord()
return np.array([x.fracCoord for x in self.speciesList])
@classmethod
def subSection(cls, parentStructure, limits):
''' Returns new structure which is cut-out
just take the bit between limits (in frac coords)
should use a P1 cell
limits are np.array([[lower bound vector], [upper bound vector]])'''
lengthReductionRatio = limits[1] - limits[0]
invRatio = np.array([1./x for x in lengthReductionRatio])
newSpeciesList = [x for x in parentStructure.speciesList if all([x.fracCoord[i] < limits[1][i] for i in xrange(3)]) and
all([x.fracCoord[i] > limits[0][i] for i in xrange(3)])]
for i in xrange(len(newSpeciesList)):
tempFC = newSpeciesList[i].fracCoord * invRatio
newSpeciesList[i].fracCoord = tempFC - np.floor(tempFC)
return cls(unitCell = UnitCell(vectors = np.array([parentStructure.unitCell.vectors[i] * (limits[1][i] - limits[0][i]) for i in xrange(3)])),
speciesList = newSpeciesList)
@classmethod
def cutOutParallelepiped(cls, parentStructure, parallelepiped, origin = np.zeros(3), periodicTestCutoff = None):
''' Rather than just take sub section (above), can allow a general parallelepiped to be cut out
Should be same as above for orthorhombic cell and no rotation (not checked yet)
parallelepiped is np.array().shape = (3,3)
periodicTestCutoff is in \\A - it will look to see whether another atom is found just outside box on other side '''
from copy import deepcopy
invVectors = np.linalg.inv(parallelepiped)
newSpeciesList = []
parentStructure.setFracCoord()
for i in xrange(len(parentStructure.speciesList)):
# fracCoord = np.dot(parentStructure.speciesList[i].cartCoord, invVectors)
fracCoord = np.dot(parentStructure.speciesList[i].cartCoord - origin, invVectors)
if all([x >= 0. and x < 1. for x in fracCoord]):
newSpeciesList.append(Species(element = parentStructure.speciesList[i].element,
core = parentStructure.speciesList[i].core,
charge = parentStructure.speciesList[i].charge,
fracCoord = fracCoord,
cartCoord = np.dot(fracCoord, parallelepiped)))
# parentStructure.speciesList[i].fracCoord = fracCoord
# parentStructure.speciesList[i].cartCoord = np.dot(fracCoord, parallelepiped)
# newSpeciesList.append(deepcopy(parentStructure.speciesList[i]))
#note that at this stage returning an unrotated cell 050517
return cls(unitCell = UnitCell(vectors = parallelepiped),
speciesList = newSpeciesList)
def withinPeriodicEnvelopment(self, bigCell, dist, distVec = None):
''' quasi periodicity is tested- are there similar atoms to ones near edges one cell translation away?
make big cell to include things that were not in self
distVec is vector of length dist along (1,1,1) - (0,0,0) '''
# if not given a vector to pick fractional cutoffs, use this
if distVec is None:
distVec = np.ones(3) * 3**-0.5 * dist
fractionalCutoffs = np.vstack([np.dot(distVec, np.linalg.inv(self.unitCell.vectors)),
1. - np.dot(distVec, np.linalg.inv(self.unitCell.vectors))])
# total if statements = 6 faces, 12 edges, 8 vertices = 26 (labelled f,c,v below)
for at in self.speciesList:
#fx0
if at.fracCoord[0] < fractionalCutoffs[0, 0]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exy0
if at.fracCoord[1] < fractionalCutoffs[0, 1]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] + self.unitCell.vectors[1]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxyz0
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] + self.unitCell.vectors[1] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxyz1
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] + self.unitCell.vectors[1] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exy1
if at.fracCoord[1] > fractionalCutoffs[1, 1]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] - self.unitCell.vectors[1]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxzy2
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] - self.unitCell.vectors[1] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxyz3
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] - self.unitCell.vectors[1] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exz0
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exz1
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[0] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#fx1
if at.fracCoord[0] > fractionalCutoffs[1, 0]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exy2
if at.fracCoord[1] < fractionalCutoffs[0, 1]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] + self.unitCell.vectors[1]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxyz4
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] + self.unitCell.vectors[1] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxyz5
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] + self.unitCell.vectors[1] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exy3
if at.fracCoord[1] > fractionalCutoffs[1, 1]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] - self.unitCell.vectors[1]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxzy6
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] - self.unitCell.vectors[1] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#vxyz7
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] - self.unitCell.vectors[1] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exz2
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#exz3
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[0] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#fy0
if at.fracCoord[1] < fractionalCutoffs[0, 1]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[1]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#eyz0
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[1] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#eyz1
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[1] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#fy1
if at.fracCoord[1] > fractionalCutoffs[1, 1]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[1]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#eyz2
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[1] + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#eyz3
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[1] - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#fz0
if at.fracCoord[2] < fractionalCutoffs[0, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord + self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
#fz1
if at.fracCoord[2] > fractionalCutoffs[1, 2]:
if not any([np.linalg.norm(at.cartCoord - x.cartCoord - self.unitCell.vectors[2]) < dist for x in bigCell.speciesList if x.element == at.element]):
return False
return True
def changeUnitCell(self, inLimits, retainUnitCell = False):
''' Changes this unit cell, such that atoms are now between new limits
N.B. only an origin at 0,0,0 is used for definition of unit cell atm
and this subroutine assumes that input self is in usual cell convention - [0,1)^3 (I believe)
example use: myStructureInstance.changeUnitCell(np.array([[-1,2], [-1,2], [-1,2]]),
retainUnitCell = True) '''