OSDN Git Service

Improve JANPA integration: show orbital descriptions etc.
[molby/Molby.git] / Scripts / commands.rb
index db841f5..86b399e 100755 (executable)
@@ -1,3 +1,4 @@
+# coding: utf-8
 #
 #  commands.rb
 #
@@ -120,6 +121,108 @@ class Molecule
        end
   end
 
+  def cmd_concat_frames
+      n = nframes
+      return if n == 0
+      if Molecule.list.length == 1
+          message_box("No other molecule.", "", :ok)
+          return
+      end
+      ls = (0...Molecule.list.length).select { |i|
+          m = Molecule[i]
+          if m == self || m.natoms != self.natoms
+              false
+              else
+              if nil != (0...m.natoms).find { |n| m.atoms[n].atomic_number != self.atoms[n].atomic_number }
+                  false
+                  else
+                  true
+              end
+          end
+      }
+      if ls.length == 0
+          message_box("No molecule has the same atomic sequence as the present one.", "", :ok)
+          return
+      end
+      labels = []
+      label_hash = Hash.new   #  Check if documents with the same name are present
+      ls.each_with_index { |i, idx|  #  i is an index to Molecule, idx is an index to ls
+          m = Molecule[i]
+          label = m.name
+          idx_h = label_hash[label]  #  If non-nil, then duplicate name
+          label_hash[label] = idx
+          if idx_h
+              if labels[idx_h] == label
+                  labels[idx_h] = label + " (" + Molecule[ls[idx_h]].dir + ")"
+              end
+              label = label + " (" + Molecule[i].dir + ")"
+          end
+          labels.push(label)
+      }
+      nf = Molecule[ls[0]].nframes
+      hash = Dialog.run("Concatenate Frames") {
+          layout(2,
+                 item(:text, :title=>"From Molecule:"),
+                 item(:popup, :subitems=>labels, :tag=>"mol", :width=>240,
+                      :action=>lambda { |it|
+                      nf = Molecule[ls[it[:value]]].nframes
+                      set_attr("start_title", :title=>"Start (0-#{nf})")
+                      set_attr("end_title", :title=>"End (0-#{nf})")
+                      }),
+                 item(:radio, :title=>"All Frames", :tag=>"all_frames", :value=>1,
+                      :action=>lambda { |it|
+                      flag = (it[:value] == 0)
+                      set_attr("start_frame", :enabled=>flag)
+                      set_attr("end_frame", :enabled=>flag)
+                      }), -1,
+                 item(:radio, :title=>"Select Frames", :tag=>"select_frames",
+                      :action=>lambda { |it|
+                      flag = (it[:value] != 0)
+                      set_attr("start_frame", :enabled=>flag)
+                      set_attr("end_frame", :enabled=>flag)
+                      }), -1,
+                 item(:text, :title=>"Start (0-#{nf})", :tag=>"start_title"),
+                 item(:textfield, :value=>"0", :tag=>"start_frame", :enabled=>false),
+                 item(:text, :title=>"End (0-#{nf})", :tag=>"end_title"),
+                 item(:textfield, :value=>"0", :tag=>"end_frame", :enabled=>false))
+                 radio_group("all_frames", "select_frames")
+      }
+      if hash[:status] == 0
+          idx_h = Integer(hash["mol"])
+          m = Molecule[ls[idx_h]]
+          f = m.frame  #  Save the current frame number
+          nf = m.nframes
+          if hash["all_frame"] != 0
+              f1 = 0
+              f2 = nf - 1
+              else
+              f1 = Integer(hash["start_frame"])
+              f2 = Integer(hash["end_frame"])
+              f1 = 0 if f1 < 0
+              f1 = nf - 1 if f1 > nf - 1
+              f2 = 0 if f2 < 0
+              f2 = nf - 1 if f2 > nf - 1
+          end
+          if f1 <= f2
+              a = (f1..f2).to_a
+              else
+              a = (f2..f1).to_a.reverse
+          end
+          prop = m.property_names
+          na = m.natoms
+          a.each { |n|
+              self.create_frame()
+              m.select_frame(n)
+              na.times { |i|
+                  self.atoms[i].r = m.atoms[i].r
+              }
+              prop.each { |pr|
+                  self.set_property(pr, m.get_property(pr))
+              }
+          }
+      end
+  end
+
   def cmd_extra_properties
     mol = self
        get_count = lambda { |it| mol.nframes }
@@ -130,7 +233,7 @@ class Molecule
            sprintf("%.8f", mol.get_property(col - 1, row)) rescue ""
          end
     }
-    mol.open_auxiliary_window("Extra Props", nil, nil, :resizable=>true, :has_close_box=>true) {
+    mol.open_auxiliary_window("Extra Props", :resizable=>true, :has_close_box=>true) {
          names = nil
          @on_document_modified = lambda { |m|
            if (n = m.property_names) != names
@@ -149,47 +252,83 @@ class Molecule
          set_min_size(320, 200)
          # listen(mol, "documentModified", on_document_modified)
          # listen(mol, "documentWillClose", lambda { |m| close } )
-         on_document_modified.call(mol)
+         @on_document_modified.call(mol)
          show
        }
   end
   
   def cmd_show_energy
-       wave = [0.0, 0.0]
-       cur = 0
-       mol = self
-       d = open_auxiliary_window("Energy", nil, nil, :resizable=>true, :has_close_box=>true) {
-         graph_item = nil   #  Forward declaration
-         target_mol = nil
-         draw_graph = lambda { |it|
-               clear
-               f = graph_item[:frame]
-               draw_rectangle(0, 0, f[2], f[3])
-               width = f[2] - 25
-               height = f[3] - 25
-               draw_line(16, 0, 16, height + 12, width + 20, height + 12)
-               xx = yy = nil
-               min = wave.min
-               h = wave.max - min
-               h = (h == 0.0 ? 1.0 : height / h)
-               w = wave.count
-               w = (w == 0 ? 1.0 : Float(width) / w)
-               a = []
-               wave.each_with_index { |d, i|
-                 a.push(i * w + 16)
-                 a.push(height - (d - min) * h + 12)
-               }
-               if wave.count == 1
-                 a.push(w + 16)
-                 a.push(height - (wave[0] - min) * h + 12)
-               end
-               draw_line(a)
-               brush(:color=>[0.2, 0.2, 1.0])
-               y = wave[cur] || 0.0
-               xx = cur * w + 16
-               yy = height - (y - min) * h + 12
-               draw_ellipse(cur * w + 16, height - (y - min) * h + 12, 6)
-         }
+    wave = [0.0, 0.0]
+    cur = 0
+    mol = self
+    wave_min = lambda { m = 1e8; wave.each { |x| if x != 0.0 && x < m; m = x; end }; m }
+    wave_max = lambda { m = -1e8; wave.each { |x| if x != 0.0 && x > m; m = x; end }; m }
+    d = open_auxiliary_window("Energy", :resizable=>true, :has_close_box=>true) {
+    graph_item = nil   #  Forward declaration
+    target_mol = nil
+    draw_graph = lambda { |it|
+      clear
+      f = graph_item[:frame]
+      draw_rectangle(0, 0, f[2], f[3])
+      width = f[2] - 25
+      height = f[3] - 25
+      draw_line(16, 0, 16, height + 12, width + 20, height + 12)
+      xx = yy = nil
+      min = wave_min.call
+      max = wave_max.call
+      h = max - min
+      h = (h == 0.0 ? 1.0 : height / h)
+      c = wave.count
+      if c == 0
+        w = 1.0
+      elsif c == 1
+        w = Float(width)
+      else
+        w = Float(width) / (c - 1)
+      end
+      lines = []
+      a = []
+      #  Skip the points that have exactly 0.0 value
+      wave.each_with_index { |d, i|
+        if d != 0.0
+          a.push(i * w + 16)
+          a.push(height - (d - min) * h + 12)
+        end
+        if d == 0.0 || i == wave.length - 1
+          #  End of this curve fragment
+          if a.length == 0
+            #  Do nothing
+          else
+            if a.length == 2
+              if wave.count == 1
+                #  If wave has only one point, then draw a horizontal line
+                a.push(a[0] + 16)
+                a.push(a[1])
+              else
+                #  Otherwise, draw a zero-length line
+                a.push(a[0])
+                a.push(a[1])
+              end
+            end
+            lines.push(a)  #  Store this line fragment
+            a = []
+          end
+        end
+      }
+      lines.each { |a|
+        draw_line(a)
+      }
+      brush(:color=>[0.2, 0.2, 1.0])
+      y = wave[cur] || 0.0
+      if y < min
+        y = min
+      elsif y > max
+        y = max
+      end
+      xx = cur * w + 16
+      yy = height - (y - min) * h + 12
+      draw_ellipse(cur * w + 16, height - (y - min) * h + 12, 6)
+    }
          @on_document_modified = lambda { |m|
                cur = mol.frame
                wave.clear
@@ -220,8 +359,14 @@ class Molecule
   end
   
   def cmd_create_surface
+    if @surface_dialog_attr == nil
+         @surface_dialog_attr = Hash.new
+         @surface_dialog_attr["hidden"] = -1
+       end
+    surface_dialog_attr = @surface_dialog_attr
     mol = self
-       mol.open_auxiliary_window("MO Surface", nil, nil, :resizable=>true, :has_close_box=>true) {
+       mol.open_auxiliary_window("MO Surface", :resizable=>true, :has_close_box=>true) {
+         tags = ["mo_ao", "mo", "color", "opacity", "threshold", "expand", "grid"]
          motype = mol.get_mo_info(:type)
          alpha = mol.get_mo_info(:alpha)
          beta = mol.get_mo_info(:beta)
@@ -229,16 +374,51 @@ class Molecule
          mo_index = 1
          mo_ao = nil
          coltable = [[0,0,1], [1,0,0], [0,1,0], [1,1,0], [0,1,1], [1,0,1], [0,0,0]]
-         if (motype == "RHF")
-           beta = nil
+         mo_ao_items = ["Molecular Orbitals", "Atomic Orbitals"]
+         mo_ao_keys = ["MO", "AO"]
+      if (nbo = mol.instance_eval { @nbo }) != nil
+           nbo.keys.each { |key|
+                 if key[0..2] == "AO/"
+                   key2 = key[3..-1]
+                       name2 = key2
+                       if key2 == "NAO"
+                         name2 = "Natural Atomic Orbitals"
+                       elsif key2 == "NBO"
+                         name2 = "Natural Bond Orbitals"
+                       elsif key2 == "NHO"
+                         name2 = "Natural Hybrid Orbitals"
+                       elsif key2 == "NLMO"
+                         name2 = "Natural Localized Molecular Orbitals"
+                       elsif key2 == "PNAO"
+                         name2 = "Pre-orthogonal Natural Atomic Orbitals"
+                       elsif key2 == "PNHO"
+                         name2 = "Pre-orthogonal Natural Hybrid Orbitals"
+                       elsif key2 == "PNBO"
+                         name2 = "Pre-orthogonal Natural Bond Orbitals"
+            elsif key2 == "AHO"
+              name2 = "Atomic Hybrid Orbitals"
+            elsif key2 == "LHO"
+              name2 = "Lewis Hybrid Orbitals"
+            elsif key2 == "PLHO"
+              name2 = "Pre-orthogonal Lewis Hybrid Orbitals"
+            elsif key2 == "LPO"
+              name2 = "Localized Property-optimized Orbitals"
+            elsif key2 == "CLPO"
+              name2 = "Chemist's Localized Property-optimized Orbitals"
+                       end
+                       mo_ao_items.push(name2)
+                       mo_ao_keys.push(key2)
+                 end
+               }
          end
-         i = (beta ? 2 : 1)
-         mo_menu = []  #  Create later
+         mult = (motype == "UHF" ? 2 : 1)
+         mo_menu = ["1000 (-00.00000000)"]  #  Dummy entry; create later
          tabvals = []
          coeffs = nil
          a_idx_old = -1
+         occ = nil
          ncomps.times { |i|
-           a_idx, label, nprims = mol.get_gaussian_shell_info(i)
+           a_idx, s_idx, label = mol.get_gaussian_component_info(i)
                if a_idx_old != a_idx
                  a_idx_old = a_idx
                  a = a_idx.to_s
@@ -248,6 +428,23 @@ class Molecule
                end
                tabvals.push([a, n, label, a_idx])
          }
+         on_update_attr = lambda {
+           tags.each { |tag|
+                 surface_dialog_attr[tag] = item_with_tag(tag)[:value]
+               }
+               it = item_with_tag("hide")
+               case surface_dialog_attr["hidden"]
+               when -1
+                 it[:title] = "Hide"
+                 it[:enabled] = false
+               when 0
+                 it[:title] = "Hide"
+                 it[:enabled] = true
+               when 1
+                 it[:title] = "Show"
+                 it[:enabled] = true
+               end
+         }
          on_get_value = lambda { |it, row, col|
            if col < 3
                  tabvals[row][col]
@@ -255,8 +452,10 @@ class Molecule
                  if coeffs == nil
                    if mo_ao == 0
                      coeffs = mol.get_mo_coefficients(mo_index)
-                   else
+                   elsif mo_ao == 1
                      coeffs = (0...ncomps).map { |i| (i == mo_index ? 1.0 : 0.0) }
+                       else
+                         coeffs = nbo["AO/" + mo_ao_keys[mo_ao]].column(mo_index).to_a[0]
                        end
                  end
                  sprintf("%.6f", coeffs[row])
@@ -265,37 +464,78 @@ class Molecule
          h = {"mo"=>nil, "color"=>nil, "opacity"=>nil, "threshold"=>nil, "expand"=>nil, "grid"=>nil}
          should_update = true
          on_action = lambda { |it|
-           should_update = false
-           h.each_key { |key|
-                 val = value(key)
-                 if val && h[key] != val
-                   should_update = true
-                   break
-                 end
-               }
-               item_with_tag("update")[:enabled] = should_update
+           tag = it[:tag]
+               value = it[:value]
+               if tag == "color" || tag == "opacity"
+                 opac = value("opacity").to_f
+                 opac = 0.0 if opac < 0.0
+                 opac = 1.0 if opac > 1.0
+                 col = value("color")
+                 color = coltable[col] + [opac]
+                 color0 = [1,1,1,opac]
+                 mol.set_surface_attr(:color=>color, :color0=>color0)
+                 h[tag] = value
+               elsif tag == "threshold"
+             thres = it[:value].to_f
+                 thres = 0.001 if thres >= 0.0 && thres < 0.001
+                 thres = -0.001 if thres <= 0.0 && thres > -0.001
+                 mol.set_surface_attr(:thres=>thres)
+                 h[tag] = value
+               else
+             should_update = false
+             h.each_key { |key|
+                   val = value(key)
+                   if val && h[key] != val
+                     should_update = true
+                     break
+                   end
+                 }
+                 item_with_tag("update")[:enabled] = should_update
+               end
+               on_update_attr.call
          }
          on_mo_action = lambda { |it|
            mo = it[:value]
                if mo_ao == 0
-                 if beta
+                 if motype == "UHF"
                    mo_index = (mo / 2) + (mo % 2 == 1 ? ncomps : 0) + 1
+                       if mo_index <= alpha || (mo_index > ncomps && mo_index <= ncomps + beta)
+                         occ_new = 1
+                       else
+                         occ_new = 0
+                       end
                  else
                    mo_index = mo + 1
+                       if mo_index <= alpha && mo_index <= beta
+                         occ_new = 1
+                       elsif mo_index <= alpha || mo_index <= beta
+                         occ_new = -1
+                       else
+                         occ_new = 0
+                       end
                  end
                else
                  mo_index = mo
+                 occ_new = occ
                end
                coeffs = nil
+               if occ_new != occ
+                 #  Set default color
+                 col = (occ_new == 0 ? 1 : (occ_new == -1 ? 2 : 0))
+                 set_value("color", col)
+                 h["color"] = col
+                 occ = occ_new
+               end
                item_with_tag("table")[:refresh] = true
            on_action.call(it)
+               on_update_attr.call
          }
          on_set_action = lambda { |it|
            if mo_ao != it[:value]
                  mo_ao = it[:value]
                  if mo_ao == 0
-                   mo_menu = (1..(ncomps * i)).map { |n|
-                 if beta
+                   mo_menu = (1..(ncomps * mult)).map { |n|
+                 if motype == "UHF"
                        i1 = (n - 1) / 2 + 1
                        i2 = n % 2
                        c1 = (i2 == 0 ? "B" : "A")
@@ -304,29 +544,54 @@ class Molecule
                        i1 = n
                        i2 = 1
                        c1 = ""
-                       c2 = (i1 >= alpha ? "*" : "")
+                               if i1 > beta && i1 > alpha
+                                 c2 = "*"
+                               elsif i1 > beta || i1 > alpha
+                                 c2 = "S"
+                               else
+                                 c2 = ""
+                               end
                      end
                      en = mol.get_mo_energy(i1 + (i2 == 0 ? ncomps : 0))
                      sprintf("%d%s%s (%.8f)", i1, c1, c2, en)
                        }
-                 else
+                 elsif mo_ao == 1
                    mo_menu = []
                    ncomps.times { |i|
                          mo_menu[i] = sprintf("AO%d: %s (%s)", i + 1, tabvals[i][2], mol.atoms[tabvals[i][3]].name)
                        }
+                 else
+                   mo_menu = []
+                       key = mo_ao_keys[mo_ao]
+                       labels = nbo[key + "_L"]
+                       if labels == nil && key[0] == ?P
+                         labels = nbo[key[1..-1] + "_L"]
+                       end
+                       ncomps.times { |i|
+                         lab = sprintf("%s%d", key, i + 1)
+                         if labels
+                           lab += ":" + labels[i]
+                         end
+                         mo_menu[i] = lab
+                       }
                  end
                  it0 = item_with_tag("mo")
                  it0[:subitems] = mo_menu
                  it0[:value] = 0
+                 h["mo"] = nil  # "Update" button is forced to be enabled
                  on_mo_action.call(it0)
                end
+               on_update_attr.call
          }
          on_update = lambda { |it|
            h.each_key { |key|
                  h[key] = value(key)
                }
                opac = h["opacity"].to_f
+               opac = 0.0 if opac < 0.0
+               opac = 1.0 if opac > 1.0
                color = coltable[h["color"]] + [opac]
+               color0 = [1, 1, 1, opac]
                thres = h["threshold"].to_f
                thres = 0.001 if thres >= 0.0 && thres < 0.001
                thres = -0.001 if thres <= 0.0 && thres > -0.001
@@ -343,20 +608,31 @@ class Molecule
                  idx = 0
                  mol.set_mo_coefficients(0, 0.0, coeffs)
                end
-               mol.create_surface(idx, :npoints=>grid, :color=>color, :thres=>thres, :expand=>expand)
-               on_action.call(it)
+               if it[:tag] == "create_cube"
+                 basename = File.basename(mol.path || mol.name, ".*")
+             fname1 = Dialog.save_panel("Cube file name", mol.dir || Dir.pwd, basename + ".cube", "Gaussian cube file (*.cube)|*.cube")
+                 if fname1
+                   mol.cubegen(fname1, idx, grid, true)
+                 end
+               else
+                 mol.create_surface(idx, :npoints=>grid, :color=>color, :thres=>thres, :expand=>expand, :color0=>color0)
+                 mol.show_surface
+                 on_action.call(it)
+                 surface_dialog_attr["hidden"] = 0
+                 on_update_attr.call
+               end
          }
          layout(1,
            layout(2,
                  item(:text, :title=>"Orbital Set"),
-                 item(:popup, :tag=>"mo_ao", :subitems=>["Molecular Orbitals", "Atomic Orbitals"], :action=>on_set_action),
+                 item(:popup, :tag=>"mo_ao", :subitems=>mo_ao_items, :action=>on_set_action),
              item(:text, :title=>"Select"),
              item(:popup, :tag=>"mo", :subitems=>mo_menu, :action=>on_mo_action)),
                layout(4,
                  item(:text, :title=>"Color"),
                  item(:popup, :tag=>"color", :subitems=>["blue", "red", "green", "yellow", "cyan", "magenta", "black"], :action=>on_action),
                  item(:text, :title=>"Opacity"),
-                 item(:textfield, :tag=>"opacity", :width=>80, :value=>"0.6", :action=>on_action),
+                 item(:textfield, :tag=>"opacity", :width=>80, :value=>"0.8", :action=>on_action),
                  item(:text, :title=>"Threshold"),
                  item(:textfield, :tag=>"threshold", :width=>80, :value=>"0.05", :action=>on_action),
                  item(:text, :title=>"Box Limit"),
@@ -369,16 +645,65 @@ class Molecule
                  :on_count=> lambda { |it| tabvals.count },
                  :on_get_value=>on_get_value,
                  :flex=>[0,0,0,0,1,1]),
-               item(:button, :tag=>"update", :title=>"Update", :action=>on_update, :flex=>[0,1,0,0,0,0]),
+               layout(3,
+                 item(:button, :tag=>"update", :title=>"Update", :action=>on_update),
+                 item(:button, :tag=>"clear", :title=>"Clear", :action=>lambda { |it|
+                   mol.clear_surface
+                       item_with_tag("update")[:enabled] = true
+                       surface_dialog_attr["hidden"] = -1
+                       on_update_attr.call
+                   } ),
+                 item(:button, :tag=>"hide", :title=>"Hide", :action=>lambda { |it|
+                   case surface_dialog_attr["hidden"]
+                       when 0
+                         surface_dialog_attr["hidden"] = 1
+                         mol.hide_surface
+                       when 1
+                         surface_dialog_attr["hidden"] = 0
+                         mol.show_surface
+                       end
+                       on_update_attr.call
+                       } ),
+                 item(:button, :tag=>"create_cube", :title=>"Create Cube", :action=>on_update),
+                 :flex=>[0,1,0,0,0,0]),
                :flex=>[0,0,0,0,1,1]
          )
+         tags.each { |tag|
+           if (val = surface_dialog_attr[tag]) != nil
+                 item_with_tag(tag)[:value] = val
+               end
+         }
+         mo_idx = surface_dialog_attr["mo"]
+         on_update_attr.call
          on_set_action.call(item_with_tag("mo_ao"))
+         item_with_tag("mo")[:value] = surface_dialog_attr["mo"] = mo_idx
          size = self.size
          set_min_size(size[0], 250)
          item_with_tag("table")[:refresh] = true
          show
     }
   end
+
+  def ask_graphic_export_scale
+    scale = get_global_settings("global.export_graphic_scale")
+       scale = (scale ? scale.to_i - 1 : 3)
+       bg_color = get_global_settings("global.export_background_color")
+       bg_color = (bg_color ? bg_color.to_i + 1 : 0)
+    hash = Dialog.run("Set Export Properties") {
+         layout(2,
+               item(:text, :title=>"Resolution:"),
+               item(:popup, :subitems=>["Screen", "Screenx2", "Screenx3", "Screenx4", "Screenx5"], :tag=>"resolution", :value=>scale),
+               item(:text, :title=>"Background color:"),
+               item(:popup, :subitems=>["Same as Screen", "Transparent", "Black", "White"], :tag=>"bg_color", :value=>bg_color))
+       }
+       if hash[:status] == 0
+         set_global_settings("global.export_graphic_scale", (hash["resolution"] + 1).to_s)
+         set_global_settings("global.export_background_color", (hash["bg_color"] - 1).to_s)
+         return 0
+       else
+         return -1
+       end
+  end
   
   #  DEBUG
   def cmd_test
@@ -414,6 +739,7 @@ register_menu("Sort by residue", :cmd_sort_by_residue, :non_empty)
 register_menu("", "")
 register_menu("Delete Frames...", :cmd_delete_frames, lambda { |m| m && m.nframes > 1 } )
 register_menu("Reverse Frames...", :cmd_reverse_frames, lambda { |m| m && m.nframes > 1 } )
+register_menu("Concatenate Frames...", :cmd_concat_frames, lambda { |m| m && m.nframes > 1 } )
 register_menu("", "")
 register_menu("Show Energy Window...", :cmd_show_energy, lambda { |m| m && m.property_names.include?("energy") } )
 register_menu("Show MO Surface...", :cmd_create_surface, lambda { |m| m && m.get_mo_info(:type) != nil } )