Class: CrystalCell::PeriodicCell

Inherits:
Cell
  • Object
show all
Defined in:
lib/crystalcell/periodiccell.rb

Overview

Class for crystal cell with periodic boundary. Coordinates of atoms are kept in the region of 0 <= x_i < 1 of internal coordinate.

Defined Under Namespace

Classes: TypeError

Instance Attribute Summary

Attributes inherited from Cell

#angle_tolerance, #atoms, #axes, #comment, #element_names, #symprec

Instance Method Summary collapse

Methods inherited from Cell

#==, #atoms_in_supercell, #brv_lattice, #brv_positions, #brv_types, #calc_volume, #cell_of_elements, #center_of_atoms, #delete_atom, #distance, #elements, #equal_atoms_in_delta?, #equal_in_delta?, #equal_lattice_in_delta?, #exchange_axes, #exchange_axes!, #hall_symbol, #hallnum, #inverse_axis, #o_shift, #operate, #positions, #reflect, #reflect!, #rotate, #rotations, #select_indices, #set_elements, #setting, #spg, #spgnum, #symmetry_operations, #t_mat, #to_pcell, #translate, #translations, #unite, #wyckoffs

Constructor Details

#initialize(*args) ⇒ PeriodicCell

Returns a new instance of PeriodicCell.



9
10
11
12
# File 'lib/crystalcell/periodiccell.rb', line 9

def initialize( *args )
  super( *args )
  reset_positions_inside
end

Instance Method Details

#add_atom(*args) ⇒ Object

Functions as like CrystalCell::Cell.add_atom, but the coordinates are converted to the region of 0 <= x_i < 1.



218
219
220
221
# File 'lib/crystalcell/periodiccell.rb', line 218

def add_atom( *args )
  super( *args )
  reset_positions_inside
end

#directions_within_distance(pos0, pos1, tolerance = nil) ⇒ Object

ある内部座標から、別のある座標とそれと周期的に等価な座標への距離が tolerance 以下のものを探し、条件を満たすセルの方向を配列にまとめて返す。

内部的に、一度 0以上1未満の座標に変換しようかと思ったが、 境界付近で問題が生じうる。

周囲 27 セルしか考慮しない。 美しさを求めるならば tolerance を完全に含む大きさのスーパーセルにすべきだが、 実装が面倒なわりに滅多に使われることがなさそうなので。 出力の順番は、上位の要素が小さなものから順。 (距離の短いものから順という考え方もなくはないが。)

tolerance を省略( = nil を与えれば )、27セルの中にある全ての方向を返す。



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
# File 'lib/crystalcell/periodiccell.rb', line 27

def directions_within_distance( pos0, pos1, tolerance = nil )
  if pos0.class != Vector3DInternal
    raise TypeError, "pos0 is not a Vector3DInternal class instance."
  end
  if pos1.class != Vector3DInternal
    raise TypeError, "pos1 is not a Vector3DInternal class instance."
  end

  pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
  pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di

  results = []
  (-1).upto(1) do |x|
    (-1).upto(1) do |y|
      (-1).upto(1) do |z|
        shift = Vector3DInternal[ x.to_f, y.to_f, z.to_f ]
        d = distance( pos0, ( pos1 + shift))
        #tolerance が nil ならば距離判定なしで登録。
        #tolerance が非 nil ならばその値で距離判定して登録。
        if ( ( ! tolerance ) || ( d < tolerance ) )
          results << [ x, y, z]
        end
      end
    end
  end
  return results
end

#find_bonds(elem0, elem1, d_min, d_max) ⇒ Object

条件にマッチした原子間距離を見つけて、端点の内部座標の組を要素とする配列を返す。 返される配列は3重配列になる。 e.g., [

[ [0.1, 0.1, 0.1], [0.2, 0.2, 0.2] ],
[ [0.1, 0.1, 0.1], [0.3, 0.4, 0.5] ],
[ [0.9, 0.9, 0.9], [0.8, 0.8, 0.8] ]

] elem0, elem1 は bond の両端の原子の組み合わせを指定。 elem0, elem1 はメソッド内部で反転したものもチェックするので、順序が反対でも同じ結果になる。 O-O のように同じ元素を指定することもでき、 この場合向きの異なる 2つの bond が重複したりしない。 d_min, d_max は距離の範囲を指定する。 境界値そのものが問題になることは少ないだろう。 d_min <= d <= d_max(以上、以下) としておくが、計算誤差のためにこれはほぼ無意味だ。 見つけて配列として返すだけで、登録はしない。

以下の案は棄却。

  • いかなる元素でもマッチ。

  • 距離の上限を無効。

理由は、

  • プログラムが煩雑になる。

  • 引数の型が統一されない。

  • ほとんど使用する機会がなさそう。

    大抵は元素を指定するし、元素を指定しない bond を描く状況は考えにくい。
    これが必要ならメソッドの外で組み合わせを作ってそれぞれで呼べば良い。
    
  • 大抵は距離の上限を定めるし、上限なしではハリネズミになるだけ。

    また、プログラム上は距離の上限を 3x3x3 スーパーセルに制限したりするが、
    論理的に距離の上限なしってのは無限のセルを対象にすることになり、整合性がとれない。
    


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
# File 'lib/crystalcell/periodiccell.rb', line 154

def find_bonds( elem0, elem1, d_min, d_max )
  results = []
  atoms.each do |inner_atom|
    atoms_in_supercell( -1, 1, -1, 1, -1, 1 ).each do |outer_atom|
      #元素の種類による判定
      ie = inner_atom.element
      oe = outer_atom.element

      next unless ( (( ie == elem0 ) && ( oe == elem1 )) || (( ie == elem1 ) && ( oe == elem0 ))) #elem0, elem1 と同じ構成
      #next unless (( ie == elem0 ) && ( oe == elem1 )) #elem0, elem1 と同じ構成

      #距離による判定
      ip = inner_atom.position
      op = outer_atom.position
      next if distance( ip, op ) < d_min.to_f
      next if distance( ip, op ) > d_max.to_f
      next if distance( ip, op ) == 0.0 ## Check identical site

      #重複判定, 正順か逆順が既に含まれていれば無視。
      next if ( results.include?( [ ip, op ] ) || results.include?( [ op, ip ] ) )
      if (ie == elem0)
        results << [ ip, op ]
      else
        results << [ op, ip ]
      end

    end
  end
  return results
end

#inverse_axis!(axis_id) ⇒ Object

superclass の inverse_axis! を行ったあと、 原子の座標をセル内部に移す。



249
250
251
252
253
# File 'lib/crystalcell/periodiccell.rb', line 249

def inverse_axis!( axis_id )
  #result = Marshal.load( Marshal.dump( self ) )
  super( axis_id )
  reset_positions_inside
end

#nearest_direction(pos0, pos1) ⇒ Object

周期性を考慮して、 1個目の内部座標( pos0 ) から見て、 一番近い 2個目の内部座標に等価なサイトの属するセルの方向を返す。 返り値は Vector3DInternal で 内部座標が 0.0 <= r < 1.0 になっていることを前提とする。 (なお、この外側であってもこの範囲に入るように周期移動する。) 座標 0.0 付近の境界は計算誤差の為におかしなことになり易いので注意が必要。

NOTE: nearest_direction というメソッド名はどうかと思う。 nearest_lattice_vector とか? 良い名前があれば、リファクタリング対象。

NOTE: 0〜1の外側なら内側に入れる、という処理は混乱し易い。 例外にした方が良いのではないだろうか。



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
# File 'lib/crystalcell/periodiccell.rb', line 69

def nearest_direction( pos0, pos1 )
  if pos0.class != Vector3DInternal
    raise TypeError,
    "pos0.class is not a Vector3DInternal"
  end
  if pos1.class != Vector3DInternal
    raise TypeError,
    "pos1.class is not a Vector3DInternal"
  end

  pos0 = pos0.map{ |i| i - i.floor }.to_a.to_v3di
  pos1 = pos1.map{ |i| i - i.floor }.to_a.to_v3di

  #set first value
  min = distance( pos0, pos1 )
  result = Vector3DInternal[ 0, 0, 0 ]

  #find
  (-1).upto(1) do |x|
    (-1).upto(1) do |y|
      (-1).upto(1) do |z|
        shift = Vector3DInternal[ x.to_f, y.to_f, z.to_f ]
        d = (pos0 - (pos1 + shift)).to_v3d(@axes).r

        if d < min
          result = Vector3DInternal[ x, y, z]
          min = d
        end
      end
    end
  end
  return result
end

#nearest_distance(pos0, pos1) ⇒ Object

周期性を考慮し、2つの地点間の最短距離を返す。 pos0, pos1 には内部座標を指定する。 内部的には周囲 3x3x3 のセル中の27地点のうち最も近いものを得る。 旧 minimum_distance



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/crystalcell/periodiccell.rb', line 107

def nearest_distance( pos0, pos1 )
  [pos0, pos1].each_with_index do |pos, index|
    unless pos.class == Vector3DInternal
      raise TypeError,
      "#{index} th argument: #{pos.inspect}, #{pos.class}"
    end
  end

  pos0 = Vector3DInternal[* pos0.map{ |i| i - i.floor }]
  pos1 = Vector3DInternal[* pos1.map{ |i| i - i.floor }]
  direction = self.nearest_direction( pos0, pos1 )
  3.times do |i|
    pos1[ i ] += direction[ i ]
  end
  distance( pos0, pos1 )
end

#pairs_within_distance(distance) ⇒ Object

引数 distance 以下の間の距離にある原子のペアを列挙して返す。 この際、あくまで unique なものを返し、A-B があれば B-A はリストに含まれない。

返す形式は以下のような形式。

[0, 1, [0, 0, 0], [0, 1, [0, 0, 1

]

最初の 0, 1 はそれぞれ配列 @atoms 内の番号で、 0番目の原子からから同じセル内の 1番目の原子 0番目の原子からから方向に隣接するセル内の 1番目の原子を意味する。 起点となる原子の番号と(上記 0 )と目的地の原子の番号(上記 1)が同じこともありうる。 異なる場合は起点となる原子の番号が、目的地の原子の番号より若くなる。

自分と同じ番号の場合は、同じセルを除外する。 すなわち、[0, 0, [0, 0, 0] や [1, 1, [0, 0, 0] は含まれない。



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/crystalcell/periodiccell.rb', line 198

def pairs_within_distance( distance )
  results = []
  n_atom = @atoms.size
  n_atom.times do |i|
    pos_i = @atoms[i].position

    n_atom.times do |j|
      pos_j = @atoms[j].position

      directions_within_distance( pos_i, pos_j, distance ).each do |dir|
        #next if ( i==j && dir==[0,0,0] ) #距離0の自分自身。下の条件に含まれる。
        next if ( dir==[0,0,0] && i >= j ) #同じセル内は番号が若い方からのみ。
        results << [ i, j, dir ]
      end
    end
  end
  return results
end

#rotate!(*args) ⇒ Object

Functions as like CrystalCell::Cell.rotate!, but the coordinates are converted to the region of 0 <= x_i < 1. Dependent function ‘rotate’ functions the same.



225
226
227
228
# File 'lib/crystalcell/periodiccell.rb', line 225

def rotate!( *args )
  super( *args )
  reset_positions_inside
end

#to_cellObject

Return a new instance converted to CrystalCell::Cell class.



238
239
240
241
242
243
244
245
# File 'lib/crystalcell/periodiccell.rb', line 238

def to_cell
  tmp = CrystalCell::Cell.new( @axes.to_a )
  tmp.comment = self.comment
  @atoms.each do |atom|
    tmp.add_atom(atom)
  end
  return tmp
end

#translate!(*args) ⇒ Object

Functions as like CrystalCell::Cell.translate!, but the coordinates are converted to the region of 0 <= x_i < 1. Dependent function ‘translate’ functions the same.



232
233
234
235
# File 'lib/crystalcell/periodiccell.rb', line 232

def translate!( *args )
  super( *args )
  reset_positions_inside
end