Tutorial
Steps
The following steps are needed to calculate the permeation of an organic chemical to a pipe using pipepermcalc:
Define segment(s) of a pipe using the segment class (pipe material, size).
Create a pipe from the segment(s).
Set the groundwater contamination conditions (chemical, groundwater concentration and temperature) and mean daily flow rate.
Validate the input parameters.
Once a pipe has been created and the groundwater conditions and flow rate set, two types of calculations (forward and reverse) are possible:
Forward - calculate the maximum or mean daily concentration in drinking water from a known groundwater or soil concentration
Reverse - calculate the maximum or mean daily allowable concentration in groundwater to meet a drinking water limit value for a given chemical
For the mathematical equations behind the calcualtions, see the Mathematical Model.
Example 1 - Simple forward example
In a forward calculation the groundwater concentration known and we want to calculate the resulting drinking water concentration for our conditions.
We always start by importing pipepermcalc Pipe and Segment classes:
In [1]: from pipepermcalc.pipe import *
In [2]: from pipepermcalc.segment import *
Step 1: Define the pipe segment(s)
For this example there is only one pipe segment made of PE40. We define the length of the pipe in contact with the contamination plume, the inner diameter and wall_thickness of the pipe in meters.
In [3]: seg1 = Segment(name='seg1',
...: material='PE40',
...: length=25,
...: inner_diameter=0.0196,
...: wall_thickness=0.0027,)
...:
Step 2: Create a pipe from the segment(s)
We create a pipe from the segment using the Pipe() class by inputing the list of segment name(s).
In [4]: pipe1 = Pipe(segment_list=[seg1])
Step 3: Set the groundwater conditions and flow rate
Next we define the groundwater and pipe conditions. These are necessary if we want to calculate the concentration in drinking water for a given groundwater concentration. We define the chemical of interest, the concentration in groundwater (g/m3), groundwater temperature (degrees Celsius) and the flow rate (m3/day). The name of the chemical is checked against the chemical database and the closest matching chemical is printed.
In [5]: pipe1.set_conditions(chemical_name="Benzeen",
...: temperature_groundwater=12,
...: concentration_groundwater=1.8,
...: flow_rate=0.5)
...:
Input chemical name: Benzeen - Matched chemical name: Benzeen
Warning, no drinking water concentration was defined so the drinking water concentration has been set to the norm value: (0.001) g/m3.
The program gives a warning that no drinking water concentration was defined and the concentration has been set at the norm value. In the next step we will override this concentration and calculate the drinking water concentration for our defined conditions. This warning can be suppressed by setting suppress_warning = True.
Step 4: Validate the input parameters
Before we proceed with any calculations we first validate the input parameters. This step ensures we chose a valid pipe material, permeation direction and input positive values for concentrations, pipe dimensions etc.
In [6]: pipe1.validate_input_parameters()
Step 5: Calculate the drinking water concentration
For the given conditions we can calculate the peak and mean daily concentration in drinking water for the pipe. The peak concentration is calculated as the concentration after a stagnation period (e.g. at night when there is little or no flow in the pipe). The default stagnation time of 8 hours is used. Note: the peak is often, though not necessarily, higher than the mean concentration. Depending on the pipe dimensions and flow rate there can be situataions when the mean concentration is lower than the peak.
In [7]: peak_conc = pipe1.calculate_peak_dw_concentration()
In [8]: print("The peak concentration is:", round(peak_conc,4), "g/m3")
The peak concentration is: 0.0159 g/m3
In [9]: mean_conc = pipe1.calculate_mean_dw_concentration()
In [10]: print("The mean daily concentration is:", round(mean_conc,4), "g/m3")
The mean daily concentration is: 0.001 g/m3
Example 2 - Simple reverse example
In a reverse calculation the groundwater concentration is unknown and the drinking water concentration is set to a given value, often this value will be the drinking water limit. This calculations gives us the maximum concentration in groundwater which is possible without exceeding the set drinking water concentration.
The initial two steps are the same, defining the pipe segments and creating a pipe:
Step 1: Create pipe segments and define pipe
In [11]: seg2 = Segment(name='seg2',
....: material='PE40',
....: length=25,
....: inner_diameter=0.0196,
....: wall_thickness=0.0027)
....:
In [12]: pipe2 = Pipe(segment_list=[seg2])
Step 2: Calculate the allowable groundwater concentration
The drinking water concentration is given in the set_conditions() function (concentration_drinking_water), or if no concentration is specified, the default is set as the drinking water norm from the internal database. Both the groundwater concentration which would not exceed the peak and the mean daily concentration can be calculated.
In [13]: pipe2.set_conditions(chemical_name="Benzeen",
....: temperature_groundwater=12,
....: flow_rate=0.5)
....:
Input chemical name: Benzeen - Matched chemical name: Benzeen
Warning, no drinking water concentration was defined so the drinking water concentration has been set to the norm value: (0.001) g/m3.
In [14]: pipe2.validate_input_parameters()
In [15]: peak_conc = pipe2.calculate_peak_allowable_gw_concentration()
In [16]: print("The peak groundwater concentration, not exceeding the norm:", round(peak_conc,4), "g/m3")
The peak groundwater concentration, not exceeding the norm: 0.1124 g/m3
In [17]: mean_conc = pipe2.calculate_mean_allowable_gw_concentration()
In [18]: print("The mean groundwater concentration, not exceeding the norm:", round(mean_conc,4), "g/m3")
The mean groundwater concentration, not exceeding the norm: 1.8086 g/m3
Example 2 - Multiple segments
In this example we create a pipe made from multiple segments with different permeation directions.
Depending on the types of pipe segment, the permeation direction can either be perpendicular (default) or parallel to the flow direction in the pipe. The diffusion path length is the length of permeation through the pipe segment.
In scenarios 1 and 3 above, the permeation is perpendicular to the flow direction and the volume is calculated from the segment dimensions. The surface area is given as the inner surface area of the segment. In pipepermcalc the default permeation direction is perpendicular and the diffusion path length equal to the wall_thickness of the pipe length.
In the example shown above, permeation is parallel to the flow direction through a connecting rubber in scenario 2. For this scenario, the volume is assumed to be zero and the permeation surface area is the annular area of the rubber. The diffusion path length in this case is equal to the length of the segment.
In the following example we create a pipe made from two 5m PE40 pipe segments, joined by a EPDM ring with permeation parallel to the flow direction:
In [19]: seg1 = Segment(name='seg1',
....: material='PE40',
....: length=5,
....: inner_diameter=0.0196,
....: wall_thickness=0.0027)
....:
In [20]: seg2 = Segment(name='seg2',
....: material = 'EPDM',
....: length=0.06,
....: inner_diameter=0.025,
....: wall_thickness=0.001,
....: diffusion_path_length = 0.06,
....: permeation_direction = 'parallel')
....:
In [21]: seg3 = Segment(name='seg3',
....: material='PE40',
....: length=5,
....: inner_diameter=0.0196,
....: wall_thickness=0.0027)
....:
In [22]: pipe2 = Pipe(segment_list=[seg1, seg2, seg3])
As seen in the example above, only the segment with the parallel flow requires a specified permeation direction (default is perpendicular) and the diffusion path length (default is the wall_thickness).
Note: it is not possible to have a pipe made exclusively of segments with parallel permeation, at lease one segment must have permeation perpendicular to the flow.
The remaining calculations are done the same as for the simple example:
In [23]: pipe2.set_conditions(chemical_name="Benzeen",
....: temperature_groundwater=12,
....: concentration_groundwater=1.8,
....: flow_rate=0.5)
....:
Input chemical name: Benzeen - Matched chemical name: Benzeen
Warning, no drinking water concentration was defined so the drinking water concentration has been set to the norm value: (0.001) g/m3.
In [24]: pipe2.validate_input_parameters()
In [25]: peak_conc = pipe2.calculate_peak_dw_concentration()
In [26]: print("The peak concentration is:", round(peak_conc,4), "g/m3")
The peak concentration is: 0.0159 g/m3
In [27]: mean_conc = pipe2.calculate_mean_dw_concentration()
In [28]: print("The mean daily concentration is:", round(mean_conc,4), "g/m3")
The mean daily concentration is: 0.0004 g/m3
Example 4 - Calculating in loops
Calculate the concentration of multiple chemicals for a pipe
In some cases you may be interested in calculating the permeation for several differnt chemicals for the same pipe. It is possible to do this by looping over a list of chemicals and performing the calculations using the same pipe.
In [29]: seg1 = Segment(name='seg1', material='PE40', length=25, inner_diameter=0.0196, wall_thickness=0.0027)
In [30]: pipe3 = Pipe(segment_list=[seg1])
In [31]: chemicals = ['benzene','ethylbenzene', 'toluene']
In [32]: for chemical in chemicals:
....: pipe3.set_conditions(
....: concentration_groundwater=0.1, #g/m3
....: chemical_name=chemical,
....: temperature_groundwater=12,
....: flow_rate=0.5,
....: suppress_print=True,
....: suppress_warning = True)
....:
In [33]: pipe3.validate_input_parameters()
In [34]: mean_conc = pipe3.calculate_mean_dw_concentration()
In [35]: print("The mean drinking water concentration for", chemical, "is:", round(mean_conc,8), "g/m3")
The mean drinking water concentration for toluene is: 0.00017556 g/m3
Example 5 - Advanced settings
Change the partitioning and diffusion coefficient
The model contains a chemical database from which the partitioning (Kpw) and diffusion (Dp) coefficients for the given plastic pipes are calculated. However, it is also possible to input a specific a partitioning and diffusion coefficient for a pipe segment. This must be done after setting the conditions of the pipe using .set_conditions().
In [36]: seg1 = Segment(name='seg1',
....: material='PE40',
....: length=25,
....: inner_diameter=0.0196,
....: wall_thickness=0.0027,
....: )
....:
In [37]: pipe3 = Pipe(segment_list=[seg1])
In [38]: pipe3.set_conditions(chemical_name="Benzeen",
....: temperature_groundwater=12,
....: concentration_groundwater=1.8,)
....:
Input chemical name: Benzeen - Matched chemical name: Benzeen
Warning, no drinking water concentration was defined so the drinking water concentration has been set to the norm value: (0.001) g/m3.
In [39]: print(seg1.log_Kpw, seg1.log_Dp)
1.47223309968451 -12.243586769549616
In [40]: seg1.log_Kpw = 0.912
In [41]: seg1.log_Dp= -10.63
In [42]: print(seg1.log_Kpw, seg1.log_Dp)
0.912 -10.63
Change the tolerance and max_iterations
When calculating the concentration in drinking water or the allowable concentration in groundwater, the calculations are iterative and it is possible to specify the tolerance and maximum number of iterations.
The tolerance is the degree of acceptable error in the accuracy of the calculation, default value of 0.01 (1%).
The maximum number of iterations is the maximum number of calculations allowed before the calculation stops. A default value of 1000 is used.
These values can be manually changed in the four concentration calculations by specifying the tolerance and/or max_iterations:
In [43]: seg1 = Segment(name='seg1',
....: material='PE40',
....: length=25,
....: inner_diameter=0.0196,
....: wall_thickness=0.0027)
....:
In [44]: pipe4 = Pipe(segment_list=[seg1])
In [45]: pipe4.set_conditions(concentration_drinking_water=0.001,
....: chemical_name="Benzeen",
....: temperature_groundwater=12,
....: flow_rate=0.5)
....:
Input chemical name: Benzeen - Matched chemical name: Benzeen
In [46]: pipe4.validate_input_parameters()
In [47]: mean_conc = pipe4.calculate_mean_allowable_gw_concentration(tolerance = 0.1,
....: max_iterations=1000)
....:
In [48]: print("The mean concentration is:", round(mean_conc,3), "g/m3")
The mean concentration is: 1.774 g/m3
In [49]: peak_conc = pipe4.calculate_mean_allowable_gw_concentration(tolerance = 0.001,
....: max_iterations=1000)
....:
In [50]: print("The peak concentration is:", round(peak_conc,3), "g/m3")
The peak concentration is: 1.801 g/m3
Miscellaneous Functions
The choice of pipe materials are: ‘PE40’, ‘PE80’, ‘SBR’, ‘EPDM’, ‘PVC’. Note: The model assumes no permeation in PVC pipes.
The individual segment information, e.g. volume, permeation surface area, logK, LogD etc., are attributes of the segments themselves:
In [51]: seg1.volume
Out[51]: 0.007542963961269094
In [52]: seg1.permeation_surface_area
Out[52]: 1.5393804002589986
In [53]: seg1.log_Dp
Out[53]: -12.24358635121127
In [54]: seg1.log_Kpw
Out[54]: 1.4722331551542442
The flow rate, chemical information and the concentrations in drinking water, groundwater and/or soil are attributes of the pipe:
In [55]: pipe1.flow_rate
Out[55]: 0.5
In [56]: pipe1.solubility
Out[56]: 1988.898826
In [57]: pipe1.concentration_drinking_water
Out[57]: 0.0010001918150199251
In [58]: pipe1.concentration_groundwater
Out[58]: 1.8
In [59]: pipe1.concentration_soil
Out[59]: 2.7397260263886545
It is possible to view the norm values and other chemical information from the database for the specific chemical defined in set_conditions:
In [60]: pipe1.chemical_information
Out[60]:
{'CAS_number': '71-43-2',
'chemical_name_NL': 'Benzeen',
'chemical_name_EN': 'Benzene',
'molecular_weight': 78.10666652,
'solubility': 1988.898826,
'log_octanol_water_partitioning_coefficient': 2.13,
'log_distribution_coefficient': 0.659555885,
'chemical_group': 'MAK',
'chemical_group_number': 1.0,
'molecular_volume': 89.4,
'Drinking_water_norm': 0.001}
To view the whole chemical database:
In [61]: print(pipe1.ppc_database)
CAS_number ... Drinking_water_norm
0 66-25-1 ... NaN
1 71-55-6 ... 0.0010
2 79-34-5 ... NaN
3 79-00-5 ... 0.0010
4 75342 ... 0.0010
.. ... ... ...
422 108-88-3 ... 0.0010
423 NaN ... 0.0001
424 1979-01-06 ... 0.0100
425 NaN ... NaN
436 1975-01-04 ... 0.0001
[299 rows x 11 columns]
To view a list of chemicals in the database:
In [62]: chemical_options = list(pipe1.ppc_database.chemical_name_NL)
In [63]: print(sorted(chemical_options))
['1,1,1-Trichloorethaan', '1,1,2,2-Tetrachloorethaan', '1,1,2-Trichloorethaan', '1,1-Dichloorethaan', '1,1-dichlooretheen', '1,2,3,4-tetrachlorobenzene', '1,2,3,5-tetrachlorobenzene', '1,2,3-Trichloorbenzeen', '1,2,3-Trimethylbenzene', '1,2,3-trichloorpropaan', '1,2,3-trimethylbenzeen', '1,2,4,5-tetrachlorobenzene', '1,2,4-Trichloorbenzeen', '1,2,4-trimethylbenzeen', '1,2-Dichloorbenzeen', '1,2-Dichloorethaan', '1,2-Dichlooretheen(cis)', '1,2-Dichloorfenol', '1,2-dichlooretheen(cisentrans)', '1,2-dichlooretheen(trans)', '1,2-dichloorpropaan', '1,3,5-Trichloorbenzeen', '1,3,5-Trimethylbenzeen', '1,3-Dichloorbenzeen', '1,3-dichloorpropaan', '1,4-Dichloorbenzeen', '1,4-Dioxane {p-dioxane} ', '1-Butanol', '1-Chloronaphthalene', '1-Hexanol {Hexylalcohol}', '1-Pentanol', '1-chloorbutaan', '1-chloornaftaleen', '2,2,4-Trimethylpentaan', '2,3,4-Trichloorfenol', '2,3,5,6-Tetrachloorfenol', '2,3,7-TriCDD', '2,4,6-Trichloorfenol', '2,4-dichloorfenol', '2,5-dichloorfenol', '2,6-dichloorfenol', '2,7/8-DiCDD', '2-CDD', '2-Chlooraniline', '2-Hexanol', '2-Pentanol', '2-Propanol {isopropyl alcohol}', '2-chloorfenol', '2-chloornaftaleen', '2-methylanthracene', '2-methylnaphthalene', '3,4,5-Trichloorfenol', '3,6-dimethylphenanthrene', '3-Chlooraniline', '3-Methylpentane', '3-chloorfenol', '4-Chlooraniline', '4-chloorfenol', '9,10-dimethylanthracene', 'Acetone', 'Acetonitrile', 'Aniline', 'Antraceen', 'BDE-1', 'BDE-100', 'BDE-126', 'BDE-15', 'BDE-153', 'BDE-154', 'BDE-17', 'BDE-2', 'BDE-28', 'BDE-47', 'BDE-66', 'BDE-71', 'BDE-85', 'BDE-99', 'Benzaldehyde', 'Benzeen', 'Benzyl alcohol', 'Benzylchloride', 'Bifenyl', 'Butylalcohol {Butanol]', 'Butyraldehyde', 'Butyraldehyde (butanal)', 'Chloordaan', 'Chlorobenzene', 'Chloroform', 'Cyclohexaan', 'Cyclopopane', 'Di-isopropylether', 'Di-isopropylketon', 'Dichloormethaan / Methyleenchloride', 'Dimethyl sulfoxide', 'Dimethylftalaat', 'ETBE (Ethyl tert-butyl ether) ', 'Ethane', 'Ethanol', 'Ethylacetaat', 'Ethylbenzeen', 'Ethylene', 'Ethylpropionate', 'Fenanthreen', 'Fenol', 'HCB', 'Heptaan (n-heptaan)', 'Heptanol', 'Hexaan', 'Hexachloorbenzeen', 'Hexaldehyde', 'Hexaldehyde', 'Hexene', 'Isobutyleen', 'MCPA', 'Methaan', 'Methanol', 'Methoxybenzeen', 'Methoxybenzene', 'Methyl iso-Buthyl Ketone', 'Methyl tert-butyl ether {MTBE}', 'Methylethylketon {Butanone}', 'Minerale olie (C10)', 'Minerale olie (C10-C12)', 'Minerale olie (C11)', 'Minerale olie (C12)', 'Minerale olie (C12-C16)', 'Minerale olie (C13)', 'Minerale olie (C14)', 'Minerale olie (C15)', 'Minerale olie (C16)', 'Minerale olie (C16-C21)', 'Minerale olie (C17)', 'Minerale olie (C18)', 'Minerale olie (C19)', 'Minerale olie (C20)', 'Minerale olie (C21)', 'Minerale olie (C21-C30)', 'Minerale olie (C22)', 'Minerale olie (C23)', 'Minerale olie (C24)', 'Minerale olie (C25)', 'Minerale olie (C26)', 'Minerale olie (C27)', 'Minerale olie (C28)', 'Minerale olie (C29)', 'Minerale olie (C30)', 'Minerale olie (C31)', 'Minerale olie (C32)', 'Minerale olie (C33)', 'Minerale olie (C34)', 'Minerale olie (C35)', 'Minerale olie (C36)', 'Minerale olie (C37)', 'Minerale olie (C38)', 'Minerale olie (C39)', 'Minerale olie (C40)', 'Monochlooranilinen', 'Monochloorbenzeen', 'Naftaleen', 'Neopentaan', 'Nitrobenzeen', 'Octaan', 'PCB 10', 'PCB 101', 'PCB 104', 'PCB 105', 'PCB 110', 'PCB 118', 'PCB 128', 'PCB 129', 'PCB 137', 'PCB 138', 'PCB 14', 'PCB 141', 'PCB 143', 'PCB 145', 'PCB 149', 'PCB 151', 'PCB 153', 'PCB 155', 'PCB 156', 'PCB 170', 'PCB 18', 'PCB 180', 'PCB 187', 'PCB 204', 'PCB 21', 'PCB 28', 'PCB 29', 'PCB 30', 'PCB 31', 'PCB 4', 'PCB 44', 'PCB 47', 'PCB 49', 'PCB 50', 'PCB 52', 'PCB 55', 'PCB 56', 'PCB 66', 'PCB 69', 'PCB 78', 'PCB 8', 'PCB 85', 'PCB 87', 'PCB 97', 'PCB 99', 'Pentachloorbenzeen', 'Pentachloorfenol', 'Pentanal', 'Pentylbenzeen', 'Propaan', 'Propionitrile', 'Propylbenzeen', 'Propylene (Propeen??)', 'Styrene', 'Tetrachloorbenzeen', 'Tetrachlooretheen', 'Tetrachloormethaan', 'Tetrafluoromethane', 'Tolueen', 'Tribroommethaan', 'Trichlooretheen', 'Vinylchloride', 'acenaphthene', 'aldrin', 'atrazine', 'benz[a]anthracene', 'benzo(g,h,i)perylene', 'benzo[a]pyrene', 'benzo[b]fluoranthene', 'benzo[e]pyrene', 'benzo[k]fluoranthene', 'bifenly ether', 'butylbenzeen', 'butylbenzylftalaat(BBP)', 'carbaryl', 'carbofuran', 'chloorethaan', 'chloorpentaan', 'chrysene', 'cis-Hex-3-en-1-ol', 'cyclohexanon', 'd12-benz[a]anthracene', 'di(2-ethylhexyl)ftalaat', 'di-ethylether', 'di-isobutylftalaat(DIBP)', 'dibuthylftalaat(DBP)', 'dieldrin', 'diethylftalaat(DEP)', 'dihexylftalaat(DHP)', 'dimethylftalaat(DMP)', 'endrin', 'fluoranthene', 'fluorene', 'heptachlor', 'heptachlor epoxide', 'i-propylbenzeen', 'indeno[1,2,3-cd]pyrene', 'm-Chloortolueen', 'm-Cresol', 'm-Nitroaniline', 'm-Xyleen', 'me-triclosan', 'methoxychlor', 'n-Butaan {Butaan}', 'n-Methylaniline', 'n-Pentaan', 'n-Pentane', 'n-nonylphenol', 'n-octylphenol', 'nonaan', 'o,p’-DDD', 'o,p’-DDE', 'o,p’-DDT', 'o-Chloortolueen', 'o-Cresol', 'o-Xyleen', 'p,p’-DDD', 'p,p’-DDE', 'p,p’-DDT', 'p-Chloorfenol', 'p-Chloortolueen', 'p-Cresol', 'p-Xyleen', 'perylene', 'phenanthrene', 'pyrene', 'pyridine', 't-octylphenol', 'tetrahydrofuran', 'tetrahydrothiophene', 'triclosan', 'α,α,α-Trichloortolueen', 'α-HCH', 'α-endosulfan', 'β-HCH', 'γ-HCH', 'δ-HCH']
Model Testing
The model has been tested by calculating the concentration in drinking water given a known groundwater concentration and feeding that drinking water concentration into the model again and verifying the same groundwater concentration is output. This is done for both the peak and mean concentrations for all chemicals in the database where the molecular weight, solubility and drinking water norm were known. In addition, the drinking water norm was less than the solubility limit.