class Crysna::TransitionFinder

Public Class Methods

new(cells, site_migrations, site_operations) click to toggle source
# File lib/crysna/transitionfinder.rb, line 20
def initialize(cells, site_migrations, site_operations)
  @site_migrations = site_migrations
  @site_operations = site_operations
  @cell_candidates = cells.map do |cell|
    new_cell = cell.minimize(@site_operations)
    new_cell.name = cell.name
    new_cell
  end
  @edges = []
  @cell_manager = Crysna::TransitionFinder::CellManager.new

  #@graph = GraphViz.new( "Transition",
  #                           :type => "graph",
  #                           :use => "dot"
  #                      )
  #@nodes = {}
  #@edges = []
end

Public Instance Methods

find_path(start, truncate_energy, result_io, dot_io) click to toggle source

Start finding and retrun path. ‘start’ is the name of the starting cell. ‘truncate_energy’ is end condition of cell enegy. If truncate_energy == false, ‘finding one MEP mode’ is selected. The returned array of cell transitions. When ‘finding one MEP mode’, return an array of one item. Return nil if the transition path cannot be found. NOTE: specification may be changed in future.

# File lib/crysna/transitionfinder.rb, line 47
def find_path(start, truncate_energy, result_io, dot_io)
  pair = meet_transit_cells(start, truncate_energy)
  if pair
    ## Show
    result_io.puts "-"*60
    result_io.puts "One direction"
    write_path(@cell_manager.ascend(pair[0]), result_io)

    result_io.puts "-"*60
    result_io.puts "The other direction"
    write_path(@cell_manager.ascend(pair[1]), result_io)

    #@graph.output(dot_io)
    #dot_io.puts @graph.output( :none => String )

    write_dot(pair, dot_io)
  else
    result_io.puts "Cannot find path."
    cell_transition = nil
  end
  #return cell_transition
end

Private Instance Methods

each_sort(arys) click to toggle source
# File lib/crysna/transitionfinder.rb, line 443
def each_sort(arys)
  arys.map {|ary| ary.sort}
end
expand_edges(cell_id) click to toggle source

Argument ‘cell_id’ indicates an index of a base cell in @cell_manager. New nodes from the base cell are added into @cell_manager. New edges between the base cell and new cells are added into @edges. New nodes:

They are found from the base cell via a single migration.
Energy of new cell is set to be the same vale of symmetric identical cell
in @cell_candidates.
If it is not found in @cell_candidates, set to be Float::INFINITY

Each edge has ‘note’ of the infomation of migration and operation; migration element, source site, destination site, symmetry operation to minimum_expression. ‘reach_flag’s of new cells are set to be false, ‘from_edges’ to be empty, energy to be set nil. NOTE: edge weight should be as in edge list.

# File lib/crysna/transitionfinder.rb, line 174
def expand_edges(cell_id)
  $log_io.puts "-" * 30
  $log_io.puts "#{self.class}.expand_edges with cell_id of #{cell_id}"

  cell = @cell_manager[cell_id]
  $log_io.puts "[cell_id = #{cell_id}]"
  $log_io.puts "  #{cell.configuration_string_full}, energy=#{cell.energy}, reach_flag=#{cell.reach_flag.to_s}, name=\"#{cell.name}\""

  $log_io.puts '-' * 60
  $log_io.puts 'Loop to find lowest edge: start'
  cell.atoms.each_with_index do |atom, atom_id|
    $log_io.puts '-' * 30
    $log_io.puts "Migrating atom: index=#{atom_id}, element=#{atom.element}, site=#{atom.sitename}"
    @site_migrations[atom.site.name].each do |site, global_vector|
      $log_io.puts "-" * 10
      $log_io.puts "To site=#{site}, global_vector=#{global_vector}"
      new_cell = cell.migrate(atom_id, site, global_vector)
      $log_io.puts "  Generated cell: #{new_cell.configuration_string}(#{new_cell.configuration_string_full})"

      min_ope = minimum_operation(new_cell)
      $log_io.puts "  minimum_operation id: #{min_ope}"

      identical_cell = new_cell.operate(@site_operations[min_ope]["operation"])
      $log_io.puts "  Symmetrically identical cell: #{identical_cell.configuration_string}(#{identical_cell.configuration_string_full})"

      ##Check including @cell_manager
      id = @cell_manager.index_same_atoms(new_cell)
      if id
        $log_io.puts "This cell is contained in @cell_manager."
        $log_io.puts "Truncated."
        next
      else
        $log_io.puts "This cell is not included in @cell_manager."
      end

      ##Check including @cell_candidates
      id = find_periodically_candidate(identical_cell)
      if id
        $log_io.puts "This cell is contained in @cell_candidates."
        energy = @cell_candidates[id].energy
      else
        $log_io.puts "This cell is not included in @cell_candidates."
        energy = Float::INFINITY
      end

      ##
      new_cell.name = new_cell.occupied_sitename
      new_cell.energy = energy

      new_cell_id = @cell_manager.add(new_cell)
      $log_io.puts "The cell is added into @cell_manager as index of #{new_cell_id}"

      energy = [@cell_manager[cell_id].energy, new_cell.energy].max
      note =
        "|[migration] elem=#{atom.element}" +
        ", #{atom.sitename}"+
        " -> #{site}#{global_vector.to_a}\n" # +

      new_edge = Crysna::TransitionFinder::Edge.new(
        [cell_id, new_cell_id],
        energy,
        note
      )
      $log_io.puts "New edge: cell_ids of (#{cell_id} -- #{new_cell_id}), energy=#{energy}"
      @edges.push(new_edge)

      conf_full = new_cell.configuration_string_full
      #@nodes[conf_full] = @graph.add_nodes(conf_full, :label => conf_full)
      #pp @nodes
      #pp cell.configuration_string_full
      #pp new_cell.configuration_string_full
      #puts
      #puts
      #pp @nodes[cell.configuration_string_full]
      #pp @nodes[new_cell.configuration_string_full]
      #@graph.add_edges(@nodes[cell.configuration_string_full],
      #                @nodes[new_cell.configuration_string_full]
      #               )
    end
  end
  $log_io.puts 'Finding lowest edge loop end'
  $log_io.puts '-'*60
end
find_periodically_candidate(cell) click to toggle source

Return index of the cell which is periodically equal to the cell in @cell_candidates Not minimize(symid)-expressed cell is minimized to be found. Rerurn nil if no periodically equal cell is included.

# File lib/crysna/transitionfinder.rb, line 132
def find_periodically_candidate(cell)
  cell = cell.minimize(@site_operations)
  result = nil
  @cell_candidates.each_with_index do |ref_cell, index|
    if cell.periodically_equal? ref_cell
      result = index
      break
    end
  end
  return result
end
from_to_node_indices(edge) click to toggle source

Retrun [from, to] nodes. If the nodes of ‘edge’ is both reached or both not_reached, raise RuntimeError.

# File lib/crysna/transitionfinder.rb, line 146
def from_to_node_indices(edge)
  flag0 = @cell_manager[edge.nodes[0]].reach?
  flag1 = @cell_manager[edge.nodes[1]].reach?
  if flag0 == true && flag1 == false
    results = [edge.nodes[0], edge.nodes[1]]
  elsif flag0 == false && flag1 == true
    results = [edge.nodes[1], edge.nodes[0]]
  else
    raise RuntimeError, "Must not happen."
  end
  return results
end
meet_transit_cells(start, truncate_energy) click to toggle source
# File lib/crysna/transitionfinder.rb, line 305
def meet_transit_cells(start, truncate_energy)
  $log_io.puts "#{self.class}.find_path"
  cur_cell = @cell_candidates.find{|tmp| tmp.name == start}
  start_index = cur_index = @cell_manager.add(cur_cell)
  @cell_manager.reach(cur_index, nil)

  conf = cur_cell.configuration_string
  conf_full = cur_cell.configuration_string_full
  $log_io.puts "Starting point: #{start}"
  $log_io.puts "#{conf}(#{conf_full}) is added as cur_index of #{cur_index}"
  #@nodes[conf_full] = @graph.add_nodes( conf_full,
  #   #:shape => "circle",
  #   #:penwidth => 2,
  #   #:fontsize => 12,
  #   #:style => :filled,
  #   #:fillcolor => "orange",
  #   :label => conf_full
  #)
  write_log_state
  expand_edges(cur_index)

  iteration = 0 
  while true
    iteration += 1
    $log_io.puts "-" * 10 + "Iteration #{iteration}" + "-" * 40

    write_log_state
    $log_io.puts "-" * 60

    $log_io.puts "reach_unreach_edges: "
    reach_unreach_edges.each do |edge|
      #$log_io.puts edge.inspect
      $log_io.puts "  #{edge.nodes.to_s}"
    end

    $log_io.puts "cur_index:" + cur_index.to_s

    cur_cell = @cell_manager[cur_index]
    $log_io.puts "[#{cur_index}] #{cur_cell.configuration_string_full}, energy=#{cur_cell.energy}, reach_flag=#{cur_cell.reach_flag.to_s}, name=\"#{cur_cell.name}\""
    if truncate_energy
      if @cell_manager.max{|cell| cell.energy} > truncate_energy
        break
      end
    else
      if @cell_manager.contain_periodically_shift_cells?
        $log_io.puts "truncate condition reached by contain_periodically_shift_cells."
        $log_io.puts @cell_manager.periodically_shift_indices.to_s
        break
      end
    end

    ##Choose the lowest edge
    $log_io.puts "-" * 30
    $log_io.puts "reach_unreach_edges"
    reach_unreach_edges.each do |edge|
      $log_io.puts "-" * 10
      $log_io.puts edge.note
      $log_io.puts "energy=#{edge.weight}"
    end

    new_edge = reach_unreach_edges.min_by {|edge| edge.weight}
    $log_io.puts "-" * 20
    if new_edge
      $log_io.puts "Chosen edge: #{new_edge.note}"
      $log_io.puts "energy=#{new_edge.weight}"
    else
      $log_io.puts "Chosen edge: nil"
      $log_io.puts "break finding loop."
      break
    end

    ##Show cells state
    $log_io.puts "-" * 30
    $log_io.puts "Cells state"
    from_index, to_index = from_to_node_indices(new_edge)
    @cell_manager.reach(to_index, new_edge)
    cur_index = to_index

    @cell_manager.cells.each_with_index do |cell, index|
      $log_io.puts "-" * 20
      $log_io.puts "[#{index}] #{cell.configuration_string_full}, energy=#{cell.energy}, reach_flag=#{cell.reach_flag.to_s}, name=\"#{cell.name}\""
      #$log_io.puts "-" * 10
      $log_io.puts "Number of from_edges: #{cell.from_edges.size}"
      cell.from_edges.each do |edge| 
        $log_io.puts "-" * 10
        if edge
          $log_io.puts "Nodes: #{edge.nodes}"
          $log_io.puts edge.note
        else
          $log_io.puts "Nil edge(Starting point)"
          next
        end
      end
    end
    $log_io.puts "-" * 30
    $log_io.puts "cur_index is set to be #{cur_index}"

    expand_edges(to_index)
    $log_io.puts "-" * 60
    $log_io.puts "find_path loop: next"
    $log_io.puts "-" * 60
    $log_io.puts
  end

  result = @cell_manager.periodically_shift_indices.find {|i| i.size > 1}
  result
end
minimum_operation(cell) click to toggle source

Return an index in @site_operations to minimuze expresion.

# File lib/crysna/transitionfinder.rb, line 259
def minimum_operation(cell)
  min_ope = @site_operations[0]["operation"]
  min_id  = @site_operations[0]["operation_id"]

  $log_io.puts "  in minimum_operation:"

  @site_operations.each do |site_ope|
    id  = site_ope["operation_id"] 
    ope = site_ope["operation"]
    $log_io.puts '-'*30
    $log_io.puts "  site_ope id: #{id}"

    min = cell.operate(min_ope)
    cur = cell.operate(ope)
    write_log_cell(cur)
    if cur < min
      min_id = id
      min_ope = ope
      $log_io.puts "  min_exp is updated."
    end
  end
  $log_io.puts '-'*30
  return min_id
end
reach_unreach_edges() click to toggle source

Generate and return an array of edges which connect between reached and not reached cells.

# File lib/crysna/transitionfinder.rb, line 118
def reach_unreach_edges
  results = []
  @edges.each do |edge|
    if @cell_manager[edge.nodes[0]].reach? ^ @cell_manager[edge.nodes[1]].reach?
      results << edge
    end
  end
  return results
end
two_indices(ary) click to toggle source
# File lib/crysna/transitionfinder.rb, line 435
def two_indices(ary)
  results = []
  ((ary.size) -1 ).times do |i|
    results << [ary[i], ary[i+1]]
  end
  results
end
write_dot(goal_pair, dot_io) click to toggle source
# File lib/crysna/transitionfinder.rb, line 72
def write_dot(goal_pair, dot_io)
  min_path_edges0 = two_indices(@cell_manager.ascend(goal_pair[0]).reverse)
  min_path_edges1 = two_indices(@cell_manager.ascend(goal_pair[1]).reverse)
  min_path_edges0 = each_sort(min_path_edges0)
  min_path_edges1 = each_sort(min_path_edges1)

  dot_io.puts "graph Transition {"
  ##nodes
  @cell_manager.cells.each_with_index do |cell, index|
    conf = cell.configuration_string_full
    node_id_str = sprintf("%3d", index)
    str = "  #{node_id_str} [label = \"#{node_id_str}\\n#{conf}\\n#{cell.energy}\"];"
    dot_io.puts str
  end

  dot_io.puts

  ##normal edges
  @edges.each do |edge|
    index0 = edge.nodes[0]
    index1 = edge.nodes[1]

    indices_for_test = [index0, index1].sort
    #str = "  #{index0} -- #{index1} "
    str = sprintf("  %3d -- %3d ", index0, index1)
    if min_path_edges0.include?(indices_for_test)
      str += "[label = \"#{edge.note.chomp}\", penwidth = 3, color = red];"
    elsif min_path_edges1.include?(indices_for_test)
      str += "[label = \"#{edge.note.chomp}\", penwidth = 3, color = green];"
    else
      str += "[label = \"#{edge.note.chomp}\"];"
    end
    dot_io.puts str
  end

  ##connect goal_pair
  str = "  #{goal_pair[0]} -- #{goal_pair[1]} [label = \"Semi-identical\", penwidth = 3, color = blue, style = dotted];"
  dot_io.puts str

  ##close
  dot_io.puts "}"

end
write_log_cell(cell) click to toggle source
# File lib/crysna/transitionfinder.rb, line 298
def write_log_cell(cell)
  $log_io.puts "  atoms of cell:"
  cell.atoms.each do |atom|
    $log_io.puts "  - #{atom.site.name} #{atom.site.global_vector}"
  end
end
write_log_state() click to toggle source
# File lib/crysna/transitionfinder.rb, line 284
def write_log_state
  $log_io.puts "state" + "-" * 30
  $log_io.puts "cells in cellmanager:"
  @cell_manager.cells.size.times do |i|
    $log_io.puts "  [#{i}]"
    cell = @cell_manager[i]
    $log_io.puts "  name: #{cell.name}"
    write_log_cell(cell)
    $log_io.puts "  energy: #{cell.energy}"
    $log_io.puts
  end

end
write_path(cell_ids, result_io) click to toggle source
# File lib/crysna/transitionfinder.rb, line 413
def write_path(cell_ids, result_io)
  cell_ids.reverse.each do |index|
    cell = @cell_manager[index]
    edge = cell.from_edges[0]
    if edge
      result_io.puts "|"
      result_io.puts "#{edge.note}"
      result_io.puts "|"
    else
    end

    result_io.puts "[cell]"
    result_io.puts "    atoms: #{cell.configuration_string_full}"

    min_ope = minimum_operation(cell)
    symid_cell = cell.operate(@site_operations[min_ope]["operation"])
    result_io.puts "    symmetrically identical cell: #{symid_cell.configuration_string}" +
      "(operation_id=#{min_ope})"
    result_io.puts "    energy: #{cell.energy}"
  end
end