Created
May 13, 2023 15:43
-
-
Save kirylrb/44673d7afd05a81899a294310a196b69 to your computer and use it in GitHub Desktop.
Laboratory experiment with samples and reagents
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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