# 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_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 @best_solution = @initial_solution @best_score = @target_function.call(@best_solution) self.class.sequence(@optimizations).each do |optimization| optimize(optimization) Rails.logger.debug { "Finished optimization phase: #{optimization.name}" } end Rails.logger.debug { "Finished all optimization phases" } @best_solution end private def optimize(optimization_klass) loop do optimized = false optimization_klass.new(@best_solution).each do |alternative_solution| score = @target_function.call(alternative_solution) next if score >= @best_score @best_solution = alternative_solution.deep_dup @best_score = score optimized = true break end return unless optimized end end end end