# Copyright (C) 2024-2025 LibreWeddingPlanner contributors # frozen_string_literal: true module VNS class Engine PERTURBATION_SIZES = [1, 1, 1, 2, 2, 3].freeze 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_optimization(klass) @optimizations ||= Set.new @optimizations << klass end def add_perturbation(klass) @perturbations ||= Set.new @perturbations << klass end attr_writer :initial_solution def run raise 'No target function defined' unless @target_function raise 'No optimizations defined' unless @optimizations raise 'No initial solution defined' unless @initial_solution @perturbations ||= Set.new @current_solution = @initial_solution @best_score = @target_function.call(@current_solution) run_all_optimizations best_solution = @current_solution 50.times do @current_solution = Tables::WheelSwap.new(best_solution).call(PERTURBATION_SIZES.sample) @best_score = @target_function.call(@current_solution) Rails.logger.debug { "After perturbation: #{@best_score}" } run_all_optimizations next unless best_solution.discomfort > @current_solution.discomfort best_solution = @current_solution Rails.logger.debug do "Found better solution after perturbation optimization: #{@current_solution.discomfort}" end end best_solution end private def run_all_optimizations self.class.sequence(@optimizations).each do |optimization| optimize(optimization) Rails.logger.debug { "Finished optimization phase: #{optimization}" } end Rails.logger.debug { 'Finished all optimization phases' } end def optimize(optimization_klass) loop do optimized = false optimization_klass.new(@current_solution).each do |alternative_solution| score = @target_function.call(alternative_solution) next if score >= @best_score @current_solution = alternative_solution.deep_dup @best_score = score optimized = true Rails.logger.debug { "[#{optimization_klass}] Found better solution with score: #{score}" } break end return unless optimized end end end end