TOOL_DIR \ = File::join(File::dirname(__FILE__)) $: << File::join(File::dirname(__FILE__), "..", "..", "common", "ruby") $global_opt = { :ppmra_threshold => 1E-1, :exit => true, }.merge($global_opt || {}) # selfとotherでother優先 def check_mode(item) case item[2] when /^elevator/, /^thrust/, /^throttle/ # 縦 $stderr.puts "Longitudunal => #{item.inspect}" return :lng else # 横 #when "dutch rolls", "aileron bank-to-bank", "spiral" $stderr.puts "Lateral => #{item.inspect}" return :lat end return nil # 該当なし end unless defined? file_prefix def file_prefix(file, item) "#{item[0]}.#{item[2].split(/ /).collect{|s| s[0, 1]}.join}" end end # 外部プログラムの起動 def extprog_common(file, item, extprog, opt = {}) final_opt = [ [:log_start, item[0]], [:log_end, item[1]]] # 保存先 out_file = opt.delete(:out) || "#{file_prefix(file, item)}.ext.csv" # --key=key2,value で重複あり [:sta, :init, :initP, :initQ, :initR].each{|k| next unless opt.include?(k) opt.delete(k).each{|k2, v| final_opt << [k, "#{k2},#{v}"] } } # --key=value で重複あり [:skip].each{|k| next unless opt.include?(k) opt.delete(k).each{|v| final_opt << [k, v] } } # その他重複がないもの opt.each{|k, v| final_opt << [k, v] } final_opt_str = final_opt.collect{|k, v| "--#{k}=#{v}"}.join(' ') cmd = "#{extprog} #{file} #{final_opt_str}" $stderr.puts "#{cmd} > #{out_file}" #open('extprog.txt', 'a'){|io| io.puts cmd} open(out_file, "w"){|dst| IO::popen(cmd){|src| dst.print src.read } } return out_file # 出力ファイル名を返す end if defined?(derivatives_lng_property) and (!defined?(derivatives_lng)) then def derivatives_lng(options = {}) property = derivatives_lng_property(options) [:u0, :theta0, :alpha0, :rho, :area, :mass, :cL, :c_bar, :i_YY, :c_XU, :c_ZU, :c_MU, :c_XA, :c_ZA, :c_MA, :c_MAA, :c_ZQ, :c_MQ, :c_ZDE, :c_MDE].each{|k| value = property[k] instance_eval %Q{def #{k}; #{value}; end} } temp = { :Xu => rho * u0 * area / (mass * 2) * (c_XU + cL * Math::tan(theta0) * 2), :Xa => rho * (u0 ** 2) * area / (mass * 2) * c_XA, :Zu => rho * u0 * area / (mass * 2) * (c_ZU - cL * 2), :Za => rho * (u0 ** 2) * area / (mass * 2) * c_ZA, :Zq => rho * u0 * area * c_bar / (mass * 4) * c_ZQ, :Zde => rho * (u0 ** 2) * area / (mass * 2) * c_ZDE, :Mu => rho * u0 * area * c_bar / (i_YY * 2) * c_MU, :Ma => rho * (u0 ** 2) * area * c_bar / (i_YY * 2) * c_MA, :Maa => rho * u0 * area * (c_bar ** 2) / (i_YY * 4) * c_MAA, :Mq => rho * u0 * area * (c_bar ** 2) / (i_YY * 4) * c_MQ, :Mde => rho * (u0 ** 2) * area * c_bar / (i_YY * 2) * c_MDE} c_alpha = Math::cos(alpha0) s_alpha = Math::sin(alpha0) sc_alpha = s_alpha * c_alpha c_alpha2 = c_alpha ** 2 s_alpha2 = s_alpha ** 2 return { :Xu => temp[:Xu] * c_alpha2 - (u0 * temp[:Xa] + temp[:Zu]) * sc_alpha + (u0 * temp[:Za] * s_alpha2), :Xa => temp[:Xa] * c_alpha2 + (u0 * temp[:Xu] - temp[:Za]) * sc_alpha - (u0 * temp[:Zu] * s_alpha2), :Zu => temp[:Zu] * c_alpha2 - (u0 * temp[:Za] - temp[:Xu]) * sc_alpha - (u0 * temp[:Xa] * s_alpha2), :Za => temp[:Za] * c_alpha2 + (u0 * temp[:Zu] + temp[:Xa]) * sc_alpha + (u0 * temp[:Xu] * s_alpha2), :Zq => temp[:Zq] * c_alpha, :Zde => temp[:Zde] * c_alpha, # + temp[:Xde] * s_alpha, :Mu => temp[:Mu] * c_alpha - u0 * temp[:Ma] * s_alpha, :Ma => temp[:Ma] * c_alpha + u0 * temp[:Mu] * s_alpha, :Maa => 0, #temp[:Maa] * c_alpha, :Mq => temp[:Mq], :Mde => temp[:Mde]} end end if defined?(derivatives_lat_property) and (!defined?(derivatives_lat)) then def derivatives_lat(options = {}) prop = derivatives_lat_property(options) ru2s = prop[:rho] * (prop[:u0] ** 2) * prop[:area] rusb = prop[:rho] * prop[:u0] * prop[:area] * prop[:b] rusb2 = rusb * prop[:b] ru2sb = ru2s * prop[:b] mass = prop[:mass] i_xx = prop[:i_XX] i_zz = prop[:i_ZZ] i_xz = prop[:i_XZ] alpha0 = prop[:alpha0] temp = { :Yb => ru2s / (mass * 2) * prop[:c_YB], :Yp => rusb / (mass * 4) * prop[:c_YP], :Yr => rusb2 / (mass * 4) * prop[:c_YR], :Ydr => ru2s / (mass * 2) * prop[:c_YDR], :Lb => ru2sb / (i_xx * 2) * prop[:c_LB], :Lp => rusb2 / (i_xx * 4) * prop[:c_LP], :Lr => rusb2 / (i_xx * 4) * prop[:c_LR], :Lda => ru2sb / (i_xx * 2) * prop[:c_LDA], :Ldr => ru2sb / (i_xx * 2) * prop[:c_LDR], :Nb => ru2sb / (i_zz * 2) * prop[:c_NB], :Np => rusb2 / (i_zz * 4) * prop[:c_NP], :Nr => rusb2 / (i_zz * 4) * prop[:c_NR], :Nda => ru2sb / (i_zz * 2) * prop[:c_NDA], :Ndr => ru2sb / (i_zz * 2) * prop[:c_NDR]} [:b, :p, :r, :da, :dr].each{|k| denom = 1.0 - (i_xz ** 2 / (i_xx * i_zz)) temp["L#{k}p".to_sym] = (temp["L#{k}".to_sym] + (i_xz / i_xx) * temp["N#{k}".to_sym]) / denom temp["N#{k}p".to_sym] = (temp["N#{k}".to_sym] + (i_xz / i_zz) * temp["L#{k}".to_sym]) / denom } c_alpha = Math::cos(alpha0) s_alpha = Math::sin(alpha0) sc_alpha = s_alpha * c_alpha c_alpha2 = c_alpha ** 2 s_alpha2 = s_alpha ** 2 return { :Yb => temp[:Yb], :Yp => temp[:Yp] * c_alpha - temp[:Yr] * s_alpha, :Yr => temp[:Yr] * c_alpha + temp[:Yp] * s_alpha, :Ydr => temp[:Ydr], :Lbp => temp[:Lbp] * c_alpha - temp[:Nbp] * s_alpha, :Lpp => temp[:Lpp] * c_alpha2 - (temp[:Lrp] + temp[:Npp]) * sc_alpha + temp[:Nrp] * s_alpha2, :Lrp => temp[:Lrp] * c_alpha2 + (temp[:Lpp] - temp[:Nrp]) * sc_alpha - temp[:Npp] * s_alpha2, :Ldap => temp[:Ldap] * c_alpha - temp[:Ndap] * s_alpha, :Ldrp => temp[:Ldrp] * c_alpha - temp[:Ndrp] * s_alpha, :Nbp => temp[:Nbp] * c_alpha + temp[:Lbp] * s_alpha, :Npp => temp[:Npp] * c_alpha2 - (temp[:Nrp] - temp[:Lpp]) * sc_alpha - temp[:Lrp] * s_alpha2, :Nrp => temp[:Nrp] * c_alpha2 + (temp[:Npp] + temp[:Lrp]) * sc_alpha + temp[:Lpp] * s_alpha2, :Ndap => temp[:Ndap] * c_alpha + temp[:Ldap] * s_alpha, :Ndrp => temp[:Ndrp] * c_alpha + temp[:Ldrp] * s_alpha} end end def opt_lng(file, item) opt = (item[3] || {}).dup # 風や初期状態などの処理、個別オプションが全体オプションよりも優先 [:sta, :init].each{|key| opt[key] ||= {} ($si_options[file][key] || {}).each{|k, v| opt[key][k] ||= v } } # 微係数の処理 if defined?(derivatives_lng) then deriv = derivatives_lng(opt[:sta]) deriv.each{|k, v| opt[:init][k] ||= v} end # 舵面の処理、オプション名が微妙に異なるのを吸収 [:de, :dt].each{|key| offset = nil if RUBY_VERSION >= "1.9.0" then offset = ?O.bytes.to_a[0] - ?A.bytes.to_a[0] else offset = ?O - ?A end opt[key] = (offset + $si_options[file][key]) [:sf, :trim].each{|suffix| key_orig = "#{key}#{suffix}".to_sym key_dst = "#{key}_#{suffix}".to_sym opt[key_orig] ||= $si_options[file][key_orig] opt[key_dst] = opt.delete(key_orig) } } # dt関係の処理 opt[:dt_sf] = 0 # dtはゼロにしておく opt[:skip] = (opt[:skip] || []) + [:Xdt, :Zdt, :Mdt, :Maa] # 推定しない return opt end def opt_lat(file, item) opt = (item[3] || {}).dup # 風や初期状態などの処理、個別オプションが全体オプションよりも優先 [:sta, :init].each{|key| opt[key] ||= {} ($si_options[file][key] || {}).each{|k, v| opt[key][k] ||= v } } # 微係数の処理 if defined?(derivatives_lat) then deriv = derivatives_lat(opt[:sta]) deriv.each{|k, v| opt[:init][k] ||= v} end # 舵面の処理、オプション名が微妙に異なるのを吸収 [:dr, :da].each{|key| opt[key] = (?O - ?A + $si_options[file][key]) [:sf, :trim].each{|suffix| key_orig = "#{key}#{suffix}".to_sym key_dst = "#{key}_#{suffix}".to_sym opt[key_orig] ||= $si_options[file][key_orig] opt[key_dst] = opt.delete(key_orig) } } return opt end def opt(file, item) p eval("opt_#{check_mode(item)}(file, item)") end def plot_common(file, item, plotter, opt = {}) final_opt = [] [ [:prefix, (file_prefix(file, item) rescue nil)], [:trange, ("#{item[0].floor}..#{item[1].ceil}" rescue nil)], ].each{|key, v| v = opt.delete(key) if opt.include?(key) final_opt << [key, v] if v } if $global_opt.include?(:plot_verbose) final_opt << [:verbose, $global_opt[:plot_verbose]] end # --key=value で重複あり [:add, :ref, :skip, :sta].each{|k| next unless opt.include?(k) opt.delete(k).each{|v| final_opt << [k, v] } } opt_str = (final_opt + opt.to_a).collect{|k, v| "--#{k}=#{v}" }.join(' ') plotter_cmd = "#{plotter} #{file} #{opt_str}" $stderr.puts plotter_cmd if block_given? then IO::popen(plotter_cmd, 'w'){|io| yield io } else system(plotter_cmd) end end def opt2_lng(file, opt) [:de, :dt, :desf, :dtsf, :detrim, :dttrim].each{|key| opt[key] = $si_options[file][key] } end def opt2_lat(file, opt) [:da, :dr, :dasf, :drsf, :datrim, :drtrim].each{|key| opt[key] = $si_options[file][key] } end def plot_lng(file, item, opt = {}) plotter = File::join(TOOL_DIR, "plot_log_longitudinal.rb") opt2_lng(file, opt) # 描画スクリプトは舵面をdegで受け付ける opt[:desf] *= (180.0 / Math::PI) plot_common(file, item, plotter, opt) end def plot_lat(file, item, opt = {}) plotter = File::join(TOOL_DIR, "plot_log_lateral.rb") opt2_lat(file, opt) # 描画スクリプトは舵面をdegで受け付ける opt[:dasf] *= (180.0 / Math::PI) opt[:drsf] *= (180.0 / Math::PI) plot_common(file, item, plotter, opt) end def plot(file, item) target = "plot_#{check_mode(item)}" $stderr.puts "Invoking #{target} ..." eval("#{target}(file, item)") end def sim_common(file, item, simulator, opt = {}) opt[:method] = :sim opt[:manuv_start] = item[0] opt[:out] ||= "#{file_prefix(file, item)}.sim.csv" return extprog_common(file, item, simulator, opt) end def sim(file, item) mode = check_mode(item) return unless defined?("derivatives_#{mode}") # 暫定的 target = "sim_#{mode}" unless eval %Q{defined?(#{target})} bin_name = (mode == :lng) ? "longitudinal.exe" : "lateral.exe" instance_eval(<<-__TEXT__) def sim_#{mode}(file, item) simulator = File::join(TOOL_DIR, "RPE_KF", "build_VC", "#{bin_name}") opt = opt_#{mode}(file, item) out_file = sim_common(file, item, simulator, opt) plot_opt = { :add => [out_file + ",Sim"], :prefix => (file_prefix(file, item) + ".sim")} plot_#{mode}(file, item, plot_opt) end __TEXT__ end $stderr.puts "Invoking #{target} ..." eval("#{target}(file, item)") end def est_common(file, item, estimators, opt = {}) mode = check_mode(item) # 初期値の設定をする opt[:initP] ||= {} ref_init = opt[:init].dup opt[:init].each{|k, v| #next if k == :Maa unless v == 0 then order = 10 ** Math::log10(v.abs).to_i opt[:init][k] = (v / order).to_i * order opt[:initP][k] ||= order.to_f / 10 end } res = {} method_index = 0 prefix = file_prefix(file, item) estimators.each{|prog, args_list| args_list.each{|args| opt_cp = opt.merge(args) method_name = args[:method] || "method#{method_index}" opt_cp[:out] = "#{prefix}.#{method_name}.csv" res[method_name] = { :out_file => extprog_common(file, item, prog, opt_cp)} } } # 推定過程をプロット est_plot = proc{|out_file_etc| est_plotter = File::join(TOOL_DIR, "plot_est.rb") out_file, opt_plotter, proc_obj = out_file_etc opt_plotter = {} unless opt_plotter opt_plotter[:mode] = mode opt_plotter[:prefix] ||= nil opt_plotter[:skip] = opt[:skip] if opt[:skip] opt_plotter[:ref] = ref_init.collect{|coef, val| "#{coef},#{val}"} opt_plotter[:sta] = opt[:sta].collect{|sta, val| "#{sta},#{val}"} if proc_obj then plot_common(out_file, item, est_plotter, opt_plotter){|io| proc_obj.call(io) } else plot_common(out_file, item, est_plotter, opt_plotter) end } res.each{|k, v| begin # 推定過程をプロット est_plot.call(v[:out_file]) # 最後の微係数を読み込んでおく open(v[:out_file]){|io| v[:finalline] = io.readlines[-1].chomp.split(',') } rescue $stderr.puts $!.inspect $stderr.puts $@.inspect end } # FEMの結果も取り込む begin method_name = :fem fem_out_file = "#{prefix}.#{method_name}.csv" values = nil open(fem_out_file){|io| values = (io.readlines.collect{|line| line.chop.split(',').collect{|s| s.to_f}}.transpose)[0] } # padding if mode == :lng then deriv = derivatives_lng(opt[:sta]) [:Xu, :Xa, :Zu, :Za, :Zq, :Zde, :Mu, :Ma, :Mq, :Mde].each_with_index{|name, i| deriv[name] = values[i] $stderr.puts "#{name} #{values[i]}" } values = [item[1]] # 終了時刻 [ :u, :a, :theta, :q, :Xu, :Xa, :Xdt, :Zu, :Za, :Zq, :Zde, :Zdt, :Mu, :Maa, :Ma, :Mq, :Mde, :Mdt].each{|name| values << (deriv[name] || 0) } else end res[method_name] = { :out_file => fem_out_file, :finalline => values} # システムの根をプロット est_plot.call(['-', {:prefix => "#{prefix}.#{method_name}"}, proc{|io| io.puts values.join(',')}]) rescue $stderr.puts $!.inspect $stderr.puts $@.inspect end return res end def est(file, item) mode = check_mode(item) return unless defined?("derivatives_#{mode}") # 暫定的 target = "est_#{mode}" unless eval %Q{defined?(#{target})} bin_name = "longitudinal.exe" res_values = [ :t, :u, :a, :theta, :q, :Xu, :Xa, :Xdt, :Zu, :Za, :Zq, :Zde, :Zdt, :Mu, :Maa, :Ma, :Mq, :Mde, :Mdt] use_from_index = res_values.index(:Xu) wfr_add = "Xde,u,de,1E-8,1E-10" if mode != :lng then bin_name = "lateral.exe" res_values = [ :t, :b, :p, :r, :phi, :psi, :Yb, :Yp, :Yr, :Ydr, :Lbp, :Lpp, :Lrp, :Ldap, :Ldrp, :Nbp, :Npp, :Nrp, :Ndap, :Ndrp] use_from_index = res_values.index(:Yb) wfr_add = "Yda,b,da,1E-8,1E-10" end res_values = res_values.collect{|v| ":#{v}"}.join(',') instance_eval(<<-__TEXT__) def est_#{mode}(file, item) estimators = { File::join(TOOL_DIR, "RPE_KF", "build_VC", "#{bin_name}") => [ {:method => :ukf}, # {:method => :ekf} ], File::join(TOOL_DIR, "PE_EquationError", "build_VC", "#{bin_name}") => [ {:method => :rls}, {:method => :ftr, :ftr_freq_min => 0.1, :ftr_freq_max => 3, :ftr_freq_step => 30}, {:method => :wfr, :wfr_threshold => $global_opt[:ppmra_threshold], :add => "#{wfr_add}"} # :wfr_ifading => 0.5, ]} opt = opt_#{mode}(file, item) res = est_common(file, item, estimators, opt) prefix = file_prefix(file, item) plot_opt = { :add => [], :prefix => (prefix + ".est"), :size => "1,0.6", :ylimit => 4} simulator = File::join(TOOL_DIR, "RPE_KF", "build_VC", "#{bin_name}") [:rls, :ftr, :ukf, :fem, :wfr].each{|method| properties = res[method] next unless properties sim_opt = opt_#{mode}(file, item) [#{res_values}].each_with_index{|k, v| if v >= #{use_from_index} then sim_opt[:init][k] = properties[:finalline][v] end } $stderr.puts "sim_opt(" + method.to_s + "): " + sim_opt.inspect sim_opt[:out] = prefix + "." + method.to_s + ".sim.csv" out_file = sim_common(file, item, simulator, sim_opt) plot_opt[:add] << (out_file + "," + method.to_s.upcase) } plot_#{mode}(file, item, plot_opt) end __TEXT__ end $stderr.puts "Invoking #{target} ..." eval("#{target}(file, item)") end $file_cache = {} def read(file) require 'flight_log_reader' $file_cache[file] ||= FlightLogReader::read(file, 1) end def split(file, item, opt = {}) data = read(file) time_index = FlightLogReader::FLIGHT_LOG_LABELS.index(:Time) range = (item[0])..(item[1]) inrange_data = data.reject{|values| !range.include?(values[time_index])} return if inrange_data.empty? out = opt[:out] || "#{file_prefix(file, item)}.csv" out_op = proc{|io| yaw_index = FlightLogReader::FLIGHT_LOG_LABELS.index(:Yaw) initial_yaw = (inrange_data.first)[yaw_index] inrange_data.each{|values| # ヨー角の修正を同時にする values_dup = values.dup values_dup[yaw_index] -= initial_yaw if values_dup[yaw_index] > Math::PI then values_dup[yaw_index] -= (Math::PI * 2) elsif values_dup[yaw_index] <= -Math::PI values_dup[yaw_index] += (Math::PI * 2) end io.puts values_dup.flatten.join(',') } } $stderr.puts "Splitting to #{out} ..." if out.kind_of?(String) then open(out, 'w'){|io| out_op.call(io) } else out_op.call(out) end end # 多重解像度解析をする def ppmra(file, item) # ログの切り出しをまず行う require 'stringio' splitted_log = StringIO::new split(file, item, {:out => splitted_log}) log = splitted_log.string # オンラインコードと条件をそろえるため、ソースコードから定数を引っ張ってくる online_src = File::join(File::dirname(__FILE__), "PE_EquationError", "common.h") wavelet_property = {} open(online_src){|io| io.each{|line| if line =~ /(\S+)Cascade\<(\d+),/ then wavelet_property[:type] = $1 wavelet_property[:order] = $2.to_i elsif line =~ /default_cascade_depth\s*=\s*(\d+)/ then wavelet_property[:window] = $1.to_i ** 2 end } } # PP-MRA (Parallel Projection using Multi Resolution Analysis) ppmra_filter = File::join(TOOL_DIR, "ppmra.rb") ppmra_prefix = "#{file_prefix(file, item)}.ppmra" ppmra_out_file = "#{ppmra_prefix}.csv" ppmra_opt = { :mode => check_mode(item), :order => 6, #wavelet_property[:order], :window => 256, #wavelet_property[:window], :threshold => $global_opt[:ppmra_threshold], #:out => ppmra_out_file, :add_noise => true, :plot => true, :prefix => ppmra_prefix } opt2_lng(file, ppmra_opt) # 縦の舵面オプションを追加 opt2_lat(file, ppmra_opt) # 横の舵面オプションを追加 ppmra_opt_str = ppmra_opt.to_a.collect{|k, v| "--#{k}=#{v}"}.join(' ') cmd = "#{ppmra_filter} - #{ppmra_opt_str}" $stderr.puts "Invoking PPMRA ... => #{cmd}" flight_log = nil IO::popen(cmd, 'r+'){|io| io.write log io.close_write flight_log = read(io) } # 定常値などの処理 if ppmra_opt_str[:mode] != :lng then else opt = opt_lng(file, item) [ [:U0, :TAS], [:W0, :AoAttack], [:theta0, :Pitch], [-9.81, :AccZ] ].each{|src, dist| next unless src.kind_of?(Symbol) || (src = opt[:sta][src]) dist = FlightLogReader::FLIGHT_LOG_LABELS.index(dist) flight_log.each{|moment| moment[dist] += src} } end # ファイルに出力 #if false open(ppmra_out_file, 'w'){|io| flight_log.each{|moment| io.puts moment.flatten.join(',') } } #end return [ppmra_prefix, ppmra_out_file] end # 多重解像度解析をした結果のファイルで推定を行う def ppmra_est(file, item) ppmra_prefix, ppmra_out_file = ppmra(file, item) # 最後に推定をかける alias :file_prefix_orig :file_prefix eval(<<-__TEXT__, TOPLEVEL_BINDING) def file_prefix(file, item) "#{ppmra_prefix}" end __TEXT__ $si_logs[ppmra_out_file] = [item] $si_options[ppmra_out_file] = $si_options[file] est(ppmra_out_file, item) alias :file_prefix :file_prefix_orig end command = ($global_opt[:ARGV] || ARGV).shift || 'plot' sucess_count = -1 ARGV.each{|arg| next if arg !~ /^--([^=]+)=/ $global_opt[$1.to_sym] = $' } case command # ファイルに依存しないコマンドはここで処理 when 'ref' sucess_count = 0 # 推定過程をプロット ref_plot = proc{|mode| plotter = File::join(TOOL_DIR, "plot_est.rb") opt = {:mode => mode, :prefix => "ref_#{mode}", :ref => []} # 参照値を取ってくる eval("derivatives_#{mode}").each{|coef, val| opt[:ref] << "#{coef},#{val}"} # 空ファイルでplot_estを起動する plot_common('-,nobeginend', nil, plotter, opt){|io| io.puts 'e'} } ref_plot.call(:lng) if defined?(derivatives_lng) ref_plot.call(:lat) if defined?(derivatives_lat) else # zipファイル対策 tmp_files = [] $si_logs.keys.each{|file| next if file !~ /\.zip\// require 'open-zip' require 'tempfile' tmp = Tempfile::new("#{File::basename(file)}.", '.') file_new = File::basename(tmp.path) begin open(file){|f| tmp.write f.read } $si_logs[file_new] = $si_logs[file] $si_options[file_new] = $si_options[file] rescue end tmp_files << tmp } Dir::open('.').each{|file| next unless $si_logs.include?(file) $si_logs[file].each{|item| $stderr.puts "Invoking #{command} ..." eval("#{command}(file, item)") } sucess_count += 1 } end exit(sucess_count >= 0 ? 0 : -1) if $global_opt[:exit]