crystalcell 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,271 +6,271 @@ require "pp"
6
6
  # Coordinates of atoms are kept in the region of 0 <= x_i < 1 of internal coordinate.
7
7
  class CrystalCell::PeriodicCell < CrystalCell::Cell
8
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."
9
+ class TypeError < Exception; end
10
+
11
+ def initialize( *args )
12
+ super( *args )
13
+ reset_positions_inside
35
14
  end
36
15
 
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
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."
53
32
  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"
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
77
57
  end
78
- if pos1.class != Vector3DInternal
79
- raise TypeError,
80
- "pos1.class is not a Vector3DInternal"
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
81
105
  end
82
106
 
83
- pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
84
- pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di
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
85
118
 
86
- #set first value
87
- min = distance( pos0, pos1 )
88
- result = Vector3DInternal[ 0, 0, 0 ]
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
89
127
 
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
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
96
182
 
97
- if d < min
98
- result = Vector3DInternal[ x, y, z]
99
- min = d
100
- end
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
101
211
  end
102
- end
212
+ return results
103
213
  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
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
117
219
  end
118
220
 
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 ]
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
124
226
  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
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
179
233
  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 ]
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)
209
241
  end
210
- end
242
+ return tmp
211
243
  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)
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
241
253
  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)
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
262
263
  end
263
- end
264
264
 
265
265
  end
266
266
 
267
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
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
276
276
  end