watertrain 1.0.0__tar.gz

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.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: watertrain
3
+ Version: 1.0.0
4
+ Summary: Automatic water-treatment-plant flowsheet generator: superstructure enumeration with pruning, analytical reliability and TAC ranking, plus equipment sizing.
5
+ Author: Anjan Tula
6
+ License: MIT
7
+ Keywords: water treatment,flowsheet,process synthesis,superstructure,reverse osmosis,desalination
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Provides-Extra: excel
14
+ Requires-Dist: openpyxl>=3.1; extra == "excel"
15
+
16
+ # watertrain — Automatic WTP Flowsheet Generator
17
+
18
+ Python port of the Excel/VBA tool `WTP_FlowsheetGenerator.xlsm`. Generates
19
+ every feasible water-treatment train for a given raw-water quality and
20
+ product spec, ranks them by a total-annualised-cost (TAC) screening index,
21
+ and sizes the equipment of any selected train.
22
+
23
+ **Method:** superstructure depth-first enumeration with pruning rules P1–P7,
24
+ backward flow / forward composition balance, analytical reliability
25
+ (log-normal approximation driven by the leak-CV column) and a TAC index.
26
+ The Python engine reproduces the VBA workbook results exactly (see
27
+ `tests/test_parity.py`).
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install . # core (no dependencies, pure Python >= 3.9)
33
+ pip install .[excel] # + openpyxl, to read cases/DB from the workbook
34
+ ```
35
+
36
+ ## Quick start (Python API)
37
+
38
+ ```python
39
+ from watertrain import WaterCase, generate, rank, design
40
+
41
+ case = WaterCase.create(
42
+ feed=dict(turbidity=10, tss=15, oil_grease=1, fe=0.3, mn=0.1,
43
+ cod=40, toc=10, tds=5384, sio2=30, hardness=193),
44
+ target=dict(turbidity=1, tss=1, oil_grease=0.1, fe=0.1, mn=0.05,
45
+ cod=10, toc=2, tds=100, sio2=5, hardness=20),
46
+ product_flow=19.3, # m3/h
47
+ source="Treated Wastewater/Sewage",
48
+ max_train_len=9, max_flowsheets=200)
49
+
50
+ flowsheets = generate(case) # 1. all feasible trains
51
+ best = rank(flowsheets, top_x=5) # 2. cheapest first (TAC)
52
+ d = design(best[0], case) # 3. size the chosen train
53
+ print(d.report()) # equipment schedule + PFD + stream table
54
+ ```
55
+
56
+ The workflow mirrors the workbook macros:
57
+ `GenerateFlowsheets → RankFlowsheets → DesignSelected`.
58
+
59
+ Built-in example cases (same 7 as the workbook):
60
+
61
+ ```python
62
+ from watertrain import get_example
63
+ case = get_example(6) # "6. PFD-1 reject reclaim"
64
+ ```
65
+
66
+ ## Command line
67
+
68
+ ```bash
69
+ watertrain --list-examples
70
+ watertrain --example 6 --top 5 --design 1
71
+ watertrain --case mycase.json --top 10
72
+ ```
73
+
74
+ `mycase.json`:
75
+
76
+ ```json
77
+ {
78
+ "feed": {"turbidity": 10, "tss": 15, "tds": 5384, "hardness": 193},
79
+ "target": {"turbidity": 1, "tss": 1, "tds": 100, "hardness": 20},
80
+ "product_flow": 19.3,
81
+ "max_train_len": 9
82
+ }
83
+ ```
84
+
85
+ Parameters omitted from `feed` default to 0 (not present).
86
+
87
+ ## Custom technology database
88
+
89
+ The default database (`watertrain.DEFAULT_TECHNOLOGIES`) is the
90
+ `2.ProcessGroups` sheet. Pass your own list to `generate()`:
91
+
92
+ ```python
93
+ from watertrain import Technology, generate
94
+
95
+ my_tech = Technology.create(
96
+ "MyMBR", stage=2, recovery=0.95, leak_cv=0.25, capex=2.0, opex=0.9,
97
+ removal=dict(turbidity=0.99, tss=0.999, cod=0.6, toc=0.5),
98
+ inlet_max=dict(turbidity=200))
99
+
100
+ techs = list(__import__("watertrain").DEFAULT_TECHNOLOGIES) + [my_tech]
101
+ flowsheets = generate(case, technologies=techs)
102
+ ```
103
+
104
+ Or keep editing the database in Excel and load it live
105
+ (requires `pip install .[excel]`):
106
+
107
+ ```python
108
+ from watertrain.excel import load_case, load_technologies
109
+ case = load_case("WTP_FlowsheetGenerator.xlsm")
110
+ techs = load_technologies("WTP_FlowsheetGenerator.xlsm")
111
+ ```
112
+
113
+ ## Water-quality parameters (fixed order)
114
+
115
+ `turbidity, tss, oil_grease, fe, mn, cod, toc, tds, sio2, hardness`
116
+
117
+ ## Notes
118
+
119
+ * Reliability is P(product meets spec) under performance uncertainty.
120
+ * TAC is a screening cost index for ranking, not a bid price.
121
+ * Run the parity tests with `pytest`.
122
+
123
+ ## Package layout
124
+
125
+ ```
126
+ src/watertrain/
127
+ model.py dataclasses: Technology, WaterCase, Flowsheet; constants
128
+ database.py default technology database (2.ProcessGroups)
129
+ examples.py the 7 example cases
130
+ engine.py generate() + rank(): DFS, pruning P1–P7, reliability, TAC
131
+ design.py design(): flow balance, equipment sizing, streams, text PFD
132
+ sfiles.py SFILES string helpers
133
+ excel.py optional openpyxl bridge to the original workbook
134
+ cli.py `watertrain` command
135
+ tests/test_parity.py reproduces the workbook numbers exactly
136
+ ```
@@ -0,0 +1,121 @@
1
+ # watertrain — Automatic WTP Flowsheet Generator
2
+
3
+ Python port of the Excel/VBA tool `WTP_FlowsheetGenerator.xlsm`. Generates
4
+ every feasible water-treatment train for a given raw-water quality and
5
+ product spec, ranks them by a total-annualised-cost (TAC) screening index,
6
+ and sizes the equipment of any selected train.
7
+
8
+ **Method:** superstructure depth-first enumeration with pruning rules P1–P7,
9
+ backward flow / forward composition balance, analytical reliability
10
+ (log-normal approximation driven by the leak-CV column) and a TAC index.
11
+ The Python engine reproduces the VBA workbook results exactly (see
12
+ `tests/test_parity.py`).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install . # core (no dependencies, pure Python >= 3.9)
18
+ pip install .[excel] # + openpyxl, to read cases/DB from the workbook
19
+ ```
20
+
21
+ ## Quick start (Python API)
22
+
23
+ ```python
24
+ from watertrain import WaterCase, generate, rank, design
25
+
26
+ case = WaterCase.create(
27
+ feed=dict(turbidity=10, tss=15, oil_grease=1, fe=0.3, mn=0.1,
28
+ cod=40, toc=10, tds=5384, sio2=30, hardness=193),
29
+ target=dict(turbidity=1, tss=1, oil_grease=0.1, fe=0.1, mn=0.05,
30
+ cod=10, toc=2, tds=100, sio2=5, hardness=20),
31
+ product_flow=19.3, # m3/h
32
+ source="Treated Wastewater/Sewage",
33
+ max_train_len=9, max_flowsheets=200)
34
+
35
+ flowsheets = generate(case) # 1. all feasible trains
36
+ best = rank(flowsheets, top_x=5) # 2. cheapest first (TAC)
37
+ d = design(best[0], case) # 3. size the chosen train
38
+ print(d.report()) # equipment schedule + PFD + stream table
39
+ ```
40
+
41
+ The workflow mirrors the workbook macros:
42
+ `GenerateFlowsheets → RankFlowsheets → DesignSelected`.
43
+
44
+ Built-in example cases (same 7 as the workbook):
45
+
46
+ ```python
47
+ from watertrain import get_example
48
+ case = get_example(6) # "6. PFD-1 reject reclaim"
49
+ ```
50
+
51
+ ## Command line
52
+
53
+ ```bash
54
+ watertrain --list-examples
55
+ watertrain --example 6 --top 5 --design 1
56
+ watertrain --case mycase.json --top 10
57
+ ```
58
+
59
+ `mycase.json`:
60
+
61
+ ```json
62
+ {
63
+ "feed": {"turbidity": 10, "tss": 15, "tds": 5384, "hardness": 193},
64
+ "target": {"turbidity": 1, "tss": 1, "tds": 100, "hardness": 20},
65
+ "product_flow": 19.3,
66
+ "max_train_len": 9
67
+ }
68
+ ```
69
+
70
+ Parameters omitted from `feed` default to 0 (not present).
71
+
72
+ ## Custom technology database
73
+
74
+ The default database (`watertrain.DEFAULT_TECHNOLOGIES`) is the
75
+ `2.ProcessGroups` sheet. Pass your own list to `generate()`:
76
+
77
+ ```python
78
+ from watertrain import Technology, generate
79
+
80
+ my_tech = Technology.create(
81
+ "MyMBR", stage=2, recovery=0.95, leak_cv=0.25, capex=2.0, opex=0.9,
82
+ removal=dict(turbidity=0.99, tss=0.999, cod=0.6, toc=0.5),
83
+ inlet_max=dict(turbidity=200))
84
+
85
+ techs = list(__import__("watertrain").DEFAULT_TECHNOLOGIES) + [my_tech]
86
+ flowsheets = generate(case, technologies=techs)
87
+ ```
88
+
89
+ Or keep editing the database in Excel and load it live
90
+ (requires `pip install .[excel]`):
91
+
92
+ ```python
93
+ from watertrain.excel import load_case, load_technologies
94
+ case = load_case("WTP_FlowsheetGenerator.xlsm")
95
+ techs = load_technologies("WTP_FlowsheetGenerator.xlsm")
96
+ ```
97
+
98
+ ## Water-quality parameters (fixed order)
99
+
100
+ `turbidity, tss, oil_grease, fe, mn, cod, toc, tds, sio2, hardness`
101
+
102
+ ## Notes
103
+
104
+ * Reliability is P(product meets spec) under performance uncertainty.
105
+ * TAC is a screening cost index for ranking, not a bid price.
106
+ * Run the parity tests with `pytest`.
107
+
108
+ ## Package layout
109
+
110
+ ```
111
+ src/watertrain/
112
+ model.py dataclasses: Technology, WaterCase, Flowsheet; constants
113
+ database.py default technology database (2.ProcessGroups)
114
+ examples.py the 7 example cases
115
+ engine.py generate() + rank(): DFS, pruning P1–P7, reliability, TAC
116
+ design.py design(): flow balance, equipment sizing, streams, text PFD
117
+ sfiles.py SFILES string helpers
118
+ excel.py optional openpyxl bridge to the original workbook
119
+ cli.py `watertrain` command
120
+ tests/test_parity.py reproduces the workbook numbers exactly
121
+ ```
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "watertrain"
7
+ version = "1.0.0"
8
+ description = "Automatic water-treatment-plant flowsheet generator: superstructure enumeration with pruning, analytical reliability and TAC ranking, plus equipment sizing."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Anjan Tula" }]
13
+ keywords = ["water treatment", "flowsheet", "process synthesis",
14
+ "superstructure", "reverse osmosis", "desalination"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Intended Audience :: Science/Research",
18
+ "Topic :: Scientific/Engineering :: Chemistry",
19
+ ]
20
+ dependencies = [] # pure standard library
21
+
22
+ [project.optional-dependencies]
23
+ excel = ["openpyxl>=3.1"] # read cases / tech DB from the original workbook
24
+
25
+ [project.scripts]
26
+ watertrain = "watertrain.cli:main"
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,42 @@
1
+ """watertrain — Automatic Water-Treatment-Plant Flowsheet Generator.
2
+
3
+ Python port of the validated Excel/VBA tool (WTP_FlowsheetGenerator.xlsm).
4
+
5
+ Typical use::
6
+
7
+ from watertrain import WaterCase, generate, rank, design
8
+
9
+ case = WaterCase.create(
10
+ feed=dict(turbidity=10, tss=15, oil_grease=1, fe=0.3, mn=0.1,
11
+ cod=40, toc=10, tds=5384, sio2=30, hardness=193),
12
+ target=dict(turbidity=1, tss=1, oil_grease=0.1, fe=0.1, mn=0.05,
13
+ cod=10, toc=2, tds=100, sio2=5, hardness=20),
14
+ product_flow=19.3, source="Treated Wastewater/Sewage",
15
+ max_train_len=9, max_flowsheets=10)
16
+
17
+ flowsheets = generate(case) # every feasible train
18
+ best = rank(flowsheets, top_x=5) # cheapest first (TAC)
19
+ print(design(best[0], case).report())
20
+
21
+ Workflow: generate -> rank -> design (same as the workbook macros
22
+ GenerateFlowsheets -> RankFlowsheets -> DesignSelected).
23
+ """
24
+ from .model import (NO_LIMIT, NP, PARAMS, Flowsheet, Technology, WaterCase)
25
+ from .database import DEFAULT_TECHNOLOGIES, TECH_BY_NAME
26
+ from .engine import generate, rank
27
+ from .design import Design, Equipment, Stream, design
28
+ from .examples import EXAMPLES, get_example
29
+ from .sfiles import sfiles_string, sfiles_tag
30
+
31
+ __version__ = "1.0.0"
32
+
33
+ __all__ = [
34
+ "PARAMS", "NP", "NO_LIMIT",
35
+ "Technology", "WaterCase", "Flowsheet",
36
+ "DEFAULT_TECHNOLOGIES", "TECH_BY_NAME",
37
+ "generate", "rank", "design",
38
+ "Design", "Equipment", "Stream",
39
+ "EXAMPLES", "get_example",
40
+ "sfiles_string", "sfiles_tag",
41
+ "__version__",
42
+ ]
@@ -0,0 +1,107 @@
1
+ """Command-line interface.
2
+
3
+ Examples::
4
+
5
+ watertrain --list-examples
6
+ watertrain --example 6 --top 5
7
+ watertrain --example 6 --top 5 --design 1
8
+ watertrain --case mycase.json --top 10 --design 2
9
+
10
+ mycase.json format::
11
+
12
+ {
13
+ "feed": {"turbidity": 10, "tss": 15, "tds": 5384, "...": 0},
14
+ "target": {"turbidity": 1, "tss": 1, "tds": 100, "...": 0},
15
+ "product_flow": 19.3,
16
+ "source": "Treated Wastewater/Sewage",
17
+ "max_train_len": 9,
18
+ "max_flowsheets": 200
19
+ }
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import json
25
+ import sys
26
+
27
+ from . import __version__
28
+ from .design import design as design_fs
29
+ from .engine import generate, rank
30
+ from .examples import EXAMPLES, get_example
31
+ from .model import WaterCase
32
+
33
+
34
+ def _load_case_json(path: str) -> WaterCase:
35
+ with open(path, "r", encoding="utf-8") as fh:
36
+ d = json.load(fh)
37
+ return WaterCase.create(
38
+ feed=d["feed"], target=d["target"],
39
+ product_flow=d["product_flow"], source=d.get("source", ""),
40
+ max_train_len=d.get("max_train_len", 7),
41
+ max_flowsheets=d.get("max_flowsheets", 200),
42
+ name=d.get("name", path))
43
+
44
+
45
+ def main(argv=None) -> int:
46
+ ap = argparse.ArgumentParser(
47
+ prog="watertrain",
48
+ description="Automatic WTP flowsheet generator "
49
+ "(generate -> rank -> design).")
50
+ ap.add_argument("--version", action="version", version=__version__)
51
+ ap.add_argument("--list-examples", action="store_true",
52
+ help="list the built-in example cases and exit")
53
+ src = ap.add_mutually_exclusive_group()
54
+ src.add_argument("--example", metavar="N",
55
+ help="use built-in example (1-7 or full name)")
56
+ src.add_argument("--case", metavar="FILE.json",
57
+ help="load a case from a JSON file")
58
+ ap.add_argument("--top", type=int, default=10, metavar="X",
59
+ help="rank: keep the X cheapest trains (default 10)")
60
+ ap.add_argument("--design", type=int, metavar="RANK",
61
+ help="print the full design of the given rank (1 = best)")
62
+ ap.add_argument("--max-flowsheets", type=int, metavar="M",
63
+ help="override the enumeration cap")
64
+ args = ap.parse_args(argv)
65
+
66
+ if args.list_examples:
67
+ for name in EXAMPLES:
68
+ print(name)
69
+ return 0
70
+
71
+ if args.example:
72
+ case = get_example(args.example)
73
+ elif args.case:
74
+ case = _load_case_json(args.case)
75
+ else:
76
+ ap.error("choose --example N or --case FILE.json "
77
+ "(or --list-examples)")
78
+
79
+ if args.max_flowsheets:
80
+ case = WaterCase.create(
81
+ feed=case.feed_dict(), target=case.target_dict(),
82
+ product_flow=case.product_flow, source=case.source,
83
+ max_train_len=case.max_train_len,
84
+ max_flowsheets=args.max_flowsheets, name=case.name)
85
+
86
+ fss = generate(case)
87
+ print(f"Case: {case.name or '(custom)'} | "
88
+ f"generated {len(fss)} feasible flowsheet(s)")
89
+ top = rank(fss, top_x=args.top)
90
+ print(f"\nTop {len(top)} by TAC:")
91
+ print(f"{'#':>3} {'TAC':>8} {'Rec%':>6} {'Rel%':>6} {'Intake':>8} SFILES")
92
+ for r, fs in enumerate(top, 1):
93
+ print(f"{r:>3} {fs.tac:8.3f} {fs.recovery_pct:6.1f} "
94
+ f"{fs.reliability_pct:6.1f} {fs.intake:8.2f} {fs.sfiles}")
95
+
96
+ if args.design:
97
+ if not (1 <= args.design <= len(top)):
98
+ print(f"\n--design {args.design} is out of range (1..{len(top)})",
99
+ file=sys.stderr)
100
+ return 2
101
+ print("\n" + "=" * 70)
102
+ print(design_fs(top[args.design - 1], case).report())
103
+ return 0
104
+
105
+
106
+ if __name__ == "__main__":
107
+ raise SystemExit(main())
@@ -0,0 +1,75 @@
1
+ """Default technology database.
2
+
3
+ Ported 1:1 from sheet '2.ProcessGroups' of WTP_FlowsheetGenerator.xlsm.
4
+ rem_* = removal fraction (0-1); in_* = max inlet the unit tolerates
5
+ (absent = no limit). Stage: 1 pre-treatment, 2 filtration, 3 RO, 4 polish.
6
+
7
+ Users can pass their own list of Technology objects to generate(); this
8
+ module is only the built-in default.
9
+ """
10
+ from .model import Technology
11
+
12
+ T = Technology.create
13
+
14
+ DEFAULT_TECHNOLOGIES = [
15
+ T("Clarifier", 1, 0.991, 0.35, 1.6, 0.5,
16
+ removal=dict(turbidity=0.98, tss=0.97, oil_grease=0.9, fe=0.8, mn=0.6,
17
+ cod=0.4, toc=0.35, tds=0.0, sio2=0.1, hardness=0.0)),
18
+ T("DAF", 1, 0.99, 0.35, 1.7, 0.8,
19
+ removal=dict(turbidity=0.9, tss=0.92, oil_grease=0.95,
20
+ cod=0.35, toc=0.25)),
21
+ T("HRSCC", 1, 0.985, 0.35, 2.2, 1.0,
22
+ removal=dict(turbidity=0.985, tss=0.98, fe=0.85, mn=0.7,
23
+ cod=0.45, toc=0.4, sio2=0.3, hardness=0.6)),
24
+ T("ABF", 2, 0.992, 0.30, 0.8, 0.2,
25
+ removal=dict(turbidity=0.6, tss=0.7, fe=0.4, mn=0.3),
26
+ inlet_max=dict(turbidity=50, tss=50)),
27
+ T("MMF", 2, 0.97, 0.30, 1.0, 0.3,
28
+ removal=dict(turbidity=0.8, tss=0.85, oil_grease=0.3, fe=0.5, mn=0.4,
29
+ cod=0.05, toc=0.05),
30
+ inlet_max=dict(turbidity=50, tss=50)),
31
+ T("GSF", 2, 0.973, 0.30, 1.1, 0.35,
32
+ removal=dict(turbidity=0.9, tss=0.92, oil_grease=0.3, fe=0.85, mn=0.8,
33
+ cod=0.05, toc=0.05),
34
+ inlet_max=dict(turbidity=50, tss=50)),
35
+ T("ACF", 2, 0.93, 0.35, 1.2, 0.6,
36
+ removal=dict(turbidity=0.4, tss=0.5, oil_grease=0.85,
37
+ cod=0.55, toc=0.65),
38
+ inlet_max=dict(turbidity=10, tss=10)),
39
+ T("SF", 2, 0.97, 0.30, 0.9, 0.25,
40
+ removal=dict(turbidity=0.8, tss=0.85),
41
+ inlet_max=dict(turbidity=20, tss=20)),
42
+ T("WAC", 2, 0.975, 0.20, 1.3, 0.9,
43
+ removal=dict(tds=0.05, hardness=0.98),
44
+ inlet_max=dict(turbidity=5, tss=5)),
45
+ T("UF", 2, 0.90, 0.20, 1.8, 0.7,
46
+ removal=dict(turbidity=0.995, tss=0.999, oil_grease=0.7, fe=0.6, mn=0.3,
47
+ cod=0.25, toc=0.2),
48
+ inlet_max=dict(turbidity=100, tss=100, oil_grease=5)),
49
+ T("RO Pass1", 3, 0.75, 0.35, 3.0, 2.0,
50
+ removal=dict(turbidity=0.95, tss=0.99, oil_grease=0.9, fe=0.98, mn=0.98,
51
+ cod=0.9, toc=0.95, tds=0.92, sio2=0.9, hardness=0.94),
52
+ inlet_max=dict(turbidity=1, tss=1, oil_grease=0.1, fe=0.3, mn=0.1,
53
+ tds=10000)),
54
+ T("SWRO", 3, 0.45, 0.30, 5.0, 4.5,
55
+ removal=dict(turbidity=0.95, tss=0.99, oil_grease=0.9, fe=0.98, mn=0.98,
56
+ cod=0.9, toc=0.95, tds=0.991, sio2=0.99, hardness=0.995),
57
+ inlet_max=dict(turbidity=1, tss=1, oil_grease=0.1, tds=50000)),
58
+ T("RO Pass2", 3, 0.85, 0.30, 2.2, 1.2,
59
+ removal=dict(cod=0.85, toc=0.9, tds=0.95, sio2=0.95, hardness=0.97),
60
+ inlet_max=dict(turbidity=0.5, tss=0.5, tds=2000)),
61
+ T("EDI", 4, 0.90, 0.25, 2.5, 1.5,
62
+ removal=dict(toc=0.5, tds=0.99, sio2=0.95, hardness=0.999),
63
+ inlet_max=dict(turbidity=0.2, fe=0.01, mn=0.01, toc=0.5,
64
+ tds=50, sio2=1, hardness=1)),
65
+ T("NRMB", 4, 1.0, 0.25, 1.4, 2.5,
66
+ removal=dict(tds=0.99, sio2=0.98, hardness=0.999),
67
+ inlet_max=dict(turbidity=0.2, tds=20)),
68
+ T("GUARD", 4, 1.0, 0.05, 0.3, 0.2,
69
+ removal=dict(turbidity=0.95, tss=0.95, fe=0.3)),
70
+ T("UV", 4, 1.0, 0.40, 0.6, 0.4,
71
+ removal=dict(cod=0.1, toc=0.3),
72
+ inlet_max=dict(turbidity=5)),
73
+ ]
74
+
75
+ TECH_BY_NAME = {t.name: t for t in DEFAULT_TECHNOLOGIES}