Changeset 2140


Ignore:
Timestamp:
Nov 18, 2013, 11:46:46 AM (7 years ago)
Author:
Nicklas Nordborg
Message:

References #527: Implement manual pooled library selection

Re-factored the volume calculations. There are some additional cases that could not happen before that must be considered. One example is that libraries may have lower remaining quantity than 5 or 10 µl which may result that there is not enough material even if the molarity is higher than 2.0nM.

For all calculations to be reproducable when generating the protocol we need to store both the target molarity and target volume on the pool so PoolTargetMolarity has been added as an annotation.

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

Legend:

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

    r2137 r2140  
    3131
    3232<script language="JavaScript">
    33 var debug = 1;
     33var debug = 0;
    3434var currentStep = 1;
    3535
    3636var selectedLibraries = [];
    3737var targetVolumePerLibIsValid = false;
     38var poolInfo;
    3839
    3940// Loaded from servlet when getting Library information
     
    264265
    265266
    266 function calculateRemarks(lib, duplicateBarcodes)
     267function calculateRemarks(lib, mixingStrategy, duplicateBarcodes)
    267268{
    268269  var remarks = [];
     
    276277    }
    277278   
    278     if (lib.volume > lib.maxVolume)
    279     {
    280       remarks[remarks.length] = 'Low molarity';
    281     }
    282 
     279    if (lib.volume != lib.actualVolume)
     280    {
     281      if (lib.volume > lib.remainingVolume)
     282      {
     283        remarks[remarks.length] = 'Low quantity';
     284      }
     285      else
     286      {
     287        remarks[remarks.length] = 'Low molarity';
     288      }
     289      if (mixingStrategy == 'fixed')
     290      {
     291        remarks[remarks.length] = 'Mix <span class="volume">'+Numbers.formatNumber(lib.actualVolume, 1) + '</span>+<span class="eb">' + Numbers.formatNumber(lib.actualEb, 1)+'µl</span>';
     292      }
     293      else
     294      {
     295        remarks[remarks.length] = 'Use <span class="volume">'+Numbers.formatNumber(lib.actualVolume, 1) + 'µl</span>';
     296      }
     297    }
     298   
    283299    if (lib.speedVacConc != null)
    284300    {
     
    286302    }
    287303   
    288     if (lib.mixFactor > 1.001)
     304    if (lib.mixFactor > 1)
    289305    {
    290306      // Larger mix than default
     
    344360      }
    345361      PoolMix.calculateLibVolume(lib, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
     362      PoolMix.calculateEbVolume(lib, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
    346363    }
    347364  }
    348365   
    349   // Calculate EB
    350   PoolMix.calculateEbForLibs(libs, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
    351  
    352366  // Calculate final pool volumes, molarity, etc.
    353   var poolInfo = PoolMix.calculateFinalPoolInfo(libs, mixingStrategy);
     367  poolInfo = PoolMix.calculateFinalPoolInfo(libs, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
    354368 
    355369  // Make remarks for each lib
     
    371385        lib.hasError = false;
    372386      }
    373       if (lib.volume > lib.maxVolume)
     387      if (lib.volume != lib.actualVolume)
    374388      {
    375389        mark = '*';
     
    380394        Main.removeClass(tr, 'warning');
    381395      }
    382       setInnerHTML('lib.'+lib.id+'.volume', lib.mixFactor > 1 ? '-' : Numbers.formatNumber(lib.volume*lib.mixFactor, 1)+mark);
     396      setInnerHTML('lib.'+lib.id+'.volume', lib.mixFactor > 1 ? '-' : Numbers.formatNumber(lib.volume, 1)+mark);
    383397      if (mixingStrategy == 'fixed' && lib.mixFactor < 1.001)
    384398      {
    385         setInnerHTML('lib.'+lib.id+'.eb', Numbers.formatNumber(lib.eb*lib.mixFactor, 1));
     399        setInnerHTML('lib.'+lib.id+'.eb', Numbers.formatNumber(lib.eb, 1));
    386400      }
    387401      else
     
    391405    }
    392406   
    393     calculateRemarks(lib, duplicateBarcodes);
     407    calculateRemarks(lib, mixingStrategy, duplicateBarcodes);
    394408    setInnerHTML('lib.'+lib.id+'.remarks', lib.remarks.join('; '));
    395409  }
    396410 
    397411  var warnMsg = null;
    398   if (poolInfo.eb < 0)
     412  if (poolInfo.ebVolumeExtra < 0)
    399413  {
    400414    warnMsg = 'Too many libs with low molarity';
    401415  }
    402416 
    403   setInnerHTML('pool.eb', Numbers.formatNumber(poolInfo.ebFinalMix, 1));
     417  setInnerHTML('pool.eb', Numbers.formatNumber(poolInfo.ebVolumeExtra, 1));
    404418
    405419  var poolDiv = document.getElementById('pool-summary');
     
    411425  if (mixingStrategy == 'dynamic')
    412426  {
    413     poolData += ' • <span class="eb">'+Numbers.formatNumber(poolInfo.ebFinalMix, 1, 'µl')+'</span>';
     427    poolData += ' • <span class="eb">'+Numbers.formatNumber(Math.max(0, poolInfo.ebVolumeExtra), 1, 'µl')+'</span>';
    414428  }
    415429  if (warnMsg)
     
    423437    setInnerHTML('pool.remarks', '');
    424438  }
     439 
    425440  poolData += '</div>';
    426441  poolDiv.innerHTML = poolData;
     
    460475  var frm = document.forms['reggie'];
    461476  submitInfo.targetVolumeInPoolPerLib = parseFloat(frm.target_volume.value);
     477  submitInfo.targetPoolMolarity = TARGET_MOLARITY_IN_POOL;
    462478  submitInfo.mixingStrategy = Forms.getCheckedRadio(frm.mixing_strategy).value;
    463479 
    464   var poolInfo = {};
    465   poolInfo.name = frm.poolName.value;
    466   poolInfo.comment = frm.poolComments.value;
    467   poolInfo.libs = [];
    468   poolInfo.excluded = [];
     480  var pool = {};
     481  pool.name = frm.poolName.value;
     482  pool.comment = frm.poolComments.value;
     483  pool.ebVolumeExtra = Math.max(0, poolInfo.ebVolumeExtra);
     484  pool.libs = [];
     485  pool.excluded = [];
    469486 
    470487  for (var libNo = 0; libNo < selectedLibraries.length; libNo++)
     
    479496      tmp.id = lib.id;
    480497      tmp.name = lib.name;
    481       tmp.volume = Math.min(lib.volume, lib.maxVolume); // Never submit more than max volume
    482       tmp.eb = Math.max(0, lib.eb); // Never submit negative EB volumes!
     498      tmp.volume = lib.actualVolume;
     499      tmp.eb = lib.actualEb;
    483500      tmp.mixFactor = lib.mixFactor;
    484501      tmp.comment = lib.comment;
    485       poolInfo.libs[poolInfo.libs.length] = tmp;
    486     }
    487   }
    488   submitInfo.pools[submitInfo.pools.length] = poolInfo;
     502      pool.libs[pool.libs.length] = tmp;
     503    }
     504  }
     505  submitInfo.pools[submitInfo.pools.length] = pool;
    489506 
    490507  Main.addClass(document.getElementById('step.1.section'), 'disabled');
     
    848865      </table>
    849866      <div style="margin: 1em; font-style: italic;">
    850       * Low molarity = The concentration of this library too low to mix to target molarity for the pool.
     867      * Low quantity = The remaining quantity of this library too low to mix to target molarity for the pool.
    851868      </div>
    852869      </div>
  • extensions/net.sf.basedb.reggie/trunk/resources/libprep/create_pools.jsp

    r2137 r2140  
    3737
    3838var targetVolumePerLibIsValid = false;
     39var POOL_DATA = [];
    3940
    4041// Loaded from servlet when getting Library information
     
    139140  var frm = document.forms['reggie'];
    140141  submitInfo.targetVolumeInPoolPerLib = parseFloat(frm.target_volume.value);
     142  submitInfo.targetPoolMolarity = TARGET_MOLARITY_IN_POOL;
    141143  submitInfo.mixingStrategy = Forms.getCheckedRadio(frm.mixing_strategy).value;
    142144
     
    149151    poolInfo.name = POOL_NAMES[poolNo];
    150152    poolInfo.comment = frm['comment.'+poolNo].value;
     153    poolInfo.ebVolumeExtra = POOL_DATA[poolNo].ebVolumeExtra;
    151154    poolInfo.libs = [];
    152155    poolInfo.excluded = [];
     
    161164        tmp.id = lib.id;
    162165        tmp.name = lib.name;
    163         tmp.volume = Math.min(lib.volume, lib.maxVolume); // Never submit more than max volume
    164         tmp.eb = Math.max(0, lib.eb); // Never submit negative EB volumes!
     166        tmp.volume = lib.actualVolume;
     167        tmp.eb = lib.actualEb;
    165168        tmp.mixFactor = lib.mixFactor;
    166169        tmp.comment = lib.comment;
     
    359362      {
    360363        PoolMix.calculateLibVolume(lib, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
     364        PoolMix.calculateEbVolume(lib, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
    361365      }
    362366    }
     
    387391  if (lib.molarity != null)
    388392  {
    389     if (lib.volume > lib.maxVolume)
    390     {
    391       remarks[remarks.length] = 'Low molarity';
     393    if (lib.volume != lib.actualVolume)
     394    {
     395      if (lib.volume > lib.remainingVolume)
     396      {
     397        remarks[remarks.length] = 'Low quantity';
     398      }
     399      else
     400      {
     401        remarks[remarks.length] = 'Low molarity';
     402      }
    392403    }
    393404
     
    397408    }
    398409   
    399     if (lib.mixFactor > 1.001)
     410    if (lib.mixFactor > 1)
    400411    {
    401412      // Larger mix than default
    402       remarks[remarks.length] = 'Use ' + Numbers.formatNumber(lib.volume+lib.eb, 1, 'µl') + ' in pool';
     413      remarks[remarks.length] = 'Use ' + Numbers.formatNumber(lib.actualVolume+lib.actualEb, 1, 'µl') + ' in pool';
    403414    }
    404415   
     
    435446    }
    436447   
    437     // Calculate EB
    438     PoolMix.calculateEbForLibs(libs, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
    439    
    440448    // Calculate final pool volumes, molarity, etc.
    441     var poolInfo = PoolMix.calculateFinalPoolInfo(libs, mixingStrategy);
     449    var poolInfo = PoolMix.calculateFinalPoolInfo(libs, TARGET_MOLARITY_IN_POOL, targetVolumePerLib, mixingStrategy);
     450    POOL_DATA[poolNo] = poolInfo;
    442451   
    443452    // Make remarks for each lib
     
    449458   
    450459    var warnMsg = null;
    451     if (poolInfo.eb < 0)
     460    if (poolInfo.ebVolumeExtra < 0)
    452461    {
    453462      warnMsg = 'Too many libs with low molarity';
     
    461470    if (mixingStrategy == 'dynamic')
    462471    {
    463       poolData += ' • <span class="pool-eb">'+Numbers.formatNumber(poolInfo.ebFinalMix, 1, 'µl')+'</span>';
     472      poolData += ' • <span class="pool-eb">'+Numbers.formatNumber(poolInfo.ebVolumeExtra, 1, 'µl')+'</span>';
    464473    }
    465474    if (warnMsg)
     
    517526        cls += ' flagged';
    518527      }
    519       if (!lib.flag && lib.volume > lib.maxVolume)
     528      if (!lib.flag && lib.actualVolume < lib.volume)
    520529      {
    521530        cls += ' low-volume';
     
    549558      if (!lib.excludeFromPool)
    550559      {
    551         mark = (lib.volume > lib.maxVolume) ? '*' : '';
     560        mark = (lib.volume > lib.actualVolume) ? '*' : '';
    552561        text += '<div>';
    553562        text += '<span class="volume">'+(isFinite(lib.volume) ? Numbers.formatNumber(lib.volume*mixFactor, 1, 'µl'+mark) : '∞')+'</span>';
    554         if (painter.mixingStrategy == 'fixed' || lib.mixFactor > 1.001)
     563        if (painter.mixingStrategy == 'fixed' || lib.mixFactor > 1)
    555564        {
    556565          text += '<span class="eb">'+(isFinite(lib.eb) ? Numbers.formatNumber(lib.eb*mixFactor, 1, 'µl') : '-∞')+'</span>';
  • extensions/net.sf.basedb.reggie/trunk/resources/libprep/pool_protocol2.jsp

    r2067 r2140  
    154154      // Calculate some pool quantities
    155155      pool.volume = 1000 * pool.originalQuantity / pool.conc;
    156      
    157       PoolMix.calculateEbForLibs(pool.libraries, pool.molarity, pool.targetVolumePerLib, pool.mixingStrategy);
    158       pool.extra = PoolMix.calculateFinalPoolInfo(pool.libraries, pool.mixingStrategy);
    159 
    160       for (var j = 0; j < pool.libraries.length; j++)
    161       {
    162         checkAndPreProcessLibrary(pool.libraries[j], pool, POOL_CURRENT_SCHEMA, POOL_CURRENT_BARCODE_VARIANT, '<%=view%>');       
    163       }
    164      
     156
     157      for (var libNo = 0; libNo < pool.libraries.length; libNo++)
     158      {
     159        var lib = pool.libraries[libNo];
     160        PoolMix.calculateLibVolumeForProtocol(lib, pool.targetPoolMolarity, pool.targetVolumePerLib, pool.mixingStrategy, true);
     161        PoolMix.calculateEbVolume(lib, pool.targetPoolMolarity, pool.targetVolumePerLib, pool.mixingStrategy);
     162        checkAndPreProcessLibrary(lib, pool, POOL_CURRENT_SCHEMA, POOL_CURRENT_BARCODE_VARIANT, '<%=view%>');       
     163      }
     164
     165      pool.extra = PoolMix.calculateFinalPoolInfo(pool.libraries, pool.targetPoolMolarity, pool.targetVolumePerLib, pool.mixingStrategy);
    165166    }
    166167   
     
    213214      }
    214215 
    215       if (lib.mixFactor > 1.001)
     216      if (lib.mixFactor > 1)
    216217      {
    217218        // Larger mix than default
    218219        if (view != 'plate')
    219220        {
    220           remarks[remarks.length] = '<span class="mix-remark">Mix <span class="volume">'+Numbers.formatNumber(lib.volume*lib.mixFactor, 1) + '</span>+<span class="eb">' + Numbers.formatNumber(lib.eb*lib.mixFactor, 1)+'µl</span></span>';
    221         }
    222         remarks[remarks.length] = 'Use <b>' + Numbers.formatNumber(lib.volume+lib.eb, 1) + 'µl</b> in pool';
     221          remarks[remarks.length] = '<span class="mix-remark">Mix <span class="volume">'+Numbers.formatNumber(lib.actualVolume*lib.mixFactor, 1) + '</span>+<span class="eb">' + Numbers.formatNumber(lib.actualEb*lib.mixFactor, 1)+'µl</span></span>';
     222        }
     223        remarks[remarks.length] = 'Use <b>' + Numbers.formatNumber(lib.actualVolume+lib.actualEb, 1) + 'µl</b> in pool';
    223224      }
    224225
     
    261262        var well = lib.bioWell;
    262263       
     264        var tr = document.createElement('tr');
    263265        if (POOL_CURRENT_SCHEMA)
    264266        {
    265267          wellIndex = createMissingRows(tbody, wellsInPool, wellIndex, well, barcodeVariant);
    266         }
    267        
    268         var tr = document.createElement('tr');
    269         tr.className = well.column % 2 == 0 ? "evencol" : "oddcol";
     268          tr.className = well.column % 2 == 0 ? "evencol" : "oddcol";
     269        }
     270        else
     271        {
     272          tr.className = 'evencol';
     273        }
    270274   
    271275        if (barcodeVariant)
     
    279283
    280284        addColumn(tr, 'lib', lib.name);
    281         addColumn(tr, 'remain', Numbers.formatNumber((lib.remainingQuantity+lib.usedQuantity)*1000, 2));
     285        addColumn(tr, 'remain', Numbers.formatNumber((lib.remainingVolume), 1));
    282286        addColumn(tr, 'molarity', Numbers.formatNumber(lib.molarity, 2));
    283287        <%
     
    296300        %>
    297301        var mixFactor = lib.mixFactor || 1.0;
    298         addColumn(tr, 'volume', mixFactor > 1.0 ? '—' : Numbers.formatNumber(lib.volume*mixFactor, 1));
    299         addColumn(tr, 'eb', pool.mixingStrategy == 'dynamic' || mixFactor > 1.001 ? '—' : Numbers.formatNumber(lib.eb*mixFactor, 1));
     302        addColumn(tr, 'volume', mixFactor > 1.0 ? '—' : Numbers.formatNumber(lib.actualVolume, 1));
     303        addColumn(tr, 'eb', pool.mixingStrategy == 'dynamic' || mixFactor > 1.0 ? '—' : Numbers.formatNumber(lib.actualEb, 1));
    300304        addColumn(tr, "remarks", lib.remarks.join('; '));
    301305        tbody.appendChild(tr);
     
    312316      poolData += '</div>';
    313317      setInnerHTML('molarity.'+pool.id, poolData);
    314       setInnerHTML('eb-volume.'+pool.id, Numbers.formatNumber(pool.extra.ebFinalMix, 1));
     318      setInnerHTML('eb-volume.'+pool.id, Numbers.formatNumber(Math.max(0, pool.extra.ebVolumeExtra), 1));
    315319      Main.show('pool.'+pool.id);
    316320    }
     
    382386      if (pool.mixingStrategy == 'dynamic')
    383387      {
    384         poolData += ' • <span class="pool-eb">'+Numbers.formatNumber(pool.extra.ebFinalMix, 1, 'µl')+'</span>';
     388        poolData += ' • <span class="pool-eb">'+Numbers.formatNumber(pool.extra.ebVolumeExtra, 1, 'µl')+'</span>';
    385389      }
    386390      poolData += '<div class="comments">'+pool.comments+'</div>';
     
    418422        var i = name.indexOf('.m');
    419423        text += '<div class="lib">'+name.substring(0, i)+'.<br>&nbsp;'+name.substring(i)+'</div>';
    420         text += '<div><span class="volume">'+Numbers.formatNumber(lib.volume*mixFactor, 1)+'µl</span>';
     424        text += '<div><span class="volume">'+Numbers.formatNumber(lib.actualVolume*mixFactor, 1)+'µl</span>';
    421425        if (painter.mixingStrategy == 'fixed' || lib.mixFactor > 1.001)
    422426        {
    423           text += '<span class="eb">'+Numbers.formatNumber(lib.eb*mixFactor, 1)+'µl</span>';
     427          text += '<span class="eb">'+Numbers.formatNumber(lib.actualEb*mixFactor, 1)+'µl</span>';
    424428        }
    425429        else
     
    746750        <tr>
    747751          <th class="lib">Library</th>
    748           <th>(ng)</th>
     752          <th>(µl)</th>
    749753          <th>(nM)</th>
    750754          <th class="workplate">plate</th>
  • extensions/net.sf.basedb.reggie/trunk/resources/libprep/pools.js

    r2137 r2140  
    294294 
    295295  /**
    296     Calculate the volume of a lib to take for a pool given the
     296    Checks if v1 is less than v2 within the given accuracy
     297    Eg. v1+accuracy <= v2
     298   */
     299  pm.lessThan = function(v1, v2, accuracy)
     300  {
     301    return v1 + accuracy < v2;
     302  }
     303 
     304  /**
     305    Calculate the volume of a lib to use for a pool given the
    297306    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.
     307    in the pool. This method calculates two different volumes:
     308   
     309    lib.volume = The theoretical volume needed to reach the same
     310    amount of DNA as the target molarity and volume suggests
     311   
     312    lib.actualVolume: The actual volume that can be used due to
     313    remaining quantity, lib molarity and mixing strategy
    304314   
    305315    The volume is rounded to one decimal in µl.
     316   
    306317  */
    307318  pm.calculateLibVolume = function(lib, targetMolarity, targetVolume, mixingStrategy)
    308319  {
    309     var maxVolume = lib.remainingVolume;
    310     if (mixingStrategy == 'fixed' && maxVolume > targetVolume)
    311     {
    312       maxVolume = targetVolume;
    313     }
    314320    var volume = pm.round(targetVolume * targetMolarity / lib.molarity);
    315321    var mixFactor = 1;
    316     if (volume < LIMIT_FOR_EXTRA_LARGE_MIX)
     322   
     323    if (pm.lessThan(volume, LIMIT_FOR_EXTRA_LARGE_MIX, 0.05))
    317324    {
    318325      mixFactor = 2;
     
    322329    }
    323330   
    324     lib.maxVolume = maxVolume;
     331    // This is the theoretical volume that should be used...
    325332    lib.volume = volume;
     333    lib.eb = null;
    326334    lib.mixFactor = mixFactor;
    327     lib.eb = null;
    328     return volume;
    329   }
    330  
    331   /**
    332     Calculate the amount of EB to mix with each of the libs. If mixing
    333     strategy is 'fixed' all libraries are mixed to the target volume,
    334     otherwise a dynamic strategy that prioritize the final pool molarity
    335     is used:
    336    
    337     1. The total amount of EB needed to achive the target molarity
    338        in the pool is calculated.
    339        
    340     2. Libs with a mixFactor > 1 are always mixed to the given target
    341        molarity since the saved lib should be standardized
    342        
    343     3. The remaining EB volume is equally split among the rest of the
    344        libs. To avoid rounding errors for the pool as a total the
    345        volume is rounded down and then any remaining EB is added to
    346        the last lib.
    347   */
    348   pm.calculateEbForLibs = function(libs, targetMolarity, targetVolume, mixingStrategy)
    349   {
    350     if (mixingStrategy == 'fixed')
    351     {
    352       // Fixed strategy
    353       // All libs are mixed with EB to target volume
    354       for (var libNo = 0; libNo < libs.length; libNo++)
    355       {
    356         var lib = libs[libNo];
    357         lib.eb = targetVolume - lib.volume;
    358       }
     335     
     336    // However, reality has limitations due to remaining volume, mixing strategy, etc.
     337    // Check what volume that can actually be used
     338    if (mixingStrategy == 'fixed' || lib.mixFactor > 1)
     339    {
     340      // Do not use more than target volume or remaining volume
     341      volume = Math.min(volume, targetVolume, lib.remainingVolume);
    359342    }
    360343    else
    361344    {
    362       // Dynamic strategy
    363       // First, calculate total volume of all libs
    364       // and their contribution to the total amount of DNA in the pool
    365       var totalLibVolume = 0;
    366       var totalLibAmount = 0;
    367       var fixedEbVolume = 0;
    368       var fixedLibs = 0;
    369       for (var libNo = 0; libNo < libs.length; libNo++)
    370       {
    371         var lib = libs[libNo];
    372         var vol = lib.maxVolume < lib.volume ? lib.maxVolume : lib.volume;
    373         totalLibVolume += vol;
    374         totalLibAmount += vol * lib.molarity;
    375        
    376         // Always mix to target molarity when doing extra large mix
    377         if (lib.mixFactor > 1)
    378         {
    379           var eb = targetVolume - lib.volume;
    380           fixedEbVolume += eb;
    381           lib.eb = eb;
    382           fixedLibs++;
    383         }
    384       }
    385 
    386      
    387       // Calculate the total volume needed to get to the
    388       // target molarity for the pooled libs
    389       // Round all summarized values to avoid precision problems (eg. 4.0 --> 3.999999998)
    390       var totalPoolVolume = pm.round(totalLibAmount / targetMolarity);
    391       totalLibVolume = pm.round(totalLibVolume);
    392       fixedEbVolume = pm.round(fixedEbVolume);
    393       var totalEbVolume = pm.round(totalPoolVolume - totalLibVolume - fixedEbVolume);
    394 
    395       // Divide EB volume among the libs (rounded to 1 decimal)
    396       var ebPerLib = pm.floor(totalEbVolume / (libs.length - fixedLibs), 1);
    397       var usedEb = 0;
    398       var adjustableLib;
    399       for (var libNo = 0; libNo < libs.length; libNo++)
    400       {
    401         var lib = libs[libNo];
    402         if (!lib.mixFactor || lib.mixFactor == 1)
    403         {
    404           lib.eb = ebPerLib;
    405           usedEb += ebPerLib;
    406           adjustableLib = lib;
    407         }
    408       }
    409      
    410       usedEb = pm.round(usedEb);
    411      
    412       // If the used EB doesn't add up to the total amount needed (due to rounding)
    413       // add the extra amount to the last lib
    414       if (Math.abs(totalEbVolume - usedEb) >= 0.05 && adjustableLib)
    415       {
    416         adjustableLib.eb += totalEbVolume - usedEb;
    417       }
    418     }
    419    
    420   }
    421  
    422   pm.calculateFinalPoolInfo = function(libs, mixingStrategy)
    423   {
    424     var info = {};
    425    
     345      volume = Math.min(volume, lib.remainingVolume);
     346    }
     347    lib.actualVolume = volume;     
     348  }
     349 
     350  /**
     351    Same as above method except that we already know the mixFactor and actualVolume of
     352    the lib since that has already been saved to the database.
     353  */
     354  pm.calculateLibVolumeForProtocol = function(lib, targetMolarity, targetVolume, mixingStrategy)
     355  {
     356    if (lib.mixFactor > 1)
     357    {
     358      lib.volume = pm.ceil(lib.mixFactor * targetVolume * targetMolarity / lib.molarity) / lib.mixFactor;
     359    }
     360    else
     361    {
     362      lib.volume = pm.round(targetVolume * targetMolarity / lib.molarity);
     363    }
     364  }
     365
     366 
     367  /**
     368    Calculate the volume of EB to mix with a lib.
     369   
     370    The volume is rounded to one decimal in µl.
     371  */
     372  pm.calculateEbVolume = function(lib, targetMolarity, targetVolume, mixingStrategy)
     373  {
     374    var eb = 0;
     375    if (mixingStrategy == 'fixed' || lib.mixFactor > 1)
     376    {
     377      eb = targetVolume - lib.volume;
     378    }
     379   
     380    // This is the theoretical volume that should be used...
     381    lib.eb = eb;
     382   
     383    // However, reality has limitations due to remaining volume, mixing strategy, etc.
     384    // Check what volume that can actually be used
     385    if (mixingStrategy == 'fixed' || lib.mixFactor > 1)
     386    {
     387      if (pm.lessThan(lib.molarity, targetMolarity, 0.01))
     388      {
     389        // Do not dilute if molarity is lower than target molarity
     390        eb = 0;
     391      }
     392      else if (pm.lessThan(lib.actualVolume, lib.volume, 0.05))
     393      {
     394        // Reduce EB by the same factor as we have to reduce the volume
     395        // so the final molarity is still
     396        eb = pm.round(eb * lib.actualVolume / lib.volume);
     397      }
     398    }   
     399    lib.actualEb = eb;
     400  }
     401
     402 
     403 
     404  /**
     405    Summarize information for the pool. This will calculate the following and
     406    return as an object:
     407   
     408    libVolume: Total volume of libs in the pool (µl)
     409    libAmount: Total amount of libs in the pool (nano-mol)
     410    ebVolumeFromLibs: Total EB volume from mixed directly with libs
     411    ebVolumeExtra: Extra EB to add to the pool (dynamic mixing)
     412    molarity: The final pool molarity after mixing everything
     413  */
     414  pm.calculateFinalPoolInfo = function(libs, targetMolarity, targetVolume, mixingStrategy)
     415  {
    426416    var libVolume = 0;
    427417    var libAmount = 0;
    428     var ebVolume = 0;
    429     var ebFinalMix = 0;
     418    var ebVolumeFromLibs = 0;
     419   
    430420    for (var libNo = 0; libNo < libs.length; libNo++)
    431421    {
    432422      var lib = libs[libNo];
    433       var vol = lib.maxVolume < lib.volume ? lib.maxVolume : lib.volume;
    434       libVolume += vol;
    435       ebVolume += lib.eb;
    436       if (mixingStrategy == 'dynamic' && (!lib.mixFactor || lib.mixFactor == 1))
    437       {
    438         ebFinalMix += lib.eb;
    439       }
    440       libAmount += vol * lib.molarity;
    441     }
    442    
    443     info.volume = libVolume;
    444     info.amount = libAmount;
    445     info.eb = ebVolume;
    446     info.ebFinalMix = ebFinalMix;
    447     if (ebVolume < 0)
    448     {
    449       info.totalVolume = libVolume;
     423     
     424      var volume = lib.actualVolume;
     425      var eb = lib.actualEb;
     426
     427      libVolume += volume;
     428      ebVolumeFromLibs += eb;
     429      libAmount += volume * lib.molarity;
     430    }
     431   
     432    var info = {};
     433    info.libVolume = libVolume;
     434    info.libAmount = libAmount;
     435    info.ebVolumeFromLibs = ebVolumeFromLibs;
     436   
     437    // Calculate extra EB and total pool volume
     438    if (mixingStrategy == 'dynamic')
     439    {
     440      // Use the target molarity to calculate the total volume
     441      var targetVolume = pm.round(libAmount / targetMolarity);
     442      info.ebVolumeExtra = pm.round(targetVolume - info.libVolume - info.ebVolumeFromLibs);
     443      if (info.ebVolumeExtra < 0)
     444      {
     445        info.totalVolume = info.libVolume + info.ebVolumeFromLibs;
     446      }
     447      else
     448      {
     449        info.totalVolume = targetVolume;
     450      }
    450451    }
    451452    else
    452453    {
    453       info.totalVolume = libVolume + ebVolume;
    454     }
    455     info.molarity = libAmount / info.totalVolume;
    456    
     454      // No extra EB
     455      info.ebVolumeExtra = 0;
     456      info.totalVolume = info.libVolume + info.ebVolumeFromLibs;
     457    }
     458    // Calcualate the final molarity (which may be different that target due to rounding)
     459    info.molarity = info.libAmount / info.totalVolume;
     460
    457461    return info;
    458462  }
  • extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/dao/Annotationtype.java

    r2134 r2140  
    595595  public static final Annotationtype POOL_TARGET_VOLUME_PER_LIB =
    596596    new Annotationtype("PoolTargetVolumePerLib", Type.FLOAT, Item.EXTRACT);
     597
     598  /**
     599    The "PoolTargetMolarity" annotation, used for extracts (PooledLibrary).
     600    Store the admin-defined setting for target molarity used when designing
     601    the pool. The actual molarity may different due to limitations implied by
     602    library concentration and rounding.
     603    @since 2.14
     604  */
     605  public static final Annotationtype POOL_TARGET_MOLARITY =
     606    new Annotationtype("PoolTargetMolarity", Type.FLOAT, Item.EXTRACT);
    597607 
    598608  /**
  • extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/servlet/InstallServlet.java

    r2134 r2140  
    277277        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_MOLARITY, 1, null, effectiveOptions, createIfMissing));
    278278        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_TARGET_VOLUME_PER_LIB, 1, null, effectiveOptions, createIfMissing));
     279        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_TARGET_MOLARITY, 1, null, effectiveOptions, createIfMissing));
    279280        jsonChecks.add(checkAnnotationType(dc, Annotationtype.POOL_MIXING_STRATEGY, 1,
    280281            new ValueOptions(PooledLibrary.MIXING_STRATEGY_FIXED, PooledLibrary.MIXING_STRATEGY_DYNAMIC),
     
    383384        jsonChecks.add(checkAnnotationTypeCategory(dc, Subtype.POOLED_LIBRARY, createIfMissing,
    384385            Annotationtype.POOL_MOLARITY, Annotationtype.POOL_CONC, Annotationtype.POOL_TARGET_VOLUME_PER_LIB,
     386            Annotationtype.POOL_TARGET_MOLARITY,
    385387            Annotationtype.POOL_MIXING_STRATEGY, Annotationtype.POOL_DATE, Annotationtype.OPERATOR,
    386388            Annotationtype.FLAG, Annotationtype.AUTO_PROCESSING));
  • extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/servlet/PoolServlet.java

    r2137 r2140  
    316316          if (targetVolumePerLib == null) targetVolumePerLib = DEFAULT_TARGET_VOLUME_IN_POOL_PER_LIB;
    317317          pool.setAnnotation("targetVolumePerLib", targetVolumePerLib);
     318          Float targetPoolMolarity = (Float)Annotationtype.POOL_TARGET_MOLARITY.getAnnotationValue(dc, pool.getExtract());
     319          if (targetPoolMolarity == null) targetPoolMolarity = TARGET_MOLARITY_IN_POOL;
     320          pool.setAnnotation("targetPoolMolarity", targetPoolMolarity);
    318321          String mixingStrategy = (String)Annotationtype.POOL_MIXING_STRATEGY.getAnnotationValue(dc, pool.getExtract());
    319322          if (mixingStrategy == null) mixingStrategy = PooledLibrary.MIXING_STRATEGY_FIXED;
     
    326329       
    327330        JSONObject poolInfo = new JSONObject();
    328         poolInfo.put("targetMolarity", TARGET_MOLARITY_IN_POOL);
    329331        poolInfo.put("extraLargeMixFactor", EXTRA_LARGE_MIX_FACTOR);
    330332        poolInfo.put("limitForExtraLargeMix", LIMIT_FOR_EXTRA_LARGE_MIX);
     
    374376        JSONArray jsonFlagged = (JSONArray)jsonReq.get("flagged");
    375377        Number targetVolumeInPoolPerLib = (Number)jsonReq.get("targetVolumeInPoolPerLib");
     378        Number targetPoolMolarity = (Number)jsonReq.get("targetPoolMolarity");
    376379        String mixingStrategy = (String)jsonReq.get("mixingStrategy");
    377380       
     
    394397          pool.setDescription((String)jsonPool.get("comment"));
    395398          Annotationtype.POOL_TARGET_VOLUME_PER_LIB.setAnnotationValue(dc, pool, targetVolumeInPoolPerLib.floatValue());
     399          Annotationtype.POOL_TARGET_MOLARITY.setAnnotationValue(dc, pool, targetPoolMolarity.floatValue());
    396400          Annotationtype.POOL_MIXING_STRATEGY.setAnnotationValue(dc, pool, mixingStrategy);
    397401         
     
    399403         
    400404          // Total pool quantities
    401           float poolVolume = 0f;  // volume in pool (µl)
     405          float poolVolume = ((Number)jsonPool.get("ebVolumeExtra")).floatValue();  // volume in pool (µl)
    402406          float poolQuantity = 0f; // quantity of DNA in pool (µg)
    403407          float poolAmount = 0f;  // Amount of DNA in pool (nano-mole)
     
    633637    // Load remaining quantity of the lib
    634638    Float remain = libEx.getRemainingQuantity();
    635     lib.setAnnotation("remainingQuantity", remain);
    636     if (remain != null && (originalConc != null || speedVacConc != null))
    637     {
    638       // Remaining volume in µl
    639       Float conc = speedVacConc == null ? originalConc : speedVacConc;
    640       lib.setAnnotation("remainingVolume", 1000 * remain / conc);
    641     }
    642639
    643640    if (pool != null)
     
    649646      Float usedVolumeForMix = 1000 * usedQuantity / usedConc;
    650647     
    651       lib.setAnnotation("volume", usedVolumeForMix);
     648      // Add the used quantity back since we want the remaining quantity *before* pooling
     649      if (remain != null) remain += usedQuantity;
     650     
     651      // The actual volume used when pooling
     652      lib.setAnnotation("actualVolume", usedVolumeForMix);
    652653     
    653654      // Check if '.dil' exists   
     
    661662        Extract dil = result.get(0);
    662663        Float dilQuantity = dil.getOriginalQuantity();
    663         //Float dilConc = (Float)Annotationtype.POOL_CONC.getAnnotationValue(dc, dil);
    664664        lib.setAnnotation("mixFactor", (dilQuantity + usedQuantity) / usedQuantity);
    665665      }
    666666     
    667667      lib.setAnnotation("dils", result.size());
     668    }
     669   
     670    lib.setAnnotation("remainingQuantity", remain);
     671    if (remain != null && (originalConc != null || speedVacConc != null))
     672    {
     673      // Remaining volume in µl
     674      Float conc = speedVacConc == null ? originalConc : speedVacConc;
     675      lib.setAnnotation("remainingVolume", 1000 * remain / conc);
    668676    }
    669677  }
Note: See TracChangeset for help on using the changeset viewer.