crystalcell 0.0.0

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.
@@ -0,0 +1,210 @@
1
+ require "rubygems"
2
+ gem "mageo"
3
+ require "mageo/vector3d.rb"
4
+ require "mageo/vector3dinternal.rb"
5
+ require "mageo/axes.rb"
6
+ gem "malge"
7
+ require "malge/simultaneousequations.rb"
8
+
9
+ # Class to deal with lattice axes in three dimensional space,
10
+ # related to cell parameter and crystal axes.
11
+ # Lattice information cannot be changed after initialized.
12
+ #
13
+ # When lattice axes of three vectors are given,
14
+ # lattice axes are automatically converted as below.
15
+ # - c axes in internal axis is along to z axis in cartesian axis.
16
+ # - b axes in internal axis is on the b-c plane.
17
+ # - a axes in internal axis is set to be a right-hand system.
18
+ # E.g.,
19
+ # [ [0.5, 0.5, 0.0], [0.5, 0.0, 0.5], [0.0, 0.5, 0.5] ]
20
+ # will be converted to
21
+ # [ [0.57735, 0.20412, 0.35355],
22
+ # [0.00000, 0.61237, 0.35355],
23
+ # [0.00000, 0.00000, 0.70710]]
24
+ class LatticeAxes < Axes
25
+
26
+ class InitializeError < Exception; end
27
+ class ArgumentError < Exception; end
28
+ class TypeError < Exception; end
29
+
30
+ include Math
31
+
32
+ # Argument 'vectors' is three vectors with the order of a, b, c.
33
+ # If you want to make LatticeAxes instances from lattice constants,
34
+ # you should convert to axes with LatticeAxes.lc_to_axes
35
+ # 任意の向きのベクトルを渡しても、必ず triangulate する。
36
+ def initialize(vectors)
37
+ raise InitializeError, "#{vectors.inspect}" unless vectors.size == 3
38
+
39
+ if vectors.class == LatticeAxes
40
+ @axes = vectors
41
+ else
42
+ begin
43
+ vectors = self.class.triangulate(vectors)
44
+ rescue Vector3D::RangeError
45
+ raise InitializeError, "#{vectors.inspect}"
46
+ end
47
+
48
+ super(vectors)
49
+ end
50
+ end
51
+
52
+
53
+ ## Class methods
54
+
55
+ # Convert six lattice constants to three axes.
56
+ # Set true to the argument of 'righthand' if a assumed lattice has righthand axis system.
57
+ def self.lc_to_axes(lc, righthand = true)
58
+ raise ArgumentError if (lc.size != 6)
59
+
60
+ a = lc[0]
61
+ b = lc[1]
62
+ c = lc[2]
63
+ alpha = (2.0*PI) * lc[3] / 360.0 # radian
64
+ beta = (2.0*PI) * lc[4] / 360.0 # radian
65
+ gamma = (2.0*PI) * lc[5] / 360.0 # radian
66
+
67
+ v_c = Vector3D[0.0, 0.0, c]
68
+ v_b = Vector3D[0.0, b * Math::sin(alpha), b * Math::cos(alpha)]
69
+ v_a_z = a * Math::cos(beta)
70
+ v_a_y = (a * (Math::cos(gamma) - Math::cos(alpha) * Math::cos(beta)))/ Math::sin(alpha)
71
+ v_a_x = Math::sqrt(a**2 - v_a_y**2 - v_a_z**2)
72
+ v_a_x *= -1.0 if righthand == false
73
+ v_a = Vector3D[v_a_x, v_a_y, v_a_z]
74
+ return [v_a, v_b, v_c]
75
+ end
76
+
77
+ # Convert three axes to six lattice constants.
78
+ def self.axes_to_lc(axes)
79
+ axes.collect!{|i| Vector3D[*i] }
80
+ a = axes[0].r
81
+ b = axes[1].r
82
+ c = axes[2].r
83
+ alpha = axes[1].angle_degree(axes[2])
84
+ beta = axes[2].angle_degree(axes[0])
85
+ gamma = axes[0].angle_degree(axes[1])
86
+ return [ a, b, c, alpha, beta, gamma ]
87
+ end
88
+
89
+ # Return true if the relation of vector order is righthand system.
90
+ def self.righthand?(axes)
91
+ axes.map! { |i| Vector3D[*i] }
92
+ return true if Vector3D.scalar_triple_product(*axes) > 0.0
93
+ return false
94
+ end
95
+
96
+ # Return true if the relation of vector order is lefthand system.
97
+ def self.lefthand?(axes)
98
+ axes.map! { |i| Vector3D[*i] }
99
+ return true if Vector3D.scalar_triple_product(*axes) < 0.0
100
+ return false
101
+ end
102
+
103
+ # Convert three axes to three axes with rules below:
104
+ # c axis is along z axis in cartesian system.
105
+ # b axis is on y-z plane in cartesian system.
106
+ # Return an array of three Vector3D instances.
107
+ # This class does not convert righthand to lefthand system.
108
+ # The name of this method 'triangulate' originates from the
109
+ # matrix indicating the vectors being triangular matrix.
110
+ #
111
+ # クラスメソッドは廃止の方向で。
112
+ # vectors の数をチェックするのは initialize でやるべきことだろうし、
113
+ # LatticeAxes クラスインスタンス以外で triangulate を使う場面が想像できない。
114
+ #
115
+ def self.triangulate(vectors)
116
+ vectors.map! { |i| Vector3D[*i] }
117
+ raise InitializeError if self.dependent?(vectors)
118
+ lc = self.axes_to_lc(vectors)
119
+ righthand = self.righthand?(vectors)
120
+ return self.lc_to_axes(lc, righthand)
121
+ end
122
+
123
+ ## Instance methods.
124
+
125
+ # Get lattice constants in six values.
126
+ def get_lattice_constants
127
+ return LatticeAxes.axes_to_lc(@axes)
128
+ end
129
+
130
+ def righthand?
131
+ self.class.righthand?(@axes)
132
+ end
133
+
134
+ def lefthand?
135
+ self.class.lefthand?(@axes)
136
+ end
137
+
138
+ # This class is obsoleted. [2011-12-22]
139
+ ## Convert internal coordinates to cartesian coordinates.
140
+ ## Return a Vector3DInternal class instance, which is not a cartesian vector.
141
+ #def internal2cartesian(internal_coord)
142
+ # Vector3DInternal[ *internal_coord ].to_v3d(axes)
143
+ #end
144
+
145
+ # This class is obsoleted. [2011-12-22]
146
+ ## Convert cartesian coordinates to internal coordinates.
147
+ ## Return a Vector3D class instance, which is a cartesian vector.
148
+ #def cartesian2internal(cartesian_coord)
149
+ # #pp cartesian_coord
150
+ # Vector3D[ *cartesian_coord ].to_v3di(axes)
151
+ #end
152
+
153
+ # Compare <other> LatticeAxes instance.
154
+ # <length_ratio> is tolerance of ratio in length of axes.
155
+ # <angle_tolerance> is tolerance of value in angle between axes.
156
+ def equal_in_delta?(other, length_ratio, angle_tolerance)
157
+ length_a = self .get_lattice_constants[0..2]
158
+ length_b = other.get_lattice_constants[0..2]
159
+ 3.times do |i|
160
+ return false unless ((length_a[i] - length_b[i]).abs <= length_ratio)
161
+ end
162
+
163
+ angle_a = self .get_lattice_constants[3..5]
164
+ angle_b = other.get_lattice_constants[3..5]
165
+ 3.times do |i|
166
+ return false unless ((angle_a[i] - angle_b[i]).abs <= angle_tolerance)
167
+ end
168
+ return true
169
+ end
170
+
171
+ def ==(other)
172
+ 3.times do |i|
173
+ 3.times do |j|
174
+ return false if self[i][j] != other[i][j]
175
+ end
176
+ end
177
+ return true
178
+ end
179
+
180
+ private
181
+
182
+ def triangulate
183
+ #self.class.triangulate(@axes)
184
+ # rotate(2, 2, 1)
185
+ # rotate(2, 0, 2)
186
+ # rotate(1, 2, 1)
187
+ end
188
+
189
+ #private
190
+
191
+ ## 保持する全ての軸を回転する。
192
+ ## 以下の index は Axes クラス保持している配列における index。
193
+ ## target_index 回転の際の角度を決めるベクトルの
194
+ ## self が保持する内部座標軸の index。
195
+ ## center_axis_index 回転の中心軸のベクトルの index in x, y, z。
196
+ ## plane_axis_index) 回転の目的地となる平面を、中心軸と共に構成するベクトルの index
197
+ ## in x, y, z。
198
+ #def rotate(target_index, center_axis_index, plane_axis_index)
199
+ # theta =
200
+
201
+ # axes[target_index]
202
+ #
203
+ # HERE
204
+
205
+ # self.each do |vector|
206
+ # vector
207
+ # end
208
+ #end
209
+
210
+ end
@@ -0,0 +1,277 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "pp"
4
+ require "crystalcell/cell.rb"
5
+
6
+ # Class for crystal cell with periodic boundary.
7
+ # 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
265
+
266
+ end
267
+
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
277
+ end
File without changes
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ require 'crystalcell'
15
+
16
+ class Test::Unit::TestCase
17
+ end