pyEQL 0.5.2__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. pyEQL/__init__.py +50 -43
  2. pyEQL/activity_correction.py +481 -707
  3. pyEQL/database/geothermal.dat +5693 -0
  4. pyEQL/database/llnl.dat +19305 -0
  5. pyEQL/database/phreeqc_license.txt +54 -0
  6. pyEQL/database/pyeql_db.json +35902 -0
  7. pyEQL/engines.py +793 -0
  8. pyEQL/equilibrium.py +148 -228
  9. pyEQL/functions.py +121 -416
  10. pyEQL/pint_custom_units.txt +2 -2
  11. pyEQL/presets/Ringers lactate.yaml +20 -0
  12. pyEQL/presets/normal saline.yaml +17 -0
  13. pyEQL/presets/rainwater.yaml +17 -0
  14. pyEQL/presets/seawater.yaml +29 -0
  15. pyEQL/presets/urine.yaml +26 -0
  16. pyEQL/presets/wastewater.yaml +21 -0
  17. pyEQL/salt_ion_match.py +53 -284
  18. pyEQL/solute.py +126 -191
  19. pyEQL/solution.py +2163 -2090
  20. pyEQL/utils.py +211 -0
  21. pyEQL-1.0.3.dist-info/AUTHORS.md +13 -0
  22. {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/COPYING +1 -1
  23. pyEQL-0.5.2.dist-info/LICENSE → pyEQL-1.0.3.dist-info/LICENSE.txt +3 -7
  24. pyEQL-1.0.3.dist-info/METADATA +131 -0
  25. pyEQL-1.0.3.dist-info/RECORD +27 -0
  26. {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/WHEEL +1 -1
  27. pyEQL/chemical_formula.py +0 -1006
  28. pyEQL/database/Erying_viscosity.tsv +0 -18
  29. pyEQL/database/Jones_Dole_B.tsv +0 -32
  30. pyEQL/database/Jones_Dole_B_inorganic_Jenkins.tsv +0 -75
  31. pyEQL/database/LICENSE +0 -4
  32. pyEQL/database/dielectric_parameter.tsv +0 -30
  33. pyEQL/database/diffusion_coefficient.tsv +0 -116
  34. pyEQL/database/hydrated_radius.tsv +0 -35
  35. pyEQL/database/ionic_radius.tsv +0 -35
  36. pyEQL/database/partial_molar_volume.tsv +0 -22
  37. pyEQL/database/pitzer_activity.tsv +0 -169
  38. pyEQL/database/pitzer_volume.tsv +0 -132
  39. pyEQL/database/template.tsv +0 -14
  40. pyEQL/database.py +0 -300
  41. pyEQL/elements.py +0 -4552
  42. pyEQL/logging_system.py +0 -53
  43. pyEQL/parameter.py +0 -435
  44. pyEQL/tests/__init__.py +0 -32
  45. pyEQL/tests/test_activity.py +0 -578
  46. pyEQL/tests/test_bulk_properties.py +0 -86
  47. pyEQL/tests/test_chemical_formula.py +0 -279
  48. pyEQL/tests/test_debye_length.py +0 -79
  49. pyEQL/tests/test_density.py +0 -106
  50. pyEQL/tests/test_dielectric.py +0 -153
  51. pyEQL/tests/test_effective_pitzer.py +0 -276
  52. pyEQL/tests/test_mixed_electrolyte_activity.py +0 -154
  53. pyEQL/tests/test_osmotic_coeff.py +0 -99
  54. pyEQL/tests/test_pyeql_volume_concentration.py +0 -428
  55. pyEQL/tests/test_salt_matching.py +0 -337
  56. pyEQL/tests/test_solute_properties.py +0 -251
  57. pyEQL/water_properties.py +0 -352
  58. pyEQL-0.5.2.dist-info/AUTHORS +0 -7
  59. pyEQL-0.5.2.dist-info/METADATA +0 -72
  60. pyEQL-0.5.2.dist-info/RECORD +0 -47
  61. pyEQL-0.5.2.dist-info/entry_points.txt +0 -3
  62. {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/top_level.txt +0 -0
pyEQL/chemical_formula.py DELETED
@@ -1,1006 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- This module contains classes, functions, and methods to facilitate the
4
- input, output, and parsing of chemical formulas for pyEQL.
5
-
6
- The correct case must be used when specifying elements.
7
-
8
- :copyright: 2013-2020 by Ryan S. Kingsbury
9
- :license: LGPL, see LICENSE for more details.
10
-
11
- """
12
-
13
- # logging system
14
- import logging
15
-
16
- # add a filter to emit only unique log messages to the handler
17
- from pyEQL.logging_system import Unique
18
-
19
- logger = logging.getLogger(__name__)
20
- unique = Unique()
21
- logger.addFilter(unique)
22
-
23
- # add a handler for console output, since pyEQL is meant to be used interactively
24
- ch = logging.StreamHandler()
25
-
26
- # create formatter for the log
27
- formatter = logging.Formatter("(%(name)s) - %(levelname)s - %(message)s")
28
-
29
- # add formatter to the handler
30
- ch.setFormatter(formatter)
31
- logger.addHandler(ch)
32
-
33
-
34
- # Formula validation and processing functions. These internal routines
35
- # parse chemical formulas into a format that can be easily processed
36
- # by user-facing functions.
37
- def _invalid_formula(reason):
38
- raise ValueError("Invalid chemical formula specified - %s" % reason)
39
- return None
40
-
41
-
42
- def _check_formula(formula):
43
- """
44
- Parse a chemical formula into a list that separates atomic symbols,
45
- numbers, and parentheses, and check the formula for compliance with
46
- formatting rules.
47
-
48
- Similar to Python's default list() function for strings.
49
-
50
- Parameters
51
- ----------
52
- formula: str
53
- String representing a molecular formula. e.g. 'H2O' or 'FeOH+'
54
- Valid molecular formulas must meet the following criteria:
55
-
56
- #. Are composed of valid atomic symbols that start with capital letters
57
- #. Contain no non-alphanumeric characters other than '(', ')',
58
- '+', or '-'
59
- #. If a '+' or '-' is present, the formula must contain ONLY '+' or
60
- '-' (e.g. 'Na+-' is invalid) and the formula must end with either
61
- a series of charges (e.g. 'Fe+++') or a numeric charge (e.g. 'Fe+3')
62
- #. Formula must contain matching numbers of '(' and ')'
63
- #. Open parentheses must precede closed parentheses
64
-
65
-
66
- Examples
67
- --------
68
- >>> _check_formula('Fe2(SO4)3')
69
- ['Fe', '2', '(', 'S', 'O', '4', ')', '3']
70
- >>> _check_formula('C10H12')
71
- ['C', '10', 'H', '12']
72
- """
73
-
74
- # check that formula starts with a letter or open parenthesis
75
- if not (formula[0].isalpha() or formula[0] == "("):
76
- _invalid_formula("formula must begin with an element or open parenthesis")
77
-
78
- # check for mismatched charges
79
- if "+" in formula and "-" in formula:
80
- _invalid_formula("ionic formulas cannot contain mismatched charge symbols")
81
-
82
- # check that ionic formulas end with either a number of a charge symbol
83
- if "+" in formula:
84
- if formula.count("+") == 1 and not (
85
- formula[-1] == "+" or formula[-1].isnumeric()
86
- ):
87
- _invalid_formula(
88
- "ionic formulas must end with one or more charge symbols or a single charge symbol and a number"
89
- )
90
- elif formula.count("+") > 1:
91
- start = formula.find("+")
92
- for char in formula[start:]:
93
- if char != "+":
94
- _invalid_formula(
95
- "ionic formulas must end with one or more charge symbols or a single charge symbol and a number"
96
- )
97
- elif "-" in formula:
98
- if formula.count("-") == 1 and not (
99
- formula[-1] == "-" or formula[-1].isnumeric()
100
- ):
101
- _invalid_formula(
102
- "ionic formulas must end with one or more charge symbols or a single charge symbol and a number"
103
- )
104
- elif formula.count("-") > 1:
105
- start = formula.find("-")
106
- for char in formula[start:]:
107
- if char != "-":
108
- _invalid_formula(
109
- "ionic formulas must end with one or more charge symbols or a single charge symbol and a number"
110
- )
111
-
112
- # check for equal parentheses
113
- if formula.count("(") != formula.count(")"):
114
- _invalid_formula("parentheses mismatch")
115
-
116
- # make sure open parenthesis doesn't end the formula
117
- if formula.endswith("("):
118
- _invalid_formula("formula cannot end with open parenthesis")
119
-
120
- # split the formula string into a list of characters
121
- input_list = list(formula)
122
-
123
- for i in range(len(input_list)):
124
- try:
125
- # check for invalid characters
126
- parentheses = ["(", ")"]
127
- charge_symbols = ["+", "-"]
128
-
129
- if not (
130
- input_list[i].isalnum()
131
- or input_list[i] in parentheses
132
- or input_list[i] in charge_symbols
133
- ):
134
- _invalid_formula("contains invalid character")
135
-
136
- elif input_list[i] == "(":
137
- # check that open parentheses are followed by an atomic symbol
138
- if not input_list[i + 1].isalpha():
139
- _invalid_formula("parentheses must contain elements")
140
- # make sure that the open parenthesis precedes the nearest closed
141
- # parenthesis
142
- try:
143
- if not i < input_list.index(")", i):
144
- _invalid_formula(
145
- "open parenthesis must precede closed parenthesis"
146
- )
147
- # add exception for ValueError, in case there is no closed parenthesis
148
- # after index i
149
- except ValueError:
150
- _invalid_formula("open parenthesis must precede closed parenthesis")
151
-
152
- # removed this rule to allow for organic structural formulas
153
- # check that closed parenthesis are followed by a number
154
- # elif input_list[i] == ')':
155
- # try:
156
- # if not input_list[i+1].isnumeric():
157
- # _invalid_formula('parnetheses must be followed by numbers')
158
- # except IndexError:
159
- # _invalid_formula('parentheses must be followed by numbers')
160
-
161
- # concatenate any uppercase letters with up to two subsequent lowercase letters
162
- elif input_list[i].isupper():
163
- try:
164
- if input_list[i + 1].islower():
165
- try:
166
- if input_list[i + 2].islower():
167
- char = input_list.pop(i + 2)
168
- input_list[i + 1] += char
169
- except IndexError:
170
- pass
171
- char = input_list.pop(i + 1)
172
- input_list[i] += char
173
- except IndexError:
174
- pass
175
-
176
- # concatenate any adjacent numbers together
177
- elif input_list[i].isnumeric():
178
- try:
179
- j = i + 1
180
- while input_list[j].isnumeric():
181
- char = input_list.pop(j)
182
- input_list[i] += char
183
- except IndexError:
184
- pass
185
-
186
- # concatenate adjacent + or -
187
- elif input_list[i] == "+" or input_list[i] == "-":
188
- try:
189
- if input_list[i + 1] == "+" or input_list[i + 1] == "-":
190
- j = i + 1
191
- while input_list[j] == "+" or input_list[j] == "-":
192
- char = input_list.pop(j)
193
- input_list[i] += char
194
- except IndexError:
195
- pass
196
-
197
- else:
198
- pass
199
-
200
- except IndexError:
201
- pass
202
-
203
- # check that all elements are valid
204
- for item in input_list:
205
- if item.isalpha():
206
- if not is_valid_element(item):
207
- _invalid_formula("invalid element symbol")
208
-
209
- return input_list
210
-
211
-
212
- def _remove_parentheses(formula):
213
- """
214
- Remove parentheses from a formula and distribute the associated numbers
215
- as appropriate.
216
-
217
- NOTE: does not support nested parentheses as these violate
218
- the formatting rules for chemical formulas
219
-
220
- >>> _remove_parentheses('(Fe2)(SO4)3')
221
- ['Fe', '2', 'S', '3', 'O', '12']
222
-
223
-
224
- See Also
225
- --------
226
- _check_formula
227
- """
228
-
229
- # perform validity check and return a list of the chemical formula's components
230
- input_list = _check_formula(formula)
231
- output_list = []
232
-
233
- # remove all parentheses from the formula and distribute numbers accordingly
234
- i = 0
235
- while i < len(input_list):
236
- if input_list[i] == "(":
237
- # locate the beginning and end indices of the parenthetical group
238
- start = i
239
- stop = input_list.index(")", i)
240
-
241
- # locate the number after the group, if any
242
- if input_list[stop + 1].isnumeric():
243
- num = int(input_list[stop + 1])
244
- # skip past the parenthetical group once the loop is done
245
- i = stop + 2
246
- else:
247
- num = 1
248
- # skip past the parenthetical group once the loop is done
249
- i = stop + 1
250
-
251
- # loop through the elements / numbers contained in the group
252
- for j in range(start + 1, stop):
253
- if input_list[j].isalpha() and input_list[j + 1].isnumeric():
254
- output_list.append(input_list[j])
255
- output_list.append(str(int(input_list[j + 1]) * num))
256
- elif input_list[j].isalpha():
257
- output_list.append(input_list[j])
258
- if num > 1:
259
- output_list.append(str(num))
260
-
261
- else:
262
- output_list.append(input_list[i])
263
- # advance to the next list element
264
- i = i + 1
265
-
266
- return output_list
267
-
268
-
269
- def _consolidate_formula(formula):
270
- """
271
- Consolidate a formula into its simplest form, containing only one
272
- instance of each element and no parentheses
273
-
274
- Examples
275
- --------
276
- >>> _consolidate_formula('CH3(CH2)6CH3')
277
- ['C', 8, 'H', 18]
278
- >>> _consolidate_formula('(Fe)2(SO4)4')
279
- ['Fe', 2, 'S', 4, 'O', 16]
280
- >>> _consolidate_formula('Fe(OH)2+')
281
- ['Fe', 1, 'O', 2, 'H', 2, '+1']
282
-
283
- """
284
- # perform validity check and return a list of the chemical formula's components
285
- input_list = _remove_parentheses(formula)
286
- output_list = []
287
-
288
- for i in range(0, len(input_list)):
289
- # is the item an element, a number, or a charge?
290
- if input_list[i].isalpha():
291
- # is it followed by a number?
292
- try:
293
- if input_list[i + 1].isnumeric():
294
- quantity = input_list[i + 1]
295
- else:
296
- quantity = 1
297
- except IndexError:
298
- quantity = 1
299
-
300
- # have we seen it before?
301
- if input_list[i] in output_list:
302
- # yes, so find it in the output list
303
- index = output_list.index(input_list[i])
304
- # add the quantity
305
- output_list[index + 1] += int(quantity)
306
- else:
307
- # no, so add it and its quantity
308
- output_list.append(input_list[i])
309
- output_list.append(int(quantity))
310
-
311
- # include any charge symbols
312
- charge = get_formal_charge(formula)
313
- if charge > 0:
314
- output_list.append("+" + str(charge))
315
- elif charge < 0:
316
- output_list.append(str(charge))
317
-
318
- return output_list
319
-
320
-
321
- # Truth Functions
322
- def is_valid_element(formula):
323
- """
324
- Check whether a string is a valid atomic symbol
325
-
326
- Parameters
327
- ----------
328
- :formula: str
329
- String representing an atomic symbol. First letter must be
330
- uppercase, second letter must be lowercase.
331
-
332
- Returns
333
- -------
334
- bool
335
- True if the string is a valid atomic symbol. False otherwise.
336
-
337
- Examples
338
- --------
339
- >>> is_valid_element('Cu')
340
- True
341
- >>> is_valid_element('Na+')
342
- False
343
- """
344
- if formula in atomic_numbers:
345
- return True
346
- else:
347
- _invalid_formula("invalid element symbol")
348
- return False
349
-
350
-
351
- def is_valid_formula(formula):
352
- """
353
- Check that a molecular formula is formatted correctly
354
-
355
- Parameters
356
- ----------
357
- formula: str
358
- String representing a molecular formula. e.g. 'H2O' or 'FeOH+'
359
- Valid molecular formulas must meet the following criteria:
360
-
361
- #. Are composed of valid atomic symbols that start with capital letters
362
- #. Contain no non-alphanumeric characters other than '(', ')',
363
- '+', or '-'
364
- #. If a '+' or '-' is present, the formula must contain ONLY '+' or
365
- '-' (e.g. 'Na+-' is invalid) and the formula must end with either
366
- a series of charges (e.g. 'Fe+++') or a numeric charge (e.g. 'Fe+3')
367
- #. Formula must contain matching numbers of '(' and ')'
368
- #. Open parentheses must precede closed parentheses
369
-
370
- Returns
371
- -------
372
- bool
373
- True if the formula is valid. False otherwise.
374
-
375
- Examples
376
- --------
377
- >>> is_valid_formula('Fe2(SO4)3')
378
- True
379
- >>> is_valid_formula('2Na+')
380
- False
381
- >>> is_valid_formula('HCO3-')
382
- True
383
- >>> is_valid_formula('Na+-')
384
- False
385
- >>> is_valid_formula('C10h12')
386
- False
387
- """
388
-
389
- try:
390
- _check_formula(formula)
391
- return True
392
- except:
393
- return False
394
-
395
-
396
- def contains(formula, element):
397
- """
398
- Check whether a formula contains a given element.
399
-
400
- Parameters
401
- ----------
402
- formula: str
403
- String representing a molecular formula. e.g. 'H2O' or 'FeOH+'
404
- Valid molecular formulas must meet the following criteria:
405
-
406
- #. Are composed of valid atomic symbols that start with capital letters
407
- #. Contain no non-alphanumeric characters other than '(', ')',
408
- '+', or '-'
409
- #. If a '+' or '-' is present, the formula must contain ONLY '+' or
410
- '-' (e.g. 'Na+-' is invalid) and the formula must end with either
411
- a series of charges (e.g. 'Fe+++') or a numeric charge (e.g. 'Fe+3')
412
- #. Formula must contain matching numbers of '(' and ')'
413
- #. Open parentheses must precede closed parentheses
414
- element: str
415
- String representing the element to check for. Must be a valid element
416
- name.
417
-
418
- Returns
419
- -------
420
- bool
421
- True if the formula contains the element. False otherwise.
422
-
423
- Examples
424
- --------
425
- >>> contains('Fe2(SO4)3','Fe')
426
- True
427
- >>> contains('NaCOOH','S')
428
- False
429
- """
430
- if is_valid_element(element):
431
- if element in get_elements(formula):
432
- return True
433
- else:
434
- return False
435
-
436
-
437
- # Information Retrieval Functions
438
- def get_element_numbers(formula):
439
- """
440
- Return the atomic numbers of the elements in a chemical formula
441
-
442
- Parameters
443
- ----------
444
- formula: str
445
- String representing a chemical formula
446
-
447
- Examples
448
- --------
449
- >>> get_element_numbers('FeSO4')
450
- [26, 16, 8]
451
-
452
-
453
- """
454
- # perform validity check and return a list of the chemical formula's components
455
- input_list = get_elements(formula)
456
- output_list = []
457
-
458
- for item in input_list:
459
- output_list.append(atomic_numbers[item][0])
460
-
461
- return output_list
462
-
463
-
464
- def get_element_names(formula):
465
- """
466
- Return the names of the elements in a chemical formula
467
-
468
- Parameters
469
- ----------
470
- formula: str
471
- String representing a chemical formula
472
-
473
- Examples
474
- --------
475
- >>> get_element_names('FeSO4')
476
- ['Iron', 'Sulfur', 'Oxygen']
477
-
478
-
479
- """
480
- # perform validity check and return a list of the chemical formula's components
481
- input_list = get_elements(formula)
482
- output_list = []
483
-
484
- for item in input_list:
485
- output_list.append(atomic_numbers[item][1])
486
-
487
- return output_list
488
-
489
-
490
- def hill_order(formula):
491
- """
492
- Return a string representing the simplest form of 'formula'
493
- in the Hill order (Carbon, Hydrgen, then other elements
494
- in alphabetical order). If no Carbon is present, then
495
- all elements are listed in alphabetical order.
496
-
497
- NOTE: this function does NOT (yet) honor exceptions to the Hill Order
498
- for acids, hydroxides, oxides, and ionic compounds. It follows the
499
- rule above no matter what.
500
-
501
- Examples
502
- --------
503
- >>> hill_order('CH2(CH3)4COOH')
504
- 'C6H15O2'
505
-
506
- >>> hill_order('NaCl')
507
- 'ClNa'
508
-
509
- >>> hill_order('NaHCO2') == hill_order('HCOONa')
510
- True
511
-
512
- >>> hill_order('Fe+2') == hill_order('Fe+3')
513
- False
514
-
515
- """
516
- # TODO - add exceptions for oxides (end in O2), acids (start with H),
517
- # ions (cation first), and hydroxides (ends in OH)
518
- temp_list = _consolidate_formula(formula)
519
- hill = ""
520
-
521
- # start the formula with C and H if Carbon is present
522
- if "C" in temp_list:
523
- for item in ["C", "H"]:
524
- if item in temp_list:
525
- index = temp_list.index(item)
526
- hill += item
527
- # copy the number only if greater than 1
528
- if temp_list[index + 1] > 1:
529
- hill += str(temp_list.pop(index + 1))
530
- elif temp_list[index + 1] == 1:
531
- temp_list.pop(index + 1)
532
- temp_list.remove(item)
533
-
534
- # convert any remaining list entries into tuples of (element,number)
535
- # so they can be sorted
536
- tuple_list = []
537
- for item in temp_list:
538
- index = temp_list.index(item)
539
- try:
540
- if item.isalpha():
541
- tuple_list.append((item, temp_list[index + 1]))
542
- except AttributeError:
543
- continue
544
-
545
- # put the remaining elements in alphabetical order
546
- tuple_list.sort()
547
-
548
- # add them to the formula
549
- for item in tuple_list:
550
- if item[1] == 1:
551
- hill += str(item[0])
552
- else:
553
- hill += str(item[0]) + str(item[1])
554
-
555
- # append the formal charge to the end of the formula, if not zero
556
- charge = get_formal_charge(formula)
557
- if charge != 0:
558
- hill += str(charge)
559
-
560
- return hill
561
-
562
-
563
- def get_elements(formula):
564
- """
565
- Return a list of strings representing the elements in a
566
- molecular formula, with no duplicates.
567
-
568
- Examples
569
- --------
570
- >>> get_elements('FeSO4')
571
- ['Fe', 'S', 'O']
572
- >>> get_elements('CH3(CH2)4(CO)3')
573
- ['C', 'H', 'O']
574
-
575
- See Also
576
- --------
577
- _check_formula
578
- """
579
- # perform validity check and return a parsed list of the chemical formula
580
- input_list = _consolidate_formula(formula)
581
- output_list = []
582
-
583
- for item in input_list:
584
- if item in atomic_numbers:
585
- output_list.append(item)
586
-
587
- return output_list
588
-
589
-
590
- def get_formal_charge(formula):
591
- """
592
- Return the formal charge on a molecule based on its formula
593
-
594
- Examples
595
- --------
596
- >>> get_formal_charge('Na+')
597
- 1
598
- >>> get_formal_charge('PO4-3')
599
- -3
600
- >>> get_formal_charge('Fe+++')
601
- 3
602
-
603
- See Also
604
- --------
605
- _check_formula
606
-
607
- """
608
- # perform validity check and return a parsed list of the chemical formula
609
- input_list = _check_formula(formula)
610
-
611
- if "+" in input_list:
612
- index = input_list.index("+")
613
- try:
614
- formal_charge = 1 * int(input_list[index + 1])
615
- except IndexError:
616
- formal_charge = 1
617
- elif "-" in input_list:
618
- index = input_list.index("-")
619
- try:
620
- formal_charge = -1 * int(input_list[index + 1])
621
- except IndexError:
622
- formal_charge = -1
623
- elif "+" in input_list[-1]:
624
- formal_charge = int(1 * input_list[-1].count("+"))
625
- elif "-" in input_list[-1]:
626
- formal_charge = int(-1 * input_list[-1].count("-"))
627
- else:
628
- formal_charge = 0
629
-
630
- return formal_charge
631
-
632
-
633
- def get_element_mole_ratio(formula, element):
634
- """
635
- compute the moles of a specific element per mole of formula
636
-
637
- Parameters
638
- ----------
639
- formula: str
640
- String representing a molecular formula. e.g. 'H2O' or 'FeOH+'
641
- Valid molecular formulas must meet the following criteria:
642
-
643
- #. Are composed of valid atomic symbols that start with capital letters
644
- #. Contain no non-alphanumeric characters other than '(', ')',
645
- '+', or '-'
646
- #. If a '+' or '-' is present, the formula must contain ONLY '+' or
647
- '-' (e.g. 'Na+-' is invalid) and the formula must end with either
648
- a series of charges (e.g. 'Fe+++') or a numeric charge (e.g. 'Fe+3')
649
- #. Formula must contain matching numbers of '(' and ')'
650
- #. Open parentheses must precede closed parentheses
651
- element: str
652
- String representing the element to check for. Must be a valid element
653
- name.
654
-
655
- Returns
656
- -------
657
- number
658
- The number of moles of element per mole of formula, mol/mol.
659
-
660
- >>> get_element_mole_ratio('NaCl','Na')
661
- 1
662
- >>> get_element_mole_ratio('H2O','H')
663
- 2
664
- >>> get_element_mole_ratio('H2O','Br')
665
- 0
666
- >>> get_element_mole_ratio('CH3CH2CH3','C')
667
- 3
668
-
669
- See Also
670
- --------
671
- contains
672
- consolidate_formula
673
- get_element_weight
674
- get_element_weight_fraction
675
-
676
- """
677
- # perform validity check and return a parsed list of the chemical formula
678
- if contains(formula, element):
679
- input_list = _consolidate_formula(formula)
680
- index = input_list.index(element)
681
- moles = input_list[index + 1]
682
- # return 0 weight if the element isn't present in the formula
683
- else:
684
- moles = 0
685
-
686
- return moles
687
-
688
-
689
- def get_element_weight(formula, element):
690
- """
691
- compute the weight of a specific element in a formula
692
-
693
- Parameters
694
- ----------
695
- formula: str
696
- String representing a molecular formula. e.g. 'H2O' or 'FeOH+'
697
- Valid molecular formulas must meet the following criteria:
698
-
699
- #. Are composed of valid atomic symbols that start with capital letters
700
- #. Contain no non-alphanumeric characters other than '(', ')',
701
- '+', or '-'
702
- #. If a '+' or '-' is present, the formula must contain ONLY '+' or
703
- '-' (e.g. 'Na+-' is invalid) and the formula must end with either
704
- a series of charges (e.g. 'Fe+++') or a numeric charge (e.g. 'Fe+3')
705
- #. Formula must contain matching numbers of '(' and ')'
706
- #. Open parentheses must precede closed parentheses
707
- element: str
708
- String representing the element to check for. Must be a valid element
709
- name.
710
-
711
- Returns
712
- -------
713
- number
714
- The weight of the specified element within the formula, g/mol.
715
-
716
- >>> get_element_weight('NaCl','Na')
717
- 22.98977
718
- >>> get_element_weight('H2O','H')
719
- 2.01588
720
- >>> get_element_weight('H2O','Br')
721
- 0.0
722
- >>> get_element_weight('CH3CH2CH3','C')
723
- 36.0321
724
-
725
- See Also
726
- --------
727
- contains
728
- _consolidate_formula
729
- elements
730
- get_element_mole_ratio
731
-
732
- """
733
- # find the number of moles of element per mole of formula
734
- moles = get_element_mole_ratio(formula, element)
735
-
736
- if moles != 0:
737
- # import elements.py - used to retreive various molecular data
738
- from pyEQL.elements import ELEMENTS
739
-
740
- # look up the molecular weight for the element
741
- mass = ELEMENTS[element].mass
742
-
743
- wt = mass * moles
744
- else:
745
- wt = 0.0
746
-
747
- return wt
748
-
749
-
750
- def get_element_weight_fraction(formula, element):
751
- """
752
- compute the weight fraction of a specific element in a formula
753
-
754
- Parameters
755
- ----------
756
- formula: str
757
- String representing a molecular formula. e.g. 'H2O' or 'FeOH+'
758
- Valid molecular formulas must meet the following criteria:
759
-
760
- #. Are composed of valid atomic symbols that start with capital letters
761
- #. Contain no non-alphanumeric characters other than '(', ')',
762
- '+', or '-'
763
- #. If a '+' or '-' is present, the formula must contain ONLY '+' or
764
- '-' (e.g. 'Na+-' is invalid) and the formula must end with either
765
- a series of charges (e.g. 'Fe+++') or a numeric charge (e.g. 'Fe+3')
766
- #. Formula must contain matching numbers of '(' and ')'
767
- #. Open parentheses must precede closed parentheses
768
- element: str
769
- String representing the element to check for. Must be a valid element
770
- name.
771
-
772
- Returns
773
- -------
774
- number
775
- The weight fraction of the specified element within the formula.
776
-
777
- >>> get_element_weight_fraction('NaCl','Na')
778
- 0.39337...
779
- >>> get_element_weight_fraction('H2O','H')
780
- 0.111898...
781
- >>> get_element_weight_fraction('H2O','Br')
782
- 0.0
783
- >>> get_element_weight_fraction('CH3CH2CH3','C')
784
- 0.8171355...
785
-
786
- See Also
787
- --------
788
- get_element_weight
789
- contains
790
- _consolidate_formula
791
- elements
792
-
793
- """
794
- # calculate the element weight in the formula
795
- wt = get_element_weight(formula, element)
796
-
797
- # calculate the fraction
798
- frac = wt / get_molecular_weight(formula)
799
-
800
- return frac
801
-
802
-
803
- def get_molecular_weight(formula):
804
- """
805
- compute the molecular weight of a formula
806
-
807
- >>> get_molecular_weight('Na+')
808
- 22.98977
809
- >>> get_molecular_weight('H2O')
810
- 18.01528
811
- >>> get_molecular_weight('CH3CH2CH3')
812
- 44.09562
813
-
814
- See Also
815
- --------
816
- _consolidate_formula
817
- elements
818
-
819
- """
820
- # import elements.py - used to retreive various molecular data
821
- from pyEQL.elements import ELEMENTS
822
-
823
- # perform validity check and return a parsed list of the chemical formula
824
- input_list = _consolidate_formula(formula)
825
- mw = 0
826
-
827
- for item in input_list:
828
- try:
829
- if item.isalpha():
830
- index = input_list.index(item)
831
- quantity = input_list[index + 1]
832
- # look up the molecular weight for the element
833
- mass = ELEMENTS[item].mass
834
- mw += mass * quantity
835
-
836
- # if the list item is a number or a charge, move on
837
- except AttributeError:
838
- pass
839
-
840
- return mw
841
-
842
- # Output functions
843
-
844
-
845
- def print_latex(formula):
846
- """
847
- Print a LaTeX - formatted version of the formula
848
-
849
- Examples
850
- ---------
851
- >>> print_latex('Fe2SO4')
852
- Fe_2SO_4
853
- >>> print_latex('CH3CH2CH3')
854
- CH_3CH_2CH_3
855
- >>> print_latex('Fe2(OH)2+2')
856
- Fe_2(OH)_2^+^2
857
-
858
- """
859
- output = ""
860
- for i in range(len(formula)):
861
- # insert LaTeX subscripts (underscore) before all numbers, unless they start
862
- # the formula or follow a charge symbol
863
- if formula[i].isnumeric() and i > 0:
864
- # insert superscript before a charge
865
- if formula[i - 1] == "+" or formula[i - 1] == "-":
866
- output += "^"
867
- else:
868
- output += "_"
869
-
870
- # insert superscripts before charges
871
- if formula[i] == "+" or formula[i] == "-":
872
- output += "^"
873
-
874
- # pass the rest of the formula to output
875
- output += formula[i]
876
-
877
- print(output)
878
-
879
-
880
- # Dictionary to correlate atomic symbols, names, and numbers
881
- atomic_numbers = {
882
- "H": (1, "Hydrogen"),
883
- "He": (2, "Helium"),
884
- "Li": (3, "Lithium"),
885
- "Be": (4, "Beryllium"),
886
- "B": (5, "Boron"),
887
- "C": (6, "Carbon"),
888
- "N": (7, "Nitrogen"),
889
- "O": (8, "Oxygen"),
890
- "F": (9, "Fluorine"),
891
- "Ne": (10, "Neon"),
892
- "Na": (11, "Sodium"),
893
- "Mg": (12, "Magnesium"),
894
- "Al": (13, "Aluminum"),
895
- "Si": (14, "Silicon"),
896
- "P": (15, "Phosphorus"),
897
- "S": (16, "Sulfur"),
898
- "Cl": (17, "Chlorine"),
899
- "Ar": (18, "Argon"),
900
- "K": (19, "Potassium"),
901
- "Ca": (20, "Calcium"),
902
- "Sc": (21, "Scandium"),
903
- "Ti": (22, "Titanium"),
904
- "V": (23, "Vanadium"),
905
- "Cr": (24, "Chromium"),
906
- "Mn": (25, "Manganese"),
907
- "Fe": (26, "Iron"),
908
- "Co": (27, "Cobalt"),
909
- "Ni": (28, "Nickel"),
910
- "Cu": (29, "Copper"),
911
- "Zn": (30, "Zinc"),
912
- "Ga": (31, "Gallium"),
913
- "Ge": (32, "Germanium"),
914
- "As": (33, "Arsenic"),
915
- "Se": (34, "Selenium"),
916
- "Br": (35, "Bromine"),
917
- "Kr": (36, "Krypton"),
918
- "Rb": (37, "Rubidium"),
919
- "Sr": (38, "Strontium"),
920
- "Y": (39, "Yttrium"),
921
- "Zr": (40, "Zirconium"),
922
- "Nb": (41, "Niobium"),
923
- "Mo": (42, "Molybdenum"),
924
- "Tc": (43, "Technetium"),
925
- "Ru": (44, "Ruthenium"),
926
- "Rh": (45, "Rhodium"),
927
- "Pd": (46, "Palladium"),
928
- "Ag": (47, "Silver"),
929
- "Cd": (48, "Cadmium"),
930
- "In": (49, "Indium"),
931
- "Sn": (50, "Tin"),
932
- "Sb": (51, "Antimony"),
933
- "Te": (52, "Tellurium"),
934
- "I": (53, "Iodine"),
935
- "Xe": (54, "Xenon"),
936
- "Cs": (55, "Cesium"),
937
- "Ba": (56, "Barium"),
938
- "La": (57, "Lanthanum"),
939
- "Ce": (58, "Cerium"),
940
- "Pr": (59, "Praseodymium"),
941
- "Nd": (60, "Neodymium"),
942
- "Pm": (61, "Promethium"),
943
- "Sm": (62, "Samarium"),
944
- "Eu": (63, "Europium"),
945
- "Gd": (64, "Gadolinium"),
946
- "Tb": (65, "Terbium"),
947
- "Dy": (66, "Dysprosium"),
948
- "Ho": (67, "Holmium"),
949
- "Er": (68, "Erbium"),
950
- "Tm": (69, "Thulium"),
951
- "Yb": (70, "Ytterbium"),
952
- "Lu": (71, "Lutetium"),
953
- "Hf": (72, "Hafnium"),
954
- "Ta": (73, "Tantalum"),
955
- "W": (74, "Tungsten"),
956
- "Re": (75, "Rhenium"),
957
- "Os": (76, "Osmium"),
958
- "Ir": (77, "Iridium"),
959
- "Pt": (78, "Platinum"),
960
- "Au": (79, "Gold"),
961
- "Hg": (80, "Mercury"),
962
- "Tl": (81, "Thallium"),
963
- "Pb": (82, "Lead"),
964
- "Bi": (83, "Bismuth"),
965
- "Po": (84, "Polonium"),
966
- "At": (85, "Astatine"),
967
- "Rn": (86, "Radon"),
968
- "Fr": (87, "Francium"),
969
- "Ra": (88, "Radium"),
970
- "Ac": (89, "Actinium"),
971
- "Th": (90, "Thorium"),
972
- "Pa": (91, "Protactinium"),
973
- "U": (92, "Uranium"),
974
- "Np": (93, "Neptunium"),
975
- "Pu": (94, "Plutonium"),
976
- "Am": (95, "Americium"),
977
- "Cm": (96, "Curium"),
978
- "Bk": (97, "Berkelium"),
979
- "Cf": (98, "Californium"),
980
- "Es": (99, "Einsteinium"),
981
- "Fm": (100, "Fermium"),
982
- "Md": (101, "Mendelevium"),
983
- "No": (102, "Nobelium"),
984
- "Lr": (103, "Lawrencium"),
985
- "Rf": (104, "Rutherfordium"),
986
- "Db": (105, "Dubnium"),
987
- "Sg": (106, "Seaborgium"),
988
- "Bh": (107, "Bohrium"),
989
- "Hs": (108, "Hassium"),
990
- "Mt": (109, "Meitnerium"),
991
- "Ds": (110, "Darmstadtium"),
992
- "Rg": (111, "Roentgenium"),
993
- "Cn": (112, "Copernicium"),
994
- "Uut": (113, "Ununtrium"),
995
- "Fl": (114, "Flerovium"),
996
- "Uup": (115, "Ununpentium"),
997
- "Lv": (116, "Livermorium"),
998
- "Uus": (117, "Ununseptium"),
999
- "Uuo": (118, "Ununoctium"),
1000
- }
1001
-
1002
- # TODO - turn doctest back on when the nosigint error is gone
1003
- # if __name__ == "__main__":
1004
- # import doctest
1005
- # doctest.testfile('tests/test_chemical_formula.rst')
1006
- # doctest.testmod()