# Copyright (C) 2024-2025 LibreWeddingPlanner contributors # frozen_string_literal: true module VNS class Engine class << self def sequence(elements) elements = elements.to_a (elements + elements.reverse).chunk(&:itself).map(&:first) end end def target_function(&function) @target_function = function end def add_perturbation(klass) @perturbations ||= Set.new @perturbations << klass end def initialize_ractors(count: Concurrent.processor_count) @ractors = count.times.map do |i| Ractor.new(name: "VNS Ractor #{i}") do # @target_function.call(Ractor.receive) Ractor.receive.discomfort # Hard-coded for now, should be dynamic end end.cycle end attr_writer :initial_solution, :ractors def run raise 'No target function defined' unless @target_function raise 'No perturbations defined' unless @perturbations raise 'No initial solution defined' unless @initial_solution @best_solution = @initial_solution @best_score = @target_function.call(@best_solution) @best_solution.freeze @best_solution.tables.freeze # @best_solution.tables.each(&:freeze) self.class.sequence(@perturbations).each do |perturbation| optimize(perturbation) end @best_solution end private def optimize(perturbation_klass) loop do optimized = false perturbation_klass.new(@best_solution).each do |alternative_solution| # original_score = # ractor = @ractors.next # Rails.logger.info("Processed by ractor #{ractor.name}") # ractor.send(alternative_solution) # new_score = ractor.take # binding.pry if new_score != original_score # score = new_score score = @target_function.call(alternative_solution) # Rails.logger.info("Evaluating alternative solution with score: #{score}") next if score >= @best_score # Rails.logger.info("Found better solution with score: #{score} (previous: #{@best_score})") @best_solution = alternative_solution.deep_dup @best_score = score optimized = true break end return unless optimized end end end end