Skip to content

Instantly share code, notes, and snippets.

@kirylrb
Created May 13, 2023 15:43
Show Gist options
  • Save kirylrb/44673d7afd05a81899a294310a196b69 to your computer and use it in GitHub Desktop.
Save kirylrb/44673d7afd05a81899a294310a196b69 to your computer and use it in GitHub Desktop.
Laboratory experiment with samples and reagents
# The Experiment class represents a laboratory experiment involving samples and reagents.
# It provides methods to assign samples and reagents to wells on a plate.
# Each well can contain one sample-reagent combination.
# Plates come in two sizes: 96 wells or 384 wells, arranged in a rectangular grid.
# Each experiment can have multiple replicates, where each replicate is a set of sample-reagent combinations.
# The class aims to minimize the number of plates used by efficiently arranging the samples and reagents.
# frozen_string_literal: true
require 'matrix'
class Experiment
attr_reader :plate_size, :samples_names, :reagents_names, :replicates_number
def initialize(params)
@plate_size = params[:plate_size]
@samples_names = params[:samples_names]
@reagents_names = params[:reagents_names]
@replicates_number = params[:replicates_number]
end
def assign_samples_to_plate_wells
fill_plate_wells(empty_plate_wells)
end
def display_plates
assign_samples_to_plate_wells.each_with_index do |plate, index|
puts "Plate #{index + 1}:"
plate.each do |row|
puts row.map { |well| well.nil? ? '[ ]' : "[#{well.join(', ')}]" }.join(' ')
end
puts "\n"
end
end
private
def fill_plate_wells(plate_wells)
pos = { row: 0, col: 0, plate_index: 0 }
@samples_names.each_with_index do |samples, experiment_index|
fill_wells_for_experiment(samples, @reagents_names[experiment_index], @replicates_number[experiment_index],
plate_wells, pos)
end
plate_wells.map(&:to_a)
end
def empty_plate_wells
Array.new(total_plates_needed) { build_empty_plate }
end
def total_plates_needed
@total_plates_needed ||= (total_replicates * total_samples * total_reagents / plate_size.to_f).ceil
end
def total_replicates
@replicates_number.sum
end
def total_samples
@samples_names.flatten.count
end
def total_reagents
@reagents_names.flatten.count
end
def build_empty_plate
Matrix.build(plate_dimensions[:rows], plate_dimensions[:cols]) { nil }
end
def plate_dimensions
if @plate_size == 96
{ rows: 8, cols: 12 }
elsif @plate_size == 384
{ rows: 16, cols: 24 }
else
raise ArgumentError, 'Invalid plate size. Please enter either 96 or 384.'
end
end
def fill_wells_for_experiment(samples, reagents, replicates, plate_wells, pos)
experiment_combinations = samples.product(reagents).cycle.first(replicates)
experiment_combinations.each do |sample, reagent|
fill_well(sample, reagent, plate_wells, pos)
end
end
def fill_well(sample, reagent, plate_wells, pos)
plate_wells[pos[:plate_index]][pos[:row], pos[:col]] = [sample, reagent]
increment_position(pos)
end
def increment_position(pos)
pos[:col] += 1
if pos[:col] >= plate_dimensions[:cols]
pos[:col] = 0
pos[:row] += 1
end
return unless pos[:row] >= plate_dimensions[:rows]
pos[:row] = 0
pos[:plate_index] += 1
end
end
experiment = Experiment.new(
plate_size: 384,
samples_names: [['Sample 1', 'Sam 2', 'Sam 3'], ['Sam 1', 'Sam 3', 'Sam 4']],
reagents_names: [['Reagent X', 'Reag Y'], ['Reag Y', 'Reag Z']],
replicates_number: [1, 3]
)
experiment.display_plates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment