# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 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 Elements of a Model
#
# Test             Class to test the Model class
#

import json
import os
from os.path import abspath, dirname, join

currdir = dirname(abspath(__file__))
import pickle

from filecmp import cmp
import pyomo.common.unittest as unittest

from pyomo.common.dependencies import yaml_available
from pyomo.common.tempfiles import TempfileManager
import pyomo.core.expr as EXPR
from pyomo.environ import (
    RangeSet,
    ConcreteModel,
    Var,
    Param,
    Block,
    AbstractModel,
    Set,
    Constraint,
    Objective,
    value,
    sum_product,
    SolverFactory,
    VarList,
    ObjectiveList,
    ConstraintList,
    Model,
)
from pyomo.opt import check_available_solvers
from pyomo.opt.parallel.local import SolverManager_Serial

if yaml_available:
    import yaml

solvers = check_available_solvers('glpk')


class Test(unittest.TestCase):
    def tearDown(self):
        if os.path.exists("unknown.lp"):
            os.unlink("unknown.lp")
        TempfileManager.clear_tempfiles()

    def test_clone_concrete_model(self):
        def _populate(b, *args):
            b.A = RangeSet(1, 3)
            b.v = Var()
            b.vv = Var(m.A)
            b.p = Param()

        m = ConcreteModel()
        _populate(m)
        m.b = Block()
        _populate(m.b)
        m.b.c = Block()
        _populate(m.b.c)
        m.bb = Block(m.A, rule=_populate)

        n = m.clone()
        self.assertNotEqual(id(m), id(n))

        self.assertEqual(id(m), id(m.b.parent_block()))
        self.assertEqual(id(m), id(m.bb.parent_block()))
        self.assertEqual(id(m), id(m.bb[1].parent_block()))
        self.assertEqual(id(m), id(m.bb[2].parent_block()))
        self.assertEqual(id(m), id(m.bb[3].parent_block()))

        self.assertEqual(id(n), id(n.b.parent_block()))
        self.assertEqual(id(n), id(n.bb.parent_block()))
        self.assertEqual(id(n), id(n.bb[1].parent_block()))
        self.assertEqual(id(n), id(n.bb[2].parent_block()))
        self.assertEqual(id(n), id(n.bb[3].parent_block()))

        for x, y in ((m, n), (m.b, n.b), (m.b.c, n.b.c), (m.bb[2], n.bb[2])):
            self.assertNotEqual(id(x), id(y))
            self.assertNotEqual(id(x.parent_block()), id(x))
            self.assertNotEqual(id(y.parent_block()), id(y))

            self.assertEqual(id(x.A.parent_block()), id(x))
            self.assertEqual(id(x.v.parent_block()), id(x))
            self.assertEqual(id(x.vv.parent_block()), id(x))
            self.assertEqual(id(x.vv[1].parent_block()), id(x))
            self.assertEqual(id(x.p.parent_block()), id(x))

            self.assertEqual(id(y.A.parent_block()), id(y))
            self.assertEqual(id(y.v.parent_block()), id(y))
            self.assertEqual(id(y.vv.parent_block()), id(y))
            self.assertEqual(id(y.vv[1].parent_block()), id(y))
            self.assertEqual(id(y.p.parent_block()), id(y))

    def test_clone_abstract_model(self):
        def _populate(b, *args):
            b.A = RangeSet(1, 3)
            b.v = Var()
            b.vv = Var(m.A)
            b.p = Param()

        m = AbstractModel()
        _populate(m)
        m.b = Block()
        _populate(m.b)
        m.b.c = Block()
        _populate(m.b.c)
        m.bb = Block(m.A, rule=_populate)

        n = m.clone()
        self.assertNotEqual(id(m), id(n))

        self.assertEqual(id(m), id(m.b.parent_block()))
        self.assertEqual(id(m), id(m.bb.parent_block()))

        self.assertEqual(id(n), id(n.b.parent_block()))
        self.assertEqual(id(n), id(n.bb.parent_block()))

        for x, y in ((m, n), (m.b, n.b), (m.b.c, n.b.c)):
            self.assertNotEqual(id(x), id(y))
            self.assertNotEqual(id(x.parent_block()), id(x))
            self.assertNotEqual(id(y.parent_block()), id(y))

            self.assertEqual(id(x.A.parent_block()), id(x))
            self.assertEqual(id(x.v.parent_block()), id(x))
            self.assertEqual(id(x.vv.parent_block()), id(x))
            self.assertEqual(id(x.p.parent_block()), id(x))

            self.assertEqual(id(y.A.parent_block()), id(y))
            self.assertEqual(id(y.v.parent_block()), id(y))
            self.assertEqual(id(y.vv.parent_block()), id(y))
            self.assertEqual(id(y.p.parent_block()), id(y))

    def test_clear_attribute(self):
        # Test coverage of the _clear_attribute method
        model = ConcreteModel()
        obj = Set()
        model.A = obj
        self.assertEqual(model.A.local_name, "A")
        self.assertEqual(obj.local_name, "A")
        self.assertIs(obj, model.A)

        obj = Var()
        model.A = obj
        self.assertEqual(model.A.local_name, "A")
        self.assertEqual(obj.local_name, "A")
        self.assertIs(obj, model.A)

        obj = Param()
        model.A = obj
        self.assertEqual(model.A.local_name, "A")
        self.assertEqual(obj.local_name, "A")
        self.assertIs(obj, model.A)

        obj = Objective()
        model.A = obj
        self.assertEqual(model.A.local_name, "A")
        self.assertEqual(obj.local_name, "A")
        self.assertIs(obj, model.A)

        obj = Constraint()
        model.A = obj
        self.assertEqual(model.A.local_name, "A")
        self.assertEqual(obj.local_name, "A")
        self.assertIs(obj, model.A)

        obj = Set()
        model.A = obj
        self.assertEqual(model.A.local_name, "A")
        self.assertEqual(obj.local_name, "A")
        self.assertIs(obj, model.A)

    def test_set_attr(self):
        model = ConcreteModel()
        model.x = Param(mutable=True)
        model.x = 5
        self.assertEqual(value(model.x), 5)
        model.x = 6
        self.assertEqual(value(model.x), 6)
        model.x = None
        self.assertEqual(model.x._value, None)

    def test_write(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.x = Var(model.A, bounds=(-1, 1))

        def obj_rule(model):
            return sum_product(model.x)

        model.obj = Objective(rule=obj_rule)
        model.write()

    def test_write2(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.x = Var(model.A, bounds=(-1, 1))

        def obj_rule(model):
            return sum_product(model.x)

        model.obj = Objective(rule=obj_rule)

        def c_rule(model):
            return (1, model.x[1] + model.x[2], 2)

        model.c = Constraint(rule=c_rule)
        model.write()

    def test_write3(self):
        # Test that the summation works correctly, even though param 'w' has a default value
        model = ConcreteModel()
        model.J = RangeSet(1, 4)
        model.w = Param(model.J, default=4)
        model.x = Var(model.J, initialize=3)

        def obj_rule(instance):
            return sum_product(instance.w, instance.x)

        model.obj = Objective(rule=obj_rule)
        self.assertEqual(value(model.obj), 48)

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_solve1(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.x = Var(model.A, bounds=(-1, 1))

        def obj_rule(model):
            return sum_product(model.x)

        model.obj = Objective(rule=obj_rule)

        def c_rule(model):
            expr = 0
            for i in model.A:
                expr += i * model.x[i]
            return expr == 0

        model.c = Constraint(rule=c_rule)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=True)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, "solve1.out"), format='json')
        with (
            open(join(currdir, "solve1.out"), 'r') as out,
            open(join(currdir, "solve1.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )

        #
        def d_rule(model):
            return model.x[1] >= 0

        model.d = Constraint(rule=d_rule)
        model.d.deactivate()
        results = opt.solve(model)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, "solve1x.out"), format='json')
        with (
            open(join(currdir, "solve1x.out"), 'r') as out,
            open(join(currdir, "solve1.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )
        #
        model.d.activate()
        results = opt.solve(model)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, "solve1a.out"), format='json')
        with (
            open(join(currdir, "solve1a.out"), 'r') as out,
            open(join(currdir, "solve1a.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )
        #
        model.d.deactivate()

        def e_rule(model, i):
            return model.x[i] >= 0

        model.e = Constraint(model.A, rule=e_rule)
        for i in model.A:
            model.e[i].deactivate()
        results = opt.solve(model)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, "solve1y.out"), format='json')
        with (
            open(join(currdir, "solve1y.out"), 'r') as out,
            open(join(currdir, "solve1.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )
        #
        model.e.activate()
        results = opt.solve(model)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, "solve1b.out"), format='json')
        with (
            open(join(currdir, "solve1b.out"), 'r') as out,
            open(join(currdir, "solve1b.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_store_to_skip_stale_vars(self):
        # test store_to() function with skip_stale_vars=True
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.x = Var(model.A, bounds=(-1, 1))

        def obj_rule(model):
            return sum_product(model.x)

        model.obj = Objective(rule=obj_rule)

        def c_rule(model):
            expr = 0
            for i in model.A:
                expr += i * model.x[i]
            return expr == 0

        model.c = Constraint(rule=c_rule)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=True)
        model.x[1].fix()
        results = opt.solve(model, symbolic_solver_labels=True)
        model.solutions.store_to(results, skip_stale_vars=False)
        for index in model.A:
            self.assertIn(model.x[index].getname(), results.solution.variable.keys())
        model.solutions.store_to(results, skip_stale_vars=True)
        for index in model.A:
            if index == 1:
                self.assertNotIn(
                    model.x[index].getname(), results.solution.variable.keys()
                )
            else:
                self.assertIn(
                    model.x[index].getname(), results.solution.variable.keys()
                )

    def test_display(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.x = Var(model.A, bounds=(-1, 1))

        def obj_rule(model):
            expr = 0
            for i in model.A:
                expr += model.x[i]
            return expr

        model.obj = Objective(rule=obj_rule)
        model.display(join(currdir, "solve3.out"))
        _out, _txt = join(currdir, "solve3.out"), join(currdir, "solve3.txt")
        self.assertTrue(cmp(_out, _txt), msg="Files %s and %s differ" % (_txt, _out))

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_solve4(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.x = Var(model.A, bounds=(-1, 1))

        def obj_rule(model):
            return sum_product(model.x)

        model.obj = Objective(rule=obj_rule)

        def c_rule(model):
            expr = 0
            for i in model.A:
                expr += i * model.x[i]
            return expr == 0

        model.c = Constraint(rule=c_rule)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=True)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve4.out'), format='json')
        with (
            open(join(currdir, "solve4.out"), 'r') as out,
            open(join(currdir, "solve1.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_solve6(self):
        #
        # Test that solution values have complete block names:
        #   b.obj
        #   b.x
        #
        model = ConcreteModel()
        model.y = Var(bounds=(-1, 1))
        model.b = Block()
        model.b.A = RangeSet(1, 4)
        model.b.x = Var(model.b.A, bounds=(-1, 1))

        def obj_rule(block):
            return sum_product(block.x)

        model.b.obj = Objective(rule=obj_rule)

        def c_rule(model):
            expr = model.y
            for i in model.b.A:
                expr += i * model.b.x[i]
            return expr == 0

        model.c = Constraint(rule=c_rule)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=True)
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve6.out'), format='json')
        with (
            open(join(currdir, "solve6.out"), 'r') as out,
            open(join(currdir, "solve6.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_solve7(self):
        #
        # Test that solution values are written with appropriate
        # quotations in results
        #
        model = ConcreteModel()
        model.y = Var(bounds=(-1, 1))
        model.A = RangeSet(1, 4)
        model.B = Set(initialize=['A B', 'C,D', 'E'])
        model.x = Var(model.A, model.B, bounds=(-1, 1))

        def obj_rule(model):
            return sum_product(model.x)

        model.obj = Objective(rule=obj_rule)

        def c_rule(model):
            expr = model.y
            for i in model.A:
                for j in model.B:
                    expr += i * model.x[i, j]
            return expr == 0

        model.c = Constraint(rule=c_rule)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=True)
        # model.display()
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve7.out'), format='json')
        with (
            open(join(currdir, "solve7.out"), 'r') as out,
            open(join(currdir, "solve7.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                json.load(txt), json.load(out), abstol=1e-4, allow_second_superset=True
            )

    def test_stats1(self):
        model = ConcreteModel()
        model.x = Var([1, 2])

        def obj_rule(model, i):
            return sum_product(model.x)

        model.obj = Objective([1, 2], rule=obj_rule)

        def c_rule(model, i):
            expr = 0
            for j in [1, 2]:
                expr += j * model.x[j]
            return expr == 0

        model.c = Constraint([1, 2], rule=c_rule)
        self.assertEqual(model.nvariables(), 2)
        self.assertEqual(model.nobjectives(), 2)
        self.assertEqual(model.nconstraints(), 2)

    def test_stats2(self):
        model = ConcreteModel()
        #
        model.x = Var([1, 2])

        def obj_rule(model, i):
            return sum_product(model.x)

        model.y = VarList()
        model.y.add()
        model.y.add()
        #
        model.obj = Objective([1, 2], rule=obj_rule)
        model.o = ObjectiveList()
        model.o.add(model.y[1])
        model.o.add(model.y[2])

        #
        def c_rule(model, i):
            expr = 0
            for j in [1, 2]:
                expr += j * model.x[j]
            return expr == 0

        model.c = Constraint([1, 2], rule=c_rule)
        model.C = ConstraintList()
        model.C.add(model.y[1] == 0)
        model.C.add(model.y[2] == 0)
        #
        self.assertEqual(model.nvariables(), 4)
        self.assertEqual(model.nobjectives(), 4)
        self.assertEqual(model.nconstraints(), 4)

    def test_stats3(self):
        model = ConcreteModel()
        model.x = Var([1, 2])

        def obj_rule(model, i):
            return sum_product(model.x)

        model.obj = Objective([1, 2], rule=obj_rule)

        def c_rule(model, i):
            expr = 0
            for j in [1, 2]:
                expr += j * model.x[j]
            return expr == 0

        model.c = Constraint([1, 2], rule=c_rule)
        #
        model.B = Block()
        model.B.x = Var([1, 2])
        model.B.o = ObjectiveList()
        model.B.o.add(model.B.x[1])
        model.B.o.add(model.B.x[2])
        model.B.c = ConstraintList()
        model.B.c.add(model.x[1] == 0)
        model.B.c.add(model.x[2] == 0)
        self.assertEqual(model.nvariables(), 4)
        self.assertEqual(model.nobjectives(), 4)
        self.assertEqual(model.nconstraints(), 4)

    def test_stats4(self):
        model = ConcreteModel()
        model.x = Var([1])

        model.B = Block()
        model.B.x = Var([1, 2, 3])
        model.B.o = ObjectiveList()
        model.B.o.add(model.B.x[1])
        model.B.c = ConstraintList()
        model.B.c.add(model.B.x[1] == 0)
        model.B.c.add(model.B.x[2] == 0)
        model.B.c.add(model.B.x[3] == 0)
        self.assertEqual(model.nvariables(), 4)
        self.assertEqual(model.nobjectives(), 1)
        self.assertEqual(model.nconstraints(), 3)
        model.clear()
        self.assertEqual(model.nvariables(), 0)
        self.assertEqual(model.nobjectives(), 0)
        self.assertEqual(model.nconstraints(), 0)

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_solve_with_pickle(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        self.assertEqual(len(model.solutions), 0)
        results = opt.solve(model, symbolic_solver_labels=True)
        self.assertEqual(len(model.solutions), 1)
        #
        self.assertEqual(model.solutions[0].gap, 0.0)
        # self.assertEqual(model.solutions[0].status, SolutionStatus.feasible)
        self.assertEqual(model.solutions[0].message, None)
        #
        buf = pickle.dumps(model)
        tmodel = pickle.loads(buf)
        self.assertEqual(len(tmodel.solutions), 1)
        self.assertEqual(tmodel.solutions[0].gap, 0.0)
        # self.assertEqual(tmodel.solutions[0].status, SolutionStatus.feasible)
        self.assertEqual(tmodel.solutions[0].message, None)

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    def test_solve_with_pickle_then_clone(self):
        # This tests github issue Pyomo-#65
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        self.assertEqual(len(model.solutions), 0)
        results = opt.solve(model, symbolic_solver_labels=True)
        self.assertEqual(len(model.solutions), 1)
        #
        self.assertEqual(model.solutions[0].gap, 0.0)
        # self.assertEqual(model.solutions[0].status, SolutionStatus.feasible)
        self.assertEqual(model.solutions[0].message, None)
        #
        buf = pickle.dumps(model)
        tmodel = pickle.loads(buf)
        self.assertEqual(len(tmodel.solutions), 1)
        self.assertEqual(tmodel.solutions[0].gap, 0.0)
        # self.assertEqual(tmodel.solutions[0].status, SolutionStatus.feasible)
        self.assertEqual(tmodel.solutions[0].message, None)
        self.assertIn(id(tmodel.b.obj), tmodel.solutions[0]._entry['objective'])
        self.assertIs(
            tmodel.b.obj, tmodel.solutions[0]._entry['objective'][id(tmodel.b.obj)][0]
        )

        inst = tmodel.clone()

        # make sure the clone has all the attributes
        self.assertTrue(hasattr(inst, 'A'))
        self.assertTrue(hasattr(inst, 'b'))
        self.assertTrue(hasattr(inst.b, 'x'))
        self.assertTrue(hasattr(inst.b, 'obj'))
        self.assertTrue(hasattr(inst, 'c'))
        # and that they were all copied
        self.assertIsNot(inst.A, tmodel.A)
        self.assertIsNot(inst.b, tmodel.b)
        self.assertIsNot(inst.b.x, tmodel.b.x)
        self.assertIsNot(inst.b.obj, tmodel.b.obj)
        self.assertIsNot(inst.c, tmodel.c)

        # Make sure the solution is on the new model
        self.assertTrue(hasattr(inst, 'solutions'))
        self.assertEqual(len(inst.solutions), 1)
        self.assertEqual(inst.solutions[0].gap, 0.0)
        # self.assertEqual(inst.solutions[0].status, SolutionStatus.feasible)
        self.assertEqual(inst.solutions[0].message, None)

        # Spot-check some components and make sure all the weakrefs in
        # the ModelSOlution got updated
        self.assertIn(id(inst.b.obj), inst.solutions[0]._entry['objective'])
        _obj = inst.solutions[0]._entry['objective'][id(inst.b.obj)]
        self.assertIs(_obj[0], inst.b.obj)

        for v in [1, 2, 3, 4]:
            self.assertIn(id(inst.b.x[v]), inst.solutions[0]._entry['variable'])
            _v = inst.solutions[0]._entry['variable'][id(inst.b.x[v])]
            self.assertIs(_v[0], inst.b.x[v])

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    @unittest.skipIf(not yaml_available, "YAML not available available")
    def test_solve_with_store1(self):
        # With symbolic solver labels
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=True)
        #
        results.write(filename=join(currdir, 'solve_with_store1.out'), format='yaml')
        with (
            open(join(currdir, "solve_with_store1.out"), 'r') as out,
            open(join(currdir, "solve_with_store1.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        model.solutions.store_to(results)
        #
        results.write(filename=join(currdir, 'solve_with_store2.out'), format='yaml')
        with (
            open(join(currdir, "solve_with_store2.out"), 'r') as out,
            open(join(currdir, "solve_with_store2.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        # Load results with string indices
        #
        tmodel = ConcreteModel()
        tmodel.A = RangeSet(1, 4)
        tmodel.b = Block()
        tmodel.b.x = Var(tmodel.A, bounds=(-1, 1))
        tmodel.b.obj = Objective(expr=sum_product(tmodel.b.x))
        tmodel.c = Constraint(expr=tmodel.b.x[1] >= 0)
        self.assertEqual(len(tmodel.solutions), 0)
        tmodel.solutions.load_from(results)
        self.assertEqual(len(tmodel.solutions), 1)

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    @unittest.skipIf(not yaml_available, "YAML not available available")
    def test_solve_with_store2(self):
        # Without symbolic solver labels
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        results = opt.solve(model, symbolic_solver_labels=False)
        #
        results.write(filename=join(currdir, 'solve_with_store1.out'), format='yaml')
        with (
            open(join(currdir, "solve_with_store1.out"), 'r') as out,
            open(join(currdir, "solve_with_store1.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        model.solutions.store_to(results)
        #
        results.write(filename=join(currdir, 'solve_with_store2.out'), format='yaml')
        with (
            open(join(currdir, "solve_with_store2.out"), 'r') as out,
            open(join(currdir, "solve_with_store2.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        # Load results with string indices
        #
        tmodel = ConcreteModel()
        tmodel.A = RangeSet(1, 4)
        tmodel.b = Block()
        tmodel.b.x = Var(tmodel.A, bounds=(-1, 1))
        tmodel.b.obj = Objective(expr=sum_product(tmodel.b.x))
        tmodel.c = Constraint(expr=tmodel.b.x[1] >= 0)
        self.assertEqual(len(tmodel.solutions), 0)
        tmodel.solutions.load_from(results)
        self.assertEqual(len(tmodel.solutions), 1)

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    @unittest.skipIf(not yaml_available, "YAML not available available")
    def test_solve_with_store2(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        results = opt.solve(model)
        #
        results.write(filename=join(currdir, 'solve_with_store3.out'), format='json')
        with (
            open(join(currdir, "solve_with_store3.out"), 'r') as out,
            open(join(currdir, "solve_with_store3.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve_with_store4.out'), format='json')
        with (
            open(join(currdir, "solve_with_store4.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        # Test that we can pickle the results object
        #
        buf = pickle.dumps(results)
        results_ = pickle.loads(buf)
        results.write(filename=join(currdir, 'solve_with_store4.out'), format='json')
        with (
            open(join(currdir, "solve_with_store4.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        # Load results with string indices
        #
        tmodel = ConcreteModel()
        tmodel.A = RangeSet(1, 3)
        tmodel.b = Block()
        tmodel.b.x = Var(tmodel.A, bounds=(-1, 1))
        tmodel.b.obj = Objective(expr=sum_product(tmodel.b.x))
        tmodel.c = Constraint(expr=tmodel.b.x[1] >= 0)
        self.assertEqual(len(tmodel.solutions), 0)
        tmodel.solutions.load_from(results, ignore_invalid_labels=True)
        self.assertEqual(len(tmodel.solutions), 1)

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    @unittest.skipIf(not yaml_available, "YAML not available available")
    def test_solve_with_store3(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        results = opt.solve(model)
        #
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve_with_store5.out'), format='json')
        with (
            open(join(currdir, "solve_with_store5.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        model.solutions.store_to(results, cuid=True)
        buf = pickle.dumps(results)
        results_ = pickle.loads(buf)
        model.solutions.load_from(results_)
        model.solutions.store_to(results_)
        results_.write(filename=join(currdir, 'solve_with_store6.out'), format='json')
        with (
            open(join(currdir, "solve_with_store6.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )
        #
        # Load results with string indices
        #
        tmodel = ConcreteModel()
        tmodel.A = RangeSet(1, 4)
        tmodel.b = Block()
        tmodel.b.x = Var(tmodel.A, bounds=(-1, 1))
        tmodel.b.obj = Objective(expr=sum_product(tmodel.b.x))
        tmodel.c = Constraint(expr=tmodel.b.x[1] >= 0)
        self.assertEqual(len(tmodel.solutions), 0)
        tmodel.solutions.load_from(results)
        self.assertEqual(len(tmodel.solutions), 1)
        tmodel.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve_with_store7.out'), format='json')
        with (
            open(join(currdir, "solve_with_store7.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    @unittest.skipIf(not yaml_available, "YAML not available available")
    def test_solve_with_store4(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)
        opt = SolverFactory('glpk')
        results = opt.solve(model, load_solutions=False)
        self.assertEqual(len(model.solutions), 0)
        self.assertEqual(len(results.solution), 1)
        model.solutions.load_from(results)
        self.assertEqual(len(model.solutions), 1)
        self.assertEqual(len(results.solution), 1)
        #
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve_with_store8.out'), format='json')
        with (
            open(join(currdir, "solve_with_store8.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )

    @unittest.skipIf('glpk' not in solvers, "glpk solver is not available")
    @unittest.skipIf(not yaml_available, "YAML not available available")
    def test_solve_with_store5(self):
        model = ConcreteModel()
        model.A = RangeSet(1, 4)
        model.b = Block()
        model.b.x = Var(model.A, bounds=(-1, 1))
        model.b.obj = Objective(expr=sum_product(model.b.x))
        model.c = Constraint(expr=model.b.x[1] >= 0)

        smanager = SolverManager_Serial()
        ah = smanager.queue(model, solver='glpk', load_solutions=False)
        results = smanager.wait_for(ah)
        self.assertEqual(len(model.solutions), 0)
        self.assertEqual(len(results.solution), 1)
        model.solutions.load_from(results)
        self.assertEqual(len(model.solutions), 1)
        self.assertEqual(len(results.solution), 1)
        #
        model.solutions.store_to(results)
        results.write(filename=join(currdir, 'solve_with_store8.out'), format='json')
        with (
            open(join(currdir, "solve_with_store8.out"), 'r') as out,
            open(join(currdir, "solve_with_store4.txt"), 'r') as txt,
        ):
            self.assertStructuredAlmostEqual(
                yaml.full_load(txt), yaml.full_load(out), allow_second_superset=True
            )

    def test_create_concrete_from_rule(self):
        def make(m):
            m.I = RangeSet(3)
            m.x = Var(m.I)
            m.c = Constraint(expr=sum(m.x[i] for i in m.I) >= 0)

        model = ConcreteModel(rule=make)
        self.assertEqual(
            [x.local_name for x in model.component_objects()], ['I', 'x', 'c']
        )
        self.assertEqual(len(list(EXPR.identify_variables(model.c.body))), 3)

    def test_create_abstract_from_rule(self):
        def make_invalid(m):
            m.I = RangeSet(3)
            m.x = Var(m.I)
            m.c = Constraint(expr=sum(m.x[i] for i in m.I) >= 0)

        def make(m):
            m.I = RangeSet(3)
            m.x = Var(m.I)

            def c(b):
                return sum(m.x[i] for i in m.I) >= 0

            m.c = Constraint(rule=c)

        with self.assertRaisesRegex(
            ValueError, r'x\[1\]: The component has not been constructed.'
        ):
            model = AbstractModel(rule=make_invalid)
            instance = model.create_instance()

        model = AbstractModel(rule=make)
        instance = model.create_instance()
        self.assertEqual([x.local_name for x in model.component_objects()], [])
        self.assertEqual(
            [x.local_name for x in instance.component_objects()], ['I', 'x', 'c']
        )
        self.assertEqual(len(list(EXPR.identify_variables(instance.c.body))), 3)

        model = AbstractModel(rule=make)
        model.y = Var()
        instance = model.create_instance()
        self.assertEqual(
            [x.local_name for x in instance.component_objects()], ['y', 'I', 'x', 'c']
        )
        self.assertEqual(len(list(EXPR.identify_variables(instance.c.body))), 3)

    def test_error_creating_model_baseclass(self):
        with self.assertRaisesRegex(
            TypeError,
            "Directly creating the 'Model' class is not allowed.  Please use the AbstractModel or ConcreteModel class instead.",
        ):
            m = Model()


if __name__ == "__main__":
    unittest.main()
