Source: commands/pipetter/sourceMethods.js

/**
 * Methods for assigning which source well to use when a source liquid is
 * available in multiple wells.
 *
 * Details:
 * Assign source well by group for items without assigned source wells; if multiple syringes need to access the same source, and that source has multiple wells, then possible methods include:
 *
 * - pick first one
 * - rotate through source wells in order
 * - rotate through source wells in order of max volume
 * - try a simple geometrical assignment considering whether there are more tips or wells; if that fails, use previous method
 * - same as above, but if wells > tips, try starting at first (wells - tips) wells and see which one produces the greatest minimum final volume
 *
 * @module
 */

var _ = require('lodash');
var assert = require('assert');
var math = require('mathjs');
import commandHelper from '../../commandHelper.js';
var expect = require('../../expect.js');
var pipetterUtils = require('./pipetterUtils.js');
var WellContents = require('../../WellContents.js');

/**
 * Pick the first well in a source set and ignore the others.
 *
 * The 'sourceWell' property of each item in 'group' will be set.
 *
 * @static
 * @param  {array} group   Array of pipetting items that are grouped together
 * @param  {object} data    Data passed to the commandHandler
 */
function sourceMethod1(group, data) {
	_.forEach(group, function (item) {
		var source = item.source;
		var sourceInfo = sourceParser.parse(item.source);
		if (sourceInfo.source) {
			var wells = expect.objectsValue({}, source+".wells", data.objects);
			assert(!_.isEmpty(wells));
			item.sourceWell = wells[0];
		}
		else {
			item.sourceWell = source;
		}
	});
}

// Rotate through source wells in order
/*function sourceMethod2(group, data) {
	var sourceToWellIndex = {};
	_.forEach(group, function (item) {
		var source = item.source;
		var sourceInfo = sourceParser.parse(item.source);
		if (sourceInfo.source) {
			var wells = getObjectsValue(source+".wells", data.objects);
			assert(!_.isEmpty(wells));
			var i = (sourceToWellIndex.hasOwnProperty(source)) ? sourceToWellIndex[source] : 0;
			item.sourceWell = wells[i];
			sourceToWellIndex[source] = (i + 1) % wells.length;
		}
		else {
			item.sourceWell = source;
		}
	});
}*/

/**
 * Rotate through source wells in order of max volume.
 * The 'sourceWell' property of each item in 'group' will be set.
 *
 * @static
 * @param  {array} group   Array of pipetting items that are grouped together
 * @param  {object} data    Data passed to the commandHandler
 * @param  {object} effects (Optional) Map from variable to effects
 */
function sourceMethod3(group, data, effects) {
	// Make our own copy of the the effects object
	var effects = (effects) ? _.cloneDeep(effects) : {};

	// Consider each source in the group separately
	var sourceToItems = _.groupBy(_.filter(group, x => x.source), 'source');
	//console.log("sourceToItems:\n"+JSON.stringify(sourceToItems, null, '  '));
	for (const items of _.values(sourceToItems)) {
		// console.log("sourceMethod3", items)
		assert(items[0].source);
		var wells = _.clone(items[0].source);
		assert(!_.isEmpty(wells));
		for (const item of items) {
			//console.log("wells: ", wells);
			if (_.isArray(wells)) {
				if (wells.length === 1) {
					item.source = wells[0];
				}
				else {
					var wellAndVolumes = _.map(wells, function(wellName) {
						var volume = WellContents.getWellVolume(wellName, data, effects);
						return {wellName, volume: volume.toNumber('ul')};
					});
					// Sort by volume
					//console.log({wellAndVolumes})
					math.sort(wellAndVolumes, function(a, b) { return -math.compare(a.volume, b.volume)});
					// Pick well with greatest volume
					var wellName = wellAndVolumes[0].wellName;
					item.source = wellName;
					//console.log("well chosen:", wellName);
					// Move the chosen well to the back of the array
					_.pull(wells, wellName);
					wells.push(wellName);

					const params = {items: [_.clone(item)]};
					params.items[0].volume = item.volume.format({precision: 14});
					const schema = {
						properties: {
							items: {
								description: "Data about what should be pipetted where", "type": "array",
								items: {type: "pipetter._PipetteItem"}
							}
						},
						required: ["items"]
					};
					//console.log("param of items:")
					//console.log(JSON.stringify(params, null, '\t'))
					const parsed = commandHelper.parseParams(params, data, schema);
					//console.log("parsed:");
					//console.log(JSON.stringify(parsed, null, '\t'))

					// Get effect of pipetting, so that source volumes are changed appropriately
					var effects2 = pipetterUtils.getEffects_pipette(parsed, data, effects);
					_.merge(effects, effects2);
					//console.log("effects2", effects2)
					//console.log("effects", effects)
				}
			}
			else {
				//item.source = item.source;
			}
		}
	}
}

module.exports = {
	sourceMethod1,
	//sourceMethod2: sourceMethod2,
	sourceMethod3
}