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 }
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 }