001package com.hfg.chem.solution;
002
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.List;
008
009import com.hfg.chem.Matter;
010import com.hfg.units.*;
011
012//------------------------------------------------------------------------------
013/**
014 Represents an aqueous solution.
015 <div>
016 @author J. Alex Taylor, hairyfatguy.com
017 </div>
018 */
019//------------------------------------------------------------------------------
020// com.hfg XML/HTML Coding Library
021//
022// This library is free software; you can redistribute it and/or
023// modify it under the terms of the GNU Lesser General Public
024// License as published by the Free Software Foundation; either
025// version 2.1 of the License, or (at your option) any later version.
026//
027// This library is distributed in the hope that it will be useful,
028// but WITHOUT ANY WARRANTY; without even the implied warranty of
029// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
030// Lesser General Public License for more details.
031//
032// You should have received a copy of the GNU Lesser General Public
033// License along with this library; if not, write to the Free Software
034// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
035//
036// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
037// jataylor@hairyfatguy.com
038//------------------------------------------------------------------------------
039
040public class AqueousSolution
041{
042   // TODO: Should probably be a Set
043   private List<MixtureComponent> mComponents = new ArrayList<>(5);
044   private Quantity mTargetQuantity;
045   private Quantity mActualQuantity;
046
047   // We don't want the auto-scaling to come back with wacky units like cL
048   // so we'll restrict the list of possible scaling factors.
049   private static SI_ScalingFactor[] sStandardMassScalingFactors;
050   private static SI_ScalingFactor[] sStandardVolumeScalingFactors;
051   static
052   {
053      sStandardMassScalingFactors = new SI_ScalingFactor[] {
054         SI_ScalingFactor.kilo,
055         SI_ScalingFactor.one,
056         SI_ScalingFactor.milli,
057         SI_ScalingFactor.micro,
058         SI_ScalingFactor.nano
059      };
060
061      sStandardVolumeScalingFactors = new SI_ScalingFactor[] {
062         SI_ScalingFactor.one,
063         SI_ScalingFactor.milli,
064         SI_ScalingFactor.micro,
065         SI_ScalingFactor.nano
066      };
067   }
068
069   //###########################################################################
070   // CONSTRUCTORS
071   //###########################################################################
072
073   //---------------------------------------------------------------------------
074   public AqueousSolution()
075   {
076
077   }
078
079   //###########################################################################
080   // PUBLIC METHODS
081   //###########################################################################
082
083   //---------------------------------------------------------------------------
084   /**
085    The target quantity is the amount of solution intended to be made.
086    The target quantity should be specified either in terms of volume or in terms of mass.
087    * @param inValue the target quantity of this aqueous solution
088    * @return this solution object to enable method chaining
089    */
090   public AqueousSolution setTargetQuantity(Quantity inValue)
091   {
092      mTargetQuantity = inValue;
093      return this;
094   }
095
096   //---------------------------------------------------------------------------
097   public Quantity getTargetQuantity()
098   {
099      return mTargetQuantity;
100   }
101
102   //---------------------------------------------------------------------------
103   /**
104    The actual quantity should be specified either in terms of volume or in terms of mass.
105    * @param inValue the actual quantity of this aqueous solution
106    * @return this solution object to enable method chaining
107    */
108   public AqueousSolution setActualQuantity(Quantity inValue)
109   {
110      mActualQuantity = inValue;
111      return this;
112   }
113
114   //---------------------------------------------------------------------------
115   public Quantity getActualQuantity()
116   {
117      return mActualQuantity;
118   }
119
120   //---------------------------------------------------------------------------
121   public AqueousSolution subtract(Quantity inValue)
122   {
123      mActualQuantity = mActualQuantity.subtract(inValue);
124      return this;
125   }
126
127   //---------------------------------------------------------------------------
128   public AqueousSolution defineComponent(Matter inSubstance, Quantity inConcentration)
129   {
130      mComponents.add(new MixtureComponent(inSubstance, inConcentration));
131      return this;
132   }
133
134   //---------------------------------------------------------------------------
135   public AqueousSolution defineComponent(MixtureComponent inComponent)
136   {
137      mComponents.add(inComponent);
138      return this;
139   }
140
141   //---------------------------------------------------------------------------
142   public Collection<MixtureComponent> getComponents()
143   {
144      return Collections.unmodifiableCollection(mComponents);
145   }
146
147   //---------------------------------------------------------------------------
148   public Quantity getQuantity(Matter inMatter)
149   {
150      Quantity quantity = null;
151      for (MixtureComponent component : mComponents)
152      {
153         if (component.getSubstance().equals(inMatter))
154         {
155            quantity = component.getQuantity();
156            break;
157         }
158      }
159
160      return quantity;
161   }
162
163   //---------------------------------------------------------------------------
164   public Quantity calcQuantityOfStockSolutionNeeded(AqueousSolution inStockSolution)
165   {
166      Quantity stockQuantityNeeded = null;
167
168      Quantity componentTargetQuantity = null;
169      Matter stockComponent = inStockSolution.getComponents().iterator().next().getSubstance();
170
171      for (MixtureComponent component : mComponents)
172      {
173         if (component.getSubstance().equals(stockComponent))
174         {
175            componentTargetQuantity = component.getQuantity();
176            break;
177         }
178      }
179
180      Quantity stockComponentQuantity = inStockSolution.getQuantity(stockComponent);
181
182      Quantity componentQuantityNeeded = new Quantity(componentTargetQuantity.doubleValue(), componentTargetQuantity.getUnit().getNumerator());
183
184      Quantity componentDenominatorQuantity = new Quantity(1, componentTargetQuantity.getUnit().getDenominator());
185      double scalingFactor = componentDenominatorQuantity.convertTo(getTargetQuantity().getUnit()).doubleValue() * getTargetQuantity().doubleValue();
186
187      componentQuantityNeeded = componentQuantityNeeded.multiplyBy(scalingFactor);
188
189      if (componentQuantityNeeded.getUnit().getQuantityType().equals(QuantityType.AMOUNT_OF_SUBSTANCE))
190      {
191         Unit stockSolutionNumerator = inStockSolution.getQuantity(stockComponent).getUnit().getNumerator();
192         if (stockSolutionNumerator.getQuantityType().equals(QuantityType.AMOUNT_OF_SUBSTANCE))
193         {
194            componentQuantityNeeded = componentQuantityNeeded.convertTo(AmountOfSubstanceUnit.mole);
195            componentQuantityNeeded = componentQuantityNeeded.divideBy(inStockSolution.getQuantity(stockComponent).doubleValue());
196
197            stockQuantityNeeded = new Quantity(componentQuantityNeeded.doubleValue(), inStockSolution.getQuantity(stockComponent).getUnit().getDenominator());
198         }
199         else if (stockSolutionNumerator.getQuantityType().equals(QuantityType.MASS))
200         {
201            componentQuantityNeeded = componentQuantityNeeded.convertTo(AmountOfSubstanceUnit.mole);
202            // Convert from moles to grams using the molecular weight
203            componentQuantityNeeded = new Quantity(componentQuantityNeeded.multiplyBy(stockComponent.getAverageMass()).doubleValue(), MassUnit.gram);
204
205            stockQuantityNeeded = new Quantity(componentQuantityNeeded.divideBy(stockComponentQuantity.doubleValue()).doubleValue(), stockComponentQuantity.getUnit().getDenominator());
206         }
207      }
208      else if (componentQuantityNeeded.getUnit().getQuantityType().equals(QuantityType.MASS))
209      {
210         Unit stockSolutionNumerator = inStockSolution.getQuantity(stockComponent).getUnit().getNumerator();
211         if (stockSolutionNumerator.getQuantityType().equals(QuantityType.AMOUNT_OF_SUBSTANCE))
212         {
213            componentQuantityNeeded = componentQuantityNeeded.convertTo(MassUnit.gram);
214            // Convert from grams to moles using the molecular weight
215            componentQuantityNeeded = new Quantity(componentQuantityNeeded.divideBy(stockComponent.getAverageMass()).doubleValue(), AmountOfSubstanceUnit.mole);
216
217            stockQuantityNeeded = new Quantity(componentQuantityNeeded.divideBy(stockComponentQuantity.doubleValue()).doubleValue(), stockComponentQuantity.getUnit().getDenominator());
218         }
219         else if (stockSolutionNumerator.getQuantityType().equals(QuantityType.MASS))
220         {
221            componentQuantityNeeded = componentQuantityNeeded.convertTo(stockSolutionNumerator);
222            componentQuantityNeeded = componentQuantityNeeded.divideBy(inStockSolution.getQuantity(stockComponent).doubleValue());
223
224            stockQuantityNeeded = new Quantity(componentQuantityNeeded.doubleValue(), inStockSolution.getQuantity(stockComponent).getUnit().getDenominator());
225         }
226      }
227      else if (componentQuantityNeeded.getUnit().getQuantityType().equals(QuantityType.VOLUME))
228      {
229         Unit stockSolutionNumerator = inStockSolution.getQuantity(stockComponent).getUnit().getNumerator();
230         if (stockSolutionNumerator.getQuantityType().equals(QuantityType.AMOUNT_OF_SUBSTANCE))
231         {
232            componentQuantityNeeded = componentQuantityNeeded.convertTo(MassUnit.gram);
233            // Convert from grams to moles using the molecular weight
234            componentQuantityNeeded = new Quantity(componentQuantityNeeded.divideBy(stockComponent.getAverageMass()).doubleValue(), AmountOfSubstanceUnit.mole);
235
236            stockQuantityNeeded = new Quantity(componentQuantityNeeded.divideBy(stockComponentQuantity.doubleValue()).doubleValue(), stockComponentQuantity.getUnit().getDenominator());
237         }
238         else if (stockSolutionNumerator.getQuantityType().equals(QuantityType.MASS))
239         {
240            componentQuantityNeeded = componentQuantityNeeded.convertTo(stockSolutionNumerator);
241            componentQuantityNeeded = componentQuantityNeeded.divideBy(inStockSolution.getQuantity(stockComponent).doubleValue());
242
243            stockQuantityNeeded = new Quantity(componentQuantityNeeded.doubleValue(), inStockSolution.getQuantity(stockComponent).getUnit().getDenominator());
244         }
245         else if (stockSolutionNumerator.getQuantityType().equals(QuantityType.VOLUME))
246         {
247            componentQuantityNeeded = componentQuantityNeeded.convertTo(stockSolutionNumerator);
248            componentQuantityNeeded = componentQuantityNeeded.divideBy(inStockSolution.getQuantity(stockComponent).doubleValue());
249
250            stockQuantityNeeded = new Quantity(componentQuantityNeeded.doubleValue(), inStockSolution.getQuantity(stockComponent).getUnit().getDenominator());
251         }
252      }
253
254      SI_ScalingFactor[] allowedAutoScalingFactors = null;
255      if (stockQuantityNeeded.getUnit().getQuantityType().equals(QuantityType.VOLUME))
256      {
257         allowedAutoScalingFactors = sStandardVolumeScalingFactors;
258      }
259      else if (stockQuantityNeeded.getUnit().getQuantityType().equals(QuantityType.MASS))
260      {
261         allowedAutoScalingFactors = sStandardMassScalingFactors;
262      }
263
264
265      stockQuantityNeeded = stockQuantityNeeded.autoScale(allowedAutoScalingFactors);
266
267      return stockQuantityNeeded;
268   }
269
270   //---------------------------------------------------------------------------
271   public Quantity calcMassOfComponentNeeded(Matter inComponent)
272   {
273      Quantity componentTargetQuantity = null;
274
275      for (MixtureComponent component : mComponents)
276      {
277         if (component.getSubstance().equals(inComponent))
278         {
279            componentTargetQuantity = component.getQuantity();
280            break;
281         }
282      }
283
284
285      Quantity componentQuantityNeeded = new Quantity(componentTargetQuantity.doubleValue(), componentTargetQuantity.getUnit().getNumerator());
286
287      Quantity componentDenominatorQuantity = new Quantity(1, componentTargetQuantity.getUnit().getDenominator());
288      double scalingFactor = componentDenominatorQuantity.convertTo(getTargetQuantity().getUnit()).doubleValue() * getTargetQuantity().doubleValue();
289
290      componentQuantityNeeded = componentQuantityNeeded.multiplyBy(scalingFactor);
291
292      if (componentQuantityNeeded.getUnit().getQuantityType().equals(QuantityType.AMOUNT_OF_SUBSTANCE))
293      {
294         componentQuantityNeeded = componentQuantityNeeded.convertTo(AmountOfSubstanceUnit.mole);
295         // Convert from moles to grams using the molecular weight
296         componentQuantityNeeded = new Quantity(componentQuantityNeeded.multiplyBy(inComponent.getAverageMass()).doubleValue(), MassUnit.gram);
297      }
298
299      componentQuantityNeeded = componentQuantityNeeded.autoScale(sStandardMassScalingFactors);
300
301      return componentQuantityNeeded;
302   }
303}