Repository: Pyomo/pyomo

Issue:
Skip stale=True variables in store_to function
I am now solving a model iteratively. In different iterations, I'll fix the value of some variables. Since the I wanna record the optimal solution of each iteration, I use the [`store_to`](https://github.com/Pyomo/pyomo/blob/1e3bb15da5107ac769bcfc029861b7b22e7c5949/pyomo/core/base/PyomoModel.py#L290) function (`model.solutions.store_to(result)`). Since I'll also fix some variables in each iteration, the `stale` flag of these variable will become `True` after solving. The fixed variables in each iteration may not be the same.
However, the store_to function will skip the variables whose `stale` flag is `True`.

```
if obj.stale:
    continue
```
Ref: https://github.com/Pyomo/pyomo/blob/1e3bb15da5107ac769bcfc029861b7b22e7c5949/pyomo/core/base/PyomoModel.py#L321

I quite understand that this will improve the efficiency of storing and loading solutions. However, if I am not loading the solution right after storing, it will cause some problems. For example, after 10 iterations, I wanna load the solution of the 3rd iteration. Since the variables fixed in iteration 3 and iteration 10 are not the same. The `stale` flag of all variables is not the same. The loading will be incomplete or even wrong.(There are many options in [`load_from`](https://github.com/Pyomo/pyomo/blob/1e3bb15da5107ac769bcfc029861b7b22e7c5949/pyomo/core/base/PyomoModel.py#L206) function to control these stuff. But I am not quite familiar ). 

I am now manually setting the `stale` flag to `True` before storing the solutions. Is it possible to add an option in the `store_to` function, which stops skipping the `stale=True` variables?


Discussion:
If the Pyomo development team thinks that adding the flag (set false by default) and a test for it would be worth it, let us know and we will submit a PR.
@ZedongPeng seems like a reasonable use-case to me. Feel free to submit a PR with this change and we'll take a look.

Resolving patch:
diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py
--- a/pyomo/core/base/PyomoModel.py
+++ b/pyomo/core/base/PyomoModel.py
@@ -287,7 +287,7 @@ def load_from(self,
                 ignore_invalid_labels=ignore_invalid_labels,
                 ignore_fixed_vars=ignore_fixed_vars)
 
-    def store_to(self, results, cuid=False):
+    def store_to(self, results, cuid=False, skip_stale_vars=False):
         """
         Return a Solution() object that is populated with the values in the model.
         """
@@ -318,7 +318,7 @@ def store_to(self, results, cuid=False):
                 soln.objective[ sm.getSymbol(obj, labeler) ] = vals
             entry = soln_._entry['variable']
             for obj in instance.component_data_objects(Var, active=True):
-                if obj.stale:
+                if obj.stale and skip_stale_vars:
                     continue
                 vals = entry.get(id(obj), None)
                 if vals is None:


Test patch:
diff --git a/pyomo/core/tests/unit/test_model.py b/pyomo/core/tests/unit/test_model.py
--- a/pyomo/core/tests/unit/test_model.py
+++ b/pyomo/core/tests/unit/test_model.py
@@ -266,6 +266,35 @@ def e_rule(model, i):
         self.assertMatchesJsonBaseline(
             join(currdir,"solve1b.out"), join(currdir,"solve1b.txt"),
             tolerance=1e-4)
+            
+    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()