Electrochemical Cell#

QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems

This module is developed by:

Smiti Mittal <smitimittal@gmail.com> Yalin Li <mailto.yalin.li@gmail.com> Anna Kogler <akogler@stanford.edu>

This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt for license details.

Reference for the default electrochemical cell modelled below:

Evaluating Membrane Performance in Electrochemical Stripping Reactor for Nitrogen Removal Julia Simon, Department of Chemical Engineering, Stanford University Principal Investigator: Professor William Tarpeh Second Reader: Professor Gerald Fuller

class qsdsan.sanunits._electrochemical_cell.ElectrochemicalCell(ID='', ins: Sequence[Stream] | None = (), outs: Sequence[Stream] | None = (), recovery={'NH3': 0.7}, removal={'K+': 0.83, 'NH3': 0.83, 'Na+': 0.8}, OPEX_over_CAPEX=0.2)#

Electrochemical cell for nutrient recovery.

This unit has the following equipment:
  • recovery (dict) – Keys refer to chemical component IDs. Values refer to recovery fractions (with 1 being 100%) for the respective chemicals.

  • removal (dict) – Keys refer to chemical component IDs. Values refer to removal fractions (with 1 being 100%) for the respective chemicals.

  • equipment (list(obj)) – List of Equipment objects part of the Electrochemical Cell.

  • OPEX_over_CAPEX (float) – Ratio with which operating costs are calculated as a fraction of capital costs


>>> # Set components
>>> import qsdsan as qs
>>> kwargs = dict(particle_size='Soluble',
...               degradability='Undegradable',
...               organic=False)
>>> H2O = qs.Component.from_chemical('H2O', phase='l', **kwargs)
>>> NH3 = qs.Component.from_chemical('NH3', phase='g', **kwargs)
>>> NH3.particle_size = 'Dissolved gas'
>>> H2SO4 = qs.Component.from_chemical('H2SO4', phase='l', **kwargs)
>>> AmmoniumSulfate = qs.Component.from_chemical('AmmoniumSulfate', phase='l',
...                                              **kwargs)
>>> Na_ion = qs.Component.from_chemical('Na+', phase='s', **kwargs)
>>> K_ion = qs.Component.from_chemical('K+', phase='s', **kwargs)
>>> Cl_ion = qs.Component.from_chemical('Cl-', phase='s', **kwargs)
>>> PO4_ion = qs.Component.from_chemical('Phosphate', phase='s', **kwargs)
>>> SO4_ion = qs.Component.from_chemical('Sulfate', phase='s', **kwargs)
>>> C = qs.Component.from_chemical('Carbon', phase='s', **kwargs)
>>> COD = qs.Component.from_chemical('O2', phase='s', **kwargs)
>>> cmps = qs.Components((H2O, NH3, H2SO4, AmmoniumSulfate, Na_ion, K_ion, Cl_ion, PO4_ion, SO4_ion, C, COD))
>>> # Assuming all has the same molar volume as water for demonstration purpose
>>> for cmp in cmps:
...     cmp.copy_models_from(H2O, names=['V'])
...     defaulted_properties = cmp.default()
>>> qs.set_thermo(cmps)
>>> # Set waste streams
>>> influent = qs.WasteStream('influent')
>>> influent.set_flow_by_concentration(flow_tot=0.5, concentrations={'NH3':3820,
...                                     'Na+':1620, 'K+':1470, 'Cl-':3060, 'Phosphate':169,
...                                     'Sulfate':1680, 'Carbon':1860, 'O2':3460}, units=('mL/min', 'mg/L'))
>>> influent.show()
WasteStream: influent
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (g/hr): H2O        29.5
              NH3        0.115
              Na+        0.0486
              K+         0.0441
              Cl-        0.0918
              Phosphate  0.00507
              Sulfate    0.0504
              Carbon     0.0558
              O2         0.104
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
  TC         : 1860.0 mg/L
  TP         : 31.9 mg/L
  TK         : 1470.0 mg/L
 Component concentrations (mg/L):
  H2O              984514.0
  NH3              3820.0
  Na+              1620.0
  K+               1470.0
  Cl-              3060.0
  Phosphate        169.0
  Sulfate          1680.0
  Carbon           1860.0
  O2               3460.0
>>> catalysts = qs.WasteStream('catalysts', H2SO4=0.00054697, units=('kg/hr'))
>>> # kg/hr imass value derived from 0.145 moles used on average for one, 26 hour experiment in the model cell
>>> catalysts.price = 8.4 #in $/kg
>>> # electricity price for ECS experiments in the default model tested by the Tarpeh Lab
>>> # if want to change the price of electricity,
>>> # use qs.PowerUtility, e.g., qs.PowerUtility.price = 0.1741
>>> # Set the unit
>>> U1 = qs.sanunits.ElectrochemicalCell('unit_1', ins=(influent, catalysts), outs=('recovered', 'removed', 'residual'))
>>> # Simulate and look at the results
>>> U1.simulate()
>>> U1.results()
Electrochemical cell                                      Units                              unit_1
Electricity         Power                                    kW                            5.42e-05
                    Cost                                 USD/hr                            4.24e-06
Design              Electrode unit_1_Main_Anode - Nu...    None                                   1
                    Electrode unit_1_Main_Anode - Ma...    None  titanium grid catalyst welded t...
                    Electrode unit_1_Main_Anode - Su...      m2                                   1
                    Electrode unit_1_Main_Cathode - ...    None                                   1
                    Electrode unit_1_Main_Cathode - ...    None  timesetl 3pcs stainless steel w...
                    Electrode unit_1_Main_Cathode - ...      m2                                30.2
                    Electrode unit_1_Current_Collect...    None                                   1
                    Electrode unit_1_Current_Collect...    None    stainless steel 26 gauge 5.5 x 7
                    Electrode unit_1_Current_Collect...      m2                                38.5
                    Electrode unit_1_Reference_Elect...    None                                   1
                    Electrode unit_1_Reference_Elect...    None  re-5b ag/agcl, 7.5 cm long, wit...
                    Electrode unit_1_Reference_Elect...      m2                                   1
                    Membrane unit_1_Cation_Exchange_...                                           1
                    Membrane unit_1_Cation_Exchange_...          CMI-7000S, polystyrene 0.45mm t...
                    Membrane unit_1_Cation_Exchange_...      m2                                30.2
                    Membrane unit_1_Gas_Permeable_Me...                                           1
                    Membrane unit_1_Gas_Permeable_Me...          Aquastill 0.3-micron polyethyle...
                    Membrane unit_1_Gas_Permeable_Me...      m2                                30.2
Purchase cost       unit_1_Main_Anode                       USD                                 288
                    unit_1_Main_Cathode                     USD                               0.847
                    unit_1_Current_Collector_Cathode        USD                                39.9
                    unit_1_Reference_Electrode              USD                                  94
                    unit_1_Cation_Exchange_Membrane         USD                                 167
                    unit_1_Gas_Permeable_Membrane           USD                                29.3
                    Exterior                                USD                                60.5
Total purchase cost                                         USD                                 679
Utility cost                                             USD/hr                            4.24e-06
Additional OPEX                                          USD/hr                                 136
>>> U1.show() 
ElectrochemicalCell: unit_1
[0] influent
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (g/hr): H2O        29.5
F_BM: dict[str, float]#

All bare-module factors for each purchase cost. Defaults to values in the class attribute _F_BM_default.

F_D: dict[str, float]#

All design factors for each purchase cost item in baseline_purchase_costs.

F_M: dict[str, float]#

All material factors for each purchase cost item in baseline_purchase_costs.

F_P: dict[str, float]#

All pressure factors for each purchase cost item in baseline_purchase_costs.

baseline_purchase_costs: dict[str, float]#

All baseline purchase costs without accounting for design, pressure, and material factors.

design_results: dict[str, object]#

All design requirements excluding utility requirements and detailed auxiliary unit requirements.

equipment_lifetime: int | dict[str, int]#

Lifetime of equipment. Defaults to values in the class attribute _default_equipment_lifetime. Use an integer to specify the lifetime for all items in the unit purchase costs. Use a dictionary to specify the lifetime of each purchase cost item.

heat_utilities: tuple[HeatUtility, ...]#

All heat utilities associated to unit. Cooling and heating requirements are stored here (including auxiliary requirements).

installed_costs: dict[str, float]#

All installed costs accounting for bare module, design, pressure, and material factors. Items here are automatically updated at the end of unit simulation.

line: str = 'Electrochemical cell'#

class-attribute Name denoting the type of Unit class. Defaults to the class name of the first child class

parallel: dict[str, int]#

Name-number pairs of baseline purchase costs and auxiliary unit operations in parallel. Use ‘self’ to refer to the main unit. Capital and heat and power utilities in parallel will become proportional to this value.

power_utility: PowerUtility#

Electric utility associated to unit (including auxiliary requirements).

prioritize: bool#

Whether to prioritize unit operation specification within recycle loop (if any).

purchase_costs: dict[str, float]#

Itemized purchase costs (including auxiliary units) accounting for design, pressure, and material factors (i.e., F_D, F_P, F_M). Items here are automatically updated at the end of unit simulation.

responses: set[bst.GenericResponse]#

Unit design decisions that must be solved to satisfy specifications. While adding responses is optional, simulations benefit from responses by being able to predict better guesses.

run_after_specifications: bool#

Whether to run mass and energy balance after calling specification functions