crysna 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/CHANGES +24 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +91 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/checkmodel +66 -0
- data/bin/collectcell +92 -0
- data/bin/fitframe +68 -0
- data/bin/occupiedpolyhedralsite +96 -0
- data/bin/occupiedscattersite +77 -0
- data/bin/site2poscar +66 -0
- data/bin/site2pov +30 -0
- data/bin/sitecombination +69 -0
- data/bin/sitemigration +80 -0
- data/bin/sitemigrationdistance +87 -0
- data/bin/siteoperation +109 -0
- data/bin/sitesingle +36 -0
- data/bin/siteuniq +32 -0
- data/bin/symidsite +65 -0
- data/bin/transitcell +46 -0
- data/crysna.gemspec +219 -0
- data/lib/crysna.rb +26 -0
- data/lib/crysna/atom.rb +97 -0
- data/lib/crysna/cell.rb +314 -0
- data/lib/crysna/frameatom.rb +13 -0
- data/lib/crysna/frameinterstitialcell.rb +309 -0
- data/lib/crysna/interstitialatom.rb +13 -0
- data/lib/crysna/modelstructure.rb +333 -0
- data/lib/crysna/optionmanager.rb +177 -0
- data/lib/crysna/site.rb +35 -0
- data/lib/crysna/siteconfiguration.rb +26 -0
- data/lib/crysna/sitenamelabeledcell.rb +220 -0
- data/lib/crysna/siteoperation.rb +56 -0
- data/lib/crysna/sitewithposition.rb +24 -0
- data/lib/crysna/transitionfinder.rb +448 -0
- data/lib/crysna/transitionfinder/cell.rb +144 -0
- data/lib/crysna/transitionfinder/cellmanager.rb +129 -0
- data/lib/crysna/transitionfinder/edge.rb +54 -0
- data/test/.gitignore +1 -0
- data/test/cell_orig/POSCAR +17 -0
- data/test/cell_orig/model.yaml +122 -0
- data/test/collectcells/.gitignore +2 -0
- data/test/collectcells/model.yaml +154 -0
- data/test/collectcells/nooutcar/minexpconfiguration.yaml +22 -0
- data/test/collectcells/normal-higher/OUTCAR +2406 -0
- data/test/collectcells/normal-higher/minexpconfiguration.yaml +22 -0
- data/test/collectcells/normal-lower/OUTCAR +2406 -0
- data/test/collectcells/normal-lower/minexpconfiguration.yaml +22 -0
- data/test/collectcells/normal/OUTCAR +2406 -0
- data/test/collectcells/normal/minexpconfiguration.yaml +22 -0
- data/test/collectcells/normalB/OUTCAR +2406 -0
- data/test/collectcells/normalB/minexpconfiguration.yaml +22 -0
- data/test/collectcells/unfinished/OUTCAR +40702 -0
- data/test/collectcells/unfinished/minexpconfiguration.yaml +22 -0
- data/test/collectcells/unidentified/OUTCAR +3541 -0
- data/test/collectcells/unidentified/minexpconfiguration.yaml +2 -0
- data/test/fitmodelstructure/.gitignore +1 -0
- data/test/fitmodelstructure/AgI/CONTCAR +17 -0
- data/test/fitmodelstructure/AgI/fitmodelstructure.log +1161 -0
- data/test/fitmodelstructure/AgI/model.yaml +45 -0
- data/test/fitmodelstructure/normal/CONTCAR +17 -0
- data/test/fitmodelstructure/normal/fitmodelstructure.log +5063 -0
- data/test/fitmodelstructure/normal/model.yaml +122 -0
- data/test/fitmodelstructure/unidentified/CONTCAR +44 -0
- data/test/fitmodelstructure/unidentified/fitmodelstructure.log +8833 -0
- data/test/fitmodelstructure/unidentified/model.yaml +154 -0
- data/test/helper.rb +17 -0
- data/test/identifypolyhedralsites/.gitignore +1 -0
- data/test/identifypolyhedralsites/identifyatomsites.log +333 -0
- data/test/identifypolyhedralsites/normal/fitmodelstructure.yaml +60 -0
- data/test/identifypolyhedralsites/normal/model.yaml +122 -0
- data/test/identifypolyhedralsites/unidentified/fitmodelstructure.yaml +2 -0
- data/test/identifypolyhedralsites/unidentified/model.yaml +154 -0
- data/test/identifypolyhedralsites/volumemismatch/fitmodelstructure.yaml +101 -0
- data/test/identifypolyhedralsites/volumemismatch/model.yaml +154 -0
- data/test/identifyscattersites/CONTCAR +17 -0
- data/test/identifyscattersites/POSCAR +12 -0
- data/test/identifyscattersites/fitmodelstructure.log +1 -0
- data/test/identifyscattersites/fitmodelstructure.yaml +0 -0
- data/test/identifyscattersites/identifyscattersites.yaml +5 -0
- data/test/identifyscattersites/model.yaml +45 -0
- data/test/minexpconfiguration/.gitignore +1 -0
- data/test/minexpconfiguration/collective/AgI/.gitignore +2 -0
- data/test/minexpconfiguration/collective/AgI/siteoperations.yaml +51265 -0
- data/test/minexpconfiguration/collective/AgI/sitesingle.yaml +15 -0
- data/test/minexpconfiguration/collective/AgI/test.sh +2 -0
- data/test/minexpconfiguration/normal/identifysites.yaml +22 -0
- data/test/minexpconfiguration/normal/minexpconfiguration.log +0 -0
- data/test/minexpconfiguration/normal/siteoperations.yaml +1793 -0
- data/test/minexpconfiguration/unidentified/identifysites.yaml +2 -0
- data/test/minexpconfiguration/unidentified/siteoperations.yaml +1793 -0
- data/test/sitecombination/initsites.yaml +7 -0
- data/test/sitecombination/initsites_test2.yaml +8 -0
- data/test/sitecombination/sitecombination.yaml +29 -0
- data/test/siteconfiguration/elements-sitenames.yaml +2 -0
- data/test/siteconfiguration/latticeaxes.yaml +3 -0
- data/test/siteconfiguration/sitenames-coordinates.yaml +4 -0
- data/test/sitemigrationsdistance/model.yaml +45 -0
- data/test/siteoperations/.gitignore +2 -0
- data/test/siteoperations/model.yaml +43 -0
- data/test/siteoperations/symmetryoperations.yaml +1441 -0
- data/test/sitesingle/.gitignore +1 -0
- data/test/sitesingle/sitecombination.yaml +29 -0
- data/test/siteuniq/minexpconfiguration.yaml +15 -0
- data/test/siteuniq/siteuniq.yaml +8 -0
- data/test/test_atom.rb +206 -0
- data/test/test_cell.rb +604 -0
- data/test/test_commands.rb +340 -0
- data/test/test_crystana.rb +7 -0
- data/test/test_frameatom.rb +22 -0
- data/test/test_frameinterstitialcell.rb +939 -0
- data/test/test_interstitialatom.rb +22 -0
- data/test/test_modelstructure.rb +807 -0
- data/test/test_optionmanager.rb +172 -0
- data/test/test_site.rb +40 -0
- data/test/test_siteconfiguration.rb +29 -0
- data/test/test_sitenamelabeledcell.rb +528 -0
- data/test/test_siteoperation.rb +79 -0
- data/test/test_sitewithposition.rb +20 -0
- data/test/test_transitionfinder.rb +432 -0
- data/test/transitcell/.gitignore +2 -0
- data/test/transitcell/collectcells.yaml +51 -0
- data/test/transitcell/sitemigrations.yaml +8 -0
- data/test/transitcell/siteoperations.yaml +17 -0
- data/test/transitcell/transitcell.log +1342 -0
- data/test/transitionfinder/collectcells.yaml +81 -0
- data/test/transitionfinder/sitemigrations.yaml +33 -0
- data/test/transitionfinder/siteoperations.yaml +16 -0
- data/test/transitionfinder/test_cell.rb +287 -0
- data/test/transitionfinder/test_cellmanager.rb +185 -0
- data/test/transitionfinder/test_edge.rb +49 -0
- data/test/uniquesitesgenerator/elements-sitenames.yaml +1 -0
- data/test/uniquesitesgenerator/siteoperations.yaml +9 -0
- metadata +406 -0
@@ -0,0 +1,309 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
#require "crystal/cell2.rb"
|
5
|
+
#require "mathextension/octahedron.rb"
|
6
|
+
#require "mathextension/tetrahedron.rb"
|
7
|
+
#require "mathextension/vector3d.rb"
|
8
|
+
#require "mathextension/vector3dinternal.rb"
|
9
|
+
#require "crysna/frameatom.rb"
|
10
|
+
#require "crysna/interstitialatom.rb"
|
11
|
+
#require "crysna/sitenamelabeledcell.rb"
|
12
|
+
#require "progressbar"
|
13
|
+
|
14
|
+
#
|
15
|
+
# Cell modeled by frame atoms and interstitial atoms.
|
16
|
+
# All atoms must be FrameAtom or InterstitialAtom class instance,
|
17
|
+
# not CrystalCell::Atom class instance.
|
18
|
+
#
|
19
|
+
#class FrameInterstitialCell < CrystalCell::PeriodicCell
|
20
|
+
class FrameInterstitialCell < CrystalCell::Cell
|
21
|
+
|
22
|
+
class TypeError < Exception; end
|
23
|
+
class NoMatchError < Exception; end
|
24
|
+
|
25
|
+
# Argument 'atoms' must be Array of atoms
|
26
|
+
# whose class is FrameAtom or InterstitialAtom.
|
27
|
+
# If 'atoms' includes CrystalCell::Atom class instance,
|
28
|
+
# raise exception 'FrameInterstitialCell::TypeError'
|
29
|
+
def initialize(axes, atoms = [])
|
30
|
+
atoms.each_with_index do |atom, i|
|
31
|
+
unless (atom.is_a?(FrameAtom) || (atom.is_a?(InterstitialAtom)))
|
32
|
+
raise TypeError,
|
33
|
+
"'atoms' includes #{atom.class} at #{i}."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
super(axes, atoms)
|
37
|
+
end
|
38
|
+
|
39
|
+
# サイト情報を参照して、骨格原子が最適になるように
|
40
|
+
# 48個の変換操作・並進移動を然るべく施したセル( self.class のインスタンス )を返す。
|
41
|
+
# 非破壊的
|
42
|
+
# 引数 sites はサイト情報を入れたハッシュ。キーがサイト名、値がサイトの内部座標。
|
43
|
+
# サイトと原子が一対一対応する候補が見つからない場合は
|
44
|
+
# 例外 FrameInterstitialCell::NoMatchError を返す。
|
45
|
+
# 原子の順番は非保存。
|
46
|
+
#def most_matching_cell_pair(sites, log_io )
|
47
|
+
def optimized_cell( sites, log_io )
|
48
|
+
# 48個のセルに対し、サイト数ごとに roughly translate したセルを作る。
|
49
|
+
# サイト数が 8 ならば 48*8 個の Array になる。
|
50
|
+
cells = equivalent_cells.map{ |cell|
|
51
|
+
tmp = []
|
52
|
+
sites.each do |name, vec|
|
53
|
+
translation_rough = vec - cell.frame_atoms[0].position
|
54
|
+
t_cell = cell.translate(translation_rough)
|
55
|
+
t_cell.comment += sprintf(
|
56
|
+
"; Rough translation: (% 7.5f, % 7.5f, % 7.5f), 0th atom of frame to #{name}",
|
57
|
+
* (translation_rough)
|
58
|
+
)
|
59
|
+
tmp << t_cell
|
60
|
+
end
|
61
|
+
tmp
|
62
|
+
}.flatten
|
63
|
+
|
64
|
+
p_io = $stderr
|
65
|
+
p_io = File.open(File::NULL, "w") if $test == true
|
66
|
+
pbar = ProgressBar.new("FrameInterstitialCell::optimized_cell", cells.size, p_io)
|
67
|
+
|
68
|
+
# 操作と評価を全てに対して行う。
|
69
|
+
min_cell = nil
|
70
|
+
min_sum = nil
|
71
|
+
cells.each do |cell|
|
72
|
+
pbar.inc
|
73
|
+
# SiteNameLabeledCell に変換できない cell が含まれるため、
|
74
|
+
# min_by メソッドは使えない。
|
75
|
+
|
76
|
+
log_io.puts(" #{cell.comment.sub("; ", " \\\n ")}")
|
77
|
+
log_io.puts(" atom coordinates:")
|
78
|
+
cell.atoms.each_with_index do |atom, index|
|
79
|
+
log_io.printf(" (% 7.5f, % 7.5f, % 7.5f), Element:%s, %02i-th atom.\n",
|
80
|
+
* (atom.position), atom.element, index )
|
81
|
+
end
|
82
|
+
|
83
|
+
f_cell = cell.frame_cell
|
84
|
+
i_cell = cell.interstitial_cell.to_pcell.to_cell.to_ficell([])
|
85
|
+
|
86
|
+
# SiteNameLabeledCell に変換
|
87
|
+
begin
|
88
|
+
snlcell = f_cell.to_snlcell(sites)
|
89
|
+
rescue SiteNameLabeledCell::InitializeError
|
90
|
+
log_io.puts " Not correspond atoms for sitenames."
|
91
|
+
log_io.puts
|
92
|
+
next # 一対一対応しない場合、無視する。
|
93
|
+
end
|
94
|
+
|
95
|
+
# 原子の重心とサイトの重心が一致するように
|
96
|
+
# 精密化した translation を行う。
|
97
|
+
translation_prec =
|
98
|
+
snlcell.center_of_sites - snlcell.center_of_atoms
|
99
|
+
snlcell.translate!(translation_prec)
|
100
|
+
i_cell.translate!(translation_prec)
|
101
|
+
[snlcell, i_cell].each do |cell|
|
102
|
+
cell.comment += sprintf(
|
103
|
+
"; Precise translation: (% 7.5f, % 7.5f, % 7.5f)\n",
|
104
|
+
* (translation_prec)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
# log 出力
|
109
|
+
log_io.puts(" Found frame atoms:")
|
110
|
+
frame_atoms.each do |atom|
|
111
|
+
log_io.printf(" (% 7.5f, % 7.5f % 7.5f), #{atom.name}\n",
|
112
|
+
* atom.position
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
# 二乗和で評価
|
117
|
+
sum = snlcell.sum_square_distances
|
118
|
+
log_io.printf( " Sum of square distances: %8.4f.\n\n", sum )
|
119
|
+
if (min_sum == nil) || (sum < min_sum)
|
120
|
+
min_sum = sum
|
121
|
+
min_cell = snlcell.to_cell.to_ficell.unite(i_cell)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
pbar.finish
|
125
|
+
|
126
|
+
unless min_cell.class == FrameInterstitialCell
|
127
|
+
raise NoMatchError,
|
128
|
+
"Class of min_cell is #{min_cell.class}"
|
129
|
+
end
|
130
|
+
|
131
|
+
log_io.puts( " Chosen cell: #{min_cell.comment}")
|
132
|
+
log_io.puts( " sum of square distances: #{min_sum}")
|
133
|
+
|
134
|
+
# サイト情報をここで全部書き出しておく。
|
135
|
+
log_io.puts(" Atoms:")
|
136
|
+
min_cell.atoms.each do |atom|
|
137
|
+
log_io.printf(
|
138
|
+
" (% 7.5f, % 7.5f, % 7.5f), element %s, %s.\n",
|
139
|
+
* atom.position,
|
140
|
+
atom.element,
|
141
|
+
atom.name,
|
142
|
+
)
|
143
|
+
end
|
144
|
+
#logs.each do |line|
|
145
|
+
# log_io.puts line
|
146
|
+
#end
|
147
|
+
log_io.puts
|
148
|
+
|
149
|
+
return min_cell
|
150
|
+
end
|
151
|
+
|
152
|
+
# @atoms のうち、 FrameAtom のものだけをまとめて Array にして返す。
|
153
|
+
def frame_atoms
|
154
|
+
@atoms.select{|atom| atom.class == FrameAtom}
|
155
|
+
end
|
156
|
+
|
157
|
+
# @atoms のうち、 InterstitialAtom のものだけをまとめて Array にして返す。
|
158
|
+
def interstitial_atoms
|
159
|
+
@atoms.select{|atom| atom.class == InterstitialAtom}
|
160
|
+
end
|
161
|
+
|
162
|
+
# @atoms のうち、 FrameAtom のものだけを含んだ
|
163
|
+
# FrameInterstitialCell を返す。
|
164
|
+
def frame_cell
|
165
|
+
result = FrameInterstitialCell.new(@axes, frame_atoms)
|
166
|
+
result.comment = self.comment
|
167
|
+
return result
|
168
|
+
end
|
169
|
+
|
170
|
+
# @atoms のうち、 InterstitialAtom のものだけを含んだ
|
171
|
+
# FrameInterstitialCell を返す。
|
172
|
+
def interstitial_cell
|
173
|
+
result = FrameInterstitialCell.new(@axes, interstitial_atoms)
|
174
|
+
result.comment = self.comment
|
175
|
+
return result
|
176
|
+
end
|
177
|
+
|
178
|
+
# 保持する原子群について、
|
179
|
+
# 元素ごとに分類し、元素を鍵とするハッシュとする。
|
180
|
+
# ハッシュの値はその元素の原子それぞれに付けられた名前で、ソート済の配列。
|
181
|
+
def elements_names
|
182
|
+
results = {}
|
183
|
+
@atoms.each do |atom|
|
184
|
+
elem = atom.element
|
185
|
+
if results[elem]
|
186
|
+
results[elem] << atom.name
|
187
|
+
else
|
188
|
+
results[elem] = [atom.name]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
results.each_value do |val|
|
192
|
+
val.sort!
|
193
|
+
end
|
194
|
+
return results
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
# セルに対して格子ベクトルの反転・交換操作を組み合わせた結果生じる
|
200
|
+
# 全 48 個のセルを配列にして返す。
|
201
|
+
# 鏡像操作は含まれない。
|
202
|
+
# 交換 → 反転 の順で操作を施す。
|
203
|
+
# 返される配列内部の順序は、以下の組み合わせ。
|
204
|
+
#
|
205
|
+
# 範囲 a反転 b反転 c反転
|
206
|
+
# 00-05 x x x
|
207
|
+
# 06-11 o x x
|
208
|
+
# 12-17 x o x
|
209
|
+
# 18-23 o o x
|
210
|
+
# 24-29 x x o
|
211
|
+
# 30-35 o x o
|
212
|
+
# 36-41 x o o
|
213
|
+
# 42-47 o o o
|
214
|
+
#
|
215
|
+
# 各々の範囲中では、6要素が以下の順序。
|
216
|
+
# 00: a,b,c(a,b 交換→ a,b交換)(交換なし)
|
217
|
+
# 01: b,c,a(a,b 交換→ b,c交換)
|
218
|
+
# 02: c,a,b(a,b 交換→ c,a交換)
|
219
|
+
# 03: b,a,c(a,b 交換)
|
220
|
+
# 04: a,c,b(b,c 交換)
|
221
|
+
# 05: c,b,a(c,a 交換)
|
222
|
+
def equivalent_cells
|
223
|
+
results = []
|
224
|
+
gen_size = 48
|
225
|
+
|
226
|
+
gen_size.times do |i|
|
227
|
+
cell = Marshal.load(Marshal.dump(self))
|
228
|
+
comments = [
|
229
|
+
sprintf("Equivalent cell No.%02d: Original", i)
|
230
|
+
]
|
231
|
+
case i % 6
|
232
|
+
when 0
|
233
|
+
#"do nothing"
|
234
|
+
when 1
|
235
|
+
cell.exchange_axes!([0,1])
|
236
|
+
cell.exchange_axes!([1,2])
|
237
|
+
comments << "Axes exchanged(a,b,c -> b,c,a)"
|
238
|
+
when 2
|
239
|
+
cell.exchange_axes!([0,1])
|
240
|
+
cell.exchange_axes!([2,0])
|
241
|
+
comments << "Axes exchanged(a,b,c -> c,a,b)"
|
242
|
+
when 3
|
243
|
+
cell.exchange_axes!([0,1])
|
244
|
+
comments << "Axes exchanged(a,b,c -> b,a,c)"
|
245
|
+
when 4
|
246
|
+
cell.exchange_axes!([1,2])
|
247
|
+
comments << "Axes exchanged(a,b,c -> a,c,b)"
|
248
|
+
when 5
|
249
|
+
cell.exchange_axes!([2,0])
|
250
|
+
comments << "Axes exchanged(a,b,c -> c,b,a)"
|
251
|
+
end
|
252
|
+
|
253
|
+
if ((i / 6) % 2 == 1)
|
254
|
+
cell.inverse_axis!(0)
|
255
|
+
comments << "Axis 'a' inversed"
|
256
|
+
end
|
257
|
+
if ((i / 12) % 2 == 1)
|
258
|
+
cell.inverse_axis!(1)
|
259
|
+
comments << "Axis 'b' inversed"
|
260
|
+
end
|
261
|
+
if ((i / 24) % 2 == 1)
|
262
|
+
cell.inverse_axis!(2)
|
263
|
+
comments << "Axis 'c' inversed"
|
264
|
+
end
|
265
|
+
|
266
|
+
#if ((i / 48) % 2 == 1)
|
267
|
+
# cell.reflect!
|
268
|
+
# comments << "Reflected"
|
269
|
+
#end
|
270
|
+
|
271
|
+
cell.comment = comments.join(" -> ") + "."
|
272
|
+
results << cell
|
273
|
+
end
|
274
|
+
return results
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
class CrystalCell::Cell
|
280
|
+
# frame_atom_ids で 骨格原子になるべき原子の indices を与える。
|
281
|
+
# frame_atom_ids が与えなければ、全ての原子が FrameAtom になる。
|
282
|
+
# 全ての原子を InterstitialAtom にするには、空配列を与えれば良い。
|
283
|
+
def to_ficell(frame_atom_ids = nil)
|
284
|
+
result = FrameInterstitialCell.new(@axes)
|
285
|
+
result.comment = self.comment
|
286
|
+
if frame_atom_ids
|
287
|
+
@atoms.each_with_index do |atom, i|
|
288
|
+
if frame_atom_ids.include?(i)
|
289
|
+
added_atom = FrameAtom.new(
|
290
|
+
atom.element, atom.position, atom.name, atom.movable_flags
|
291
|
+
)
|
292
|
+
else
|
293
|
+
added_atom = InterstitialAtom.new(
|
294
|
+
atom.element, atom.position, atom.name, atom.movable_flags
|
295
|
+
)
|
296
|
+
end
|
297
|
+
result.add_atom(added_atom)
|
298
|
+
end
|
299
|
+
else
|
300
|
+
@atoms.each_with_index do |atom, i|
|
301
|
+
added_atom = FrameAtom.new(
|
302
|
+
atom.element, atom.position, atom.name, atom.movable_flags
|
303
|
+
)
|
304
|
+
result.add_atom(added_atom)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
return result
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
#require "crystal/atom2.rb"
|
5
|
+
|
6
|
+
#
|
7
|
+
# Class for frame atom.
|
8
|
+
# This class is the same as CrystalCell::Atom, except for the class name.
|
9
|
+
# This class is assumed to be used in FrameInterstitialCell.
|
10
|
+
#
|
11
|
+
class InterstitialAtom < CrystalCell::Atom
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,333 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#coding: utf-8
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
#サイト情報などを抽象化したモデル構造を扱うクラス。
|
7
|
+
#内部的に、格子定数 1.0 の cubic なセルと見做して処理している。
|
8
|
+
#initialize 時点で作った以降は内部データが変更されないことを前提としている。
|
9
|
+
#(@polyhedra を作っておいた方が速そうとか思ったが、テストしてみたら変わらんかった。)
|
10
|
+
#
|
11
|
+
#体積関係のエラーチェックは、ininialize 時には行わない。
|
12
|
+
class Crysna::ModelStructure #< CrystalCell::Cell
|
13
|
+
ELEMENT = 0
|
14
|
+
TOLERANCE = 1.0E-10
|
15
|
+
|
16
|
+
class InitializeError < Exception; end
|
17
|
+
class SiteVolumeError < Exception; end
|
18
|
+
class NoEntrySiteError < Exception; end
|
19
|
+
class SiteRangeError < Exception; end
|
20
|
+
class SiteDetectionError < Exception; end
|
21
|
+
|
22
|
+
attr_reader :symmetry_operations,
|
23
|
+
:frame_elements,
|
24
|
+
:frame_sites,
|
25
|
+
:axes,
|
26
|
+
:octahedral_sites,
|
27
|
+
:tetrahedral_sites,
|
28
|
+
:scatter_sites
|
29
|
+
|
30
|
+
def initialize(hash)
|
31
|
+
##不可欠な要素がなければ例外、というのはやめる[2014-12-09]
|
32
|
+
#raise InitializeError if hash["frame_elements"] == nil
|
33
|
+
#raise InitializeError if hash["frame_sites"] == nil
|
34
|
+
#raise InitializeError if hash["symmetry_operations"] == nil
|
35
|
+
|
36
|
+
items = [
|
37
|
+
"frame_elements",
|
38
|
+
"frame_sites",
|
39
|
+
"symmetry_operations",
|
40
|
+
"tetrahedral_sites",
|
41
|
+
"octahedral_sites",
|
42
|
+
"scatter_sites",
|
43
|
+
]
|
44
|
+
hash.keys.each do |key|
|
45
|
+
#未知の項目があれば例外。
|
46
|
+
raise InitializeError, "#{key} is not found" unless items.include?(key)
|
47
|
+
end
|
48
|
+
|
49
|
+
@axes = CrystalCell::LatticeAxes.new([
|
50
|
+
[1.0, 0.0, 0.0],
|
51
|
+
[0.0, 1.0, 0.0],
|
52
|
+
[0.0, 0.0, 1.0],
|
53
|
+
])
|
54
|
+
|
55
|
+
@frame_elements = hash["frame_elements"]
|
56
|
+
|
57
|
+
@frame_sites = {}
|
58
|
+
hash["frame_sites"].each do |name, pos|
|
59
|
+
@frame_sites[name] = pos.to_v3di
|
60
|
+
end
|
61
|
+
|
62
|
+
@symmetry_operations = hash["symmetry_operations"] || []
|
63
|
+
@tetrahedral_sites = hash["tetrahedral_sites"] || []
|
64
|
+
@octahedral_sites = hash["octahedral_sites" ] || []
|
65
|
+
|
66
|
+
@scatter_sites = {}
|
67
|
+
if hash["scatter_sites"]
|
68
|
+
hash["scatter_sites"].each do |name, pos|
|
69
|
+
@scatter_sites[name] = pos.to_v3di
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##Check coodinates are 0 <= x,y,z < 1
|
74
|
+
## This check is not needed. [2013-07-30]
|
75
|
+
#frame_sites.each do |name, vec|
|
76
|
+
# vec.each do |x|
|
77
|
+
# raise SiteRangeError, "#{name}, #{vec.to_s}" if (x < 0.0 || 1.0 <= x)
|
78
|
+
# end
|
79
|
+
#end
|
80
|
+
|
81
|
+
## This check is not needed. [2013-07-30]
|
82
|
+
#polyhedra.each do |name, polyhedron |
|
83
|
+
# vec = polyhedron.center
|
84
|
+
# vec.each do |x|
|
85
|
+
# raise SiteRangeError, "#{name}, #{vec.to_s}" if (x < 0.0 || 1.0 <= x)
|
86
|
+
# end
|
87
|
+
#end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
#yaml から生成
|
92
|
+
def self.load_file(yaml)
|
93
|
+
hash = YAML.load_file(yaml)
|
94
|
+
self.new(hash)
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_volume
|
98
|
+
#def check_sanity
|
99
|
+
#### check invalid symmetry operation
|
100
|
+
@symmetry_operations.each do |ope|
|
101
|
+
#pp ope
|
102
|
+
unless ope.has_key?("rotation") && ope.has_key?("translation")
|
103
|
+
message = "invalid symmetry_operation, #{@symmetry_operations}"
|
104
|
+
raise InitializeError, message
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
#体積の総和が 1.0 に近いか。
|
110
|
+
sum_volumes = polyhedra.values.inject(0.0) do |sum, polyhedron|
|
111
|
+
sum += polyhedron.volume
|
112
|
+
end
|
113
|
+
unless (sum_volumes - 1.0).abs < TOLERANCE
|
114
|
+
message = "Sum of volumes(#{sum_volumes}) not 1.0: \n"
|
115
|
+
polyhedra.each do |key, polyhedron|
|
116
|
+
message += sprintf("%10s: %10.5f\n", key, polyhedron.volume)
|
117
|
+
end
|
118
|
+
raise SiteVolumeError, message
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#Return all site names.
|
123
|
+
def site_names
|
124
|
+
polyhedra.keys
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
#与えられた座標が、どの格子間サイトを占有するか、そのサイト名の文字列を返す。
|
129
|
+
#第1引数は対象の内部座標
|
130
|
+
# 内部座標 positions が 0-1 の範囲になければ、
|
131
|
+
# 自動的にセル内部に周期的並進移動させる。
|
132
|
+
#
|
133
|
+
#前提として全てのサイトが完全直交のような形になっていなければならないが、
|
134
|
+
#このチェックはこのメソッドに食わせる以前にチェックされているものとする。
|
135
|
+
#どのサイトにも該当しない場合についても、
|
136
|
+
#このメソッドに食わせる以前にチェックされているものとする。
|
137
|
+
#
|
138
|
+
#リストのどの格子間サイトにも含まれていないと判定されれば RuntimeError を投げる。
|
139
|
+
#しかしこれは正しくサイト設定されていれば起こらない筈。TODO そうでもなさそう。[2013-08-03]見つからなければ SiteDetectionError を投げる。
|
140
|
+
#が、演算誤差はやっぱり怖いので tolerance を設定する。
|
141
|
+
#periodic cell でtranslate したセルの座標は隙間ができそう。
|
142
|
+
def find_interstitial_site(position, tolerance, log_io)
|
143
|
+
candidates = select_interstitial_sites27(position, tolerance, log_io)
|
144
|
+
raise SiteDetectionError if candidates.empty?
|
145
|
+
choose_zyx(candidates).name
|
146
|
+
end
|
147
|
+
|
148
|
+
#Return Array of all sites which include the position
|
149
|
+
#among surrounding 3x3x3 cells.
|
150
|
+
#If the position is on the border, the results should be plural.
|
151
|
+
def select_interstitial_sites27(position, tolerance, log_io)
|
152
|
+
#MEMO:
|
153
|
+
#概念的には position に対して 多面体を 3x3x3 = 27個用意してどれかに入るか、
|
154
|
+
#というチェックを行うのだが、
|
155
|
+
#実装としては多面体は1つ固定して、 position を 27 個移動させてどれかが入るか、
|
156
|
+
#というチェックを行う。
|
157
|
+
#数学的に等価な判定になるし、後者の方が速度的に有利。
|
158
|
+
|
159
|
+
#セルの内側であることを保証する。
|
160
|
+
position = Mageo::Vector3D[*position]
|
161
|
+
position -= position.floor
|
162
|
+
|
163
|
+
results = []
|
164
|
+
polyhedra.each do |name, polyhedron|
|
165
|
+
log_io.puts(" Checking #{name}:")
|
166
|
+
|
167
|
+
log_io.puts(" Vertices:")
|
168
|
+
polyhedron.vertices.each do |vertex|
|
169
|
+
log_io.printf(
|
170
|
+
" (% 7.5f, % 7.5f, % 7.5f)inter.\n", * vertex
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
log_io.puts( " Generating 3x3x3 periodic coordinates of atom:")
|
175
|
+
log_io.printf(" orig = (% 5.3f, % 5.3f, % 5.3f)\n",
|
176
|
+
* position)
|
177
|
+
(-1).upto(1).each do |c|
|
178
|
+
(-1).upto(1).each do |b|
|
179
|
+
(-1).upto(1).each do |a|
|
180
|
+
log_io.printf(" orig+(% 1d, % 1d, % 1d)inter.", a, b, c)
|
181
|
+
pos = position.to_v3d +
|
182
|
+
Mageo::Vector3D[-1.0, 0.0, 0.0] * a +
|
183
|
+
Mageo::Vector3D[ 0.0, -1.0, 0.0] * b +
|
184
|
+
Mageo::Vector3D[ 0.0, 0.0, -1.0] * c
|
185
|
+
#-1 factor is because polyhedron is fix and position is shifted.
|
186
|
+
|
187
|
+
log_io.printf(" -> (% 7.3f, % 7.3f, % 7.3f)cart. : ",
|
188
|
+
* pos )
|
189
|
+
if polyhedron.include?(pos, tolerance)
|
190
|
+
log_io.puts("Inside.")
|
191
|
+
log_io.puts(" Found at atom with translation in internal vector [#{a}, #{b}, #{c}].")
|
192
|
+
|
193
|
+
results << Crysna::SiteWithPosition.new(
|
194
|
+
name,
|
195
|
+
Mageo::Vector3DInternal[a,b,c],
|
196
|
+
polyhedron.center.internal_coordinates(@axes)
|
197
|
+
)
|
198
|
+
else
|
199
|
+
log_io.puts("Outside.")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
log_io.puts
|
206
|
+
end
|
207
|
+
return results
|
208
|
+
end
|
209
|
+
|
210
|
+
#Assuming a cubic cell with lattice constants of 1,1,1,90,90,90.
|
211
|
+
def find_nearest_frame_site(position)
|
212
|
+
find_site(position, @frame_sites)
|
213
|
+
end
|
214
|
+
|
215
|
+
def find_site(position, sites)
|
216
|
+
position = Mageo::Vector3D[* position]
|
217
|
+
position -= position.floor
|
218
|
+
|
219
|
+
distance = 1.0/0.0
|
220
|
+
minimum_global_vector = [0, 0, 0]
|
221
|
+
minimum_site_name = nil
|
222
|
+
sites.each do |fsite|
|
223
|
+
site_name = fsite[0]
|
224
|
+
site_vector = fsite[1].to_v3d([[1,0,0], [0,1,0], [0,0,1]])
|
225
|
+
|
226
|
+
|
227
|
+
(-1).upto(1).each do |x|
|
228
|
+
(-1).upto(1).each do |y|
|
229
|
+
(-1).upto(1).each do |z|
|
230
|
+
tmp_vec =
|
231
|
+
position - (site_vector + Mageo::Vector3D[x,y,z])
|
232
|
+
new_distance = tmp_vec.r
|
233
|
+
|
234
|
+
if distance > new_distance
|
235
|
+
minimum_global_vector = [x,y,z]
|
236
|
+
minimum_site_name = site_name
|
237
|
+
distance = new_distance
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
return [minimum_site_name, minimum_global_vector]
|
245
|
+
end
|
246
|
+
|
247
|
+
#Return a hash whose keys and values are site names and center point of polyhedra,
|
248
|
+
#respectively.
|
249
|
+
def ideal_interstitial_sites
|
250
|
+
results = {}
|
251
|
+
polyhedra.each {|name, polyhedron| results[name] = polyhedron.center.internal_coordinates(@axes)}
|
252
|
+
results
|
253
|
+
end
|
254
|
+
|
255
|
+
def polyhedra
|
256
|
+
tetrahedra(@tetrahedral_sites).merge(octahedra(@octahedral_sites))
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
#
|
262
|
+
#
|
263
|
+
def tetrahedra(sites)
|
264
|
+
results = {}
|
265
|
+
sites.each do |site_name, vertices|
|
266
|
+
coords = vertices.map do |vertex|
|
267
|
+
to_coord(vertex[0], vertex[1])
|
268
|
+
end
|
269
|
+
begin
|
270
|
+
results[site_name] = Mageo::Tetrahedron.new(coords)
|
271
|
+
rescue Mageo::Tetrahedron::InitializeError
|
272
|
+
message = "Volume of #{site_name} is zero."
|
273
|
+
raise SiteVolumeError , message
|
274
|
+
end
|
275
|
+
end
|
276
|
+
return results
|
277
|
+
end
|
278
|
+
|
279
|
+
def octahedra(sites)
|
280
|
+
results = {}
|
281
|
+
sites.each do |site_name, vertex_pairs|
|
282
|
+
coords_pairs = vertex_pairs.map do |vertex_pair|
|
283
|
+
vertex_pair.map{|vertex| to_coord(* vertex)}
|
284
|
+
end
|
285
|
+
|
286
|
+
begin
|
287
|
+
results[site_name] = Mageo::Octahedron.new(coords_pairs)
|
288
|
+
rescue Mageo::Octahedron::InitializeError
|
289
|
+
message = "Volume of #{site_name} is zero."
|
290
|
+
raise SiteVolumeError , message
|
291
|
+
end
|
292
|
+
end
|
293
|
+
return results
|
294
|
+
end
|
295
|
+
|
296
|
+
#サイト名と並進操作の組み合わせから cartesian 座標を得る。
|
297
|
+
def to_coord(name, translation)
|
298
|
+
unless @frame_sites.keys.include?(name)
|
299
|
+
raise NoEntrySiteError, "#{name} not in #{@frame_sites.keys}"
|
300
|
+
end
|
301
|
+
(@frame_sites[name] + Mageo::Vector3DInternal[*translation]).to_v3d(@axes)
|
302
|
+
end
|
303
|
+
|
304
|
+
def choose_zyx(candidates)
|
305
|
+
result = candidates[0]
|
306
|
+
candidates[1..-1].each do |site|
|
307
|
+
if result.coordinates[2] < site.coordinates[2]
|
308
|
+
result = site
|
309
|
+
next
|
310
|
+
elsif result.coordinates[2] > site.coordinates[2]
|
311
|
+
next
|
312
|
+
end
|
313
|
+
|
314
|
+
if result.coordinates[1] < site.coordinates[1]
|
315
|
+
result = site
|
316
|
+
next
|
317
|
+
elsif result.coordinates[1] > site.coordinates[1]
|
318
|
+
next
|
319
|
+
end
|
320
|
+
|
321
|
+
if result.coordinates[0] < site.coordinates[0]
|
322
|
+
result = site
|
323
|
+
next
|
324
|
+
elsif result.coordinates[0] > site.coordinates[0]
|
325
|
+
next
|
326
|
+
end
|
327
|
+
raise "Must not happen! Exist the same site?"
|
328
|
+
end
|
329
|
+
return result
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|