.. _how_to_use_unit_test_harness: 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. .. testsetup:: # quiet idaes logs import idaes.logger as idaeslogger idaeslogger.getLogger('ideas.core').setLevel('CRITICAL') idaeslogger.getLogger('ideas.core.util.scaling').setLevel('CRITICAL') idaeslogger.getLogger('idaes.init').setLevel('CRITICAL') .. testcode:: 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. .. testcode:: 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