crystalcell 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,277 +1,276 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
3
  require "pp"
4
- require "crystalcell/cell.rb"
5
4
 
6
5
  # Class for crystal cell with periodic boundary.
7
6
  # Coordinates of atoms are kept in the region of 0 <= x_i < 1 of internal coordinate.
8
- # Written by Ippei Kishida [2010-12-19].
9
- class PeriodicCell < Cell
10
- class TypeError < Exception; end
11
-
12
- def initialize( *args )
13
- super( *args )
14
- reset_positions_inside
15
- end
16
-
17
- #ある内部座標から、別のある座標とそれと周期的に等価な座標への距離が
18
- #tolerance 以下のものを探し、条件を満たすセルの方向を配列にまとめて返す。
19
- #
20
- #内部的に、一度 0以上1未満の座標に変換しようかと思ったが、
21
- #境界付近で問題が生じうる。
22
- #
23
- #周囲 27 セルしか考慮しない。
24
- #美しさを求めるならば tolerance を完全に含む大きさのスーパーセルにすべきだが、
25
- #実装が面倒なわりに滅多に使われることがなさそうなので。
26
- #出力の順番は、上位の要素が小さなものから順。
27
- #(距離の短いものから順という考え方もなくはないが。)
28
- #
29
- #tolerance を省略( = nil を与えれば )、27セルの中にある全ての方向を返す。
30
- def directions_within_distance( pos0, pos1, tolerance = nil )
31
- if pos0.class != Vector3DInternal
32
- raise TypeError, "pos0 is not a Vector3DInternal class instance."
33
- end
34
- if pos1.class != Vector3DInternal
35
- raise TypeError, "pos1 is not a Vector3DInternal class instance."
36
- end
37
-
38
- pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
39
- pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di
40
-
41
- #pp pos0
42
-
43
- results = []
44
- (-1).upto(1) do |x|
45
- (-1).upto(1) do |y|
46
- (-1).upto(1) do |z|
47
- shift = Vector3DInternal[ x.to_f, y.to_f, z.to_f ]
48
- d = distance( pos0, ( pos1 + shift))
49
- #tolerance が nil ならば距離判定なしで登録。
50
- #tolerance が非 nil ならばその値で距離判定して登録。
51
- if ( ( ! tolerance ) || ( d < tolerance ) )
52
- results << [ x, y, z]
53
- end
54
- end
55
- end
56
- end
57
- return results
58
- end
59
-
60
- # 周期性を考慮して、
61
- # 1個目の内部座標( pos0 ) から見て、
62
- # 一番近い 2個目の内部座標に等価なサイトの属するセルの方向を返す。
63
- # 返り値は Vector3DInternal で
64
- # 内部座標が 0.0 <= r < 1.0 になっていることを前提とする。
65
- # (なお、この外側であってもこの範囲に入るように周期移動する。)
66
- # 座標 0.0 付近の境界は計算誤差の為におかしなことになり易いので注意が必要。
67
- #
68
- # NOTE: nearest_direction というメソッド名はどうかと思う。
69
- # nearest_lattice_vector とか?
70
- # 良い名前があれば、リファクタリング対象。
71
- #
72
- # NOTE: 0〜1の外側なら内側に入れる、という処理は混乱し易い。
73
- # 例外にした方が良いのではないだろうか。
74
- def nearest_direction( pos0, pos1 )
75
- if pos0.class != Vector3DInternal
76
- raise TypeError,
77
- "pos0.class is not a Vector3DInternal"
78
- end
79
- if pos1.class != Vector3DInternal
80
- raise TypeError,
81
- "pos1.class is not a Vector3DInternal"
82
- end
83
-
84
- pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
85
- pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di
86
-
87
- #set first value
88
- min = distance( pos0, pos1 )
89
- result = Vector3DInternal[ 0, 0, 0 ]
90
-
91
- #find
92
- (-1).upto(1) do |x|
93
- (-1).upto(1) do |y|
94
- (-1).upto(1) do |z|
95
- shift = Vector3DInternal[ x.to_f, y.to_f, z.to_f ]
96
- d = (pos0 - (pos1 + shift)).to_v3d(@axes).r
97
-
98
- if d < min
99
- result = Vector3DInternal[ x, y, z]
100
- min = d
101
- end
102
- end
103
- end
104
- end
105
- return result
106
- end
107
-
108
- #周期性を考慮し、2つの地点間の最短距離を返す。
109
- #pos0, pos1 には内部座標を指定する。
110
- #内部的には周囲 3x3x3 のセル中の27地点のうち最も近いものを得る。
111
- #旧 minimum_distance
112
- def nearest_distance( pos0, pos1 )
113
- [pos0, pos1].each_with_index do |pos, index|
114
- unless pos.class == Vector3DInternal
115
- raise TypeError,
116
- "#{index} th argument: #{pos.inspect}, #{pos.class}"
117
- end
118
- end
119
-
120
- pos0 = Vector3DInternal[* pos0.map{ |i| i - i.floor }]
121
- pos1 = Vector3DInternal[* pos1.map{ |i| i - i.floor }]
122
- direction = nearest_direction( pos0, pos1 )
123
- 3.times do |i|
124
- pos1[ i ] += direction[ i ]
125
- end
126
- distance( pos0, pos1 )
127
- end
128
-
129
- #条件にマッチした原子間距離を見つけて、端点の内部座標の組を要素とする配列を返す。
130
- #返される配列は3重配列になる。
131
- #e.g.,
132
- # [
133
- # [ [0.1, 0.1, 0.1], [0.2, 0.2, 0.2] ],
134
- # [ [0.1, 0.1, 0.1], [0.3, 0.4, 0.5] ],
135
- # [ [0.9, 0.9, 0.9], [0.8, 0.8, 0.8] ]
136
- # ]
137
- #@bonds への登録はしない。
138
- #elem0, elem1 は bond の両端の原子の組み合わせを指定。
139
- #elem0, elem1 はメソッド内部で反転したものもチェックするので、順序が反対でも同じ結果になる。
140
- #O-O のように同じ元素を指定することもでき、
141
- #この場合向きの異なる 2つの bond が重複したりしない。
142
- #d_min, d_max は距離の範囲を指定する。
143
- #境界値そのものが問題になることは少ないだろう。
144
- #d_min <= d <= d_max(以上、以下) としておくが、計算誤差のためにこれはほぼ無意味だ。
145
- #見つけて配列として返すだけで、登録はしない。
146
- #
147
- #以下の案は棄却。
148
- # - いかなる元素でもマッチ。
149
- # - 距離の上限を無効。
150
- #理由は、
151
- # - プログラムが煩雑になる。
152
- # - 引数の型が統一されない。
153
- # - ほとんど使用する機会がなさそう。
154
- # 大抵は元素を指定するし、元素を指定しない bond を描く状況は考えにくい。
155
- # これが必要ならメソッドの外で組み合わせを作ってそれぞれで呼べば良い。
156
- # - 大抵は距離の上限を定めるし、上限なしではハリネズミになるだけ。
157
- # また、プログラム上は距離の上限を 3x3x3 スーパーセルに制限したりするが、
158
- # 論理的に距離の上限なしってのは無限のセルを対象にすることになり、整合性がとれない。
159
- def find_bonds( elem0, elem1, d_min, d_max )
160
- results = []
161
- atoms.each do |inner_atom|
162
- atoms_in_supercell( -1, 1, -1, 1, -1, 1 ).each do |outer_atom|
163
- #元素の種類による判定
164
- ie = inner_atom.element
165
- oe = outer_atom.element
166
- next unless ( (( ie == elem0 ) && ( oe == elem1 )) || (( ie == elem1 ) && ( oe == elem0 ))) #elem0, elem1 と同じ構成
167
-
168
- #距離による判定
169
- ip = inner_atom.position
170
- op = outer_atom.position
171
- next if distance( ip, op ) < d_min
172
- next if distance( ip, op ) > d_max
173
- next if distance( ip, op ) == 0.0 #同一サイト判定
174
-
175
- #重複判定, 正順か逆順が既に含まれていれば無視。
176
- next if ( results.include?( [ ip, op ] ) || results.include?( [ op, ip ] ) )
177
-
178
- results << [ ip, op ]
179
- end
180
- end
181
- return results
182
- end
183
-
184
- #引数 distance 以下の間の距離にある原子のペアを列挙して返す。
185
- #この際、あくまで unique なものを返し、A-B があれば B-A はリストに含まれない。
186
- #
187
- #返す形式は以下のような形式。
188
- #[ [0, 1, [0, 0, 0], [0, 1, [0, 0, 1] ]
189
- #最初の 0, 1 はそれぞれ配列 @atoms 内の番号で、
190
- #0番目の原子からから同じセル内の 1番目の原子
191
- #0番目の原子からから[0,0,1]方向に隣接するセル内の 1番目の原子を意味する。
192
- #起点となる原子の番号と(上記 0 )と目的地の原子の番号(上記 1)が同じこともありうる。
193
- #異なる場合は起点となる原子の番号が、目的地の原子の番号より若くなる。
194
- #
195
- #自分と同じ番号の場合は、同じセルを除外する。
196
- #すなわち、[0, 0, [0, 0, 0] や [1, 1, [0, 0, 0] は含まれない。
197
- def pairs_within_distance( distance )
198
- results = []
199
- n_atom = @atoms.size
200
- n_atom.times do |i|
201
- pos_i = @atoms[i].position
202
-
203
- n_atom.times do |j|
204
- pos_j = @atoms[j].position
205
-
206
- directions_within_distance( pos_i, pos_j, distance ).each do |dir|
207
- #next if ( i==j && dir==[0,0,0] ) #距離0の自分自身。下の条件に含まれる。
208
- next if ( dir==[0,0,0] && i >= j ) #同じセル内は番号が若い方からのみ。
209
- results << [ i, j, dir ]
210
- end
211
- end
212
- end
213
- return results
214
- end
215
-
216
- # Functions as like Cell.add_atom, but the coordinates are converted to the region of 0 <= x_i < 1.
217
- def add_atom( *args )
218
- super( *args )
219
- reset_positions_inside
220
- end
221
-
222
- # Functions as like Cell.rotate!, but the coordinates are converted to the region of 0 <= x_i < 1.
223
- # Dependent function 'rotate' functions the same.
224
- def rotate!( *args )
225
- super( *args )
226
- reset_positions_inside
227
- end
228
-
229
- # Functions as like Cell.translate!, but the coordinates are converted to the region of 0 <= x_i < 1.
230
- # Dependent function 'translate' functions the same.
231
- def translate!( *args )
232
- super( *args )
233
- reset_positions_inside
234
- end
235
-
236
- # Return a new instance converted to Cell class.
237
- def to_cell
238
- tmp = Cell.new( @axes.to_a )
239
- tmp.comment = self.comment
240
- @atoms.each do |atom|
241
- tmp.add_atom(atom)
242
- end
243
- return tmp
244
- end
245
-
246
- undef center_of_atoms
247
-
248
- # superclass の inverse_axis! を行ったあと、
249
- # 原子の座標をセル内部に移す。
250
- def inverse_axis!( axis_id )
251
- result = Marshal.load( Marshal.dump( self ) )
252
- super( axis_id )
253
- reset_positions_inside
254
- end
255
-
256
- private
257
-
258
- # Reset internal coordinates of all atoms inside the region of 0 <= x_i < 1.
259
- def reset_positions_inside
260
- @atoms.each do |atom|
261
- coords = atom.position
262
- atom.set_position(coords.map {|coord| coord - coord.floor}.to_a.to_v3di)
263
- end
264
- end
7
+ class CrystalCell::PeriodicCell < CrystalCell::Cell
8
+
9
+ class TypeError < Exception; end
10
+
11
+ def initialize( *args )
12
+ super( *args )
13
+ reset_positions_inside
14
+ end
15
+
16
+ #ある内部座標から、別のある座標とそれと周期的に等価な座標への距離が
17
+ #tolerance 以下のものを探し、条件を満たすセルの方向を配列にまとめて返す。
18
+ #
19
+ #内部的に、一度 0以上1未満の座標に変換しようかと思ったが、
20
+ #境界付近で問題が生じうる。
21
+ #
22
+ #周囲 27 セルしか考慮しない。
23
+ #美しさを求めるならば tolerance を完全に含む大きさのスーパーセルにすべきだが、
24
+ #実装が面倒なわりに滅多に使われることがなさそうなので。
25
+ #出力の順番は、上位の要素が小さなものから順。
26
+ #(距離の短いものから順という考え方もなくはないが。)
27
+ #
28
+ #tolerance を省略( = nil を与えれば )、27セルの中にある全ての方向を返す。
29
+ def directions_within_distance( pos0, pos1, tolerance = nil )
30
+ if pos0.class != Vector3DInternal
31
+ raise TypeError, "pos0 is not a Vector3DInternal class instance."
32
+ end
33
+ if pos1.class != Vector3DInternal
34
+ raise TypeError, "pos1 is not a Vector3DInternal class instance."
35
+ end
36
+
37
+ pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
38
+ pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di
39
+
40
+ #pp pos0
41
+
42
+ results = []
43
+ (-1).upto(1) do |x|
44
+ (-1).upto(1) do |y|
45
+ (-1).upto(1) do |z|
46
+ shift = Vector3DInternal[ x.to_f, y.to_f, z.to_f ]
47
+ d = distance( pos0, ( pos1 + shift))
48
+ #tolerance が nil ならば距離判定なしで登録。
49
+ #tolerance が非 nil ならばその値で距離判定して登録。
50
+ if ( ( ! tolerance ) || ( d < tolerance ) )
51
+ results << [ x, y, z]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ return results
57
+ end
58
+
59
+ # 周期性を考慮して、
60
+ # 1個目の内部座標( pos0 ) から見て、
61
+ # 一番近い 2個目の内部座標に等価なサイトの属するセルの方向を返す。
62
+ # 返り値は Vector3DInternal で
63
+ # 内部座標が 0.0 <= r < 1.0 になっていることを前提とする。
64
+ # (なお、この外側であってもこの範囲に入るように周期移動する。)
65
+ # 座標 0.0 付近の境界は計算誤差の為におかしなことになり易いので注意が必要。
66
+ #
67
+ # NOTE: nearest_direction というメソッド名はどうかと思う。
68
+ # nearest_lattice_vector とか?
69
+ # 良い名前があれば、リファクタリング対象。
70
+ #
71
+ # NOTE: 0〜1の外側なら内側に入れる、という処理は混乱し易い。
72
+ # 例外にした方が良いのではないだろうか。
73
+ def nearest_direction( pos0, pos1 )
74
+ if pos0.class != Vector3DInternal
75
+ raise TypeError,
76
+ "pos0.class is not a Vector3DInternal"
77
+ end
78
+ if pos1.class != Vector3DInternal
79
+ raise TypeError,
80
+ "pos1.class is not a Vector3DInternal"
81
+ end
82
+
83
+ pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
84
+ pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di
85
+
86
+ #set first value
87
+ min = distance( pos0, pos1 )
88
+ result = Vector3DInternal[ 0, 0, 0 ]
89
+
90
+ #find
91
+ (-1).upto(1) do |x|
92
+ (-1).upto(1) do |y|
93
+ (-1).upto(1) do |z|
94
+ shift = Vector3DInternal[ x.to_f, y.to_f, z.to_f ]
95
+ d = (pos0 - (pos1 + shift)).to_v3d(@axes).r
96
+
97
+ if d < min
98
+ result = Vector3DInternal[ x, y, z]
99
+ min = d
100
+ end
101
+ end
102
+ end
103
+ end
104
+ return result
105
+ end
106
+
107
+ #周期性を考慮し、2つの地点間の最短距離を返す。
108
+ #pos0, pos1 には内部座標を指定する。
109
+ #内部的には周囲 3x3x3 のセル中の27地点のうち最も近いものを得る。
110
+ #旧 minimum_distance
111
+ def nearest_distance( pos0, pos1 )
112
+ [pos0, pos1].each_with_index do |pos, index|
113
+ unless pos.class == Vector3DInternal
114
+ raise TypeError,
115
+ "#{index} th argument: #{pos.inspect}, #{pos.class}"
116
+ end
117
+ end
118
+
119
+ pos0 = Vector3DInternal[* pos0.map{ |i| i - i.floor }]
120
+ pos1 = Vector3DInternal[* pos1.map{ |i| i - i.floor }]
121
+ direction = nearest_direction( pos0, pos1 )
122
+ 3.times do |i|
123
+ pos1[ i ] += direction[ i ]
124
+ end
125
+ distance( pos0, pos1 )
126
+ end
127
+
128
+ #条件にマッチした原子間距離を見つけて、端点の内部座標の組を要素とする配列を返す。
129
+ #返される配列は3重配列になる。
130
+ #e.g.,
131
+ # [
132
+ # [ [0.1, 0.1, 0.1], [0.2, 0.2, 0.2] ],
133
+ # [ [0.1, 0.1, 0.1], [0.3, 0.4, 0.5] ],
134
+ # [ [0.9, 0.9, 0.9], [0.8, 0.8, 0.8] ]
135
+ # ]
136
+ #@bonds への登録はしない。
137
+ #elem0, elem1 は bond の両端の原子の組み合わせを指定。
138
+ #elem0, elem1 はメソッド内部で反転したものもチェックするので、順序が反対でも同じ結果になる。
139
+ #O-O のように同じ元素を指定することもでき、
140
+ #この場合向きの異なる 2つの bond が重複したりしない。
141
+ #d_min, d_max は距離の範囲を指定する。
142
+ #境界値そのものが問題になることは少ないだろう。
143
+ #d_min <= d <= d_max(以上、以下) としておくが、計算誤差のためにこれはほぼ無意味だ。
144
+ #見つけて配列として返すだけで、登録はしない。
145
+ #
146
+ #以下の案は棄却。
147
+ # - いかなる元素でもマッチ。
148
+ # - 距離の上限を無効。
149
+ #理由は、
150
+ # - プログラムが煩雑になる。
151
+ # - 引数の型が統一されない。
152
+ # - ほとんど使用する機会がなさそう。
153
+ # 大抵は元素を指定するし、元素を指定しない bond を描く状況は考えにくい。
154
+ # これが必要ならメソッドの外で組み合わせを作ってそれぞれで呼べば良い。
155
+ # - 大抵は距離の上限を定めるし、上限なしではハリネズミになるだけ。
156
+ # また、プログラム上は距離の上限を 3x3x3 スーパーセルに制限したりするが、
157
+ # 論理的に距離の上限なしってのは無限のセルを対象にすることになり、整合性がとれない。
158
+ def find_bonds( elem0, elem1, d_min, d_max )
159
+ results = []
160
+ atoms.each do |inner_atom|
161
+ atoms_in_supercell( -1, 1, -1, 1, -1, 1 ).each do |outer_atom|
162
+ #元素の種類による判定
163
+ ie = inner_atom.element
164
+ oe = outer_atom.element
165
+ next unless ( (( ie == elem0 ) && ( oe == elem1 )) || (( ie == elem1 ) && ( oe == elem0 ))) #elem0, elem1 と同じ構成
166
+
167
+ #距離による判定
168
+ ip = inner_atom.position
169
+ op = outer_atom.position
170
+ next if distance( ip, op ) < d_min
171
+ next if distance( ip, op ) > d_max
172
+ next if distance( ip, op ) == 0.0 #同一サイト判定
173
+
174
+ #重複判定, 正順か逆順が既に含まれていれば無視。
175
+ next if ( results.include?( [ ip, op ] ) || results.include?( [ op, ip ] ) )
176
+
177
+ results << [ ip, op ]
178
+ end
179
+ end
180
+ return results
181
+ end
182
+
183
+ #引数 distance 以下の間の距離にある原子のペアを列挙して返す。
184
+ #この際、あくまで unique なものを返し、A-B があれば B-A はリストに含まれない。
185
+ #
186
+ #返す形式は以下のような形式。
187
+ #[ [0, 1, [0, 0, 0], [0, 1, [0, 0, 1] ]
188
+ #最初の 0, 1 はそれぞれ配列 @atoms 内の番号で、
189
+ #0番目の原子からから同じセル内の 1番目の原子
190
+ #0番目の原子からから[0,0,1]方向に隣接するセル内の 1番目の原子を意味する。
191
+ #起点となる原子の番号と(上記 0 )と目的地の原子の番号(上記 1)が同じこともありうる。
192
+ #異なる場合は起点となる原子の番号が、目的地の原子の番号より若くなる。
193
+ #
194
+ #自分と同じ番号の場合は、同じセルを除外する。
195
+ #すなわち、[0, 0, [0, 0, 0] や [1, 1, [0, 0, 0] は含まれない。
196
+ def pairs_within_distance( distance )
197
+ results = []
198
+ n_atom = @atoms.size
199
+ n_atom.times do |i|
200
+ pos_i = @atoms[i].position
201
+
202
+ n_atom.times do |j|
203
+ pos_j = @atoms[j].position
204
+
205
+ directions_within_distance( pos_i, pos_j, distance ).each do |dir|
206
+ #next if ( i==j && dir==[0,0,0] ) #距離0の自分自身。下の条件に含まれる。
207
+ next if ( dir==[0,0,0] && i >= j ) #同じセル内は番号が若い方からのみ。
208
+ results << [ i, j, dir ]
209
+ end
210
+ end
211
+ end
212
+ return results
213
+ end
214
+
215
+ # Functions as like CrystalCell::Cell.add_atom, but the coordinates are converted to the region of 0 <= x_i < 1.
216
+ def add_atom( *args )
217
+ super( *args )
218
+ reset_positions_inside
219
+ end
220
+
221
+ # Functions as like CrystalCell::Cell.rotate!, but the coordinates are converted to the region of 0 <= x_i < 1.
222
+ # Dependent function 'rotate' functions the same.
223
+ def rotate!( *args )
224
+ super( *args )
225
+ reset_positions_inside
226
+ end
227
+
228
+ # Functions as like CrystalCell::Cell.translate!, but the coordinates are converted to the region of 0 <= x_i < 1.
229
+ # Dependent function 'translate' functions the same.
230
+ def translate!( *args )
231
+ super( *args )
232
+ reset_positions_inside
233
+ end
234
+
235
+ # Return a new instance converted to CrystalCell::Cell class.
236
+ def to_cell
237
+ tmp = CrystalCell::Cell.new( @axes.to_a )
238
+ tmp.comment = self.comment
239
+ @atoms.each do |atom|
240
+ tmp.add_atom(atom)
241
+ end
242
+ return tmp
243
+ end
244
+
245
+ undef center_of_atoms
246
+
247
+ # superclass の inverse_axis! を行ったあと、
248
+ # 原子の座標をセル内部に移す。
249
+ def inverse_axis!( axis_id )
250
+ result = Marshal.load( Marshal.dump( self ) )
251
+ super( axis_id )
252
+ reset_positions_inside
253
+ end
254
+
255
+ private
256
+
257
+ # Reset internal coordinates of all atoms inside the region of 0 <= x_i < 1.
258
+ def reset_positions_inside
259
+ @atoms.each do |atom|
260
+ coords = atom.position
261
+ atom.set_position(coords.map {|coord| coord - coord.floor}.to_a.to_v3di)
262
+ end
263
+ end
265
264
 
266
265
  end
267
266
 
268
- class Cell
269
- #require "Crystal/PeriodicCell.rb"
270
- # Return a new instance converted to PeriodicCell class.
271
- def to_pcell
272
- atoms = Marshal.load(Marshal.dump(@atoms))
273
- result = PeriodicCell.new( @axes.to_a, atoms )
274
- result.comment = self.comment
275
- return result
276
- end
267
+ class CrystalCell::Cell
268
+ #require "Crystal/PeriodicCell.rb"
269
+ # Return a new instance converted to PeriodicCell class.
270
+ def to_pcell
271
+ atoms = Marshal.load(Marshal.dump(@atoms))
272
+ result = CrystalCell::PeriodicCell.new( @axes.to_a, atoms )
273
+ result.comment = self.comment
274
+ return result
275
+ end
277
276
  end