func choose_stock_concentrations(minrequired map[string]float64, maxrequired map[string]float64, Smax map[string]float64, vmin float64, T map[string]float64) map[string]float64 {
	// we want to find the minimum concentrations
	// which fulfill the constraints

	// the optimization is set up as follows:
	//
	// 	let:
	//		Ck 	= 	concentration of component k		// we do not optimize this directly
	//		Gmk	=	min required concentration of k
	//		GMk	= 	max required concentration of k
	//		Tk	=	Minimum total final volume for k
	//		vmin	=	minimum channel capacity
	//		Skmax	=	Max solubility of component k
	//		Xk	=	GMk / Ck
	//
	//	Maximise	sum of -Xk
	//
	//	Subject to
	//			-Xk	<= -vmin GMk / Gmk Tk		(for each k)	-- min volume constraint
	//			-Xk	<= -GMk / Skmax			(for each k)	-- max conc constraint, this is set as a column constraint
	//			Sum Xk	<= 1
	//

	nc := len(minrequired)

	// no concentrations -> end here

	if nc == 0 {
		return (make(map[string]float64, 1))
	}

	// need to do these things in a consistent order
	names := make([]string, nc)

	cur := 0
	for name, _ := range minrequired {
		names[cur] = name
		cur += 1
	}

	lp := glpk.New()
	defer lp.Delete()

	lp.SetProbName("Concentrations")
	lp.SetObjName("Z")
	lp.SetObjDir(glpk.MAX)

	// sets up number of constraints and B vector
	lp.AddRows(2*nc + 1)

	cur = 1

	for _, name := range names {
		lp.SetRowBnds(cur, glpk.UP, -999999.0, (-1.0*vmin*maxrequired[name])/(T[name]*minrequired[name]))
		cur += 1
	}

	for _, name := range names {
		lp.SetRowBnds(cur, glpk.UP, -999999.0, (-1.0 * maxrequired[name] / Smax[name]))
		cur += 1
	}

	lp.SetRowBnds(cur, glpk.UP, 0.0, 1.0)

	// sets up objective and constraint coefficients

	lp.AddCols(nc)

	cur = 1
	for _, name := range names {
		name = name
		lp.SetObjCoef(cur, -1.0)
		lp.SetColName(cur, fmt.Sprintf("X%d", cur))
		lp.SetColBnds(cur, glpk.LO, 0.0, 0.0)
		cur += 1
	}

	cur = 1

	// constraint coeffs

	ind := wutil.Series(0, nc)

	for j := 0; j < 2; j++ {
		for i := 0; i < nc; i++ {
			row := make([]float64, nc+1)
			row[i+1] = -1.0
			lp.SetMatRow(cur, ind, row)
			cur += 1
		}
	}
	// now the sum constraint
	row := make([]float64, nc+1)
	for i := 0; i < nc; i++ {
		row[i+1] = 1.0
	}
	lp.SetMatRow(cur, ind, row)

	// solve it

	prm := glpk.NewSmcp()
	prm.SetMsgLev(0)
	lp.Simplex(prm)

	// now look at the solution

	concentrations := make(map[string]float64, nc)

	stat := lp.Status()

	if stat != glpk.OPT {
		// some problem
		return concentrations
	}

	cur = 1
	for _, name := range names {
		concentrations[name] = maxrequired[name] / lp.ColPrim(cur)
		cur += 1
	}

	//fmt.Println()

	return concentrations
}
Exemple #2
0
func choose_plate_assignments(component_volumes map[string]wunit.Volume, plate_types []*wtype.LHPlate, weight_constraint map[string]float64) map[string]map[*wtype.LHPlate]int {

	//
	//	optimization is set up as follows:
	//
	//		let:
	//			Xk 	= 	Number of wells of type Y containing component Z (k = 1...YZ)
	//			Vy	= 	Working volume of well Y
	//			RVy	= 	Residual volume of well Y
	//			TVz	= 	Total volume of component Z required
	//			WRy	=	Rate of wells of type y in their plate
	//			PMax	=	Maximum number of plates
	//			WMax	= 	Maximum number of wells
	//
	//	Minimise:
	//			sum of Xk Wrv RVy
	//
	//	Subject to:
	//			sum of Xk Vy 	>= TVz	for each component Z
	//			sum of WRy Xk 	<= PMax
	//			sum of Xk	<= WMax
	//

	// setup

	lp := glpk.New()
	defer lp.Delete()

	lp.SetProbName("Assignments")
	lp.SetObjName("Z")

	// CHECK THIS

	lp.SetObjDir(glpk.MAX)

	// constraints:
	// 		total component volume
	//		number of plates
	//		number of wells
	n_rows := len(component_volumes) + 2

	lp.AddRows(n_rows)

	cur := 1

	component_order := make([]string, len(component_volumes))

	// volume constraints
	for cmp, vol := range component_volumes {
		component_order[cur-1] = cmp
		lp.SetRowBnds(cur, glpk.LO, vol.ConvertTo(wunit.ParsePrefixedUnit("ul")), 99999.0)
		cur += 1
	}

	// from now on we always have to use component_order

	// plate number constraints

	max_n_plates := weight_constraint["MAX_N_PLATES"] - 1.0
	lp.SetRowBnds(cur, glpk.UP, -99999.0, max_n_plates)
	cur += 1

	// well number constraints
	max_n_wells := weight_constraint["MAX_N_WELLS"]
	lp.SetRowBnds(cur, glpk.UP, -99999.0, max_n_wells)
	cur += 1

	// set up the matrix columns

	num_cols := len(component_order) * len(plate_types)
	lp.AddCols(num_cols)
	cur = 1

	for _, component := range component_order {
		for _, plate := range plate_types {
			// set up objective coefficient, column name and lower bound
			rv := plate.Welltype.ResidualVolume()
			coef := rv.ConvertTo(wunit.ParsePrefixedUnit("ul")) * weight_constraint["RESIDUAL_VOLUME_WEIGHT"]
			lp.SetObjCoef(cur, coef)
			lp.SetColName(cur, component+"_"+plate.PlateName)
			lp.SetColBnds(cur, glpk.LO, 0.0, 0.0)
			lp.SetColKind(cur, glpk.IV)
			cur += 1
		}
	}

	// now set up the constraint coefficients
	cur = 1

	ind := wutil.Series(0, num_cols)

	for c, _ := range component_order {
		row := make([]float64, num_cols+1)
		col := 0
		for i := 0; i < len(component_order); i++ {
			for j := 0; j < len(plate_types); j++ {
				vc := 0.0
				// pick out a set of columns according to which row we're on
				// volume constraints are the working volumes of the wells
				if c == i {
					vol := wunit.NewVolume(plate_types[j].Welltype.Vol, plate_types[j].Welltype.Vunit)
					rvol := wunit.NewVolume(plate_types[j].Welltype.Rvol, plate_types[j].Welltype.Vunit)
					vol.Subtract(&rvol)
					vc = vol.ConvertTo(wunit.ParsePrefixedUnit("ul"))
				}
				row[col+1] = vc
				col += 1
			}
		}
		lp.SetMatRow(cur, ind, row)
		cur += 1
	}

	// now the plate constraint

	row := make([]float64, num_cols+1)
	col := 1
	for i := 0; i < len(component_order); i++ {
		for j := 0; j < len(plate_types); j++ {
			// the coefficient here is 1/the number of this well type per plate
			r := 1.0 / float64(plate_types[j].Nwells)
			row[col] = r
			col += 1
		}
	}

	lp.SetMatRow(cur, ind, row)
	cur += 1

	// finally the well constraint

	row = make([]float64, num_cols+1)
	col = 1
	for i := 0; i < len(component_order); i++ {
		for j := 0; j < len(plate_types); j++ {
			// the number of wells is constrained so we just count
			row[col] = 1.0
			col += 1
		}
	}

	lp.SetMatRow(cur, ind, row)

	iocp := glpk.NewIocp()
	iocp.SetPresolve(true)
	iocp.SetMsgLev(0)
	lp.Intopt(iocp)

	// check constraints

	/*
		for i := 1; i <= n_rows; i++ {
			fmt.Println("ROW : ", i, " VAL : ", lp.MipRowVal(i))
		}
	*/
	// fill assignments - this is the number of wells in the plate of each type needed

	assignments := make(map[string]map[*wtype.LHPlate]int, len(component_volumes))

	cur = 1

	for i := 0; i < len(component_order); i++ {
		cmap := make(map[*wtype.LHPlate]int)
		for j := 0; j < len(plate_types); j++ {
			nwells := lp.MipColVal(cur)

			if nwells > 0 {
				//fmt.Println(component_order[i], " : ", plate_types[j].Type, " N WELLS: ", nwells)
				cmap[plate_types[j]] = int(nwells)
			}
			cur += 1
		}
		assignments[component_order[i]] = cmap
	}

	return assignments
}