class CrystalCell::PeriodicCell

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

Public Class Methods

new( *args ) click to toggle source
Calls superclass method CrystalCell::Cell::new
# File lib/crystalcell/periodiccell.rb, line 9
def initialize( *args )
  super( *args )
  reset_positions_inside
end

Public Instance Methods

add_atom( *args ) click to toggle source

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

Calls superclass method CrystalCell::Cell#add_atom
# File lib/crystalcell/periodiccell.rb, line 218
def add_atom( *args )
  super( *args )
  reset_positions_inside
end
directions_within_distance( pos0, pos1, tolerance = nil ) click to toggle source

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

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

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

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

# 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 ) click to toggle source

条件にマッチした原子間距離を見つけて、端点の内部座標の組を要素とする配列を返す。 返される配列は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] ]
]

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

以下の案は棄却。

- いかなる元素でもマッチ。
- 距離の上限を無効。

理由は、

- プログラムが煩雑になる。
- 引数の型が統一されない。
- ほとんど使用する機会がなさそう。
      大抵は元素を指定するし、元素を指定しない bond を描く状況は考えにくい。
      これが必要ならメソッドの外で組み合わせを作ってそれぞれで呼べば良い。
- 大抵は距離の上限を定めるし、上限なしではハリネズミになるだけ。
      また、プログラム上は距離の上限を 3x3x3 スーパーセルに制限したりするが、
      論理的に距離の上限なしってのは無限のセルを対象にすることになり、整合性がとれない。
# 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 ) click to toggle source

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

Calls superclass method CrystalCell::Cell#inverse_axis!
# 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 ) click to toggle source

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

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

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

# 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 ) click to toggle source

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

# 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 ) click to toggle source

引数 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] は含まれない。

# 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 ) click to toggle source

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.

Calls superclass method CrystalCell::Cell#rotate!
# File lib/crystalcell/periodiccell.rb, line 225
def rotate!( *args )
  super( *args )
  reset_positions_inside
end
to_cell() click to toggle source

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

# 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 ) click to toggle source

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.

Calls superclass method CrystalCell::Cell#translate!
# File lib/crystalcell/periodiccell.rb, line 232
def translate!( *args )
  super( *args )
  reset_positions_inside
end

Private Instance Methods

reset_positions_inside() click to toggle source

Reset internal coordinates of all atoms inside the region of 0 <= x_i < 1.

# File lib/crystalcell/periodiccell.rb, line 258
def reset_positions_inside
  @atoms.each do |atom|
    coords = atom.position
    atom.set_position(coords.map {|coord| coord - coord.floor}.to_a.to_v3di)
  end
end