analyzer/lib/generators/code/algorithm/units/changes_context_provider.rb
module VersatileDiamond
using Patches::RichArray
module Generators
module Code
module Algorithm::Units
# The context for units of reaction applying algoritnm builder
class ChangesContextProvider
include Modules::SpecLinksAdsorber
# @param [Hash] reaction
# @param [Array] main
# @param [Array] full
def initialize(reaction, main, full)
@main = main
@full = full
@prd_to_src_mirror = @full.map { |src| [src.product.spec_atom, src] }.to_h
@src_mirror = @full.map { |src| [src.spec_atom, src] }.to_h
@prd_mirror = @full.map { |src| [src.product.spec_atom, src.product] }.to_h
positions = reaction.reaction.reaction.links
surface_products =
@main.map(&:product).reject(&:gas?).map(&:spec_atom).map(&:first).uniq
@product_links = collaps(adsorb_links(positions, surface_products))
@source_links = collaps(reaction.links)
@_significant_neighbours = {}
@_phase_changes = nil
end
# @return [Array]
def phase_changes
@_phase_changes ||= @main.select { |src| src.transit? || src.different? }
end
# @return [Array]
def significant
significant_neighbours.reject(&method(:main?)).uniq
end
# @param [Nodes::SourceNode] node
# @return [Array]
def latticed_neighbours_with_params(node)
nbrs_with_params = significant_neighbours_of(node)
# TODO: logic of neighbours selection depends from diamond crystal lattice!
if nbrs_with_params.first.size < 2
raise ArgumentError, "Cannot find neighbours for node #{node.inspect}"
else
nbrs_with_params
end
end
# @param [Nodes::ChangeNode] node
# @return [Array]
def direct_neighbours_of(node)
mirror = @full.include?(node) ? @src_mirror : @prd_mirror
neighbour_spec_atoms(node).map(&mirror.public_method(:[])).select(&:itself)
end
private
# @param [Nodes::SourceNode] node
# @return [Boolean]
def main?(node)
@main.include?(node)
end
# @param [Array] nodes
# @return [Concept::Bond] or nil
def relation_between_sources(*nodes)
relation_between_in(@source_links, nodes)
end
# @param [Array] nodes
# @return [Concept::Bond] or nil
def relation_between_products(*nodes)
relation_between_in(@product_links, nodes.map(&:product))
end
# @return [Array]
def significant_neighbours
phase_changes.reduce([]) do |acc, node|
acc + significant_neighbours_of(node).first
end
end
# @return [Array]
def significant_neighbours_of(node)
return @_significant_neighbours[node] if @_significant_neighbours[node]
latticed_nrps = latticed_relation_params_of(node)
main_nrps = latticed_nrps.select { |nbr, _| main?(nbr) }
neighbours = best_neighbours(main_nrps)
neighbours = best_neighbours(latticed_nrps) if neighbours.size < 2
@_significant_neighbours[node] =
neighbours.size < 2 ? [[], []] : neighbours
end
# @param [Array] relations
# @return [Array]
def best_neighbours(relations)
if relations.empty?
[]
else
groups = relations.group_by(&:last)
max_group = groups.max_by { |_, group| group.size }
[max_group.last.map(&:first), max_group.first]
end
end
# @param [Nodes::ChangeNode] node
# @return [Array]
def latticed_relation_params_of(node)
neighbour_spec_atoms(node.product).each_with_object([]) do |sa, acc|
nbr = @prd_to_src_mirror[sa]
if nbr && nbr.lattice
prd_rel = relation_between_products(nbr, node)
if prd_rel
acc << [nbr, prd_rel.params] if prd_rel.exist?
else
src_rel = relation_between_sources(nbr, node)
acc << [nbr, src_rel.params] if src_rel && src_rel.exist?
end
end
end
end
# @param [Nodes::ChangeNode] node
# @return [Array]
def neighbour_spec_atoms(node)
spec, atom = node.spec_atom
exists_bonds = spec.links[atom].select { |_, r| r.bond? && r.exist? }
exists_bonds.map { |a, _| [spec, a] }
end
# @param [Hash] links
# @param [Array] nodes
# @return [Concept::Bond]
def relation_between_in(links, nodes)
a, b = nodes.map(&:spec_atom)
if links[a]
result = links[a].find { |sa, _| sa == b }
result && result.last
else
nil
end
end
# @param [Hash] links
# @return [Concept::Bond]
def collaps(links)
links.each_with_object({}) do |(key, rels), acc|
groups = rels.groups
singles = groups.select(&:one?).reduce(:+) || []
manies = groups.reject(&:one?).map(&method(:squize_multi_bond))
acc[key] = singles + manies
end
end
# @param [Array] group
# @return [Array]
def squize_multi_bond(group)
keys, relations = group.transpose
if relations.any?(&:belongs_to_crystal?)
raise ArgumentError, 'Cannot squize crystal relations'
else
[keys.first, Concepts::MultiBond.new(relations.size)]
end
end
end
end
end
end
end