newmen/versatile-diamond

View on GitHub
analyzer/lib/concepts/surface_spec.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module VersatileDiamond
  using Patches::RichArray

  module Concepts

    # Represents surface structure
    class SurfaceSpec < Spec
      include Lattices::BasicRelations
      include SurfaceLinker

      # Dups current base spec
      # @param [Sybmol] new_name of new spec
      # @param [Hash] atom_renames the mirror of atom keynames
      # @return [Spec] the dupped concept base spec
      def dup(new_name = nil, atom_renames = {})
        copy = self.class.new(new_name || name)
        copy.adsorb(self)
        atom_renames.each { |pair| copy.rename_atom(*pair) }
        copy
      end

      # Returns that spec is not gas
      # @return [Boolean] gas or not
      def gas?
        false
      end

      # Finds position relation between two atoms, where first atom belongs to
      # largest structure (specie) and relation has direction from atom of
      # first spec to atom of second spec
      #
      # @param [Atom] first the first atom
      # @param [Atom] second the second atom
      # @return [Position] the position relation or nil
      def position_between(first, second)
        relation = relation_between(first, second)
        relation && relation.belongs_to_crystal? && relation.make_position
      end

    protected

      # After linking finds position by relation rules from crystal lattice
      # @param [Array] args the argumens of super method
      # @raise [Position::UnspecifiedAtoms] unless at least one atom
      #   belonging to lattice
      # @override
      def link_together(*atoms, instance)
        if instance != undirected_bond
          raise Position::UnspecifiedAtoms unless at_least_one_latticed?(atoms)
          super(*sort(atoms), instance)
          find_positions
        else
          raise Position::UnspecifiedAtoms unless at_least_one_latticed?(@atoms.values)
          super(*atoms, instance)
        end
      end

    private

      # Gets opposite relation between first and second atoms for passed
      # relation instance
      #
      # @param [Atom] first the first of two linking atoms
      # @param [Atom] second the second of two linking atoms
      # @param [Bond] relation the instance of relation
      # @raise [Lattices::Base::UndefinedRelation] when passed relation is
      #   undefined
      # @return [Bond] the opposite relation
      def opposite_relation(first, second, relation)
        target_lattice = first.lattice || second.lattice
        if target_lattice
          other_lattice = first.lattice == target_lattice ? second.lattice : nil
          target_lattice.opposite_relation(other_lattice, relation)
        else
          raise 'Wrong relation' unless relation == undirected_bond
          relation
        end
      end

      # Finds and store positions between transitive bonded atoms
      def find_positions
        atom_instances.combination(2).each do |atoms|
          next if !at_least_one_latticed?(atoms) || relation_between(*atoms)
          first, second = sort(atoms)

          positions =
            first.lattice.positions_between(first, second, links)
          next unless positions

          link_with_other(first, second, *positions)
        end
      end

      # Checks that at least one atoms belongs to lattice
      # @param [Array] atoms the array of atoms
      # @return [Boolean] has latticed atom or not
      def at_least_one_latticed?(atoms)
        atoms.any?(&:lattice)
      end

      # Sorts atoms by having crystall lattice
      # @param [Array] atoms the array of atoms
      # @return [Array] the sorted atoms array
      def sort(atoms)
        atoms_dup = atoms.dup
        first = atoms_dup.delete_one(&:lattice)
        [first, atoms_dup.pop]
      end
    end

  end
end