How to use the unit test harness
Overview
This guide shows you how to use the unit test harness to generate tests for WaterTAP unit models. The purpose of this tool is to standardize testing so developers don’t need to write tests from the ground-up for each unit model.
How To
Begin by importing the following essential functions. This example assumes a test file is being created for an anaerobic digester.
import pytest
from pyomo.environ import ConcreteModel
from idaes.core import FlowsheetBlock, UnitModelCostingBlock
from watertap.core.solvers import get_solver
import idaes.core.util.scaling as iscale
from watertap.costing import WaterTAPCosting
# The following imports are unit-model specific
from watertap.unit_models.anaerobic_digester import AD
from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties import ADM1ParameterBlock
from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties_vapor import ADM1_vaporParameterBlock
from watertap.property_models.unit_specific.anaerobic_digestion.adm1_reactions import ADM1ReactionParameterBlock
from watertap.unit_models.tests.unit_test_harness import UnitTestHarness
# Get the default solver for testing
solver = get_solver()
Next, set up the build function which will create the flowsheet and specify the property package, reaction package, unit model configuration (named fs.unit), operating conditions, and scaling factors for any variables that are badly scaled. Then, set up the configure function which will use the model, m, returned from build to assert that the specified unit model variables are equivalent to their expected values. This function must also include at least one conservation check in which the user specifies an inlet expression and an outlet expression, and the test harness will assert that the two expressions are equivalent. This functionality should be used to ensure the unit model maintains conservation (mass, energy, momentum, etc.). If failures arise after running the test file, error messages will be displayed that prompt you to modify the build function, address the discrepancy between the expected value for a variable (user-input) and its actual value, and/or address the discrepancy between the conservation expressions.
def build():
# Create the ConcreteModel and FlowsheetBlock
m = ConcreteModel()
m.fs = FlowsheetBlock(dynamic=False)
# Set up the property package and a reaction package, if relevant
m.fs.props = ADM1ParameterBlock()
m.fs.props_vap = ADM1_vaporParameterBlock()
m.fs.rxn_props = ADM1ReactionParameterBlock(property_package=m.fs.props)
# Create the unit model and specify configration options
m.fs.unit = AD(
liquid_property_package=m.fs.props,
vapor_property_package=m.fs.props_vap,
reaction_package=m.fs.rxn_props,
has_heat_transfer=True,
has_pressure_change=False,
)
# Set the operating conditions
m.fs.unit.inlet.flow_vol.fix(170 / 24 / 3600)
m.fs.unit.inlet.temperature.fix(308.15)
m.fs.unit.inlet.pressure.fix(101325)
m.fs.unit.inlet.conc_mass_comp[0, "S_su"].fix(0.01)
m.fs.unit.inlet.conc_mass_comp[0, "S_aa"].fix(0.001)
m.fs.unit.inlet.conc_mass_comp[0, "S_fa"].fix(0.001)
m.fs.unit.inlet.conc_mass_comp[0, "S_va"].fix(0.001)
m.fs.unit.inlet.conc_mass_comp[0, "S_bu"].fix(0.001)
m.fs.unit.inlet.conc_mass_comp[0, "S_pro"].fix(0.001)
m.fs.unit.inlet.conc_mass_comp[0, "S_ac"].fix(0.001)
m.fs.unit.inlet.conc_mass_comp[0, "S_h2"].fix(1e-8)
m.fs.unit.inlet.conc_mass_comp[0, "S_ch4"].fix(1e-5)
m.fs.unit.inlet.conc_mass_comp[0, "S_IC"].fix(0.48)
m.fs.unit.inlet.conc_mass_comp[0, "S_IN"].fix(0.14)
m.fs.unit.inlet.conc_mass_comp[0, "S_I"].fix(0.02)
m.fs.unit.inlet.conc_mass_comp[0, "X_c"].fix(2)
m.fs.unit.inlet.conc_mass_comp[0, "X_ch"].fix(5)
m.fs.unit.inlet.conc_mass_comp[0, "X_pr"].fix(20)
m.fs.unit.inlet.conc_mass_comp[0, "X_li"].fix(5)
m.fs.unit.inlet.conc_mass_comp[0, "X_su"].fix(0.0)
m.fs.unit.inlet.conc_mass_comp[0, "X_aa"].fix(0.010)
m.fs.unit.inlet.conc_mass_comp[0, "X_fa"].fix(0.010)
m.fs.unit.inlet.conc_mass_comp[0, "X_c4"].fix(0.010)
m.fs.unit.inlet.conc_mass_comp[0, "X_pro"].fix(0.010)
m.fs.unit.inlet.conc_mass_comp[0, "X_ac"].fix(0.010)
m.fs.unit.inlet.conc_mass_comp[0, "X_h2"].fix(0.010)
m.fs.unit.inlet.conc_mass_comp[0, "X_I"].fix(25)
m.fs.unit.inlet.cations[0].fix(0.04)
m.fs.unit.inlet.anions[0].fix(0.02)
m.fs.unit.volume_liquid.fix(3400)
m.fs.unit.volume_vapor.fix(300)
m.fs.unit.liquid_outlet.temperature.fix(308.15)
# Set scaling factors for badly scaled variables
iscale.set_scaling_factor(
m.fs.unit.liquid_phase.mass_transfer_term[0, "Liq", "S_h2"], 1e7
)
iscale.calculate_scaling_factors(m.fs.unit)
return m
class TestAnaerobicDigester(UnitTestHarness):
def configure(self):
m = build()
# Check the expected unit model outputs
self.unit_solutions[m.fs.unit.liquid_outlet.pressure[0]] = 101325
self.unit_solutions[m.fs.unit.liquid_outlet.temperature[0]] = 308.15
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_I"]
] = 0.3287724
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_aa"]
] = 0.00531408
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_ac"]
] = 0.1977833
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_bu"]
] = 0.0132484
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_ch4"]
] = 0.0549707
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_fa"]
] = 0.0986058
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_h2"]
] = 2.35916e-07
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_pro"]
] = 0.0157813
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_su"]
] = 0.01195333
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_va"]
] = 0.011622969
self.unit_solutions[m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_I"]] = 25.6217
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_aa"]
] = 1.1793147
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_ac"]
] = 0.760653
self.unit_solutions[m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_c"]] = 0.308718
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_c4"]
] = 0.431974
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_ch"]
] = 0.027947465
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_fa"]
] = 0.2430681
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_h2"]
] = 0.3170629
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_li"]
] = 0.0294834
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_pr"]
] = 0.102574392
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_pro"]
] = 0.137323
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "X_su"]
] = 0.420219
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_IC"]
] = 1.8320212
self.unit_solutions[
m.fs.unit.liquid_outlet.conc_mass_comp[0, "S_IN"]
] = 1.8235307
self.unit_solutions[m.fs.unit.liquid_outlet.anions[0]] = 0.0200033
self.unit_solutions[m.fs.unit.liquid_outlet.cations[0]] = 0.0400066
self.unit_solutions[m.fs.unit.vapor_outlet.pressure[0]] = 106659.5225
self.unit_solutions[m.fs.unit.vapor_outlet.temperature[0]] = 308.15
self.unit_solutions[m.fs.unit.vapor_outlet.flow_vol[0]] = 0.03249637
self.unit_solutions[
m.fs.unit.vapor_outlet.conc_mass_comp[0, "S_ch4"]
] = 1.6216465
self.unit_solutions[
m.fs.unit.vapor_outlet.conc_mass_comp[0, "S_co2"]
] = 0.169417
self.unit_solutions[m.fs.unit.KH_co2[0]] = 0.02714666
self.unit_solutions[m.fs.unit.KH_ch4[0]] = 0.001161902
self.unit_solutions[m.fs.unit.KH_h2[0]] = 0.0007384652
self.unit_solutions[m.fs.unit.electricity_consumption[0]] = 23.7291667
self.unit_solutions[m.fs.unit.hydraulic_retention_time[0]] = 1880470.588
self.unit_solutions[m.fs.unit.costing.capital_cost] = 2166581.415
# Conservation check
self.conservation_equality = {
"Check 1": {
"in": m.fs.unit.inlet.flow_vol[0],
"out": (
m.fs.unit.liquid_outlet.flow_vol[0] * m.fs.props.dens_mass
+ m.fs.unit.vapor_outlet.flow_vol[0] * m.fs.props_vap.dens_mass
)
/ m.fs.props.dens_mass,
},
"Check 2": {
"in": (
m.fs.unit.inlet.flow_vol[0]
* m.fs.props.dens_mass
* m.fs.props.cp_mass
* (m.fs.unit.inlet.temperature[0] - m.fs.props.temperature_ref)
)
- (
m.fs.unit.liquid_outlet.flow_vol[0]
* m.fs.props.dens_mass
* m.fs.props.cp_mass
* (
m.fs.unit.liquid_outlet.temperature[0]
- m.fs.props.temperature_ref
)
),
"out": -1 * m.fs.unit.liquid_phase.enthalpy_transfer[0],
},
}
return m