def build_watertank_vert_cylin():
    # Create a Concrete Model as the top level object
    m = pyo.ConcreteModel()
    # Add a flowsheet object to the model
    m.fs = FlowsheetBlock(default={"dynamic": False})
    # Add property packages to flowsheet library
    m.fs.prop_water = iapws95.Iapws95ParameterBlock()
    m.fs.unit = WaterTank(
        default={
            "tank_type": "vertical_cylindrical_tank",
            "property_package": m.fs.prop_water,
            "has_holdup": False,
            "has_heat_transfer": True,
            "has_pressure_change": True
        })

    # fix inputs for horizontal cylindrical tank
    m.fs.unit.tank_diameter.fix(1.2)  # tank diameter
    m.fs.unit.tank_level[:].fix(0.6)  # tank level
    m.fs.unit.heat_duty[:].fix(0.0)  # assume no heat loss
    return m
def get_model(dynamic=False):
    m = pyo.ConcreteModel(name="Testing PID controller model")
    if dynamic:
        m.dynamic = True
        m.fs = FlowsheetBlock(default={
            "dynamic": True,
            "time_set": [0, 50, 1000],
            "time_units": pyo.units.s
        })
    else:
        m.dynamic = False
        m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.prop_water = iapws95.Iapws95ParameterBlock()

    # water pump
    m.fs.pump = WaterPump(default={
        "dynamic": False,
        "property_package": m.fs.prop_water,
    })

    # water tank
    m.fs.tank = WaterTank(
        default={
            "tank_type": "simple_tank",
            "has_holdup": True,
            "property_package": m.fs.prop_water,
        })

    m.fs.valve = WaterValve(
        default={
            "dynamic": False,
            "has_holdup": False,
            "phase": "Liq",
            "property_package": m.fs.prop_water
        })

    if dynamic:
        m.fs.controller = PIDController(
            default={
                "pv": m.fs.tank.tank_level,
                "mv": m.fs.valve.valve_opening,
                "type": 'PI',
                "bounded_output": False
            })

        m.discretizer = pyo.TransformationFactory('dae.finite_difference')
        m.discretizer.apply_to(m, nfe=20, wrt=m.fs.time, scheme="BACKWARD")

        m.fs.controller.gain_p.fix(-1e-1)
        m.fs.controller.gain_i.fix(-1e-2)
        m.fs.controller.setpoint.fix(5)
        m.fs.controller.mv_ref.fix(0.5)
        m.fs.controller.integral_of_error[0].fix(0)

    m.fs.pump_to_tank = Arc(source=m.fs.pump.outlet,
                            destination=m.fs.tank.inlet)
    m.fs.tank_to_valve = Arc(source=m.fs.tank.outlet,
                             destination=m.fs.valve.inlet)
    pyo.TransformationFactory('network.expand_arcs').apply_to(m.fs)

    m.fs.pump.efficiency_isentropic.fix(0.8)
    m.fs.pump.deltaP.fix(5e4)

    m.fs.tank.tank_cross_sect_area.fix(20)
    m.fs.tank.tank_level[:].fix(5)

    m.fs.valve.Cv.fix(63.4676)
    m.fs.valve.valve_opening.fix(0.5)
    m.fs.pump.inlet.flow_mol[:].fix(7000)
    m.fs.pump.inlet.enth_mol[:].fix(3924)
    m.fs.pump.inlet.pressure[:].fix(1e7)

    m.fs.valve.outlet.pressure[:].fix(10050000)

    set_scaling_factors(m)

    if dynamic:
        m.fs.tank.set_initial_condition()

    if not dynamic:
        m.fs.pump.initialize()

        m.fs.tank.inlet.flow_mol[:].value = m.fs.pump.outlet.flow_mol[0].value
        m.fs.tank.inlet.enth_mol[:].value = m.fs.pump.outlet.enth_mol[0].value
        m.fs.tank.inlet.pressure[:].value = m.fs.pump.outlet.pressure[0].value

        m.fs.tank.initialize()

        m.fs.valve.inlet.flow_mol[:].value = m.fs.tank.outlet.flow_mol[0].value
        m.fs.valve.inlet.enth_mol[:].value = m.fs.tank.outlet.enth_mol[0].value
        m.fs.valve.inlet.pressure[:].value = m.fs.tank.outlet.pressure[0].value

        # Solve for outlet pressure
        m.fs.valve.initialize()

        m.fs.valve.valve_opening.unfix()
        dof = degrees_of_freedom(m)
        assert dof == 0
        solver = pyo.SolverFactory("ipopt")
        solver.options = {
            "tol": 1e-7,
            "linear_solver": "ma27",
            "max_iter": 100,
        }
        solver.solve(m, tee=True)

    else:
        m.fs.tank.tank_level.unfix()
        m.fs.tank.tank_level[0].fix()
        m.fs.valve.valve_opening.unfix()
    return m