Changeset 2027


Ignore:
Timestamp:
Sep 30, 2013, 9:36:22 AM (8 years ago)
Author:
Nicklas Nordborg
Message:

References #503: Change pooling strategy

The new implementation works like this:

  1. Use the target volume and target molarity to calculate the amount of each library to take. The resulting value is rounded to one decimal. The result may be higher that the target volume if the concentration is low.
  2. Libraries with high concentration are special cases and are always mixed to 2nM in double the target volume.
  3. Sum up everything and spread out the remaining EB volume equally among all libs. Rounding issues are considered and added to the last lib which may get a higher EB volume.
  4. The selected target volume is stored as an annotation on the pool since the value is needed again when generating the lab protocols.


There is currently not enough checking for extreme cases, eg. if there are many libraries with low concentration the final EB volume may be negative.

NOTE! Since the new implementation is very different from the old, case is needed when installing this update. Pools that have been created with the old Reggie wizard should be completed in the lab and registered before installing this update. Otherwise the lab protocols may show incorrect mixing values.

The incorrect calculations for 'dil' (see #507) items have been fixed.

Location:
extensions/net.sf.basedb.reggie/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • extensions/net.sf.basedb.reggie/trunk/resources/libprep/create_pools.jsp

    r2025 r2027  
    328328  targetVolumePerLibIsValid = true;
    329329
     330  // Pre-process the Library items
     331  var wells = Plate.getWells();
     332  for (var i = 0; i < wells.length; i++)
     333  {
     334    var lib = wells[i].extract;
     335    if (lib)
     336    {
     337      PoolMix.calculateLibVolume(lib, targetVolumePerLib, TARGET_MOLARITY_IN_POOL);
     338    }
     339  }
     340 
     341  updatePoolData();
     342  Plate.paint(Plate.getWells());
     343}
     344
     345function calculateRemarks(lib, schema, barcodeVariant)
     346{
     347  var well = lib.bioWell;
     348  var remarks = [];
     349
     350  // Check if default barcode has been modified
     351  var indexSet = barcodeVariant.indexSets[well.column];
     352  if (indexSet)
     353  {
     354    var defaultBarcode = indexSet.barcodes[well.row];
     355    if (defaultBarcode && lib.barcode.name != defaultBarcode)
     356    {
     357      remarks[remarks.length] = 'Modified barcode';
     358      lib.barcode.modified = true;
     359    }
     360  }
     361 
     362  if (lib.molarity)
     363  {
     364    if (lib.molarity < TARGET_MOLARITY_IN_POOL)
     365    {
     366      remarks[remarks.length] = 'Low molarity';
     367    }
     368
     369    if (lib.speedVacConc != null)
     370    {
     371      remarks[remarks.length] = 'SpeedVac';
     372    }
     373   
     374    if (lib.mixFactor > 1.001)
     375    {
     376      // Larger mix than default
     377      var mixedVolume = (lib.volume+lib.eb)*(lib.mixFactor);
     378      remarks[remarks.length] = 'Mix ' + Numbers.formatNumber(mixedVolume, 1) + 'µl';
     379    }
     380   
     381  }
     382  lib.stratagene = lib.name.indexOf(GENERIC_STRATAGENE_NAME) == 0;
     383  lib.external = lib.name.indexOf(EXTERNAL_RNA_NAME) == 0;
     384  lib.remarks = remarks;
     385}
     386
     387
     388function updatePoolData()
     389{
     390  var frm = document.forms['reggie'];
     391  targetVolumePerLib = parseFloat(frm.target_volume.value);
     392
    330393  var libPlate = frm.bioplate[frm.bioplate.selectedIndex].bioplate;
    331394  var schema = PoolSchema.getById(libPlate.poolSchema);
    332395  var barcodeVariant = PoolSchema.getBarcodeVariantByName(schema, libPlate.barcodeVariant);
    333396 
    334   // Pre-process the Library items
    335   var wells = Plate.getWells();
    336   for (var i = 0; i < wells.length; i++)
    337   {
    338     var lib = wells[i].extract;
    339     if (lib)
    340     {
    341       calculateMixingVolumes(lib, targetVolumePerLib, schema, barcodeVariant);
    342     }
    343   }
    344  
    345   Plate.paint(Plate.getWells());
    346   updatePoolData();
    347 }
    348 
    349 function calculateMixingVolumes(lib, targetVolumePerLib, schema, barcodeVariant)
    350 {
    351   var well = lib.bioWell;
    352   var remarks = [];
    353 
    354   // Check if default barcode has been modified
    355   var indexSet = barcodeVariant.indexSets[well.column];
    356   if (indexSet)
    357   {
    358     var defaultBarcode = indexSet.barcodes[well.row];
    359     if (defaultBarcode && lib.barcode.name != defaultBarcode)
    360     {
    361       remarks[remarks.length] = 'Modified barcode';
    362       lib.barcode.modified = true;
    363     }
    364   }
    365  
    366   if (lib.molarity)
    367   {
    368     if (lib.molarity < 1.0)
    369     {
    370       remarks[remarks.length] = 'Low molarity';
    371     }
    372 
    373     if (lib.speedVacConc != null)
    374     {
    375       remarks[remarks.length] = 'SpeedVac';
    376     }
    377    
    378     // Calculate mix volumes, but do not correct for negative volumes
    379     // due to low concentration (this will be notified in the protocols)
    380     // Round to 1 decimal since that is the expected accuracy of pipettes
    381     // Remember to use the rounded values in the calculations
    382     var usedVolumeForMix = Math.round(10 * targetVolumePerLib * TARGET_MOLARITY_IN_POOL / lib.molarity) / 10;
    383     var mixFactor = 1.0;
    384    
    385     if (usedVolumeForMix < LIMIT_FOR_EXTRA_LARGE_MIX)
    386     {
    387       // Extra large mix is required if the usedVolume is small due to
    388       // difficulties to pipette the exact amount for small volumes
    389       mixFactor = EXTRA_LARGE_MIX_FACTOR;
    390       // Re-calculate volume to get the rounding correct
    391       usedVolumeForMix = Math.round(10 * mixFactor * targetVolumePerLib * TARGET_MOLARITY_IN_POOL / lib.molarity) / 10 / mixFactor;
    392     }
    393    
    394     lib.volume = usedVolumeForMix;
    395     lib.eb = targetVolumePerLib - usedVolumeForMix;
    396     lib.mixMolarity = lib.molarity * usedVolumeForMix / targetVolumePerLib;
    397     lib.mixFactor = mixFactor;
    398    
    399     if (lib.volume > targetVolumePerLib + 0.001)  // +0.001 to allow for rounding errors
    400     {
    401       remarks[remarks.length] = 'Use ' + targetVolumePerLib +' µl';
    402     }
    403 
    404     if (lib.mixFactor > 1.001)
    405     {
    406       // Larger mix than default
    407       var mixedVolume = (lib.volume+lib.eb)*(lib.mixFactor);
    408       remarks[remarks.length] = 'Mix ' + Numbers.formatNumber(mixedVolume, 1) + 'µl';
    409     }
    410    
    411   }
    412   lib.stratagene = lib.name.indexOf(GENERIC_STRATAGENE_NAME) == 0;
    413   lib.external = lib.name.indexOf(EXTERNAL_RNA_NAME) == 0;
    414   lib.remarks = remarks;
    415 }
    416 
    417 
    418 function updatePoolData()
    419 {
    420   var frm = document.forms['reggie'];
    421   targetVolumePerLib = parseFloat(frm.target_volume.value);
    422  
    423397  for (var poolNo = 0; poolNo < Plate.getPools(); poolNo++)
    424398  {
     399    // Get all libs into an array
     400    var libs = [];
    425401    var wells = Plate.getPool(poolNo);
    426     var poolMolarity = 0;
    427     var poolVolume = 0;
    428     var numLibs = 0;
    429402    for (var wellNo = 0; wellNo < wells.length; wellNo++)
    430403    {
     
    433406      if (lib && !lib.excludeFromPool)
    434407      {
    435         poolMolarity += Math.min(lib.molarity, lib.mixMolarity);
    436         poolVolume += targetVolumePerLib;
    437         numLibs++;
     408        libs[libs.length] = lib;
    438409      }
    439410    }
    440411   
     412    // Calculate EB
     413    PoolMix.calculateEbForLibs(libs, TARGET_MOLARITY_IN_POOL, targetVolumePerLib);
     414   
     415    // Calculate final pool volumes, molarity, etc.
     416    var poolInfo = PoolMix.calculateFinalPoolInfo(libs);
     417   
     418    // Make remarks for each lib
     419    for (var libNo = 0; libNo < libs.length; libNo++)
     420    {
     421      var lib = libs[libNo];
     422      calculateRemarks(lib, schema, barcodeVariant);
     423    }
     424   
    441425    var poolData = '<div class="pool-data">';
    442     poolData += numLibs + ' libs; ';
    443     poolData += Numbers.formatNumber(poolMolarity/numLibs, 2)+'nM; ';
    444     poolData += Numbers.formatNumber(poolVolume, 0) + 'µl';
     426    poolData += libs.length + ' libs; ';
     427    poolData += Numbers.formatNumber(poolInfo.molarity, 2)+'nM; ';
     428    poolData += Numbers.formatNumber(poolInfo.volume+poolInfo.eb, 1) + 'µl';
     429    //poolData += '; libVolume:'+poolInfo.volume+'; libAmount:'+poolInfo.amount + '; ebVolume:'+poolInfo.eb;
    445430    poolData += '</div>';
    446431    document.getElementById('pool.'+poolNo).innerHTML = POOL_NAMES[poolNo] + poolData;
     
    476461      cls += ' flagged';
    477462    }
    478     if (lib && lib.molarity < 1.0)
     463    if (lib && lib.molarity < TARGET_MOLARITY_IN_POOL)
    479464    {
    480465      cls += ' low-molarity';
     
    564549  }
    565550 
    566   Plate.paint(wells);
    567551  updatePoolData();
     552  Plate.paint(Plate.getWells());
    568553}
    569554
     
    592577  }
    593578 
    594   Plate.paint(wells);
    595579  updatePoolData();
     580  Plate.paint(Plate.getWells());
    596581}
    597582
     
    847832            value="5" style="width: 4em;"
    848833            onblur="targetVolumeOnBlur()"
    849             onkeypress="return Numbers.numberOnly(event)"> µl
     834            onkeypress="doOnEnter(event, targetVolumeOnBlur); return Numbers.numberOnly(event)"> µl
    850835          <span id="target_volume"></span>
    851836        </td>
  • extensions/net.sf.basedb.reggie/trunk/resources/libprep/pool_protocol2.jsp

    r2025 r2027  
    7272 
    7373  <script language="JavaScript">
    74   var debug = false;
     74  var debug = 1;
    7575  var currentStep = 1;
    7676 
    7777  // Loaded from servlet when getting Library information
    78   var TARGET_MOLARITY_IN_POOL;
    79   var TARGET_VOLUME_IN_POOL_PER_LIB; 
    8078  var POOL_SCHEMA;
    8179  var POOL_BARCODE_VARIANT;
     
    144142    var pools = response.pools;
    145143    var poolInfo = response.poolInfo;
    146     TARGET_MOLARITY_IN_POOL = poolInfo.targetMolarity;
    147     TARGET_VOLUME_IN_POOL_PER_LIB = poolInfo.targetVolumePerLib;
    148144
    149145    var pageTitle = 'Lab protocol for pooling ';
     
    154150      if (i > 0) pageTitle += ','
    155151      pageTitle += ' ' + pool.name;
     152     
     153      // Calculate some pool quantities
     154      pool.volume = 1000 * pool.originalQuantity / pool.conc;
     155     
     156      PoolMix.calculateEbForLibs(pool.libraries, pool.molarity, pool.targetVolumePerLib);
     157     
    156158      for (var j = 0; j < pool.libraries.length; j++)
    157159      {
    158         checkAndPreProcessLibrary(pool.libraries[j], POOL_SCHEMA, POOL_BARCODE_VARIANT);       
    159       }
     160        checkAndPreProcessLibrary(pool.libraries[j], pool, POOL_SCHEMA, POOL_BARCODE_VARIANT);       
     161      }
     162     
    160163    }
    161164   
     
    175178    {
    176179      %>
    177       viewAsPlate(pools, POOL_SCHEMA, POOL_BARCODE_VARIANT)
     180      viewAsPlate(pools, POOL_SCHEMA, POOL_BARCODE_VARIANT);
    178181      <%
    179182    }
     
    181184  }
    182185 
    183   function checkAndPreProcessLibrary(lib, schema, barcodeVariant)
     186  function checkAndPreProcessLibrary(lib, pool, schema, barcodeVariant)
    184187  {
    185188    var well = lib.bioWell;
     
    203206    if (lib.molarity)
    204207    {
     208      if (lib.molarity < pool.molarity)
     209      {
     210        remarks[remarks.length] = 'Low molarity';
     211      }
     212     
    205213      if (lib.speedVacConc != null)
    206214      {
    207215        remarks[remarks.length] = 'SpeedVac';
    208       }
    209       if (lib.volume > TARGET_VOLUME_IN_POOL_PER_LIB + 0.001)
    210       {
    211         remarks[remarks.length] = 'Use 5 µl';
    212216      }
    213217 
     
    216220        // Larger mix than default
    217221        var mixedVolume = (lib.volume+lib.eb)*(lib.mixFactor);
    218         remarks[remarks.length] = 'Mix ' + Numbers.formatNumber(mixedVolume, 1) + 'µl - use ' + Numbers.formatNumber(lib.volume+lib.eb, 1) + 'µl in pool';
     222        remarks[remarks.length] = 'Mix ' + Numbers.formatNumber(mixedVolume, 1) + 'µl; Use ' + Numbers.formatNumber(lib.volume+lib.eb, 1) + 'µl in pool';
    219223      }
    220224
     
    291295        tbody.appendChild(tr);
    292296      }
    293       var poolVol = 1000*pool.originalQuantity / pool.conc;
    294       var volPerLib = poolVol / pool.libraries.length;
    295       setInnerHTML('molarity.'+pool.id, Numbers.formatNumber(pool.molarity, 2) + ' nM; ' + Numbers.formatNumber(poolVol, 0)+' µl');
    296       setInnerHTML('volPerLib.'+pool.id, Numbers.formatNumber(volPerLib, 0) + '/' + Numbers.formatNumber(volPerLib*2, 0) + ' µl');
     297      setInnerHTML('molarity.'+pool.id, Numbers.formatNumber(pool.molarity, 2) + ' nM; ' + Numbers.formatNumber(pool.volume, 1)+' µl');
    297298      Main.show('pool.'+pool.id);
    298299    }
     
    328329    Main.show('plateview');
    329330   
    330    
    331331    for (var poolNo = 0; poolNo < pools.length; poolNo++)
    332332    {
    333333      var pool = pools[poolNo];
    334       //setInnerHTML('molarity.'+pool.id, Numbers.formatNumber(pool.molarity, 2) + ' nM; ' + Numbers.formatNumber(1000*pool.originalQuantity / pool.conc, 0)+' µl');
    335 
    336334      var poolData = '<div class="pool-data">';
    337335      poolData += Numbers.formatNumber(pool.molarity, 2)+'nM; ';
    338       poolData += Numbers.formatNumber(1000*pool.originalQuantity / pool.conc, 0) + 'µl';
     336      poolData += Numbers.formatNumber(pool.volume, 1) + 'µl';
    339337      poolData += '</div>';
    340338      document.getElementById('pool.'+poolNo).innerHTML = POOL_NAMES[poolNo] + poolData;
    341 
    342339    }
    343340  }
     
    644641          <th>DNA</th>
    645642          <th class="workplate">Work</th>
    646           <th colspan="2">2nM, <span id="volPerLib.<%=poolId%>"></span></th>
     643          <th>Lib</th>
     644          <th>EB</th>
    647645          <th></th>
    648646        </tr>
     
    653651          <th class="workplate">plate</th>
    654652          <th>(µl)</th>
    655           <th>EB</th>
     653          <th>(µl)</th>
    656654          <th>Remarks</th>
    657655        </tr>
  • extensions/net.sf.basedb.reggie/trunk/resources/libprep/pools.js

    r1865 r2027  
    251251
    252252
     253var PoolMix = function()
     254{
     255 
     256  var LIMIT_FOR_EXTRA_LARGE_MIX = 1.0;
     257  var AVAILABLE_VOLUME = 10.0;
     258  var AVAILABLE_VOLUME_AFTER_SPEEDVAC = 5.0;
     259 
     260  var pm = {};
     261 
     262  /**
     263    Round the value to the nearest value with
     264    the given number of decimals (default=1).
     265  */
     266  pm.round = function(value, numDecimals)
     267  {
     268    if (!numDecimals) numDecimals = 1;
     269    var pow = Math.pow(10, numDecimals);
     270    return Math.round(value * pow) / pow;
     271  }
     272 
     273  /**
     274    Round the value down to the nearest value with
     275    the given number of decimals (default=1).
     276  */
     277  pm.floor = function(value, numDecimals)
     278  {
     279    if (!numDecimals) numDecimals = 1;
     280    var pow = Math.pow(10, numDecimals);
     281    return Math.floor(value * pow) / pow;
     282  }
     283 
     284  /**
     285    Round the value up to the nearest value with
     286    the given number of decimals (default=1).
     287  */
     288  pm.ceil = function(value, numDecimals)
     289  {
     290    if (!numDecimals) numDecimals = 1;
     291    var pow = Math.pow(10, numDecimals);
     292    return Math.ceil(value * pow) / pow;
     293  }
     294 
     295  /**
     296    Calculate the volume of a lib to take for a pool given the
     297    target molarity for the pool and average volume for each lib
     298    in the pool. The volume is affect by two limits:
     299   
     300    1. It may not be lower than LIMIT_FOR_EXTRA_LARGE_MIX. In this case
     301       a larger mix is made and only half is used.
     302       
     303    2. It may not be higher than AVAILABLE_VOLUME or AVAILABLE_VOLUME_AFTER_SPEEDVAC.
     304   
     305    The volume is rounded to one decimal in µl.
     306  */
     307  pm.calculateLibVolume = function(lib, targetMolarity, targetVolume)
     308  {
     309    var maxVolume = lib.speedVacConc != null ? AVAILABLE_VOLUME_AFTER_SPEEDVAC : AVAILABLE_VOLUME;
     310    var volume = pm.round(targetVolume * targetMolarity / lib.molarity);
     311    var mixFactor = 1;
     312    if (volume > maxVolume)
     313    {
     314      volume = maxVolume;
     315    }
     316    else if (volume < LIMIT_FOR_EXTRA_LARGE_MIX)
     317    {
     318      mixFactor = 2;
     319      // Re-calculate the volume to get the rounding correct (eg. to avoid dubling rounding errors)
     320      // round UP this time to avoid storing something that is less than 2nM
     321      volume = pm.ceil(mixFactor * targetVolume * targetMolarity / lib.molarity) / mixFactor;
     322    }
     323   
     324    lib.volume = volume;
     325    lib.mixFactor = mixFactor;
     326    return volume;
     327  }
     328 
     329  /**
     330    Calculate the amount of EB to mix with each of the libs. There are
     331    a few rules to follow:
     332   
     333    1. The total amount of EB needed to achive the target molarity
     334       in the pool is calculated.
     335       
     336    2. Libs with a mixFactor > 1 are always mixed to the given target
     337       molarity since the saved lib should be standardized
     338       
     339    3. The remaining EB volume is equally split among the rest of the
     340       libs. To avoid rounding errors for the pool as a total the
     341       volume is rounded down and then any remaining EB is added to
     342       the last lib.
     343  */
     344  pm.calculateEbForLibs = function(libs, targetMolarity, targetVolume)
     345  {
     346    // First, calculate total volume of all libs
     347    // and their contribution to the total amount of DNA in the pool
     348    var totalLibVolume = 0;
     349    var totalLibAmount = 0;
     350    var fixedEbVolume = 0;
     351    var fixedLibs = 0;
     352    for (var libNo = 0; libNo < libs.length; libNo++)
     353    {
     354      var lib = libs[libNo];
     355      totalLibVolume += lib.volume;
     356      totalLibAmount += lib.volume * lib.molarity;
     357     
     358      // Always mix to target molarity when doing extra large mix
     359      if (lib.mixFactor > 1)
     360      {
     361        var eb = targetVolume - lib.volume;
     362        fixedEbVolume += eb;
     363        lib.eb = eb;
     364        fixedLibs++;
     365      }
     366    }
     367   
     368    // Calculate the total volume needed to get to the
     369    // target molarity for the pooled libs
     370    var totalPoolVolume = totalLibAmount / targetMolarity;
     371    var totalEbVolume = totalPoolVolume - totalLibVolume - fixedEbVolume;
     372
     373    // Divide EB volume among the libs (rounded to 1 decimal)
     374    var ebPerLib = pm.floor(totalEbVolume / (libs.length - fixedLibs), 1);
     375    var usedEb = 0;
     376    var adjustableLib;
     377    for (var libNo = 0; libNo < libs.length; libNo++)
     378    {
     379      var lib = libs[libNo];
     380      if (!lib.mixFactor || lib.mixFactor == 1)
     381      {
     382        lib.eb = ebPerLib;
     383        usedEb += ebPerLib;
     384        adjustableLib = lib;
     385      }
     386      // Adjust the mixed molarity
     387      lib.mixMolarity = lib.molarity * lib.volume / (lib.volume+lib.eb);
     388    }
     389   
     390    // If the used EB doesn't add up to the total amount needed (due to rounding)
     391    // add the extra amount to the last lib
     392    if (Math.abs(totalEbVolume - usedEb) > 0.1 && adjustableLib)
     393    {
     394      adjustableLib.eb += totalEbVolume - usedEb;
     395      adjustableLib.mixMolarity = adjustableLib.molarity * adjustableLib.volume / (adjustableLib.volume+adjustableLib.eb);
     396    }
     397   
     398  }
     399 
     400  pm.calculateFinalPoolInfo = function(libs)
     401  {
     402    var info = {};
     403   
     404    var libVolume = 0;
     405    var libAmount = 0;
     406    var ebVolume = 0;
     407    for (var libNo = 0; libNo < libs.length; libNo++)
     408    {
     409      var lib = libs[libNo];
     410      libVolume += lib.volume;
     411      ebVolume += lib.eb;
     412      libAmount += lib.volume * lib.molarity;
     413    }
     414   
     415    info.volume = libVolume;
     416    info.amount = libAmount;
     417    info.eb = ebVolume;
     418    info.molarity = libAmount / (libVolume + ebVolume);
     419   
     420    return info;
     421  }
     422 
     423  return pm;
     424}();
  • extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/dao/Annotationtype.java

    r2023 r2027  
    529529
    530530  /**
     531    The "PoolTargetVolumePerLib" annotation, used for extracts (PooledLibrary).
     532    Store the admin-defined setting for target volume per library in a
     533    pool. The actual volume may be different due to limitations implied by
     534    library concentration and rounding.
     535    @since 2.13
     536  */
     537  public static final Annotationtype POOL_TARGET_VOLUME_PER_LIB =
     538    new Annotationtype("PoolTargetVolumePerLib", Type.FLOAT, Item.EXTRACT);
     539 
     540  /**
    531541    The "FlowCellID" annotation, used for physical bioassays (FlowCell).
    532542    @since 2.13
  • extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/servlet/InstallServlet.java

    r2023 r2027  
    262262        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_CONC, 1, null, effectiveOptions, createIfMissing));
    263263        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_MOLARITY, 1, null, effectiveOptions, createIfMissing));
     264        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_TARGET_VOLUME_PER_LIB, 1, null, effectiveOptions, createIfMissing));
    264265        jsonChecks.add(checkAnnotationType(dc, Annotationtype.OPERATOR, 1, null, effectiveOptions, createIfMissing));
    265266        jsonChecks.add(checkAnnotationType(dc, Annotationtype.DILUTION_DATE, 1, null, effectiveOptions, createIfMissing));
     
    349350
    350351        jsonChecks.add(checkAnnotationTypeCategory(dc, Subtype.POOLED_LIBRARY, createIfMissing,
    351             Annotationtype.POOL_MOLARITY, Annotationtype.POOL_CONC, Annotationtype.OPERATOR));
     352            Annotationtype.POOL_MOLARITY, Annotationtype.POOL_CONC, Annotationtype.POOL_TARGET_VOLUME_PER_LIB,
     353            Annotationtype.OPERATOR));
    352354
    353355        jsonChecks.add(checkAnnotationTypeCategory(dc, Subtype.FLOW_CELL, createIfMissing,
  • extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/servlet/PoolServlet.java

    r2025 r2027  
    294294          pool.loadAnnotations(dc, "molarity", Annotationtype.POOL_MOLARITY, null);
    295295          pool.loadAnnotations(dc, "conc", Annotationtype.POOL_CONC, null);
     296         
     297          Float targetVolumePerLib = (Float)Annotationtype.POOL_TARGET_VOLUME_PER_LIB.getAnnotationValue(dc, pool.getExtract());
     298          if (targetVolumePerLib == null) targetVolumePerLib = DEFAULT_TARGET_VOLUME_IN_POOL_PER_LIB;
     299          pool.setAnnotation("targetVolumePerLib", targetVolumePerLib);
    296300          pool.setAnnotation("originalQuantity", pool.getExtract().getOriginalQuantity());
    297301          jsonPools.add(pool.asJSONObject());
     
    303307        poolInfo.put("extraLargeMixFactor", EXTRA_LARGE_MIX_FACTOR);
    304308        poolInfo.put("limitForExtraLargeMix", LIMIT_FOR_EXTRA_LARGE_MIX);
    305         poolInfo.put("targetVolumePerLib", DEFAULT_TARGET_VOLUME_IN_POOL_PER_LIB);
    306309        json.put("poolInfo", poolInfo);
    307310      }
     
    366369          pool.setItemSubtype(pooledLibraryType);
    367370          pool.setName((String)jsonPool.get("name"));
     371          Annotationtype.POOL_TARGET_VOLUME_PER_LIB.setAnnotationValue(dc, pool, targetVolumeInPoolPerLib.floatValue());
    368372         
    369373          BioMaterialEvent poolEvent = pool.getCreationEvent();
    370374         
    371           float poolVolume = 0f;
    372           float poolQuantity = 0f;
    373           float summedPoolMolarity = 0f;
     375          // Total pool quantities
     376          float poolVolume = 0f;  // volume in pool (µl)
     377          float poolQuantity = 0f; // quantity of DNA in pool (µg)
     378          float poolAmount = 0f;  // Amount of DNA in pool (nano-mole)
    374379         
    375380          for (int libNo = 0; libNo < jsonLibs.size(); libNo++)
     
    382387            lib.setDescription((String)jsonLib.get("comment"));
    383388           
    384             // Calculate resulting molarity after mixing
    385             Float molarity = (Float)Annotationtype.CA_MOLARITY.getAnnotationValue(dc, lib);
    386             float usedVolumeForPool = ((Number)jsonLib.get("volume")).floatValue();
    387             float usedEbForPool = ((Number)jsonLib.get("eb")).floatValue();
    388            
    389             // Calculate resulting molarity given the used volumes
    390             float molarityForPool = TARGET_MOLARITY_IN_POOL;
    391             if (usedVolumeForPool > targetVolumeInPoolPerLib.floatValue())
    392             {
    393               // We can never use more than the target volume
    394               usedVolumeForPool = targetVolumeInPoolPerLib.floatValue();
    395               usedEbForPool = 0f;
    396               molarityForPool = molarity;
    397             }
    398             else
    399             {
    400               molarityForPool = molarity * (usedVolumeForPool / (usedVolumeForPool + usedEbForPool));
    401             }
    402            
    403             // Calculate used quantities, remember that concentration values are stored in ng/µl
     389            // Load molarity and concentration for lib
     390            Float libMolarity = (Float)Annotationtype.CA_MOLARITY.getAnnotationValue(dc, lib);
    404391            Float originalConc = (Float)Annotationtype.QUBIT_CONC.getAnnotationValue(dc, lib);
    405392            Float speedVacConc = (Float)Annotationtype.QUBIT_CONC_AFTER_SPEEDVAC.getAnnotationValue(dc, lib);
    406             Float usedConc = speedVacConc == null ? originalConc : speedVacConc;
    407             float usedQuantityForPool = Math.min(usedVolumeForPool * usedConc / 1000, lib.getRemainingQuantity());
     393            Float libConc = speedVacConc == null ? originalConc : speedVacConc;
     394
     395            // Get mixing information from the request
     396            float libVolume = ((Number)jsonLib.get("volume")).floatValue();
     397            float ebVolume = ((Number)jsonLib.get("eb")).floatValue();
     398            float mixFactor = ((Number)jsonLib.get("mixFactor")).floatValue();
    408399           
     400            // Calculate amount of DNA and resulting mix molarity
     401            float libAmount = libVolume * libMolarity;
     402            float libQuantity = libVolume * libConc / 1000;
     403            float mixMolarity = libAmount / (libVolume + ebVolume);
     404            float mixConc = 1000 * libQuantity / (libVolume + ebVolume);
     405
    409406            // Add the library to the pooling event
    410407            BioMaterialEventSource poolSrc = poolEvent.addSource(lib);
    411             poolSrc.setUsedQuantity(usedQuantityForPool);
     408            poolSrc.setUsedQuantity(libQuantity);
    412409           
    413             // Summarize quantity, volumes, molarity for the pool
    414             poolQuantity += usedQuantityForPool;
    415             poolVolume += usedVolumeForPool + usedEbForPool;
    416             summedPoolMolarity += molarityForPool;
    417            
    418             //
    419             float mixFactor = ((Number)jsonLib.get("mixFactor")).floatValue();
    420             float usedQuantityForExtra = 0f;
     410            float extraQuantity = 0f;
    421411            if (mixFactor > 1.001)
    422412            {
    423413              // Create 'dil' extract for keeping track of remaining quantity that has been mixed
    424               float usedVolumeForExtra = (mixFactor - 1) * usedVolumeForPool;
    425               usedQuantityForExtra = usedVolumeForExtra * usedConc / 1000;
     414              extraQuantity = libQuantity * (mixFactor - 1f);
     415              float extraVolume = 1000 * extraQuantity / mixConc;
    426416              Extract dil = Extract.getNew(dc);
    427417              dil.setItemSubtype(libraryType);
    428418              dil.setName(lib.getName()+".dil");
    429               dil.setDescription("Represents the remaining 5µl aliquot after mixing 10µl/2nM solution for some libraries before pooling. Stored at a temporary location.");
     419              dil.setDescription("Represents the remaining " + Values.formatNumber(extraVolume, 1) +
     420                "µl aliquot after mixing " + Values.formatNumber(extraVolume*mixFactor, 1) +
     421                "µl solution before pooling. Stored at a temporary location.");
    430422              BioMaterialEventSource libSrc = dil.getCreationEvent().addSource(lib);
    431               Annotationtype.POOL_MOLARITY.setAnnotationValue(dc, dil, molarityForPool);
    432               Annotationtype.POOL_CONC.setAnnotationValue(dc, dil, 1000 * usedQuantityForExtra / usedVolumeForExtra);
    433               libSrc.setUsedQuantity(usedQuantityForExtra);
    434               dil.setOriginalQuantity(usedQuantityForExtra);
     423              Annotationtype.POOL_MOLARITY.setAnnotationValue(dc, dil, mixMolarity);
     424              Annotationtype.POOL_CONC.setAnnotationValue(dc, dil, mixConc);
     425              libSrc.setUsedQuantity(extraQuantity);
     426              dil.setOriginalQuantity(extraQuantity);
    435427              dil.setTag(lib.getTag());
    436428              dc.saveItem(dil);
    437              
    438               //jsonMessages.add("Created " + dil.getName() + "; volume=" + Values.formatNumber(usedVolumeForExtra, 2));
     429              /*
     430              jsonMessages.add("Created " + dil.getName() +
     431                "; " + Values.formatNumber(mixMolarity, 2) + "nM / "+
     432                Values.formatNumber(1000 * extraQuantity / mixConc, 1) + "µl");
     433              */
     434
    439435            }
    440436           
    441             //jsonMessages.add("Using " + Values.formatNumber(usedQuantityForPool*1000, 2) + "+" + Values.formatNumber(usedQuantityForExtra*1000, 2) +"ng of " + Values.formatNumber(lib.getOriginalQuantity()*1000,2) + "ng from " + lib.getName()+"; molarity="+Values.formatNumber(molarity, 2) + "(" + Values.formatNumber(molarityForPool, 2) + ")");
     437            // Summarize quantity, volumes, molarity for the pool
     438            poolQuantity += libQuantity;
     439            poolVolume += libVolume + ebVolume;
     440            poolAmount += libAmount;
     441
     442            /*
     443            jsonMessages.add("Using " + Values.formatNumber(libQuantity*1000, 2) + "+" +
     444                Values.formatNumber(extraQuantity*1000, 2) +"ng of " +
     445                Values.formatNumber(lib.getOriginalQuantity()*1000,2) + "ng from " +
     446                lib.getName()+"; molarity="+Values.formatNumber(libMolarity, 2) + " (" + Values.formatNumber(mixMolarity, 2) + ")");
     447              */
    442448          }
    443449         
     450          float poolConc = 1000 * poolQuantity / poolVolume;
     451          float poolMolarity = poolAmount / poolVolume;
     452         
    444453          pool.setOriginalQuantity(poolQuantity);
    445           Annotationtype.POOL_CONC.setAnnotationValue(dc, pool, poolQuantity * 1000 / poolVolume);
    446           Annotationtype.POOL_MOLARITY.setAnnotationValue(dc, pool, summedPoolMolarity / jsonLibs.size());
     454          Annotationtype.POOL_CONC.setAnnotationValue(dc, pool, poolConc);
     455          Annotationtype.POOL_MOLARITY.setAnnotationValue(dc, pool, poolMolarity);
    447456          dc.saveItem(pool);
    448457         
     
    464473          }
    465474         
    466           jsonMessages.add("Created '" + pool.getName() + "' from " + jsonLibs.size() + " libraries" + excluded + "; pool molarity=" + Values.formatNumber(summedPoolMolarity / jsonLibs.size(), 2)); // conc="+Values.formatNumber(poolQuantity * 1000 / poolVolume, 2) + "ng/µl; quantity="+Values.formatNumber(poolQuantity*1000, 2)+"ng; volume="+Values.formatNumber(poolVolume, 2) + "µl");
     475          jsonMessages.add("Created '" + pool.getName() + "' from " + jsonLibs.size() + " libraries" + excluded +
     476            "; " + Values.formatNumber(poolMolarity, 2) + "nM / " +
     477            Values.formatNumber(poolVolume, 1) + "µl");
    467478        }
    468479       
     
    593604      Float usedVolumeForMix = 1000 * usedQuantity / usedConc;
    594605     
    595       Float poolConc = (Float)Annotationtype.POOL_CONC.getAnnotationValue(dc, pool.getExtract());
    596       Float poolQuantity = pool.getExtract().getOriginalQuantity();
    597       Float TARGET_VOLUME_IN_POOL_PER_LIB = 1000 * poolQuantity / poolConc / numLibs;
     606      lib.setAnnotation("volume", usedVolumeForMix);
    598607     
    599       Float ebVolumeForMix = TARGET_VOLUME_IN_POOL_PER_LIB - usedVolumeForMix;
    600       lib.setAnnotation("volume", usedVolumeForMix);
    601       lib.setAnnotation("eb", ebVolumeForMix);
    602      
    603       // Check if '.dil' exists
    604      
     608      // Check if '.dil' exists   
    605609      ItemQuery<Extract> query = Extract.getQuery();
    606610      query.restrict(Restrictions.eq(Hql.property("name"), Expressions.string(lib.getName()+".dil")));
     
    618622      lib.setAnnotation("dils", result.size());
    619623    }
    620     /*
    621     else
    622     {
    623       // Calculate mix volumes, but do not correct for negative volumes
    624       // due to low concentration (this will be notified in the protocols)
    625       // Round to 1 decimal since that is the expected accuracy of pipettes
    626       // Remember to use the rounded values in the calculations
    627       float usedVolumeForMix = Reggie.round(targetVolumePerLib * TARGET_MOLARITY_IN_POOL / molarity, 1);
    628       float mixFactor = 1.0f;
    629       if (usedVolumeForMix < LIMIT_FOR_EXTRA_LARGE_MIX)
    630       {
    631         // Extra large mix is required if the usedVolume is small due to
    632         // difficulties to pipette the exact amount for small volumes
    633         mixFactor = EXTRA_LARGE_MIX_FACTOR;
    634         // Re-calculate volume to get the rounding correct
    635         usedVolumeForMix = Reggie.round(mixFactor * targetVolumePerLib * TARGET_MOLARITY_IN_POOL / molarity, 1) / mixFactor;
    636       }
    637      
    638       float ebVolumeForMix = targetVolumePerLib - usedVolumeForMix;
    639       lib.setAnnotation("volume", usedVolumeForMix);
    640       lib.setAnnotation("eb", ebVolumeForMix);
    641       lib.setAnnotation("mixMolarity", molarity * usedVolumeForMix / targetVolumePerLib);
    642       lib.setAnnotation("mixFactor", mixFactor);
    643     }
    644     */
    645624  }
    646625 
Note: See TracChangeset for help on using the changeset viewer.