analyzer/lib/generators/code/algorithm/units/base_pure_unit.rb
module VersatileDiamond
using Patches::RichArray
module Generators
module Code
module Algorithm::Units
# The basic unit for each other
# @abstract
class BasePureUnit < GenerableUnit
include Modules::OrderProvider
include Modules::ProcsReducer
attr_reader :nodes
# @param [Expressions::VarsDictionary] dict
# @param [Array] nodes
def initialize(dict, nodes)
super(dict)
@nodes = nodes
@_species, @_anchored_species, @_atoms, @_symmetric_atoms = nil
end
# @param [BasePureUnit] other
# @return [Integer]
def <=>(other)
order(self, other, :nodes, :size) do
order(self, other, :atoms, :size) do
order(self, other, :species, :size, &comparing_core(other))
end
end
end
# @return [Array]
def species
@_species ||= uniq_from_nodes(:uniq_specie)
end
# @return [Array]
def anchored_species
@_anchored_species ||= nodes.select(&:anchor?).map(&:uniq_specie).uniq
end
# @return [Array]
def atoms
@_atoms ||= uniq_from_nodes(:atom)
end
# @return [Array]
def symmetric_atoms
@_symmetric_atoms ||= nodes.flat_map(&:symmetric_atoms).uniq
end
# @return [Array]
def complete_inner_units
filled_inner_units.flat_map(&method(:split_on_compliance)).sort
end
# @param [Symbol] method_name
# @param [Array] calling_atoms
# @yield [SpecieInstance, Atom] checks that atom belongs to specie
# @return [Array]
def atom_with_specie_calls(method_name, calling_atoms, &block)
pack_with_species(calling_atoms, &block).map do |atom, specie|
dict.var_of(atom).public_send(method_name, specie)
end
end
# Checks that atoms have specific types
# @param [Array] checking_atoms
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def check_atoms_roles(checking_atoms, &block)
checks = atom_with_specie_calls(:role_in, checking_atoms, &:atom?)
Expressions::AndCondition[checks, block.call]
end
# @param [BasePureUnit] nbr
# @param [Proc] crystal_rels_proc
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def check_amorph_bonds_if_have(nbr, crystal_rels_proc, &block)
lattices = (atoms + nbr.atoms).map(&:lattice)
if lattices.all? && lattices.uniq.one?
redefine_self_and_nbr_atoms_if_need(nbr) do
crystal_rels_proc[&block]
end
elsif atoms.one? && nbr.atoms.one?
iterate_amorph_bonds(nbr, &block)
else
raise ArgumentError, 'Cannot itearte relations between units'
end
end
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def iterate_specie_symmetries(&block)
defined_species = defined_symmetric_species
if defined_species.one?
iterate_defined_specie_symmetries(defined_species.first, &block)
elsif defined_species.empty?
raise 'Symmetric specie was not defined'
else
raise 'Too many defined symmetric species'
end
end
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def iterate_for_loop_symmetries(&block)
define_undefined_atoms do
redefine_atoms_as_array do
dict.var_of(atoms).iterate(dict.make_iterator(:a), block.call)
end
end
end
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def define_undefined_atoms(&block)
if all_defined?(atoms)
block.call
else
make_undefined_atoms_from_defined_species.define_var + block.call
end
end
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def define_undefined_species(&block)
if all_defined?(anchored_species)
block.call
elsif select_undefined(anchored_species).uniq { |s| s.spec.spec }.one?
check_undefined_species(&block)
else
call_procs(define_undefined_species_procs, &block)
end
end
# @return [Boolean]
def symmetric?
fully_symmetric? || partially_symmetric?
end
def to_s
inspect
end
def inspect
sis = species.map(&:inspect)
nas = nodes.uniq(&:atom)
spops = nas.map(&:sub_properties).map(&:inspect)
pkns = nas.map do |n|
n.spec.spec.keyname(n.uniq_specie.send(:original_atom, n.atom))
end
ppops = nas.map(&:properties).map(&:inspect)
ckns = nas.map do |n|
ds = n.uniq_specie.spec
ch = ds.instance_variable_get(:@child) || ds
ch.spec.keyname(n.atom)
end
pkwps = pkns.zip(spops).map { |kp| kp.join(':') }
ckwps = ckns.zip(ppops).map { |kp| kp.join(':') }
bs = pkwps.zip(ckwps).map { |p, c| "(#{p})‡(#{c})" }
"•[#{sis.join(' ')}] [#{bs.join(' ')}]•"
end
protected
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def redefine_atoms_as_array(&block)
if atoms.one? || dict.var_of(atoms)
block.call # all atoms already belongs to same array
else
remake_atoms_as_array.define_var + block.call
end
end
private
# @param [BasePureUnit] other
# @return [Proc]
def comparing_core(other)
-> do
sps, ops = [self, other].map { |u| u.nodes.map(&:sub_properties) }
ops <=> sps
end
end
# @return [Array]
def define_undefined_species_procs
units.map do |unit|
-> &block { unit.define_undefined_species(&block) }
end
end
# @param [Symbol] method_name
# @return [Array]
def uniq_from_nodes(method_name)
nodes.map(&method_name).uniq
end
# @param [Array] atoms
# @return [Array]
def nodes_with_atoms(atoms)
nodes.select { |node| atoms.include?(node.atom) }
end
# @param [Array] species
# @return [Array]
def nodes_with_species(species)
nodes.select { |node| species.include?(node.uniq_specie) }
end
# @param [BasePureUnit] inner_unit
# @return [Array]
def split_on_compliance(inner_unit)
complete_unit?(inner_unit) ? [inner_unit] : inner_unit.units
end
# @param [BasePureUnit] inner_unit
# @return [Boolean]
def complete_unit?(inner_unit)
!inner_unit.atoms.one? || coincident_nodes_of?(inner_unit)
end
# @param [BasePureUnit] inner_unit
# @return [Boolean]
def coincident_nodes_of?(inner_unit)
values = inner_unit.nodes.map(&:coincide?)
!values.any? || values.all?
end
# @return [Boolean]
def fully_symmetric?
atoms.one? && symmetric_atoms.size > species.size
end
# @return [Boolean]
def partially_symmetric?
!atoms.one? && !symmetric_atoms.empty? &&
symmetric_atoms.to_set < atoms.to_set
end
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def check_undefined_species(&block)
var = make_undefined_species_from_anchors
checking_exprs = var.collection? ? var.items : [var]
var.define_var + Expressions::AndCondition[checking_exprs, block.call]
end
# @return [Array]
def defined_symmetric_species
symmetric_nodes = nodes.select(&:symmetric_atoms?)
if symmetric_nodes.empty?
select_defined(species)
else
select_defined(symmetric_nodes.map(&:uniq_specie).uniq)
end
end
# @param [BasePureUnit] nbr
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def iterate_amorph_bonds(nbr, &block)
predefn_vars = dict.defined_vars # get before make inner nbr atoms var
atom_var = dict.var_of(atoms)
nbr_var = dict.make_atom_s(nbr.atoms)
if !atoms.any?(&:lattice) && nbr.atoms.any?(&:lattice)
atom_var.iterate_crystal_nbrs(predefn_vars, nbr_var, block.call)
else
atom_var.iterate_amorph_nbrs(predefn_vars, nbr_var, block.call)
end
end
# @param [Instances::SpecieInstance] specie
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def iterate_defined_specie_symmetries(specie, &block)
predefn_vars = dict.defined_vars # get before make inner specie var
ext_var = dict.var_of(specie)
kwargs = { type: abstract_type, name: specie.symmetric_var_name }
inner_var = dict.make_specie_s(specie, **kwargs)
ext_var.iterate_symmetries(predefn_vars, inner_var, block.call)
end
# @return [Expressions::Core::Variable]
def make_undefined_atoms_from_defined_species
undefined_atoms = select_undefined(atoms)
vars = vars_for(nodes_with_atoms(undefined_atoms).map(&:uniq_specie))
pairs = vars.smart_zip(undefined_atoms).select(&:first)
calls = pairs.map { |v, a| v.atom_value(a) }
selected_atoms = pairs.map(&:last)
if selected_atoms.one? && calls.size > 1
dict.make_atom_s(selected_atoms.first, value: calls.first)
else
dict.make_atom_s(selected_atoms, value: calls)
end
end
# @return [Expressions::Core::Variable]
def make_undefined_species_from_anchors
undefined_species = select_undefined(anchored_species).sort
vars = vars_for(nodes_with_species(undefined_species).map(&:atom))
calls = vars.zip(undefined_species).map { |v, s| v.one_specie_by_role(s) }
dict.make_specie_s(undefined_species, value: calls)
end
# @param [BasePureUnit] nbr
# @yield incorporating statement
# @return [Expressions::Core::Statement]
def redefine_self_and_nbr_atoms_if_need(nbr, &block)
redefine_atoms_as_array do
if all_defined?(nbr.atoms)
nbr.redefine_atoms_as_array(&block)
else
block.call
end
end
end
# @return [Expressions::Core::Collection]
def remake_atoms_as_array
dict.make_atom_s(atoms, value: vars_for(atoms))
end
# @param [Array] packing_atoms
# @yield [SpecieInstance, Atom] checks that atom belongs to specie
# @return [Array]
def pack_with_species(packing_atoms, &block)
packing_atoms.zip(packing_species(packing_atoms, &block))
end
# @param [Array] packing_atoms
# @yield [SpecieInstance, Atom] checks that atom belongs to specie
# @return [Array]
def packing_species(packing_atoms, &block)
packing_atoms.map { |atom| chose_specie_with(atom, &block) }
end
# @param [Concepts::Atom | Concepts::AtomRelation | Concepts::SpecificAtom]
# packing_atom the specie for which will be chosed
# @yield [SpecieInstance, Atom] checks that atom belongs to specie
# @return [Instances::SpecieInstance]
def chose_specie_with(packing_atom, &block)
species.find { |specie| block[specie, packing_atom] }
end
end
end
end
end
end