Working with solutions

A more advanced entity in hydrosolver is Solution. Solutions consist of a few compositions and can be constructed in different ways. Solutions can be added, scaled, extended and merged.

Defining a solution

To define a solution we must first define the compositions constituting it. Let us consider a simple example:

>>> from hydrosolver.composition import Composition
>>> from hydrosolver.solution import Solution
>>> water = Composition('Pure water')
>>> CN = Composition.from_dict(
...     {'Calcium nitrate tetrahydrate':
...         {'N (NO3-)': 0.1186, 'Ca': 0.1697}}
...     )
>>> solution_CN_10 = Solution(
...     compositions=[CN, water],
...     formulation=[0.1, 0.9],
...     )
>>> solution_CN_10
Composition                     Amount in kg    Amount in g
----------------------------  --------------  -------------
Calcium nitrate tetrahydrate             0.1            100
Pure water                               0.9            900
Total:                                   1             1000

Composition: Resulting composition

Nutrient      Ratio    Amount mg/kg
----------  -------  --------------
N (NO3-)    0.01186           11860
Ca          0.01697           16970

Here we just defined a 10% (by mass) aqueous solution of calcium nitrate tetrahydrate. It’s total mass is given by solution_CN_10.mass and equals to 1 [kg]. However, if the solution to construct consists of multiple compositions, it becomes more difficult to adjust the mass of the water. For this purpose there is an alternative constructor Solution.dissolve():

>>> solution_CN_10 = Solution.dissolve(
...     mass=1,
...     water=water,
...     compositions_=[CN],
...     formulation_=[0.1],
...     )
>>> solution_CN_10
Composition                     Amount in kg    Amount in g
----------------------------  --------------  -------------
Calcium nitrate tetrahydrate             0.1            100
Pure water                               0.9            900
Total:                                   1             1000

Composition: Resulting composition

Nutrient      Ratio    Amount mg/kg
----------  -------  --------------
N (NO3-)    0.01186           11860
Ca          0.01697           16970

As one can see, for dissolve we first pass the desired total mass of the solution, then the composition which will be used for aligning (typically the water) and the truncated lists of compositions and their amounts without the last element, which will be substituted with water. This way fits more for defining solutions consisting of many compositions:

>>> MS = Composition.from_dict(
...     {'Magnesium sulfate heptahydrate':
...         {'Mg': 0.0986, 'S': 0.1301}}
...     )
>>> my_solution = Solution.dissolve(
...     mass=1,
...     water=water,
...     compositions_=[CN, MS],
...     formulation_=[0.002, 0.001],
...     )
>>> my_solution
Composition                       Amount in kg    Amount in g
------------------------------  --------------  -------------
Calcium nitrate tetrahydrate             0.002              2
Magnesium sulfate heptahydrate           0.001              1
Pure water                               0.997            997
Total:                                   1               1000

Composition: Resulting composition

Nutrient        Ratio    Amount mg/kg
----------  ---------  --------------
N (NO3-)    0.0002372           237.2
Mg          9.86e-05             98.6
Ca          0.0003394           339.4
S           0.0001301           130.1

Operations on solutions

The available operations on solutions can be split into two cathegories.

Operations preserving compositions

Any solution can be multiplied by a scalar. Two solutions defined in the same basis (i.e. consisting of the same compositions listed in the same order) can be added (and hence subtracted):

>>> 100 * my_solution
Composition                       Amount in kg    Amount in g
------------------------------  --------------  -------------
Calcium nitrate tetrahydrate               0.2            200
Magnesium sulfate heptahydrate             0.1            100
Pure water                                99.7          99700
Total:                                   100           100000

Composition: Resulting composition

Nutrient        Ratio    Amount mg/kg
----------  ---------  --------------
N (NO3-)    0.0002372           237.2
Mg          9.86e-05             98.6
Ca          0.0003394           339.4
S           0.0001301           130.1
>>> solution_CN_20 = Solution.dissolve(1, water, [CN], [0.2])
>>> 5 * solution_CN_20 + 10 * solution_CN_20
Composition                     Amount in kg    Amount in g
----------------------------  --------------  -------------
Calcium nitrate tetrahydrate               3           3000
Pure water                                12          12000
Total:                                    15          15000

Composition: Resulting composition

Nutrient      Ratio    Amount mg/kg
----------  -------  --------------
N (NO3-)    0.02372           23720
Ca          0.03394           33940

Another operation preserving the compositions is align(). It adjusts the total mass of the solution to the specified value by changing the amount of the last composition (typically water):

>>> solution_CN_20.align(10)
>>> solution_CN_20
Composition                     Amount in kg    Amount in g
----------------------------  --------------  -------------
Calcium nitrate tetrahydrate             0.2            200
Pure water                               9.8           9800
Total:                                  10            10000

Composition: Resulting composition

Nutrient       Ratio    Amount mg/kg
----------  --------  --------------
N (NO3-)    0.002372            2372
Ca          0.003394            3394

Operations extending compositions

An existing solution can be modified by adding another composition in the specified amount:

>>> MAP = Composition.from_dict(
...     {'Monoammonium phosphate':
...         {'N (NH4+)': 0.12177, 'P': 0.26928}}
...     )
>>> my_solution.add(MAP, 0.001)
>>> my_solution
Composition                       Amount in kg    Amount in g
------------------------------  --------------  -------------
Calcium nitrate tetrahydrate             0.002              2
Magnesium sulfate heptahydrate           0.001              1
Monoammonium phosphate                   0.001              1
Pure water                               0.996            996
Total:                                   1               1000

Composition: Resulting composition

Nutrient         Ratio    Amount mg/kg
----------  ----------  --------------
N (NO3-)    0.0002372           237.2
N (NH4+)    0.00012177          121.77
P           0.00026928          269.28
Mg          9.86e-05             98.6
Ca          0.0003394           339.4
S           0.0001301           130.1

This operation does not return a new solution but always modifies the given one in place. Notice that by default the aligning operation is performed when add is called.

Any solutions can be merged which will result in a nes solution:

>>> solution_a = Solution.dissolve(1, water, [CN], [0.002])
>>> solution_b = Solution.dissolve(1, water, [MS, MAP], [0.001, 0.001])
>>> solution_a.merge(solution_b)
Composition                       Amount in kg    Amount in g
------------------------------  --------------  -------------
Calcium nitrate tetrahydrate             0.002              2
Magnesium sulfate heptahydrate           0.001              1
Monoammonium phosphate                   0.001              1
Pure water                               1.996           1996
Total:                                   2               2000

Composition: Resulting composition

Nutrient         Ratio    Amount mg/kg
----------  ----------  --------------
N (NO3-)    0.0001186          118.6
N (NH4+)    6.0885e-05          60.885
P           0.00013464         134.64
Mg          4.93e-05            49.3
Ca          0.0001697          169.7
S           6.505e-05           65.05

Correcting solutions

Adjusting the pH level

It is a common task to adjust the pH level of an existing nutrient solution by adding some accid (typically either nitric acid or phosphoric acid) or some base (typically potassium hydroxide). For this purpose one needs to weight the pH corrector and add it to the solution:

>>> solution_ms = Solution.dissolve(1, water, [MS], [0.002])
>>> KOH_94 = 0.94 * Composition.from_dict(
...     {'Potassium hydroxide': {'K': 0.69687}}
...     )
>>> solution_ms.add(KOH_94, 0.000120)
>>> solution_ms
Composition                       Amount in kg    Amount in g
------------------------------  --------------  -------------
Magnesium sulfate heptahydrate         0.002             2
0.94 * (Potassium hydroxide)           0.00012           0.12
Pure water                             0.99788         997.88
Total:                                 1              1000

Composition: Resulting composition

Nutrient          Ratio    Amount mg/kg
----------  -----------  --------------
K           7.86069e-05         78.6069
Mg          0.0001972          197.2
S           0.0002602          260.2