crystalcell 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/lib/crystalcell/atom.rb +133 -0
- data/lib/crystalcell/cell.rb +547 -0
- data/lib/crystalcell/element.rb +148 -0
- data/lib/crystalcell/latticeaxes.rb +210 -0
- data/lib/crystalcell/periodiccell.rb +277 -0
- data/lib/crystalcell.rb +0 -0
- data/test/helper.rb +17 -0
- data/test/test_atom.rb +210 -0
- data/test/test_cell.rb +1167 -0
- data/test/test_element.rb +656 -0
- data/test/test_latticeaxes.rb +294 -0
- data/test/test_periodiccell.rb +965 -0
- metadata +197 -0
@@ -0,0 +1,547 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
require "matrix"
|
5
|
+
|
6
|
+
require "crystalcell/atom.rb"
|
7
|
+
require "crystalcell/latticeaxes.rb"
|
8
|
+
|
9
|
+
require "rubygems"
|
10
|
+
gem "builtinextension"
|
11
|
+
require "array_select_indices.rb"
|
12
|
+
|
13
|
+
gem "mageo"
|
14
|
+
require "mageo/vector3d.rb"
|
15
|
+
require "mageo/vector3dinternal.rb"
|
16
|
+
|
17
|
+
gem "maset"
|
18
|
+
require "maset/mapping.rb"
|
19
|
+
|
20
|
+
# Class for crystal cell with lattice axes and atoms.
|
21
|
+
# Symmetry operations are not considered in this class.
|
22
|
+
# A sub class SymmetricCell can do, which overrides equal_in_delta methods.
|
23
|
+
#
|
24
|
+
# Written by Ippei Kishida [2010-12-19].
|
25
|
+
#
|
26
|
+
## Cell
|
27
|
+
# セル内の原子は、内部的には配列として保持するが、
|
28
|
+
# この順序は制御できないものとする。
|
29
|
+
# たとえば Li, Ge, O の順序にソートされているなどと思ってはいけない。
|
30
|
+
# 順序に依存するプログラムを作ってはいけない。
|
31
|
+
#
|
32
|
+
## Note:
|
33
|
+
# Cell クラスは元素情報をネイティブには持たない
|
34
|
+
# ボツ案:
|
35
|
+
# たとえば構成元素の情報を持ち、
|
36
|
+
# さらに Atom クラスインスタンスも持つとする。
|
37
|
+
# 原子の追加の仕方によっては、
|
38
|
+
# Atoms クラスの元素情報と矛盾するという状況は十分に考えられる。
|
39
|
+
#
|
40
|
+
# 構成元素として Li があっても、
|
41
|
+
# Li 原子リストが空リストだったらその元素はあると判定されるべきか、
|
42
|
+
# 疑問が生じる。
|
43
|
+
class Cell
|
44
|
+
|
45
|
+
class NoAtomError < Exception; end
|
46
|
+
class AxesMismatchError < Exception; end
|
47
|
+
class AxesRangeError < Exception; end
|
48
|
+
class SameAxesError < Exception; end
|
49
|
+
class TypeError < Exception; end
|
50
|
+
class ArgumentError < Exception; end # その他的エラー
|
51
|
+
|
52
|
+
attr_reader :element_names, :atoms, :axes
|
53
|
+
attr_accessor :comment
|
54
|
+
|
55
|
+
# Argument 'axes' must have :to_a method and expressed in 3x3 Array.
|
56
|
+
def initialize(axes, atoms = [])
|
57
|
+
#raise CellTypeError unless axes.is_a?(Axes)
|
58
|
+
if axes.class == LatticeAxes
|
59
|
+
@axes = axes
|
60
|
+
else
|
61
|
+
@axes = LatticeAxes.new( axes.to_a )
|
62
|
+
end
|
63
|
+
|
64
|
+
atoms.each do |atom|
|
65
|
+
#pp atom
|
66
|
+
unless atom.is_a?(Atom)
|
67
|
+
raise CellTypeError,
|
68
|
+
"#{atom} is not a kind of Atom."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
@atoms = atoms
|
72
|
+
end
|
73
|
+
|
74
|
+
# セルに原子を追加する。
|
75
|
+
def add_atom(atom)
|
76
|
+
#raise "Cell::add_atom, 2nd argument must be Array." if pos.class != Array
|
77
|
+
raise CellTypeError unless atom.is_a?(Atom)
|
78
|
+
@atoms << atom
|
79
|
+
end
|
80
|
+
|
81
|
+
# Delete an atom from a cell.
|
82
|
+
# i は Cell クラスが保持している原子の番号。
|
83
|
+
# Cell クラスは原子を配列として保持しており、
|
84
|
+
# その番号を指定すると考えると分かり易かろう。
|
85
|
+
def delete_atom( i )
|
86
|
+
#raise "Atom ID[#{i}] not exist" if @atoms[i] == nil
|
87
|
+
@atoms.delete_at( i )
|
88
|
+
end
|
89
|
+
|
90
|
+
# 全ての原子の元素情報のリストを返す。
|
91
|
+
# unique なものを抽出したりはしない。
|
92
|
+
# unique なものが必要なら返り値に .uniq をつければ良い。
|
93
|
+
# e.g., #=> ['Li', 'N', 'Li']
|
94
|
+
# e.g., #=> [0, 1, 2, 1]
|
95
|
+
def elements
|
96
|
+
@atoms.collect{ |i| i.element }
|
97
|
+
end
|
98
|
+
|
99
|
+
# 全ての原子の位置情報のリストを返す。
|
100
|
+
def positions
|
101
|
+
@atoms.collect{ |i| i.position }
|
102
|
+
end
|
103
|
+
|
104
|
+
# 元素情報が elem の原子の index を配列にまとめて返す。
|
105
|
+
# index は原子の永続的な id ではない。
|
106
|
+
# Array#select は index ではなく要素そのものを配列にして返すので、少し違う。
|
107
|
+
def select_indices( &block )
|
108
|
+
return @atoms.select_indices( &block )
|
109
|
+
end
|
110
|
+
|
111
|
+
# Set element name to each atom in self.
|
112
|
+
# Argument 'elems' is a list of new names, which has [] method. e.g.,
|
113
|
+
# 1. Array, [ 'Li', 'O' ]
|
114
|
+
# 2. Hash , { 0 => 'Li', 1 => 'O' ]
|
115
|
+
# 3. Hash , { 'Li' => 'Na' }
|
116
|
+
# 1. and 2. of the above examples induce the same result.
|
117
|
+
# Case 1. can be convenient for element names of array from POTCAR.
|
118
|
+
#
|
119
|
+
# The atoms with the name which is not included the hash key do not change their names.
|
120
|
+
def set_elements( elems )
|
121
|
+
@atoms.each do |atom|
|
122
|
+
begin
|
123
|
+
new_elem = elems[ atom.element ]
|
124
|
+
rescue
|
125
|
+
next
|
126
|
+
end
|
127
|
+
next if new_elem == nil
|
128
|
+
atom.element = new_elem
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
#セルを拡張したスーパーセルを考えたとき、中に含まれる原子のリストを返す。
|
133
|
+
#引数の意味は以下の通り。
|
134
|
+
#a_min : a 軸方向のセルの方向を整数で示したときの最小値
|
135
|
+
#a_max : a 軸方向のセルの方向を整数で示したときの最大値
|
136
|
+
#b_min : b 軸方向のセルの方向を整数で示したときの最小値
|
137
|
+
#b_max : b 軸方向のセルの方向を整数で示したときの最大値
|
138
|
+
#c_min : c 軸方向のセルの方向を整数で示したときの最小値
|
139
|
+
#c_max : c 軸方向のセルの方向を整数で示したときの最大値
|
140
|
+
#-1, 1, -1, 1, -1, 1 と指定すれば 3x3x3 の 27倍体積の構造になる。
|
141
|
+
def atoms_in_supercell( a_min, a_max, b_min, b_max, c_min, c_max )
|
142
|
+
results = []
|
143
|
+
@atoms.each do |atom|
|
144
|
+
a_min.upto( a_max ) do |a|
|
145
|
+
b_min.upto( b_max ) do |b|
|
146
|
+
c_min.upto( c_max ) do |c|
|
147
|
+
results << Atom.new( atom.element, (atom.position.to_v3di + [ a, b, c ].to_v3di).to_a )
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
results
|
153
|
+
end
|
154
|
+
|
155
|
+
#他のセルと格子定数が等価であれば true を、そうでなければ false を返す。
|
156
|
+
#other: 他のセル
|
157
|
+
#length_ratio: 長さ(a, b, c) の許容値を比で指定
|
158
|
+
#angle_tolerance: 角度(alpha, beta, gamma) の許容値を角度の値で指定
|
159
|
+
def equal_lattice_in_delta?( other, length_ratio, angle_tolerance )
|
160
|
+
@axes.equal_in_delta?(
|
161
|
+
LatticeAxes.new( other.axes.to_a ), length_ratio, angle_tolerance
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
#含まれる全原子が等価比較で一対一対応が付けられれば true を返す。
|
166
|
+
#Cell に保持される順番に関係なく、等価な原子同士が一対一に対応づけられるかで
|
167
|
+
#チェックする。
|
168
|
+
def equal_atoms_in_delta?( other, position_tolerance )
|
169
|
+
return false unless Mapping::map?(@atoms, other.atoms ){ |i,j| i.equal_in_delta?( j, position_tolerance ) }
|
170
|
+
return true
|
171
|
+
end
|
172
|
+
|
173
|
+
# 等価判定。
|
174
|
+
# 格子定数の長さの比率の許容値、格子定数の角度の許容値、原子座標の許容値。
|
175
|
+
def equal_in_delta?( other, length_ratio, angle_tolerance, position_tolerance )
|
176
|
+
return false unless equal_lattice_in_delta?(other, length_ratio, angle_tolerance)
|
177
|
+
return false unless equal_atoms_in_delta?(other, position_tolerance)
|
178
|
+
return true
|
179
|
+
end
|
180
|
+
|
181
|
+
# 等価判定。
|
182
|
+
# 「==」による等価判定は実数の等価判定と同じく、基本的には使うべきではない。
|
183
|
+
# しかし、これを定義しておくとテストが楽になることが多い。
|
184
|
+
def ==( other )
|
185
|
+
#pp axes;
|
186
|
+
#pp other.axes;
|
187
|
+
|
188
|
+
return false unless self.axes == other.axes #equal_in_delta( 0.0, 0.0, 0.0 ) とすると計算誤差でうまくいかないことがある。
|
189
|
+
equal_atoms_in_delta?( other, 0.0 )
|
190
|
+
end
|
191
|
+
|
192
|
+
# 2つの地点間の距離を返す。
|
193
|
+
# それぞれ、内部座標 Vector3DInternal クラスインスタンスなら絶対座標に変換される。
|
194
|
+
# 絶対座標ならばそのまま計算する。
|
195
|
+
# Vector3D か Vector3DInternal 以外のクラスなら例外 Cell::TypeError を投げる。
|
196
|
+
# 周期性を考慮したりはしない。
|
197
|
+
# 周期性を考慮した距離は PeriodicCell#nearest_distance で行うべき。
|
198
|
+
def distance( pos0, pos1 )
|
199
|
+
if ((pos0.class != Vector3DInternal) && (pos0.class != Vector3D))
|
200
|
+
raise Cell::TypeError
|
201
|
+
end
|
202
|
+
if ((pos1.class != Vector3DInternal) && (pos1.class != Vector3D))
|
203
|
+
raise Cell::TypeError
|
204
|
+
end
|
205
|
+
|
206
|
+
v0 = pos0.to_v3d(@axes) if pos0.class == Vector3DInternal
|
207
|
+
v1 = pos1.to_v3d(@axes) if pos1.class == Vector3DInternal
|
208
|
+
|
209
|
+
(v0 - v1).r
|
210
|
+
end
|
211
|
+
|
212
|
+
# Dump string in POSCAR format.
|
213
|
+
# Argument <io> can be a file handle or nil.
|
214
|
+
# POSCAR を作るには、元素の順番を指定する必要があるので
|
215
|
+
# それを element_order で指定している。
|
216
|
+
# element_order の要素と == で一致する Atom instance を
|
217
|
+
# それぞれ全て出力する。
|
218
|
+
# e.g.,
|
219
|
+
# cell.dump_poscar( STDOUT ) #=> output to stdout.
|
220
|
+
# cell.dump_poscar( fileIo ) #=> output to file.
|
221
|
+
# cell.dump_poscar( nil ) #=> return in String instance.
|
222
|
+
# cell.dump_poscar #=> return in String instance.
|
223
|
+
def dump_poscar( element_order, io = nil )
|
224
|
+
if (io == nil)
|
225
|
+
return create_poscar( element_order )
|
226
|
+
else
|
227
|
+
io.puts create_poscar( element_order )
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Cell rotation.( Destructive method)
|
232
|
+
# Argument 'matrix' is 3x3 Array of float.
|
233
|
+
# This method does not modify the position to the range between 0 and 1,
|
234
|
+
# even if it was out of range.
|
235
|
+
def rotate!( matrix )
|
236
|
+
@atoms.each { |atom|
|
237
|
+
old_pos = atom.position
|
238
|
+
new_pos = [0.0, 0.0, 0.0]
|
239
|
+
3.times do |y|
|
240
|
+
3.times do |x|
|
241
|
+
new_pos[y] += (matrix[y][x] * old_pos[x])
|
242
|
+
end
|
243
|
+
end
|
244
|
+
atom.set_position( new_pos )
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
# Cell rotation.( Nondestructive method)
|
249
|
+
# Argument 'matrix' is 3x3 Array of float.
|
250
|
+
# This method does not modify the position to the range between 0 and 1,
|
251
|
+
# even if it was out of range.
|
252
|
+
def rotate( matrix )
|
253
|
+
t = Marshal.load( Marshal.dump( self ) )
|
254
|
+
t.rotate!( matrix )
|
255
|
+
return t
|
256
|
+
end
|
257
|
+
|
258
|
+
#並進移動を行う破壊的メソッド。
|
259
|
+
#ary は Float 3 要素の配列。
|
260
|
+
def translate!( ary )
|
261
|
+
@atoms.each { |atom| atom.translate!( ary ) }
|
262
|
+
end
|
263
|
+
|
264
|
+
#並進移動を行う非破壊的メソッド。
|
265
|
+
#ary は Float 3 要素の配列。
|
266
|
+
def translate( ary )
|
267
|
+
t = Marshal.load( Marshal.dump( self ) )
|
268
|
+
t.translate!( ary )
|
269
|
+
return t
|
270
|
+
end
|
271
|
+
|
272
|
+
# Return arithmetic mean of atomic positions in an internal coordinates.
|
273
|
+
# Raise 'Cell::NoAtomError' if no atoms included in self.
|
274
|
+
def center_of_atoms
|
275
|
+
raise Cell::NoAtomError if @atoms.size == 0
|
276
|
+
|
277
|
+
vec = Vector3DInternal[ 0.0, 0.0, 0.0 ]
|
278
|
+
@atoms.each { |i|
|
279
|
+
3.times { |j| vec[j] += i.position[j] }
|
280
|
+
}
|
281
|
+
vec *= 1.0/ @atoms.size
|
282
|
+
end
|
283
|
+
|
284
|
+
# Calculate volume.
|
285
|
+
def calc_volume
|
286
|
+
axes = @axes.to_a.map { |i| Vector3D[*i] }
|
287
|
+
vA, vB, vC = axes[0..2]
|
288
|
+
Vector3D.scalar_triple_product( vA, vB, vC ).abs
|
289
|
+
end
|
290
|
+
|
291
|
+
# Generate a new cell with the same lattice consants,
|
292
|
+
# containing atoms of indicated elements.
|
293
|
+
# Argument 'elems' must be an array of element names.
|
294
|
+
# 含まれる @atoms の順序は、保存される。元素ごとに並び換えたりしない。
|
295
|
+
# Atom.element が elems の要素のどれかと完全一致しているもののみ対象となる。
|
296
|
+
# サブクラスのインスタンスで実行した場合には、
|
297
|
+
# サブクラスのインスタンスとして生成する。
|
298
|
+
def cell_of_elements( elems )
|
299
|
+
result = self.class.new( @axes )
|
300
|
+
@atoms.each do |atom|
|
301
|
+
result.add_atom(atom) if elems.include?( atom.element )
|
302
|
+
end
|
303
|
+
return result
|
304
|
+
end
|
305
|
+
|
306
|
+
# 格子定数の同じ2つのセルを合わせて、全ての原子が含まれる1つのセルを返す
|
307
|
+
# 非破壊的メソッド。
|
308
|
+
# 2つのセルの格子定数が異なれば例外 Cell::AxesMismatchError を発生させる。
|
309
|
+
# 内部的には @atoms はレシーバの @atoms のあとに引数の @atoms を追加した形になる。
|
310
|
+
# comment は空文字になる。
|
311
|
+
# 原子座標の重複チェックなどは行わない。
|
312
|
+
def unite( cell )
|
313
|
+
#raise Cell::AxesMismatchError unless @axes == cell.axes
|
314
|
+
result = Marshal.load( Marshal.dump( self ) )
|
315
|
+
cell.atoms.each do |atom|
|
316
|
+
result.add_atom(atom)
|
317
|
+
end
|
318
|
+
return result
|
319
|
+
end
|
320
|
+
|
321
|
+
# 任意の格子軸のベクトルを反転する破壊的メソッド。
|
322
|
+
# 大まかなイメージとしては、
|
323
|
+
# 格子軸の原点をセルを構成する8つの頂点のどれかに移動する操作と考えれば良い。
|
324
|
+
# ただし厳密には、格子ベクトルは LatticeAxes.new によって triangulate されるため、
|
325
|
+
# b 軸を反転させた時は a 軸も反転する。( b 軸の y成分を正にするため)
|
326
|
+
# c 軸を反転させた時は a, b 軸も反転する。( c 軸の z成分を正にするため)
|
327
|
+
# セルの形状、内部のモチーフは保存する。
|
328
|
+
# 原子の絶対座標は移動せず、内部座標の表現が変わる。
|
329
|
+
# 引数 axis_id は 0, 1, 2 のいずれかの値を取り、それぞれ x, y, z軸を表す。
|
330
|
+
# x, y, z軸の関係は、右手系と左手系が入れ替わる。
|
331
|
+
def inverse_axis!( axis_id )
|
332
|
+
axis_id = axis_id.to_i
|
333
|
+
raise Cell::AxesRangeError if ( axis_id < 0 || 2 < axis_id )
|
334
|
+
|
335
|
+
axes = []
|
336
|
+
3.times do |i|
|
337
|
+
if ( i == axis_id )
|
338
|
+
axes << @axes[ i ] * (-1.0)
|
339
|
+
else
|
340
|
+
axes << @axes[ i ]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
@axes = LatticeAxes.new( axes )
|
344
|
+
|
345
|
+
atoms = []
|
346
|
+
@atoms.each do |atom|
|
347
|
+
position = []
|
348
|
+
3.times do |i|
|
349
|
+
if i == axis_id
|
350
|
+
position[i] = atom.position[i] * (-1)
|
351
|
+
else
|
352
|
+
position[i] = atom.position[i]
|
353
|
+
end
|
354
|
+
end
|
355
|
+
atom.position = Vector3DInternal[*position]
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
#def inverse_axis!( axis_id )
|
360
|
+
# axis_id = axis_id.to_i
|
361
|
+
# raise Cell::AxesRangeError if ( axis_id < 0 || 2 < axis_id )
|
362
|
+
|
363
|
+
# axes = []
|
364
|
+
# 3.times do |i|
|
365
|
+
# if ( i == axis_id )
|
366
|
+
# axes << @axes[ i ] * (-1.0)
|
367
|
+
# else
|
368
|
+
# axes << @axes[ i ]
|
369
|
+
# end
|
370
|
+
# end
|
371
|
+
# @axes = LatticeAxes.new( axes )
|
372
|
+
|
373
|
+
# atoms = []
|
374
|
+
# @atoms.each do |atom|
|
375
|
+
# position = []
|
376
|
+
# 3.times do |i|
|
377
|
+
# if i == axis_id
|
378
|
+
# position[i] = atom.position[i] * (-1)
|
379
|
+
# else
|
380
|
+
# position[i] = atom.position[i]
|
381
|
+
# end
|
382
|
+
# end
|
383
|
+
# atom.position
|
384
|
+
# atoms << Atom.new( atom.element, position, atom.name, atom.movable_flags )
|
385
|
+
# end
|
386
|
+
# @atoms = atoms
|
387
|
+
#end
|
388
|
+
|
389
|
+
# inverse_axis! の非破壊版。
|
390
|
+
def inverse_axis( axis_id )
|
391
|
+
result = Marshal.load( Marshal.dump( self ) )
|
392
|
+
result.inverse_axis!( axis_id )
|
393
|
+
return result
|
394
|
+
end
|
395
|
+
|
396
|
+
# 2つの格子ベクトルを交換する破壊的メソッド。
|
397
|
+
# Argument 'axis_ids' must have 2 items of integer.
|
398
|
+
# 0, 1, and 2 mean x, y, and z axes, respectively.
|
399
|
+
# この範囲の整数でなければ例外 Cell::AxesRangeError.
|
400
|
+
# axis_ids に含まれる 2つの数字が同じならば
|
401
|
+
# 例外 Cell::SameAxesError.
|
402
|
+
def exchange_axes!( axis_ids )
|
403
|
+
raise ArgumentError if axis_ids.size != 2
|
404
|
+
axis_ids.each{ |i| raise AxesRangeError if ( i < 0 || 2 < i ) }
|
405
|
+
raise Cell::SameAxesError if ( axis_ids[0] == axis_ids[1] )
|
406
|
+
|
407
|
+
# 格子定数を交換。
|
408
|
+
axes = @axes.axes
|
409
|
+
axes[ axis_ids[0]], axes[ axis_ids[1]] = axes[ axis_ids[1]], axes[ axis_ids[0]]
|
410
|
+
@axes = LatticeAxes.new( axes )
|
411
|
+
|
412
|
+
# 内部座標を交換。
|
413
|
+
new_atoms = []
|
414
|
+
@atoms.each do |atom|
|
415
|
+
new_pos = atom.position
|
416
|
+
new_pos[ axis_ids[0]], new_pos[ axis_ids[1]] =
|
417
|
+
new_pos[ axis_ids[1]], new_pos[ axis_ids[0]]
|
418
|
+
new_atoms << Atom.new( atom.element, new_pos, atom.name, atom.movable_flags )
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
# exchange_axes! の非破壊版。
|
423
|
+
def exchange_axes( axis_ids )
|
424
|
+
result = Marshal.load( Marshal.dump( self ) )
|
425
|
+
result.exchange_axes!( axis_ids )
|
426
|
+
return result
|
427
|
+
end
|
428
|
+
|
429
|
+
# 鏡像となるセルに変換する破壊的メソッド。
|
430
|
+
def reflect!
|
431
|
+
axes = @axes.to_a
|
432
|
+
axes[0][0] *= -1
|
433
|
+
@axes = LatticeAxes.new( axes )
|
434
|
+
end
|
435
|
+
|
436
|
+
# 鏡像となるセルに変換する非破壊的メソッド。
|
437
|
+
def reflect
|
438
|
+
result = Marshal.load( Marshal.dump( self ) )
|
439
|
+
result.reflect!
|
440
|
+
return result
|
441
|
+
end
|
442
|
+
|
443
|
+
# rotation と translation からなる操作(e.g., 対称操作)
|
444
|
+
# を加えたセルを返す。
|
445
|
+
def operate(rotation, translation)
|
446
|
+
rotation = Matrix[*rotation]
|
447
|
+
translation = translation.to_v3d
|
448
|
+
new_atoms = atoms.map do |atom|
|
449
|
+
position = atom.position.to_v3d([
|
450
|
+
[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
|
451
|
+
])
|
452
|
+
new_pos = (rotation * position + translation).to_a.to_v3di
|
453
|
+
Atom.new(atom.element, new_pos, atom.name)
|
454
|
+
end
|
455
|
+
Cell.new(@axes, new_atoms)
|
456
|
+
end
|
457
|
+
|
458
|
+
private
|
459
|
+
|
460
|
+
# POSCAR の内容の文字列を生成。
|
461
|
+
# 文字列の配列ではなく、改行文字を含む1つの文字列である点に注意。
|
462
|
+
#
|
463
|
+
# VASP の挙動として、Selective dynamics 指定ありの時に
|
464
|
+
# 原子に T or F 指定していなかったり 3要素に足りなかったりすれば、
|
465
|
+
# error となって実行されない。
|
466
|
+
# なので dump_poscar では Selective dynamics 指定が必要な時には
|
467
|
+
# 全ての原子に T/F を記述する。
|
468
|
+
# POSCAR から生成された Cell の場合は Selective dynamics がついていれば
|
469
|
+
# 全ての原子に 3つの T/F が付いていることを前提としても良いだろう。
|
470
|
+
# 原子を追加するとかで、一部の原子の movable_flags が nil になっているときは、
|
471
|
+
# デフォルト値として [ true, true, true ] を入れることにする。
|
472
|
+
# nil ならば false を連想すると思うが、敢えて true で埋めている理由は、
|
473
|
+
# Selective dynamics をつけていない状態で VASP は全方向に緩和する、
|
474
|
+
# すなわち T T T と指定したことと同じになっているから。
|
475
|
+
# 換言すればこのデフォルト値の設定は VASP に合わせた仕様ということになる。
|
476
|
+
# VASP に由来する仕様が Cell クラスに持ち込まれていることになるが、
|
477
|
+
# VASP へのインターフェイスである POSCAR ファイルへの書き出しに限定されるので
|
478
|
+
# 他への影響はほとんどなく、気にしなくて良いだろう。
|
479
|
+
def create_poscar( element_order )
|
480
|
+
#element_order と elements が一対一対応していなければ raise
|
481
|
+
raise "Cell::create_poscar, element_order mismatches to elements." if (! Mapping::map?( elements.uniq, element_order ){ |i, j| i == j } )
|
482
|
+
|
483
|
+
results = []
|
484
|
+
results << @comment
|
485
|
+
results << "1.0" #scale
|
486
|
+
3.times do |i|
|
487
|
+
results << sprintf( "%20.14f %20.14f %20.14f", @axes[i][0], @axes[i][1], @axes[i][2]
|
488
|
+
)
|
489
|
+
end
|
490
|
+
|
491
|
+
##collect information
|
492
|
+
elem_list = Hash.new
|
493
|
+
element_order.each do |elem|
|
494
|
+
elem_list[ elem ] = @atoms.select{ |atom| atom.element == elem }
|
495
|
+
end
|
496
|
+
|
497
|
+
##numbers of element atoms
|
498
|
+
tmp = ''
|
499
|
+
element_order.each do |elem|
|
500
|
+
tmp += " #{elem_list[elem].size.to_s}"
|
501
|
+
end
|
502
|
+
results << tmp
|
503
|
+
|
504
|
+
##Selective dynamics
|
505
|
+
##どれか1つでも getMovableFlag が真であれば Selective dynamics をオンにする
|
506
|
+
selective_dynamics = false
|
507
|
+
@atoms.each do |atom|
|
508
|
+
if atom.movable_flags
|
509
|
+
selective_dynamics = true
|
510
|
+
results << "Selective dynamics"
|
511
|
+
break
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
element_order.each do |elem|
|
516
|
+
elem_list[ elem ].each do |atom|
|
517
|
+
if atom.movable_flags
|
518
|
+
selective_dynamics = true
|
519
|
+
break
|
520
|
+
end
|
521
|
+
end
|
522
|
+
break if selective_dynamics
|
523
|
+
end
|
524
|
+
|
525
|
+
results << "Direct" #now, Direct only
|
526
|
+
|
527
|
+
##positions of atoms
|
528
|
+
element_order.each do |elem|
|
529
|
+
elem_list[ elem ].each do |atom|
|
530
|
+
tmp = sprintf( "%20.14f %20.14f %20.14f", * atom.position )
|
531
|
+
if selective_dynamics
|
532
|
+
if atom.movable_flags == nil
|
533
|
+
tmp += " T T T"
|
534
|
+
else
|
535
|
+
atom.movable_flags.each do |mov|
|
536
|
+
( mov == true ) ? tmp += " T" : tmp += " F"
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
results << tmp
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
results.join("\n")
|
545
|
+
end
|
546
|
+
|
547
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
#
|
2
|
+
# 元素(原子種)に関する情報を集めたクラス
|
3
|
+
# 基本理念として、原子番号を最もネイティブなデータとして ID 付けをする。
|
4
|
+
# 原子番号は人間がいなかったとしてもそれで区分できるが、
|
5
|
+
# 原子記号は人間がつけたものだから。
|
6
|
+
#
|
7
|
+
class Element
|
8
|
+
# 全データを生成しておく。
|
9
|
+
def initialize
|
10
|
+
#原子記号, 原子番号, 周期, 族, 質量数(平均), イオン半径
|
11
|
+
@symbol = Array.new #; @mass = Array.new @radius = Array.new
|
12
|
+
#i = 1; @symbol[i], @mass[i], @radius[i] = 'H', 1.008, nil
|
13
|
+
i = 0; @symbol[i] = nil
|
14
|
+
i = 1; @symbol[i] = 'H'
|
15
|
+
i = 2; @symbol[i] = 'He'
|
16
|
+
i = 3; @symbol[i] = 'Li'
|
17
|
+
i = 4; @symbol[i] = 'Be'
|
18
|
+
i = 5; @symbol[i] = 'B'
|
19
|
+
i = 6; @symbol[i] = 'C'
|
20
|
+
i = 7; @symbol[i] = 'N'
|
21
|
+
i = 8; @symbol[i] = 'O'
|
22
|
+
i = 9; @symbol[i] = 'F'
|
23
|
+
i = 10; @symbol[i] = 'Ne'
|
24
|
+
i = 11; @symbol[i] = 'Na'
|
25
|
+
i = 12; @symbol[i] = 'Mg'
|
26
|
+
i = 13; @symbol[i] = 'Al'
|
27
|
+
i = 14; @symbol[i] = 'Si'
|
28
|
+
i = 15; @symbol[i] = 'P'
|
29
|
+
i = 16; @symbol[i] = 'S'
|
30
|
+
i = 17; @symbol[i] = 'Cl'
|
31
|
+
i = 18; @symbol[i] = 'Ar'
|
32
|
+
i = 19; @symbol[i] = 'K'
|
33
|
+
i = 20; @symbol[i] = 'Ca'
|
34
|
+
i = 21; @symbol[i] = 'Sc'
|
35
|
+
i = 22; @symbol[i] = 'Ti'
|
36
|
+
i = 23; @symbol[i] = 'V'
|
37
|
+
i = 24; @symbol[i] = 'Cr'
|
38
|
+
i = 25; @symbol[i] = 'Mn'
|
39
|
+
i = 26; @symbol[i] = 'Fe'
|
40
|
+
i = 27; @symbol[i] = 'Co'
|
41
|
+
i = 28; @symbol[i] = 'Ni'
|
42
|
+
i = 29; @symbol[i] = 'Cu'
|
43
|
+
i = 30; @symbol[i] = 'Zn'
|
44
|
+
i = 31; @symbol[i] = 'Ga'
|
45
|
+
i = 32; @symbol[i] = 'Ge'
|
46
|
+
i = 33; @symbol[i] = 'As'
|
47
|
+
i = 34; @symbol[i] = 'Se'
|
48
|
+
i = 35; @symbol[i] = 'Br'
|
49
|
+
i = 36; @symbol[i] = 'Kr'
|
50
|
+
i = 37; @symbol[i] = 'Rb'
|
51
|
+
i = 38; @symbol[i] = 'Sr'
|
52
|
+
i = 39; @symbol[i] = 'Y'
|
53
|
+
i = 40; @symbol[i] = 'Zr'
|
54
|
+
i = 41; @symbol[i] = 'Nb'
|
55
|
+
i = 42; @symbol[i] = 'Mo'
|
56
|
+
i = 43; @symbol[i] = 'Tc'
|
57
|
+
i = 44; @symbol[i] = 'Ru'
|
58
|
+
i = 45; @symbol[i] = 'Rh'
|
59
|
+
i = 46; @symbol[i] = 'Pd'
|
60
|
+
i = 47; @symbol[i] = 'Ag'
|
61
|
+
i = 48; @symbol[i] = 'Cd'
|
62
|
+
i = 49; @symbol[i] = 'In'
|
63
|
+
i = 50; @symbol[i] = 'Sn'
|
64
|
+
i = 51; @symbol[i] = 'Sb'
|
65
|
+
i = 52; @symbol[i] = 'Te'
|
66
|
+
i = 53; @symbol[i] = 'I'
|
67
|
+
i = 54; @symbol[i] = 'Xe'
|
68
|
+
i = 55; @symbol[i] = 'Cs'
|
69
|
+
i = 56; @symbol[i] = 'Ba'
|
70
|
+
i = 57; @symbol[i] = 'La'
|
71
|
+
i = 58; @symbol[i] = 'Ce'
|
72
|
+
i = 59; @symbol[i] = 'Pr'
|
73
|
+
i = 60; @symbol[i] = 'Nd'
|
74
|
+
i = 61; @symbol[i] = 'Pm'
|
75
|
+
i = 62; @symbol[i] = 'Sm'
|
76
|
+
i = 63; @symbol[i] = 'Eu'
|
77
|
+
i = 64; @symbol[i] = 'Gd'
|
78
|
+
i = 65; @symbol[i] = 'Tb'
|
79
|
+
i = 66; @symbol[i] = 'Dy'
|
80
|
+
i = 67; @symbol[i] = 'Ho'
|
81
|
+
i = 68; @symbol[i] = 'Er'
|
82
|
+
i = 69; @symbol[i] = 'Tm'
|
83
|
+
i = 70; @symbol[i] = 'Yb'
|
84
|
+
i = 71; @symbol[i] = 'Lu'
|
85
|
+
i = 72; @symbol[i] = 'Hf'
|
86
|
+
i = 73; @symbol[i] = 'Ta'
|
87
|
+
i = 74; @symbol[i] = 'W'
|
88
|
+
i = 75; @symbol[i] = 'Re'
|
89
|
+
i = 76; @symbol[i] = 'Os'
|
90
|
+
i = 77; @symbol[i] = 'Ir'
|
91
|
+
i = 78; @symbol[i] = 'Pt'
|
92
|
+
i = 79; @symbol[i] = 'Au'
|
93
|
+
i = 80; @symbol[i] = 'Hg'
|
94
|
+
i = 81; @symbol[i] = 'Tl'
|
95
|
+
i = 82; @symbol[i] = 'Pb'
|
96
|
+
i = 83; @symbol[i] = 'Bi'
|
97
|
+
i = 84; @symbol[i] = 'Po'
|
98
|
+
i = 85; @symbol[i] = 'At'
|
99
|
+
i = 86; @symbol[i] = 'Rn'
|
100
|
+
i = 87; @symbol[i] = 'Fr'
|
101
|
+
i = 88; @symbol[i] = 'Ra'
|
102
|
+
i = 89; @symbol[i] = 'Ac'
|
103
|
+
i = 90; @symbol[i] = 'Th'
|
104
|
+
i = 91; @symbol[i] = 'Pa'
|
105
|
+
i = 92; @symbol[i] = 'U'
|
106
|
+
i = 93; @symbol[i] = 'Np'
|
107
|
+
i = 94; @symbol[i] = 'Pu'
|
108
|
+
i = 95; @symbol[i] = 'Am'
|
109
|
+
i = 96; @symbol[i] = 'Cm'
|
110
|
+
i = 97; @symbol[i] = 'Bk'
|
111
|
+
i = 98; @symbol[i] = 'Cf'
|
112
|
+
i = 99; @symbol[i] = 'Es'
|
113
|
+
i = 100; @symbol[i] = 'Fm'
|
114
|
+
i = 101; @symbol[i] = 'Md'
|
115
|
+
i = 102; @symbol[i] = 'No'
|
116
|
+
i = 103; @symbol[i] = 'Lr'
|
117
|
+
end
|
118
|
+
|
119
|
+
# 原子記号から原子番号を取得。e.g., H から 1
|
120
|
+
# 与えられたものが原子番号ならそのまま返す、ようにしたいがまだできてない。
|
121
|
+
# リストに存在しなければ raise する。
|
122
|
+
def getAtomicNumber( name )
|
123
|
+
symbol = @symbol.index( name )
|
124
|
+
raise "Symbol #{name} not found." if (symbol == nil || symbol == 0)
|
125
|
+
symbol
|
126
|
+
end
|
127
|
+
|
128
|
+
# 原子番号から原子記号を取得。e.g., 1 から H
|
129
|
+
# 与えられたものが原子記号ならそのまま返す、ようにしたいがまだできてない。
|
130
|
+
# リストに存在しなければ raise する。
|
131
|
+
def getSymbol( num )
|
132
|
+
raise "getSymbol(num) requires Fixnum, but #{num}" if (num.class != Fixnum)
|
133
|
+
raise "Atomic number is out of range." if (num <= 0 || @symbol.size <= num)
|
134
|
+
@symbol[num]
|
135
|
+
end
|
136
|
+
|
137
|
+
# 引数 id がリストに含まれているか?
|
138
|
+
# id は原子番号でも原子記号でも可にしたいが、とりあえず symbol だけ。
|
139
|
+
def include?( symbol )
|
140
|
+
return false if symbol == nil
|
141
|
+
@symbol.include?( symbol )
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
#def getRadius
|
146
|
+
#end
|
147
|
+
|
148
|
+
end
|