Repository: Pyomo/pyomo

Issue:
Pickling logical to linear transformed model fails
## Summary

<!-- Explain, in a clear and concise way, the command you ran and the result you were trying to achieve.
Example: "I ran `pyomo solve` to optimize a model and ..." -->

(dill) pickling a transformed gdp model containing a disjunction fails.

### Steps to reproduce the issue

<!-- Please include the command run and/or provide an example script that demonstrates the problem. -->

```console
$ python picktest.py
```

```python
# picktest.py

import dill as dill

from pyomo.environ import *
from pyomo.gdp import *

model = ConcreteModel()

model.x = Var(bounds=(0,5))
model.y = Var(bounds=(0,5))

# Two conditions
def _d(disjunct, flag):
   model = disjunct.model()
   if flag:
      # x == 0
      disjunct.c = Constraint(expr=model.x == 0)
   else:
      # y == 0
      disjunct.c = Constraint(expr=model.y == 0)
model.d = Disjunct([0,1], rule=_d)

# Define the disjunction
def _c(model):
   return [model.d[0], model.d[1]]
model.c = Disjunction(rule=_c)

# works
with open('test_gdp_pickle', "wb") as f:
   dill.dump(model, f)

with open('test_gdp_pickle', "rb") as f:
   dill.load(f)

TransformationFactory('gdp.bigm').apply_to(model)

# fails
with open('test_gdp_pickle', "wb") as f:
   dill.dump(model, f)

with open('test_gdp_pickle', "rb") as f:
   dill.load(f)
```

### Error Message

<!-- If Pyomo reported an error, provide the error message. PLEASE INCLUDE THE ENTIRE STACK TRACE. -->

<!-- If it did not report an error but the output appears incorrect, provide the incorrect output. If there was no error message and no output but the result is incorrect, describe how it does not match what you expect. -->
```console
$ Traceback (most recent call last):
  File "/mnt/c/Users/Loke/tex/masters/mathprog/RRI/RRI/picktest.py", line 40, in <module>
    dill.dump(model, f)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 276, in dump
    Pickler(file, protocol, **_kwds).dump(obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 498, in dump
    StockPickler.dump(self, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 487, in dump
    self.save(obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 603, in save
    self.save_reduce(obj=obj, *rv)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 717, in save_reduce
    save(state)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 932, in save_list
    self._batch_appends(obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 956, in _batch_appends
    save(x)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 887, in save_tuple
    save(element)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 603, in save
    self.save_reduce(obj=obj, *rv)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 717, in save_reduce
    save(state)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 603, in save
    self.save_reduce(obj=obj, *rv)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 717, in save_reduce
    save(state)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 1317, in save_weakref
    pickler.save_reduce(_create_weakref, (refobj,), obj=obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 692, in save_reduce
    save(args)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 887, in save_tuple
    save(element)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 603, in save
    self.save_reduce(obj=obj, *rv)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 717, in save_reduce
    save(state)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 932, in save_list
    self._batch_appends(obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 956, in _batch_appends
    save(x)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 887, in save_tuple
    save(element)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 603, in save
    self.save_reduce(obj=obj, *rv)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 717, in save_reduce
    save(state)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 932, in save_list
    self._batch_appends(obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 956, in _batch_appends
    save(x)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 887, in save_tuple
    save(element)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 603, in save
    self.save_reduce(obj=obj, *rv)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 717, in save_reduce
    save(state)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 560, in save
    f(self, obj)  # Call unbound method with explicit self
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/dill/_dill.py", line 990, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 972, in save_dict
    self._batch_setitems(obj.items())
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 998, in _batch_setitems
    save(v)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/pickle.py", line 578, in save
    rv = reduce(self.proto)
  File "/home/loke/miniconda3/envs/matprog/lib/python3.10/site-packages/pyomo/core/base/global_set.py", line 23, in __reduce__
    return _get_global_set, (self.local_name,)
AttributeError: '_UnindexedComponent_set' object has no attribute 'local_name'
```

### Information on your system

Pyomo version: 6.4.1
Python version: 3.10.4
Operating system: windows 10 (wsl)
How Pyomo was installed (PyPI, conda, source): conda
Solver: Cplex


### Additional information

<!-- If you have any additional information, please list it here. -->

Pickling the model returned by create_using(model) also fails, and the same happens with the hull transformation.

My use case is that I want to later create multiple new models each of which is a modification of the pickled model and use the solution to the pickled model as part of their own (it's important they all agree on this part of the solution). So a sufficient work-around would be to store only the results of solving the first model, build the new models from scratch, and then load the requisite variables from the first into them. I would appreciate some guidance on the best way to do this with gdp models.

Another approach (this is actually what I did at first) would be to build and solve the base model over again every time, since in theory CPLEX should return the same solution every time (with the deterministic setting on of course). I say in theory since this was not case for me. Whether this was to due with the transformation or something else I dont' know.

<!-- We encourage you to try, as much as possible, to reduce your problem to the minimal example that still reproduces the issue. That would help us a lot in fixing it quickly and effectively!

If you want to ask a question (how to use Pyomo, what it can do, etc.), please refer to our [online documentation](https://github.com/Pyomo/pyomo#getting-help) on ways to get help. -->




Discussion:

Resolving patch:
diff --git a/pyomo/core/base/global_set.py b/pyomo/core/base/global_set.py
--- a/pyomo/core/base/global_set.py
+++ b/pyomo/core/base/global_set.py
@@ -58,6 +58,7 @@ def _parent(self, val):
 #
 #UnindexedComponent_set = set([None])
 class _UnindexedComponent_set(GlobalSetBase):
+    local_name = 'UnindexedComponent_set'
     def __init__(self, name):
         self.name = name
     def __contains__(self, val):
@@ -89,5 +90,6 @@ def isordered(self):
         # As this set only has a single element, it is implicitly "ordered"
         return True
 UnindexedComponent_set = _UnindexedComponent_set('UnindexedComponent_set')
+GlobalSets[UnindexedComponent_set.local_name] = UnindexedComponent_set
 
 UnindexedComponent_index = next(iter(UnindexedComponent_set))


Test patch: