Compare commits

..

No commits in common. "12174b6f20b53933279ebcf0d941ddc9aa627001" and "ac659bef860be1756e1520eea09f0d6464a0bdc8" have entirely different histories.

11 changed files with 13 additions and 79 deletions

View File

@ -9,10 +9,10 @@ class TablesArrangementsController < ApplicationController
render json: TablesArrangement render json: TablesArrangement
.order(valid: :desc) .order(valid: :desc)
.order(discomfort: :asc) .order(discomfort: :asc)
.select(:id, :name, :discomfort, :status, :progress) .select(:id, :name, :discomfort, :status)
.select("digest = '#{current_digest}'::uuid OR discomfort IS NULL as valid") .select("digest = '#{current_digest}'::uuid as valid")
.limit(20) .limit(20)
.as_json(only: %i[id name discomfort valid status progress]) .as_json(only: %i[id name discomfort valid status])
end end
def show def show
@ -25,10 +25,7 @@ class TablesArrangementsController < ApplicationController
end end
def create def create
ActiveRecord::Base.transaction do TableSimulatorJob.perform_later(current_tenant.id)
tables_arrangement = TablesArrangement.create!(status: :not_started)
TableSimulatorJob.perform_later(current_tenant.id, tables_arrangement.id)
end
render json: {}, status: :created render json: {}, status: :created
end end

View File

@ -8,35 +8,16 @@ class TableSimulatorJob < ApplicationJob
MIN_PER_TABLE = 8 MIN_PER_TABLE = 8
MAX_PER_TABLE = 10 MAX_PER_TABLE = 10
def perform(wedding_id, tables_arrangement_id) def perform(wedding_id)
Rails.logger.info "Starting table simulation #{tables_arrangement_id} for wedding #{wedding_id}"
ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do
engine = VNS::Engine.new engine = VNS::Engine.new
engine.add_optimization(Tables::Swap) engine.add_optimization(Tables::Swap)
engine.add_optimization(Tables::Shift) engine.add_optimization(Tables::Shift)
tables_arrangement = TablesArrangement.find(tables_arrangement_id) initial_solution = Tables::Distribution.new(min_per_table: MIN_PER_TABLE, max_per_table: MAX_PER_TABLE)
initial_solution = Tables::Distribution.new(
min_per_table: MIN_PER_TABLE,
max_per_table: MAX_PER_TABLE,
tables_arrangement_id:
)
initial_solution.random_distribution(Guest.potential.shuffle) initial_solution.random_distribution(Guest.potential.shuffle)
initial_solution.save!
engine.notify_progress do |current_progress|
tables_arrangement.update_columns(status: :in_progress, progress: current_progress)
end
engine.on_better_solution do |better_solution|
better_solution.save!
tables_arrangement.update_columns(discomfort: better_solution.discomfort) # TODO: remove?
end
engine.initial_solution = initial_solution engine.initial_solution = initial_solution
engine.target_function(&:discomfort) engine.target_function(&:discomfort)
@ -44,8 +25,6 @@ class TableSimulatorJob < ApplicationJob
best_solution = engine.run best_solution = engine.run
best_solution.save! best_solution.save!
tables_arrangement.update_columns(status: :completed)
end end
end end
end end

View File

@ -10,7 +10,6 @@
# digest :uuid not null # digest :uuid not null
# discomfort :integer # discomfort :integer
# name :string not null # name :string not null
# progress :float default(0.0), not null
# status :string default("complete"), not null # status :string default("complete"), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null

View File

@ -14,7 +14,7 @@ module Tables
attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy, :tables_arrangement_id attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy, :tables_arrangement_id
def initialize(min_per_table:, max_per_table:, tables_arrangement_id:, hierarchy: AffinityGroupsHierarchy.new) def initialize(min_per_table:, max_per_table:, hierarchy: AffinityGroupsHierarchy.new, tables_arrangement_id: nil)
@min_per_table = min_per_table @min_per_table = min_per_table
@max_per_table = max_per_table @max_per_table = max_per_table
@hierarchy = hierarchy @hierarchy = hierarchy
@ -43,19 +43,15 @@ module Tables
end end
def deep_dup def deep_dup
self.class.new( self.class.new(min_per_table: @min_per_table, max_per_table: @max_per_table,
min_per_table: @min_per_table, hierarchy: @hierarchy).tap do |new_distribution|
max_per_table: @max_per_table,
hierarchy: @hierarchy,
tables_arrangement_id: @tables_arrangement_id
).tap do |new_distribution|
new_distribution.tables = @tables.map(&:dup) new_distribution.tables = @tables.map(&:dup)
end end
end end
def save! def save!
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
arrangement = TablesArrangement.find(tables_arrangement_id) arrangement = TablesArrangement.find_or_create_by!(id: tables_arrangement_id)
self.tables_arrangement_id = arrangement.id self.tables_arrangement_id = arrangement.id

View File

@ -5,7 +5,6 @@
module VNS module VNS
class Engine class Engine
PERTURBATION_SIZES = [1, 1, 1, 2, 2, 3].freeze PERTURBATION_SIZES = [1, 1, 1, 2, 2, 3].freeze
ITERATIONS = 50
class << self class << self
def sequence(elements) def sequence(elements)
elements = elements.to_a elements = elements.to_a
@ -27,14 +26,6 @@ module VNS
@perturbations << klass @perturbations << klass
end end
def notify_progress(&block)
@progress_notifier = block
end
def on_better_solution(&block)
@better_solution_notifier = block
end
attr_writer :initial_solution attr_writer :initial_solution
def run def run
@ -49,24 +40,18 @@ module VNS
run_all_optimizations run_all_optimizations
@progress_notifier&.call(Rational(1, ITERATIONS + 1))
best_solution = @current_solution best_solution = @current_solution
(1..ITERATIONS).each do |iteration| 50.times do
@current_solution = Tables::WheelSwap.new(best_solution).call(PERTURBATION_SIZES.sample) @current_solution = Tables::WheelSwap.new(best_solution).call(PERTURBATION_SIZES.sample)
@best_score = @target_function.call(@current_solution) @best_score = @target_function.call(@current_solution)
Rails.logger.debug { "After perturbation: #{@best_score}" } Rails.logger.debug { "After perturbation: #{@best_score}" }
run_all_optimizations run_all_optimizations
@progress_notifier&.call(Rational(iteration + 1, ITERATIONS + 1))
next unless best_solution.discomfort > @current_solution.discomfort next unless best_solution.discomfort > @current_solution.discomfort
best_solution = @current_solution best_solution = @current_solution
@better_solution_notifier&.call(best_solution)
Rails.logger.debug do Rails.logger.debug do
"Found better solution after perturbation optimization: #{@current_solution.discomfort}" "Found better solution after perturbation optimization: #{@current_solution.discomfort}"
end end

View File

@ -3,5 +3,4 @@
require_relative "../config/environment" require_relative "../config/environment"
require "solid_queue/cli" require "solid_queue/cli"
SolidQueue.logger = ActiveSupport::Logger.new($stdout)
SolidQueue::Cli.start(ARGV) SolidQueue::Cli.start(ARGV)

View File

@ -1,5 +0,0 @@
class AddProgressToTablesArrangements < ActiveRecord::Migration[8.0]
def change
add_column :tables_arrangements, :progress, :float, default: 0, null: false
end
end

3
db/schema.rb generated
View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do ActiveRecord::Schema[8.0].define(version: 2025_08_01_102437) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -217,7 +217,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do
t.uuid "wedding_id", null: false t.uuid "wedding_id", null: false
t.uuid "digest", default: -> { "gen_random_uuid()" }, null: false t.uuid "digest", default: -> { "gen_random_uuid()" }, null: false
t.string "status", default: "complete", null: false t.string "status", default: "complete", null: false
t.float "progress", default: 0.0, null: false
t.index ["wedding_id"], name: "index_tables_arrangements_on_wedding_id" t.index ["wedding_id"], name: "index_tables_arrangements_on_wedding_id"
end end

View File

@ -86,9 +86,7 @@ ActsAsTenant.with_tenant(wedding) do
# TODO: Clean up invitations with no guests # TODO: Clean up invitations with no guests
3.times { TablesArrangement.create! } ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new(wedding.id) })
.map { |arrangement| TableSimulatorJob.new(wedding.id, arrangement.id) }
.then { |jobs| ActiveJob.perform_all_later }
"red".dup.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } "red".dup.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) }

View File

@ -21,14 +21,6 @@ namespace :vns do
engine.target_function(&:discomfort) engine.target_function(&:discomfort)
engine.notify_progress do |current_progress|
Rails.logger.info "Progress: #{(current_progress * 100.0).round(2)}%"
end
engine.on_better_solution do |better_solution|
Rails.logger.info "New best solution found with discomfort: #{better_solution.discomfort}"
end
solution = Rails.benchmark('VNS Benchmarking') { engine.run } solution = Rails.benchmark('VNS Benchmarking') { engine.run }
Rails.logger.info "Best solution found with discomfort: #{solution.discomfort}" Rails.logger.info "Best solution found with discomfort: #{solution.discomfort}"

View File

@ -12,11 +12,6 @@ server {
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
} }
location /jobs/ {
proxy_pass http://backend:3000/jobs/;
proxy_set_header Host $http_host;
}
location /captcha/v2/media/ { location /captcha/v2/media/ {
proxy_pass http://libre-captcha:8888/v2/media/; proxy_pass http://libre-captcha:8888/v2/media/;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;