# ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC # Under the terms of Contract DE-NA0003525 with National Technology and # Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. # This software is distributed under the 3-clause BSD License. # ____________________________________________________________________________ """ Unit Tests for interfacing with sIPOPT """ import pyutilib.th as unittest from pyomo.environ import ConcreteModel, Param, Var, Block, Suffix, value from pyomo.opt import SolverFactory from pyomo.dae import ContinuousSet from pyomo.dae.simulator import scipy_available from pyomo.core.expr.current import identify_variables from pyomo.contrib.sensitivity_toolbox.sens import sipopt import pyomo.contrib.sensitivity_toolbox.examples.feedbackController as fc import pyomo.contrib.sensitivity_toolbox.examples.rangeInequality as ri import pyomo.contrib.sensitivity_toolbox.examples.HIV_Transmission as hiv opt = SolverFactory('ipopt_sens', solver_io='nl') class TestSensitivityToolbox(unittest.TestCase): #test arguments @unittest.skipIf(not opt.available(False), "ipopt_sens is not available") def test_bad_arg(self): m = ConcreteModel() m.t = ContinuousSet(bounds=(0,1)) m.a = Param(initialize=1, mutable=True) m.b = Param(initialize=2, mutable=True) m.c = Param(initialize=3, mutable=False) m.x = Var(m.t) list_one = [m.a,m.b] list_two = [m.a,m.b,m.c] list_three = [m.a, m.x] list_four = [m.a,m.c] #verify ValueError thrown when param and perturb list are different # lengths try: Result = sipopt(m,list_one,list_two) self.fail("Expected ValueError: for different length lists") except ValueError: pass #verify ValueError thrown when param list has a Var in it try: Result = sipopt(m,list_three,list_two) self.fail("Expected ValueError: variable sent through paramSubList") except ValueError: pass #verify ValueError thrown when perturb list has Var in it try: Result = sipopt(m,list_one,list_three) self.fail("Expected ValueError: variable sent through perturbList") except ValueError: pass #verify ValueError thrown when param list has an unmutable param try: Result = sipopt(m,list_four,list_one) self.fail("Expected ValueError:" "unmutable param sent through paramSubList") except ValueError: pass #test feedbackController Solution when the model gets cloned @unittest.skipIf(not scipy_available, "scipy is required for this test") @unittest.skipIf(not opt.available(False), "ipopt_sens is not available") def test_clonedModel_soln(self): m_orig = fc.create_model() fc.initialize_model(m_orig,100) m_orig.perturbed_a = Param(initialize=-0.25) m_orig.perturbed_H = Param(initialize=0.55) m_sipopt = sipopt(m_orig,[m_orig.a,m_orig.H], [m_orig.perturbed_a,m_orig.perturbed_H], cloneModel=True) #verify cloned model has _sipopt_data block # and original model is untouched self.assertFalse(m_sipopt == m_orig) self.assertTrue(hasattr(m_sipopt,'_sipopt_data') and m_sipopt._sipopt_data.ctype is Block) self.assertFalse(hasattr(m_orig,'_sipopt_data')) self.assertFalse(hasattr(m_orig,'b')) #verify variable declaration self.assertTrue(hasattr(m_sipopt._sipopt_data,'a') and m_sipopt._sipopt_data.a.ctype is Var) self.assertTrue(hasattr(m_sipopt._sipopt_data,'H') and m_sipopt._sipopt_data.H.ctype is Var) #verify suffixes self.assertTrue(hasattr(m_sipopt,'sens_state_0') and m_sipopt.sens_state_0.ctype is Suffix and m_sipopt.sens_state_0[m_sipopt._sipopt_data.H]==2 and m_sipopt.sens_state_0[m_sipopt._sipopt_data.a]==1) self.assertTrue(hasattr(m_sipopt,'sens_state_1') and m_sipopt.sens_state_1.ctype is Suffix and m_sipopt.sens_state_1[m_sipopt._sipopt_data.H]==2 and m_sipopt.sens_state_1[m_sipopt._sipopt_data.a]==1) self.assertTrue(hasattr(m_sipopt,'sens_state_value_1') and m_sipopt.sens_state_value_1.ctype is Suffix and m_sipopt.sens_state_value_1[ m_sipopt._sipopt_data.H]==0.55 and m_sipopt.sens_state_value_1[ m_sipopt._sipopt_data.a]==-0.25) self.assertTrue(hasattr(m_sipopt,'sens_init_constr') and m_sipopt.sens_init_constr.ctype is Suffix and m_sipopt.sens_init_constr[ m_sipopt._sipopt_data.paramConst[1]]==1 and m_sipopt.sens_init_constr[ m_sipopt._sipopt_data.paramConst[2]]==2) self.assertTrue(hasattr(m_sipopt,'sens_sol_state_1') and m_sipopt.sens_sol_state_1.ctype is Suffix) self.assertAlmostEqual( m_sipopt.sens_sol_state_1[ m_sipopt.F[15]],-0.00102016765,8) self.assertTrue(hasattr(m_sipopt,'sens_sol_state_1_z_L') and m_sipopt.sens_sol_state_1_z_L.ctype is Suffix) self.assertAlmostEqual( m_sipopt.sens_sol_state_1_z_L[ m_sipopt.u[15]],-2.181712e-09,13) self.assertTrue(hasattr(m_sipopt,'sens_sol_state_1_z_U') and m_sipopt.sens_sol_state_1_z_U.ctype is Suffix) self.assertAlmostEqual( m_sipopt.sens_sol_state_1_z_U[ m_sipopt.u[15]],6.580899e-09,13) #verify deactivated constraints for cloned model self.assertFalse(m_sipopt.FDiffCon[0].active and m_sipopt.FDiffCon[7.5].active and m_sipopt.FDiffCon[15].active ) self.assertFalse(m_sipopt.x_dot[0].active and m_sipopt.x_dot[7.5].active and m_sipopt.x_dot[15].active ) #verify constraints on original model are still active self.assertTrue(m_orig.FDiffCon[0].active and m_orig.FDiffCon[7.5].active and m_orig.FDiffCon[15].active ) self.assertTrue(m_orig.x_dot[0].active and m_orig.x_dot[7.5].active and m_orig.x_dot[15].active ) #verify solution self.assertAlmostEqual(value(m_sipopt.J),0.0048956783,8) @unittest.skipIf(not scipy_available, "scipy is required for this test") @unittest.skipIf(not opt.available(False), "ipopt_sens is not available") def test_noClone_soln(self): m_orig = fc.create_model() fc.initialize_model(m_orig,100) m_orig.perturbed_a = Param(initialize=-0.25) m_orig.perturbed_H = Param(initialize=0.55) m_sipopt = sipopt(m_orig,[m_orig.a,m_orig.H], [m_orig.perturbed_a,m_orig.perturbed_H], cloneModel=False) self.assertTrue(m_sipopt == m_orig) #test _sipopt_data block exists self.assertTrue(hasattr(m_orig,'_sipopt_data') and m_orig._sipopt_data.ctype is Block) #test variable declaration self.assertTrue(hasattr(m_sipopt._sipopt_data,'a') and m_sipopt._sipopt_data.a.ctype is Var) self.assertTrue(hasattr(m_sipopt._sipopt_data,'H') and m_sipopt._sipopt_data.H.ctype is Var) #test for suffixes self.assertTrue(hasattr(m_sipopt,'sens_state_0') and m_sipopt.sens_state_0.ctype is Suffix and m_sipopt.sens_state_0[m_sipopt._sipopt_data.H]==2 and m_sipopt.sens_state_0[m_sipopt._sipopt_data.a]==1) self.assertTrue(hasattr(m_sipopt,'sens_state_1') and m_sipopt.sens_state_1.ctype is Suffix and m_sipopt.sens_state_1[m_sipopt._sipopt_data.H]==2 and m_sipopt.sens_state_1[m_sipopt._sipopt_data.a]==1) self.assertTrue(hasattr(m_sipopt,'sens_state_value_1') and m_sipopt.sens_state_value_1.ctype is Suffix and m_sipopt.sens_state_value_1[ m_sipopt._sipopt_data.H]==0.55 and m_sipopt.sens_state_value_1[ m_sipopt._sipopt_data.a]==-0.25) self.assertTrue(hasattr(m_sipopt,'sens_init_constr') and m_sipopt.sens_init_constr.ctype is Suffix and m_sipopt.sens_init_constr[ m_sipopt._sipopt_data.paramConst[1]]==1 and m_sipopt.sens_init_constr[ m_sipopt._sipopt_data.paramConst[2]]==2) self.assertTrue(hasattr(m_sipopt,'sens_sol_state_1') and m_sipopt.sens_sol_state_1.ctype is Suffix) self.assertAlmostEqual( m_sipopt.sens_sol_state_1[ m_sipopt.F[15]],-0.00102016765,8) self.assertTrue(hasattr(m_sipopt,'sens_sol_state_1_z_L') and m_sipopt.sens_sol_state_1_z_L.ctype is Suffix) self.assertAlmostEqual( m_sipopt.sens_sol_state_1_z_L[ m_sipopt.u[15]],-2.181712e-09,13) self.assertTrue(hasattr(m_sipopt,'sens_sol_state_1_z_U') and m_sipopt.sens_sol_state_1_z_U.ctype is Suffix) self.assertAlmostEqual( m_sipopt.sens_sol_state_1_z_U[ m_sipopt.u[15]],6.580899e-09,13) #verify deactivated constraints on model self.assertFalse(m_sipopt.FDiffCon[0].active and m_sipopt.FDiffCon[7.5].active and m_sipopt.FDiffCon[15].active ) self.assertFalse(m_sipopt.x_dot[0].active and m_sipopt.x_dot[7.5].active and m_sipopt.x_dot[15].active ) #test model solution self.assertAlmostEqual(value(m_sipopt.J),0.0048956783,8) #test indexed param mapping to var and perturbed values @unittest.skipIf(not scipy_available, "scipy is required for this test") @unittest.skipIf(not opt.available(False), "ipopt_sens is not available") def test_indexedParamsMapping(self): m = hiv.create_model() hiv.initialize_model(m,10,5,1) m.epsDelta = Param(initialize = 0.75001) q_del = {} q_del[(0,0)] = 1.001 q_del[(0,1)] = 1.002 q_del[(1,0)] = 1.003 q_del[(1,1)] = 1.004 q_del[(2,0)] = 0.83001 q_del[(2,1)] = 0.83002 q_del[(3,0)] = 0.42001 q_del[(4,0)] = 0.17001 m.qqDelta = Param(m.ij, initialize = q_del) m.aaDelta = Param(initialize =0.0001001) m_sipopt = sipopt(m, [m.eps,m.qq,m.aa], [m.epsDelta,m.qqDelta,m.aaDelta]) #param to var data self.assertEqual( m_sipopt._sipopt_data.paramConst[1].lower.local_name, 'eps') self.assertEqual( m_sipopt._sipopt_data.paramConst[1].body.local_name, 'eps') self.assertEqual( m_sipopt._sipopt_data.paramConst[1].upper.local_name, 'eps') self.assertEqual( m_sipopt._sipopt_data.paramConst[6].lower.local_name, 'qq[2,0]') self.assertEqual( m_sipopt._sipopt_data.paramConst[6].body.local_name, 'qq[2,0]') self.assertEqual( m_sipopt._sipopt_data.paramConst[6].upper.local_name, 'qq[2,0]') self.assertEqual( m_sipopt._sipopt_data.paramConst[10].lower.local_name, 'aa') self.assertEqual( m_sipopt._sipopt_data.paramConst[10].body.local_name, 'aa') self.assertEqual( m_sipopt._sipopt_data.paramConst[10].upper.local_name, 'aa') #test Constraint substitution @unittest.skipIf(not opt.available(False), "ipopt_sens is not available") def test_constraintSub(self): m = ri.create_model() m.pert_a = Param(initialize=0.01) m.pert_b = Param(initialize=1.01) m_sipopt = sipopt(m,[m.a,m.b], [m.pert_a,m.pert_b]) #verify substitutions in equality constraint self.assertTrue(m_sipopt.C_equal.lower.ctype is Param and m_sipopt.C_equal.upper.ctype is Param) self.assertFalse(m_sipopt.C_equal.active) self.assertTrue(m_sipopt._sipopt_data.constList[3].lower == 0.0 and m_sipopt._sipopt_data.constList[3].upper == 0.0 and len(list(identify_variables( m_sipopt._sipopt_data.constList[3].body))) == 2) #verify substitutions in one-sided bounded constraint self.assertTrue(m_sipopt.C_singleBnd.lower is None and m_sipopt.C_singleBnd.upper.ctype is Param) self.assertFalse(m_sipopt.C_singleBnd.active) self.assertTrue(m_sipopt._sipopt_data.constList[4].lower is None and m_sipopt._sipopt_data.constList[4].upper == 0.0 and len(list(identify_variables( m_sipopt._sipopt_data.constList[4].body))) == 2) #verify substitutions in ranged inequality constraint self.assertTrue(m_sipopt.C_rangedIn.lower.ctype is Param and m_sipopt.C_rangedIn.upper.ctype is Param) self.assertFalse(m_sipopt.C_rangedIn.active) self.assertTrue(m_sipopt._sipopt_data.constList[1].lower is None and m_sipopt._sipopt_data.constList[1].upper == 0.0 and len(list(identify_variables( m_sipopt._sipopt_data.constList[1].body))) == 2) self.assertTrue(m_sipopt._sipopt_data.constList[2].lower is None and m_sipopt._sipopt_data.constList[2].upper == 0.0 and len(list(identify_variables( m_sipopt._sipopt_data.constList[2].body))) == 2) # Test example `parameter.py` @unittest.skipIf(not opt.available(False), "ipopt_sens is not available") def test_parameter_example(self): from pyomo.contrib.sensitivity_toolbox.examples.parameter import run_example d = run_example() d_correct = {'eta1':4.5, 'eta2':1.0, 'x1_init':0.15, 'x2_init':0.15, 'x3_init':0.0, 'cost_sln':0.5, 'x1_sln':0.5, 'x2_sln':0.5, 'x3_sln':0.0, 'eta1_pert':4.0, 'eta2_pert':1.0, 'x1_pert':0.3333333,'x2_pert':0.6666667,'x3_pert':0.0, 'cost_pert':0.55555556} for k in d_correct.keys(): # Check each element of the 'correct' dictionary against the returned # dictionary to 3 decimal places self.assertAlmostEqual(d[k],d_correct[k],3) if __name__=="__main__": unittest.main()