vasputils 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/Gemfile +1 -1
  2. data/VERSION +1 -1
  3. data/bin/lsvasp +90 -0
  4. data/bin/qsubvasp +42 -6
  5. data/bin/runvasp +35 -0
  6. data/lib/vasputils/calcinspector.rb +15 -15
  7. data/lib/vasputils/incar.rb +30 -30
  8. data/lib/vasputils/kpoints.rb +34 -34
  9. data/lib/vasputils/outcar.rb +45 -45
  10. data/lib/vasputils/poscar.rb +145 -145
  11. data/lib/vasputils/potcar.rb +12 -12
  12. data/lib/vasputils/vaspdir.rb +52 -77
  13. data/lib/vasputils/vaspgeomopt.rb +114 -0
  14. data/memo.txt +7 -0
  15. data/test/test_vaspdir.rb +24 -6
  16. data/test/test_vaspgeomopt.rb +95 -0
  17. data/test/{vaspdir/finished/lock → vaspgeomopt/ended-Iter1/try00/CONTCAR} +0 -0
  18. data/test/{vaspdir/locked/lock → vaspgeomopt/ended-Iter1/try00/INCAR} +0 -0
  19. data/test/{vaspdir/started/lock → vaspgeomopt/ended-Iter1/try00/KPOINTS} +0 -0
  20. data/test/vaspgeomopt/ended-Iter1/try00/OUTCAR +0 -0
  21. data/test/vaspgeomopt/ended-Iter1/try00/POSCAR +0 -0
  22. data/test/vaspgeomopt/ended-Iter1/try00/POTCAR +0 -0
  23. data/test/vaspgeomopt/ended-Iter1/try01/INCAR +0 -0
  24. data/test/vaspgeomopt/ended-Iter1/try01/KPOINTS +0 -0
  25. data/test/vaspgeomopt/ended-Iter1/try01/OUTCAR +16 -0
  26. data/test/vaspgeomopt/ended-Iter1/try01/POSCAR +0 -0
  27. data/test/vaspgeomopt/ended-Iter1/try01/POTCAR +0 -0
  28. data/test/vaspgeomopt/ended-Iter2/try00/CONTCAR +0 -0
  29. data/test/vaspgeomopt/ended-Iter2/try00/INCAR +0 -0
  30. data/test/vaspgeomopt/ended-Iter2/try00/KPOINTS +0 -0
  31. data/test/vaspgeomopt/ended-Iter2/try00/OUTCAR +0 -0
  32. data/test/vaspgeomopt/ended-Iter2/try00/POSCAR +0 -0
  33. data/test/vaspgeomopt/ended-Iter2/try00/POTCAR +0 -0
  34. data/test/vaspgeomopt/ended-Iter2/try01/INCAR +0 -0
  35. data/test/vaspgeomopt/ended-Iter2/try01/KPOINTS +0 -0
  36. data/test/vaspgeomopt/ended-Iter2/try01/OUTCAR +18 -0
  37. data/test/vaspgeomopt/ended-Iter2/try01/POSCAR +0 -0
  38. data/test/vaspgeomopt/ended-Iter2/try01/POTCAR +0 -0
  39. data/test/vaspgeomopt/not-yet/try00/INCAR +0 -0
  40. data/test/vaspgeomopt/not-yet/try00/KPOINTS +0 -0
  41. data/test/vaspgeomopt/not-yet/try00/POSCAR +0 -0
  42. data/test/vaspgeomopt/not-yet/try00/POTCAR +0 -0
  43. data/test/vaspgeomopt/prepare_next/try00/CHG +0 -0
  44. data/test/vaspgeomopt/prepare_next/try00/CHGCAR +0 -0
  45. data/test/vaspgeomopt/prepare_next/try00/CONTCAR +0 -0
  46. data/test/vaspgeomopt/prepare_next/try00/DOSCAR +0 -0
  47. data/test/vaspgeomopt/prepare_next/try00/EIGENVAL +0 -0
  48. data/test/vaspgeomopt/prepare_next/try00/INCAR +0 -0
  49. data/test/vaspgeomopt/prepare_next/try00/KPOINTS +0 -0
  50. data/test/vaspgeomopt/prepare_next/try00/OSZICAR +0 -0
  51. data/test/vaspgeomopt/prepare_next/try00/OUTCAR +0 -0
  52. data/test/vaspgeomopt/prepare_next/try00/PCDAT +0 -0
  53. data/test/vaspgeomopt/prepare_next/try00/POSCAR +0 -0
  54. data/test/vaspgeomopt/prepare_next/try00/POTCAR +0 -0
  55. data/test/vaspgeomopt/prepare_next/try00/WAVECAR +0 -0
  56. data/test/vaspgeomopt/prepare_next/try00/XDATCAR +0 -0
  57. data/test/vaspgeomopt/prepare_next/try00/vasprun.xml +0 -0
  58. data/test/vaspgeomopt/started/try00/INCAR +0 -0
  59. data/test/vaspgeomopt/started/try00/KPOINTS +0 -0
  60. data/test/vaspgeomopt/started/try00/POSCAR +0 -0
  61. data/test/vaspgeomopt/started/try00/POTCAR +0 -0
  62. data/test/vaspgeomopt/till01/try00/CONTCAR +0 -0
  63. data/test/vaspgeomopt/till01/try00/INCAR +0 -0
  64. data/test/vaspgeomopt/till01/try00/KPOINTS +0 -0
  65. data/test/vaspgeomopt/till01/try00/OUTCAR +0 -0
  66. data/test/vaspgeomopt/till01/try00/POSCAR +0 -0
  67. data/test/vaspgeomopt/till01/try00/POTCAR +0 -0
  68. data/test/vaspgeomopt/till01/try01/INCAR +0 -0
  69. data/test/vaspgeomopt/till01/try01/KPOINTS +0 -0
  70. data/test/vaspgeomopt/till01/try01/POSCAR +0 -0
  71. data/test/vaspgeomopt/till01/try01/POTCAR +0 -0
  72. data/vasputils.gemspec +66 -14
  73. metadata +86 -33
  74. data/bin/repeatvasp +0 -47
  75. data/lib/vasputils/calcrepeater.rb +0 -27
  76. data/lib/vasputils/calcseries.rb +0 -98
  77. data/test/test_calcrepeater.rb +0 -69
  78. data/test/test_calcseries.rb +0 -77
@@ -18,151 +18,151 @@ require "crystalcell/cell.rb"
18
18
  # VASP 5 系を使うようになれば事情が変わるだろう。
19
19
  class Poscar
20
20
 
21
- class ElementMismatchError < Exception; end
22
- class ParseError < Exception; end
23
-
24
- # io を読み込んで Cell クラスインスタンスを返す。
25
- # 構文解析できなければ例外 Poscar::ParseError を投げる。
26
- def self.parse(io)
27
- # analyze POSCAR.
28
-
29
- begin
30
- #line 1: comment (string)
31
- comment = io.readline.chomp
32
-
33
- #line 2: universal scaling factor (float)
34
- scale = io.readline.to_f
35
- raise "Poscar.load_file cannot use negative scaling factor.\n" if scale < 0
36
-
37
- #line 3-5: axes (3x3 Array of float)
38
- axes = []
39
- 3.times do |i| #each axis of a, b, c.
40
- vec = io.readline.strip.split(/\s+/) #in x,y,z directions
41
- axes << vec.collect! { |i| i.to_f * scale } #multiply scaling factor
42
- end
43
-
44
- #line 6: numbers of elements. e.g.,[1, 1, 2]
45
- nums_elements = io.readline.strip.split( /\s+/ ).map{|i| i.to_i}
46
-
47
- #line 7-(8): 'Selective dynamics' or not (bool)
48
- line = io.readline
49
- if line =~ /^\s*s/i
50
- selective_dynamics = true
51
- line = io.readline # when this situation, reading one more line is nessesarry
52
- end
53
-
54
- if (line =~ /^\s*d/i ) # allow only 'Direct' now
55
- direct = true
56
- else
57
- raise "Not 'direct' indication."
58
- end
59
-
60
- #line 9(8): position
61
- #e.g., positions_of_elements
62
- #e.g., movable_flags_of_elements
63
-
64
- atoms = []
65
- nums_elements.size.times do |elem_index|
66
- nums_elements[elem_index].times do |index|
67
- items = io.readline.strip.split( /\s+/ )
68
- pos = items[0..2].map {|coord| coord.to_f}
69
- #pp pos
70
-
71
- mov_flags = []
72
- if items.size >= 6 then
73
- items[3..5].each do |i|
74
- ( i =~ /^t/i ) ? mov_flags << true : mov_flags << false
75
- end
76
- atoms << Atom.new(elem_index, pos, mov_flags)
77
- else
78
- atoms << Atom.new(elem_index, pos)
79
- end
80
- end
81
- end
82
- rescue EOFError
83
- raise ParseError, "end of file reached"
84
- end
85
-
86
- cell = Cell.new(axes, atoms )
87
- cell.comment = comment
88
- cell
89
- end
90
-
91
- # file で与えられた名前のファイルを読み込んで Cell クラスインスタンスを返す。
92
- # 構文解析できなければ例外 Poscar::ParseError を投げる。
93
- def self.load_file(file)
94
- io = File.open(file, "r")
95
- self.parse(io)
96
- end
97
-
98
- # POSCAR 形式で書き出す。
99
- # cell は Cell クラスインスタンスと同等のメソッドを持つもの。
100
- # elems は書き出す元素の順番。
101
- # elems が cell の持つ元素リストとマッチしなければ
102
- # 例外 Poscar::ElementMismatchError を投げる。
103
- # io は書き出すファイルハンドル。
104
- def self.dump(cell, elems, io)
105
- unless (Mapping::map?(cell.elements.uniq, elems){ |i, j| i == j })
106
- raise ElementMismatchError,
107
- "elems [#{elems.join(",")}] mismatches to cell.elements [#{cell.elements.join(",")}."
108
- end
109
-
110
- io.puts cell.comment
111
- io.puts "1.0" #scale
112
- 3.times do |i|
113
- io.printf( " % 18.15f % 18.15f % 18.15f\n", cell.axes[i][0], cell.axes[i][1], cell.axes[i][2]
114
- )
115
- end
116
-
117
- # collect information
118
- elem_list = Hash.new
119
- elems.each do |elem|
120
- elem_list[ elem ] = cell.atoms.select{ |atom| atom.element == elem }
121
- end
122
- io.puts(elems.map { |elem| elem_list[elem].size }.join(" "))
123
-
124
- # Selective dynamics
125
- # どれか1つでも getMovableFlag が真であれば Selective dynamics をオンにする
126
- selective_dynamics = false
127
- cell.atoms.each do |atom|
128
- if atom.movable_flags
129
- selective_dynamics = true
130
- io.puts "Selective dynamics"
131
- break
132
- end
133
- end
134
-
135
- elems.each do |elem|
136
- elem_list[ elem ].each do |atom|
137
- if atom.movable_flags
138
- selective_dynamics = true
139
- break
140
- end
141
- end
142
- break if selective_dynamics
143
- end
144
-
145
- io.puts "Direct"
146
-
147
- # positions of atoms
148
- elems.each do |elem|
149
- elem_list[ elem ].each do |atom|
150
- tmp = sprintf(
151
- " % 18.15f % 18.15f % 18.15f",
152
- * atom.position )
153
- if selective_dynamics
154
- if atom.movable_flags == nil
155
- tmp += " T T T"
156
- else
157
- atom.movable_flags.each do |mov|
158
- ( mov == true ) ? tmp += " T" : tmp += " F"
159
- end
160
- end
161
- end
162
- io.puts tmp
163
- end
164
- end
165
- end
21
+ class ElementMismatchError < Exception; end
22
+ class ParseError < Exception; end
23
+
24
+ # io を読み込んで Cell クラスインスタンスを返す。
25
+ # 構文解析できなければ例外 Poscar::ParseError を投げる。
26
+ def self.parse(io)
27
+ # analyze POSCAR.
28
+
29
+ begin
30
+ #line 1: comment (string)
31
+ comment = io.readline.chomp
32
+
33
+ #line 2: universal scaling factor (float)
34
+ scale = io.readline.to_f
35
+ raise "Poscar.load_file cannot use negative scaling factor.\n" if scale < 0
36
+
37
+ #line 3-5: axes (3x3 Array of float)
38
+ axes = []
39
+ 3.times do |i| #each axis of a, b, c.
40
+ vec = io.readline.strip.split(/\s+/) #in x,y,z directions
41
+ axes << vec.collect! { |i| i.to_f * scale } #multiply scaling factor
42
+ end
43
+
44
+ #line 6: numbers of elements. e.g.,[1, 1, 2]
45
+ nums_elements = io.readline.strip.split( /\s+/ ).map{|i| i.to_i}
46
+
47
+ #line 7-(8): 'Selective dynamics' or not (bool)
48
+ line = io.readline
49
+ if line =~ /^\s*s/i
50
+ selective_dynamics = true
51
+ line = io.readline # when this situation, reading one more line is nessesarry
52
+ end
53
+
54
+ if (line =~ /^\s*d/i ) # allow only 'Direct' now
55
+ direct = true
56
+ else
57
+ raise "Not 'direct' indication."
58
+ end
59
+
60
+ #line 9(8): position
61
+ #e.g., positions_of_elements
62
+ #e.g., movable_flags_of_elements
63
+
64
+ atoms = []
65
+ nums_elements.size.times do |elem_index|
66
+ nums_elements[elem_index].times do |index|
67
+ items = io.readline.strip.split( /\s+/ )
68
+ pos = items[0..2].map {|coord| coord.to_f}
69
+ #pp pos
70
+
71
+ mov_flags = []
72
+ if items.size >= 6 then
73
+ items[3..5].each do |i|
74
+ ( i =~ /^t/i ) ? mov_flags << true : mov_flags << false
75
+ end
76
+ atoms << Atom.new(elem_index, pos, mov_flags)
77
+ else
78
+ atoms << Atom.new(elem_index, pos)
79
+ end
80
+ end
81
+ end
82
+ rescue EOFError
83
+ raise ParseError, "end of file reached"
84
+ end
85
+
86
+ cell = Cell.new(axes, atoms )
87
+ cell.comment = comment
88
+ cell
89
+ end
90
+
91
+ # file で与えられた名前のファイルを読み込んで Cell クラスインスタンスを返す。
92
+ # 構文解析できなければ例外 Poscar::ParseError を投げる。
93
+ def self.load_file(file)
94
+ io = File.open(file, "r")
95
+ self.parse(io)
96
+ end
97
+
98
+ # POSCAR 形式で書き出す。
99
+ # cell は Cell クラスインスタンスと同等のメソッドを持つもの。
100
+ # elems は書き出す元素の順番。
101
+ # elems が cell の持つ元素リストとマッチしなければ
102
+ # 例外 Poscar::ElementMismatchError を投げる。
103
+ # io は書き出すファイルハンドル。
104
+ def self.dump(cell, elems, io)
105
+ unless (Mapping::map?(cell.elements.uniq, elems){ |i, j| i == j })
106
+ raise ElementMismatchError,
107
+ "elems [#{elems.join(",")}] mismatches to cell.elements [#{cell.elements.join(",")}."
108
+ end
109
+
110
+ io.puts cell.comment
111
+ io.puts "1.0" #scale
112
+ 3.times do |i|
113
+ io.printf( " % 18.15f % 18.15f % 18.15f\n", cell.axes[i][0], cell.axes[i][1], cell.axes[i][2]
114
+ )
115
+ end
116
+
117
+ # collect information
118
+ elem_list = Hash.new
119
+ elems.each do |elem|
120
+ elem_list[ elem ] = cell.atoms.select{ |atom| atom.element == elem }
121
+ end
122
+ io.puts(elems.map { |elem| elem_list[elem].size }.join(" "))
123
+
124
+ # Selective dynamics
125
+ # どれか1つでも getMovableFlag が真であれば Selective dynamics をオンにする
126
+ selective_dynamics = false
127
+ cell.atoms.each do |atom|
128
+ if atom.movable_flags
129
+ selective_dynamics = true
130
+ io.puts "Selective dynamics"
131
+ break
132
+ end
133
+ end
134
+
135
+ elems.each do |elem|
136
+ elem_list[ elem ].each do |atom|
137
+ if atom.movable_flags
138
+ selective_dynamics = true
139
+ break
140
+ end
141
+ end
142
+ break if selective_dynamics
143
+ end
144
+
145
+ io.puts "Direct"
146
+
147
+ # positions of atoms
148
+ elems.each do |elem|
149
+ elem_list[ elem ].each do |atom|
150
+ tmp = sprintf(
151
+ " % 18.15f % 18.15f % 18.15f",
152
+ * atom.position )
153
+ if selective_dynamics
154
+ if atom.movable_flags == nil
155
+ tmp += " T T T"
156
+ else
157
+ atom.movable_flags.each do |mov|
158
+ ( mov == true ) ? tmp += " T" : tmp += " F"
159
+ end
160
+ end
161
+ end
162
+ io.puts tmp
163
+ end
164
+ end
165
+ end
166
166
 
167
167
  end
168
168
 
@@ -2,17 +2,17 @@
2
2
  # Class for dealing with POTCAR.
3
3
  #
4
4
  module Potcar
5
- def self.load_file(file)
6
- results = {}
7
- results[:name] = file
5
+ def self.load_file(file)
6
+ results = {}
7
+ results[:name] = file
8
8
 
9
- elements = Array.new
10
- File.open( file, "r" ).each do |line|
11
- if line =~ /VRHFIN\s*=\s*([A-Za-z]*)/
12
- elements << $1
13
- end
14
- end
15
- results[:elements] = elements
16
- results
17
- end
9
+ elements = Array.new
10
+ File.open( file, "r" ).each do |line|
11
+ if line =~ /VRHFIN\s*=\s*([A-Za-z]*)/
12
+ elements << $1
13
+ end
14
+ end
15
+ results[:elements] = elements
16
+ results
17
+ end
18
18
  end
@@ -4,10 +4,12 @@
4
4
  require "fileutils"
5
5
  require "pp"
6
6
  require "date"
7
+ require "yaml"
7
8
 
8
9
  require "rubygems"
9
10
  gem "comana"
10
- require "comana.rb"
11
+ require "comana/computationmanager.rb"
12
+ require "comana/machineinfo.rb"
11
13
 
12
14
  require "vasputils/incar.rb"
13
15
  require "vasputils/outcar.rb"
@@ -29,6 +31,18 @@ require "vasputils/kpoints.rb"
29
31
  class VaspDir < Comana
30
32
  class InitializeError < Exception; end
31
33
 
34
+ #INCAR 解析とかして、モードを調べる。
35
+ #- 格子定数の構造最適化モード(ISIF = 3)
36
+ #- 格子定数を固定した構造最適化モード(ISIF = 2)
37
+ ##- k 点探索モードは無理だろう。
38
+ def initialize(dir)
39
+ super(dir)
40
+ %w(INCAR KPOINTS POSCAR POTCAR).each do |file|
41
+ infile = "#{@dir}/#{file}"
42
+ raise InitializeError, infile unless FileTest.exist? infile
43
+ end
44
+ end
45
+
32
46
  # 配下の OUTCAR を Outcar インスタンスにして返す。
33
47
  # 存在しなければ例外 Errno::ENOENT を返す。
34
48
  def outcar
@@ -54,6 +68,19 @@ class VaspDir < Comana
54
68
  Kpoints.load_file("#{@dir}/KPOINTS")
55
69
  end
56
70
 
71
+ # 正常に終了していれば true を返す。
72
+ # 実行する前や実行中、OUTCAR が完遂していなければ false。
73
+ #
74
+ # MEMO
75
+ # PI12345 ファイルは実行中のみ存在し、終了後 vasp (mpi?) に自動的に削除される。
76
+ def finished?
77
+ begin
78
+ return Outcar.load_file("#{@dir}/OUTCAR")[:normal_ended]
79
+ rescue Errno::ENOENT
80
+ return false
81
+ end
82
+ end
83
+
57
84
  private
58
85
 
59
86
  # vasp を投げる。
@@ -68,7 +95,7 @@ class VaspDir < Comana
68
95
  # machinefile を生成しないとどのホストで計算するか、安定しない。
69
96
  # そのうち mpiexec from torque に対応するが、
70
97
  # まずは mpirun で動くように作る。
71
- def send_command
98
+ def calculate
72
99
  #File.open(lock_file, "w") do |lock_io|
73
100
  # lock_io.puts "HOST: #{ENV["HOST"]}"
74
101
  # lock_io.puts "START: #{Time.now.to_s}"
@@ -85,89 +112,37 @@ class VaspDir < Comana
85
112
  # io.puts "localhost:#{num_cores}"
86
113
  # end
87
114
  #end
88
- num_cores = 4
89
- command = "cd #{@dir};" +
90
- "/usr/local/calc/mpich-1.2.7-ifc7/bin/mpirun " +
91
- "-np #{num_cores} " +
92
- "-machinefile machines " +
93
- "/usr/local/calc/vasp/vasp4631mpi" +
94
- "> stdout"
95
-
96
- if $TEST
97
- generated_files = [
98
- "CHG",
99
- "CHGCAR",
100
- "CONTCAR",
101
- "DOSCAR",
102
- "EIGENVAL",
103
- "IBZKPT",
104
- "OSZICAR",
105
- "OUTCAR",
106
- "PCDAT",
107
- "WAVECAR",
108
- "XDATCAR",
109
- "machines",
110
- "vasprun.xml",
111
- "lock",
112
- ]
113
- generated_files.map!{|i| "#{@dir}/#{i}"}
114
- command = "touch #{generated_files.join(" ")}"
115
- end
116
-
117
- system command
118
- #status = system command
119
- #if status
120
- # lock_io.puts "STATUS: normal ended."
121
- #else
122
- # lock_io.puts "STATUS: irregular ended, status #{$?}."
123
- #end
124
- end
115
+ #num_cores = 4
125
116
 
126
- #INCAR 解析とかして、モードを調べる。
127
- #- 格子定数の構造最適化モード(ISIF = 3)
128
- #- 格子定数を固定した構造最適化モード(ISIF = 2)
129
- ##- k 点探索モードは無理だろう。
130
- def set_parameters
131
- #pp @dir; exit;
132
- %w(INCAR KPOINTS POSCAR POTCAR).each do |file|
133
- infile = "#{@dir}/#{file}"
134
- raise InitializeError, infile unless FileTest.exist? infile
117
+ #settings = YAML.load_file("#{ENV["HOME"]}/.machineinfo")
118
+ #setting = settings[ENV["HOST"]]
119
+ begin
120
+ hi = MachineInfo.load_file("#{ENV["HOME"]}/.machineinfo").get_host(ENV["HOST"])
121
+ vasp = hi["vasp"]
122
+ rescue
123
+ vasp = "vasp"
135
124
  end
125
+ command = "cd #{@dir};"
126
+ command += vasp
127
+ command += "> stdout"
136
128
 
137
- #@incar = Incar.load_file("#{@dir}/INCAR")
138
- #case @incar["IBRION"]
139
- #when "-1"
140
- # @mode = :single_point
141
- ##when "1"
142
- ## @mode = :molecular_dynamics
143
- #when "2"
144
- # if (@incar["ISIF"] == "2")
145
- # @mode = :geom_opt_atoms
146
- # elsif (@incar["ISIF"] == "3")
147
- # @mode = :geom_opt_lattice
148
- # else
149
- # @mode = :geom_opt
150
- # end
129
+ #if ENV["PBS_JOBID"]
130
+ # command += "/usr/local/calc/mpiexec/bin/mpiexec /usr/local/calc/bin/vasp5212-mpich2"
151
131
  #else
152
- # @mode = nil
132
+ # /usr/local/calc/bin/vasp5212-mpich2"
133
+ ## command = "cd #{@dir};" +
134
+ ## "/usr/local/calc/mpich-1.2.7-ifc7/bin/mpirun " +
135
+ ## "-np #{num_cores} " +
136
+ ## "-machinefile machines " +
137
+ ## "/usr/local/calc/vasp/vasp4631mpi" +
138
+ ## "> stdout"
153
139
  #end
154
140
 
155
- @lockdir = "lock"
156
- @alive_time = 3600
157
- @outfiles = ["OUTCAR"] # Files only to output should be indicated.
141
+ system command
158
142
  end
159
143
 
160
- # 正常に終了していれば true を返す。
161
- # 実行する前や実行中、OUTCAR が完遂していなければ false。
162
- #
163
- # MEMO
164
- # PI12345 ファイルは実行中のみ存在し、終了後 vasp (mpi?) に自動的に削除される。
165
- def finished?
166
- begin
167
- return Outcar.load_file("#{@dir}/OUTCAR")[:normal_ended]
168
- rescue Errno::ENOENT
169
- return false
170
- end
144
+ def prepare_next
145
+ #do_nothing
171
146
  end
172
147
 
173
148
  end