Repository: Pyomo/pyomo

Issue:
Support cython function to initialize set
## Summary

Currently it's impossible to pass a "cython" function to initialize a Set. 

The point is that, in the "Initializer" function of pyomo.core.base.util, the cython function is not correctly recognized and a "ConstantInitializer" is returned. Ideally a "ScalarCallInitializer" or "IndexedCallInitializer" should be returned. 

It would be nice to support passing "cython" function.

### Rationale
The main reason to support cython function is that there is no good reason to not accept them. They are behaving mostly like normal python function.

 In particular I think that the only modification needed will be in the Initializer function of pyomo.core.base.util.

Pyomo is currently relying on "inspect.isfunction" which does not recognized cython function. Note that other methods of "inspect" such as "inspect.getfullargspec(init)" and "inspect.ismethod(init)" are working correctly on cython function.

### Description

There are many discussions around the question whether "inspect.isfunction" should return true or false for cython function. It seems that it will not change soon. The reason is that cython function are not supporting ALL the features expected from types.FunctionType. But these features are not needed by pyomo. 



As workaround in my code I'm currently using the following patching:

```
from unittest.mock import patch

def extended_isfunction(f):
    return isinstance(f, types.FunctionType) or (type(f).__name__ == 'cython_function_or_method')

with patch('inspect.isfunction', new=extended_isfunction):
    my_pyomo_code
```
It would be nicer if this logic could be implemented in pyomo directly.


Discussion:
Is this possibly related to the issue that I am having with pyomo and cython: https://stackoverflow.com/questions/71902579/pyomo-doesnt-load-when-called-via-cython.
This too seems to be an initialization issue.
This issue is unrelated to the linked SO question (The linked question is probably either a problem with how the pyomo driver script interacts with argparse [it really shouldn't be doing that much when pyomo.environ is imported - that is almost certainly a bug], or with how cython and argparse interact).

@bertrandhaut, I think your proposed solution is a reasonable starting point, although I find it troublesome that we would need to hack around a standard module (`inspect`) to support cython.  This really feels like a fundamental problem with cython.

I believe that there is a simpler workaround, as well.  You should be able to explicitly create the desired `ScalarCallInitializer` or `IndexedCallInitializer`, i.e.
```python
from pyomo.core.base.initializer import ScalarCallInitializer, IndexedCallInitializer
# ...
model.I = pyo.Set(initialize=ScalarCallInitializer(my_cython_fcn))
# or
model.J = pyo.Set(model.I, initialize=IndexedCallInitializer(my_indexed_cython_fcn))
```
@[jsiirola](https://github.com/jsiirola), Thanks I will enter a ticket for this as a bug.


Resolving patch:
diff --git a/pyomo/core/__init__.py b/pyomo/core/__init__.py
--- a/pyomo/core/__init__.py
+++ b/pyomo/core/__init__.py
@@ -119,8 +119,6 @@
     Transformation,
     TransformationFactory,
 )
-#
-from pyomo.core.base import util
 
 from pyomo.core.base.instance2dat import instance2dat
 
diff --git a/pyomo/core/base/initializer.py b/pyomo/core/base/initializer.py
--- a/pyomo/core/base/initializer.py
+++ b/pyomo/core/base/initializer.py
@@ -22,6 +22,12 @@
 
 initializer_map = {}
 sequence_types = set()
+# initialize with function, method, and method-wrapper types.
+function_types = set([
+    type(PyomoObject.is_expression_type),
+    type(PyomoObject().is_expression_type),
+    type(PyomoObject.is_expression_type.__call__),
+])
 
 #
 # The following set of "Initializer" classes are a general functionality
@@ -49,13 +55,22 @@ def Initializer(init,
             return ItemInitializer(init)
         else:
             return ConstantInitializer(init)
-    if inspect.isfunction(init) or inspect.ismethod(init):
+    if init.__class__ in function_types:
+        # Note: we do not use "inspect.isfunction or inspect.ismethod"
+        # because some function-like things (notably cythonized
+        # functions) return False
         if not allow_generators and inspect.isgeneratorfunction(init):
             raise ValueError("Generator functions are not allowed")
         # Historically pyomo.core.base.misc.apply_indexed_rule
         # accepted rules that took only the parent block (even for
         # indexed components).  We will preserve that functionality
         # here.
+        #
+        # I was concerned that some builtins aren't compatible with
+        # getfullargspec (and would need the same try-except logic as in
+        # the partial handling), but I have been unable to come up with
+        # an example.  The closest was getattr(), but that falls back on
+        # getattr.__call__, which does support getfullargspec.
         _args = inspect.getfullargspec(init)
         _nargs = len(_args.args)
         if inspect.ismethod(init) and init.__self__ is not None:
@@ -111,7 +126,13 @@ def Initializer(init,
         # generator into a tuple and then store it as a constant.
         return ConstantInitializer(tuple(init))
     if type(init) is functools.partial:
-        _args = inspect.getfullargspec(init.func)
+        try:
+            _args = inspect.getfullargspec(init.func)
+        except:
+            # Inspect doesn't work for some built-in callables (notably
+            # 'int').  We will just have to assume this is a "normal"
+            # IndexedCallInitializer
+            return IndexedCallInitializer(init)
         if len(_args.args) - len(init.args) == 1 and _args.varargs is None:
             return ScalarCallInitializer(init)
         else:
@@ -125,10 +146,18 @@ def Initializer(init,
         return ConstantInitializer(init)
     if callable(init) and not isinstance(init, type):
         # We assume any callable thing could be a functor; but, we must
-        # filter out types, as isfunction() and ismethod() both return
-        # False for type.__call__
+        # filter out types, as we use types as special identifiers that
+        # should not be called (e.g., UnknownSetDimen)
+        if inspect.isfunction(init) or inspect.ismethod(init):
+            # Add this to the set of known function types and try again
+            function_types.add(type(init))
+        else:
+            # Try again, but use the __call__ method (for supporting
+            # things like functors and cythonized functions).  __call__
+            # is almost certainly going to be a method-wrapper
+            init = init.__call__
         return Initializer(
-            init.__call__,
+            init,
             allow_generators=allow_generators,
             treat_sequences_as_mappings=treat_sequences_as_mappings,
             arg_not_specified=arg_not_specified,


Test patch:
diff --git a/pyomo/core/tests/unit/test_initializer.py b/pyomo/core/tests/unit/test_initializer.py
--- a/pyomo/core/tests/unit/test_initializer.py
+++ b/pyomo/core/tests/unit/test_initializer.py
@@ -9,11 +9,15 @@
 #  This software is distributed under the 3-clause BSD License.
 #  ___________________________________________________________________________
 
+import functools
 import pickle
+import types
 
 import pyomo.common.unittest as unittest
 from pyomo.common.config import ConfigValue, ConfigList, ConfigDict
-from pyomo.common.dependencies import pandas as pd, pandas_available
+from pyomo.common.dependencies import (
+    pandas as pd, pandas_available, numpy as np, numpy_available
+)
 
 from pyomo.core.base.util import flatten_tuple
 from pyomo.core.base.initializer import (
@@ -44,7 +48,6 @@ def test_flattener(self):
         self.assertEqual((1, 0, 2, 3), flatten_tuple(ex))
 
     def test_constant(self):
-        m = ConcreteModel()
         a = Initializer(5)
         self.assertIs(type(a), ConstantInitializer)
         self.assertTrue(a.constant())
@@ -57,7 +60,6 @@ def test_constant(self):
         self.assertEqual(a(None, 1), 5)
 
     def test_dict(self):
-        m = ConcreteModel()
         a = Initializer({1:5})
         self.assertIs(type(a), ItemInitializer)
         self.assertFalse(a.constant())
@@ -67,7 +69,6 @@ def test_dict(self):
         self.assertEqual(a(None, 1), 5)
 
     def test_sequence(self):
-        m = ConcreteModel()
         a = Initializer([0,5])
         self.assertIs(type(a), ItemInitializer)
         self.assertFalse(a.constant())
@@ -84,7 +85,6 @@ def test_sequence(self):
         self.assertEqual(a(None, 1), [0,5])
 
     def test_function(self):
-        m = ConcreteModel()
         def a_init(m):
             return 0
         a = Initializer(a_init)
@@ -94,7 +94,6 @@ def a_init(m):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.x = Var([1,2,3])
         def x_init(m, i):
             return i+1
         a = Initializer(x_init)
@@ -113,7 +112,6 @@ def x2_init(m):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.y = Var([1,2,3], [4,5,6])
         def y_init(m, i, j):
             return j*(i+1)
         a = Initializer(y_init)
@@ -123,11 +121,53 @@ def y_init(m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, (1, 4)), 8)
 
+    def test_counted_call(self):
+        def x_init(m, i):
+            return i+1
+
+        def y_init(m, i, j):
+            return j*(i+1)
+
+        def z_init(m, i, j, k):
+            return i*100 + j*10 + k
+
+        def bogus(m, i, j):
+            return None
+
+        m = ConcreteModel()
+        m.x = Var([1,2,3])
+        a = Initializer(x_init)
         b = CountedCallInitializer(m.x, a)
         self.assertIs(type(b), CountedCallInitializer)
         self.assertFalse(b.constant())
         self.assertFalse(b.verified)
-        self.assertFalse(a.contains_indices())
+        self.assertFalse(b.contains_indices())
+        self.assertFalse(b._scalar)
+        self.assertIs(a._fcn, b._fcn)
+        c = b(None, 1)
+        self.assertIs(type(c), int)
+        self.assertEqual(c, 2)
+
+        a = Initializer(bogus)
+        b = CountedCallInitializer(m.x, a)
+        self.assertIs(type(b), CountedCallInitializer)
+        self.assertFalse(b.constant())
+        self.assertFalse(b.verified)
+        self.assertFalse(b.contains_indices())
+        self.assertFalse(b._scalar)
+        self.assertIs(a._fcn, b._fcn)
+        c = b(None, 1)
+        self.assertIs(type(c), CountedCallGenerator)
+        with self.assertRaisesRegex(
+                ValueError, 'Counted Var rule returned None'):
+            next(c)
+
+        a = Initializer(y_init)
+        b = CountedCallInitializer(m.x, a)
+        self.assertIs(type(b), CountedCallInitializer)
+        self.assertFalse(b.constant())
+        self.assertFalse(b.verified)
+        self.assertFalse(b.contains_indices())
         self.assertFalse(b._scalar)
         self.assertIs(a._fcn, b._fcn)
         c = b(None, 1)
@@ -136,6 +176,33 @@ def y_init(m, i, j):
         self.assertEqual(next(c), 3)
         self.assertEqual(next(c), 4)
 
+        m.y = Var([(1,2), (3,5)])
+        a = Initializer(y_init)
+        b = CountedCallInitializer(m.y, a)
+        self.assertIs(type(b), CountedCallInitializer)
+        self.assertFalse(b.constant())
+        self.assertFalse(b.verified)
+        self.assertFalse(b.contains_indices())
+        self.assertFalse(b._scalar)
+        self.assertIs(a._fcn, b._fcn)
+        c = b(None, (3, 5))
+        self.assertIs(type(c), int)
+        self.assertEqual(c, 20)
+
+        a = Initializer(z_init)
+        b = CountedCallInitializer(m.y, a)
+        self.assertIs(type(b), CountedCallInitializer)
+        self.assertFalse(b.constant())
+        self.assertFalse(b.verified)
+        self.assertFalse(b.contains_indices())
+        self.assertFalse(b._scalar)
+        self.assertIs(a._fcn, b._fcn)
+        c = b(None, (3, 5))
+        self.assertIs(type(c), CountedCallGenerator)
+        self.assertEqual(next(c), 135)
+        self.assertEqual(next(c), 235)
+        self.assertEqual(next(c), 335)
+        
     def test_method(self):
         class Init(object):
             def a_init(self, m):
@@ -152,7 +219,6 @@ def y_init(self, m, i, j):
 
         init = Init()
 
-        m = ConcreteModel()
         a = Initializer(init.a_init)
         self.assertIs(type(a), ScalarCallInitializer)
         self.assertTrue(a.constant())
@@ -160,7 +226,6 @@ def y_init(self, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.x = Var([1,2,3])
         a = Initializer(init.x_init)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -175,7 +240,6 @@ def y_init(self, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.y = Var([1,2,3], [4,5,6])
         a = Initializer(init.y_init)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -183,6 +247,9 @@ def y_init(self, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, (1, 4)), 8)
 
+        m = ConcreteModel()
+        m.x = Var([1,2,3])
+        a = Initializer(init.y_init)
         b = CountedCallInitializer(m.x, a)
         self.assertIs(type(b), CountedCallInitializer)
         self.assertFalse(b.constant())
@@ -190,11 +257,11 @@ def y_init(self, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertFalse(b._scalar)
         self.assertIs(a._fcn, b._fcn)
-        c = b(None, 1)
+        c = b(None, 10)
         self.assertIs(type(c), CountedCallGenerator)
-        self.assertEqual(next(c), 2)
-        self.assertEqual(next(c), 3)
-        self.assertEqual(next(c), 4)
+        self.assertEqual(next(c), 20)
+        self.assertEqual(next(c), 30)
+        self.assertEqual(next(c), 40)
 
     def test_classmethod(self):
         class Init(object):
@@ -214,7 +281,6 @@ def x2_init(cls, m):
             def y_init(cls, m, i, j):
                 return j*(i+1)
 
-        m = ConcreteModel()
         a = Initializer(Init.a_init)
         self.assertIs(type(a), ScalarCallInitializer)
         self.assertTrue(a.constant())
@@ -222,7 +288,6 @@ def y_init(cls, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.x = Var([1,2,3])
         a = Initializer(Init.x_init)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -237,7 +302,6 @@ def y_init(cls, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.y = Var([1,2,3], [4,5,6])
         a = Initializer(Init.y_init)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -245,6 +309,9 @@ def y_init(cls, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, (1, 4)), 8)
 
+        m = ConcreteModel()
+        m.x = Var([1,2,3])
+        a = Initializer(Init.y_init)
         b = CountedCallInitializer(m.x, a)
         self.assertIs(type(b), CountedCallInitializer)
         self.assertFalse(b.constant())
@@ -252,11 +319,11 @@ def y_init(cls, m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertFalse(b._scalar)
         self.assertIs(a._fcn, b._fcn)
-        c = b(None, 1)
+        c = b(None, 10)
         self.assertIs(type(c), CountedCallGenerator)
-        self.assertEqual(next(c), 2)
-        self.assertEqual(next(c), 3)
-        self.assertEqual(next(c), 4)
+        self.assertEqual(next(c), 20)
+        self.assertEqual(next(c), 30)
+        self.assertEqual(next(c), 40)
 
     def test_staticmethod(self):
         class Init(object):
@@ -276,7 +343,6 @@ def x2_init(m):
             def y_init(m, i, j):
                 return j*(i+1)
 
-        m = ConcreteModel()
         a = Initializer(Init.a_init)
         self.assertIs(type(a), ScalarCallInitializer)
         self.assertTrue(a.constant())
@@ -284,7 +350,6 @@ def y_init(m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.x = Var([1,2,3])
         a = Initializer(Init.x_init)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -299,7 +364,6 @@ def y_init(m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, 1), 0)
 
-        m.y = Var([1,2,3], [4,5,6])
         a = Initializer(Init.y_init)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -307,21 +371,23 @@ def y_init(m, i, j):
         self.assertFalse(a.contains_indices())
         self.assertEqual(a(None, (1, 4)), 8)
 
+        m = ConcreteModel()
+        m.x = Var([1,2,3])
+        a = Initializer(Init.y_init)
         b = CountedCallInitializer(m.x, a)
         self.assertIs(type(b), CountedCallInitializer)
         self.assertFalse(b.constant())
         self.assertFalse(b.verified)
-        self.assertFalse(a.contains_indices())
+        self.assertFalse(b.contains_indices())
         self.assertFalse(b._scalar)
         self.assertIs(a._fcn, b._fcn)
-        c = b(None, 1)
+        c = b(None, 10)
         self.assertIs(type(c), CountedCallGenerator)
-        self.assertEqual(next(c), 2)
-        self.assertEqual(next(c), 3)
-        self.assertEqual(next(c), 4)
+        self.assertEqual(next(c), 20)
+        self.assertEqual(next(c), 30)
+        self.assertEqual(next(c), 40)
 
     def test_generator_fcn(self):
-        m = ConcreteModel()
         def a_init(m):
             yield 0
             yield 3
@@ -335,7 +401,6 @@ def a_init(m):
         self.assertFalse(a.verified)
         self.assertEqual(list(a(None, 1)), [0,3])
 
-        m.x = Var([1,2,3])
         def x_init(m, i):
             yield i
             yield i+1
@@ -345,7 +410,6 @@ def x_init(m, i):
         self.assertFalse(a.verified)
         self.assertEqual(list(a(None, 1)), [1,2])
 
-        m.y = Var([1,2,3], [4,5,6])
         def y_init(m, i, j):
             yield j
             yield i+1
@@ -370,7 +434,6 @@ def y_init(self, m, i, j):
                 yield i+1
         init = Init()
 
-        m = ConcreteModel()
         with self.assertRaisesRegex(
                 ValueError, "Generator functions are not allowed"):
             a = Initializer(init.a_init)
@@ -381,14 +444,12 @@ def y_init(self, m, i, j):
         self.assertFalse(a.verified)
         self.assertEqual(list(a(None, 1)), [0,3])
 
-        m.x = Var([1,2,3])
         a = Initializer(init.x_init, allow_generators=True)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
         self.assertFalse(a.verified)
         self.assertEqual(list(a(None, 1)), [1,2])
 
-        m.y = Var([1,2,3], [4,5,6])
         a = Initializer(init.y_init, allow_generators=True)
         self.assertIs(type(a), IndexedCallInitializer)
         self.assertFalse(a.constant())
@@ -396,7 +457,6 @@ def y_init(self, m, i, j):
         self.assertEqual(list(a(None, (1, 4))), [4,2])
 
     def test_generators(self):
-        m = ConcreteModel()
         with self.assertRaisesRegex(
                 ValueError, "Generators are not allowed"):
             a = Initializer(iter([0,3]))
@@ -420,6 +480,102 @@ def x_init():
         self.assertFalse(a.verified)
         self.assertEqual(list(a(None, 1)), [0,3])
 
+    def test_functor(self):
+        class InitScalar(object):
+            def __init__(self, val):
+                self.val = val
+
+            def __call__(self, m):
+                return self.val
+
+        a = Initializer(InitScalar(10))
+        self.assertIs(type(a), ScalarCallInitializer)
+        self.assertTrue(a.constant())
+        self.assertFalse(a.verified)
+        self.assertEqual(a(None, None), 10)
+
+        class InitIndexed(object):
+            def __init__(self, val):
+                self.val = val
+
+            def __call__(self, m, i):
+                return self.val + i
+
+        a = Initializer(InitIndexed(10))
+        self.assertIs(type(a), IndexedCallInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertEqual(a(None, 5), 15)
+
+    def test_derived_function(self):
+        def _scalar(m):
+            return 10
+        dynf = types.FunctionType(_scalar.__code__, {})
+
+        a = Initializer(dynf)
+        self.assertIs(type(a), ScalarCallInitializer)
+        self.assertTrue(a.constant())
+        self.assertFalse(a.verified)
+        self.assertEqual(a(None, None), 10)
+
+        def _indexed(m, i):
+            return 10 + i
+        dynf = types.FunctionType(_indexed.__code__, {})
+
+        a = Initializer(dynf)
+        self.assertIs(type(a), IndexedCallInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertEqual(a(None, 5), 15)
+
+    def test_no_argspec(self):
+        a = Initializer(getattr)
+        self.assertIs(type(a), IndexedCallInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertFalse(a.contains_indices())
+        self.assertEqual(a(None, '__class__'), type(None))
+
+        basetwo = functools.partial(int)
+        a = Initializer(basetwo)
+        self.assertIs(type(a), IndexedCallInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertFalse(a.contains_indices())
+        self.assertEqual(a('111', 2), 7)
+
+        # Special case: getfullargspec fails on int, so we assume it is
+        # always an IndexedCallInitializer
+        basetwo = functools.partial(int, '101', base=2)
+        a = Initializer(basetwo)
+        self.assertIs(type(a), IndexedCallInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertFalse(a.contains_indices())
+        # but this is not callable, as int won't accept the 'model'
+        #self.assertEqual(a(None, None), 5)
+
+    def test_partial(self):
+        def fcn(k, m, i, j):
+            return i*100 + j*10 + k
+        part = functools.partial(fcn, 2)
+        a = Initializer(part)
+        self.assertIs(type(a), IndexedCallInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertFalse(a.contains_indices())
+        self.assertEqual(a(None, (5, 7)), 572)
+
+        def fcn(k, i, j, m):
+            return i*100 + j*10 + k
+        part = functools.partial(fcn, 2, 5, 7)
+        a = Initializer(part)
+        self.assertIs(type(a), ScalarCallInitializer)
+        self.assertTrue(a.constant())
+        self.assertFalse(a.verified)
+        self.assertFalse(a.contains_indices())
+        self.assertEqual(a(None, None), 572)
+
     @unittest.skipUnless(pandas_available, "Pandas is not installed")
     def test_dataframe(self):
         d = {'col1': [1, 2, 4]}
@@ -462,6 +618,52 @@ def test_dataframe(self):
         self.assertEqual(a(None, (1, 0)), 30)
         self.assertEqual(a(None, (1, 1)), 40)
 
+    @unittest.skipUnless(pandas_available, "Pandas is not installed")
+    def test_series(self):
+        d = pd.Series({0:1, 1:2, 2:4})
+        a = Initializer(d)
+        self.assertIs(type(a), ItemInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertTrue(a.contains_indices())
+        self.assertEqual(list(a.indices()), [0,1,2])
+        self.assertEqual(a(None, 0), 1)
+        self.assertEqual(a(None, 1), 2)
+        self.assertEqual(a(None, 2), 4)
+
+    @unittest.skipUnless(numpy_available, "Numpy is not installed")
+    def test_ndarray(self):
+        d = np.array([1,2,4])
+        a = Initializer(d)
+        self.assertIs(type(a), ItemInitializer)
+        self.assertFalse(a.constant())
+        self.assertFalse(a.verified)
+        self.assertTrue(a.contains_indices())
+        self.assertEqual(list(a.indices()), [0,1,2])
+        self.assertEqual(a(None, 0), 1)
+        self.assertEqual(a(None, 1), 2)
+        self.assertEqual(a(None, 2), 4)
+
+        # TODO: How should we handle ndarray matrices?
+        # d = np.array([[1,2],[4,6]])
+        # a = Initializer(d)
+        # self.assertIs(type(a), ItemInitializer)
+        # self.assertFalse(a.constant())
+        # self.assertFalse(a.verified)
+        # self.assertTrue(a.contains_indices())
+        # self.assertEqual(list(a.indices()), [0,1,2])
+        # self.assertEqual(a(None, 0), 1)
+        # self.assertEqual(a(None, 1), 2)
+        # self.assertEqual(a(None, 2), 4)
+
+    def test_str(self):
+        a = Initializer("a string")
+        self.assertIs(type(a), ConstantInitializer)
+        self.assertTrue(a.constant())
+        self.assertFalse(a.verified)
+        self.assertFalse(a.contains_indices())
+        self.assertEqual(a(None, None), "a string")
+
     @unittest.skipUnless(pandas_available, "Pandas is not installed")
     def test_initializer_initializer(self):
         d = {'col1': [1, 2, 4], 'col2': [10, 20, 40]}
@@ -477,7 +679,6 @@ def test_initializer_initializer(self):
         self.assertEqual(a(None, 2), 40)
 
     def test_pickle(self):
-        m = ConcreteModel()
         a = Initializer(5)
         a.verified = True
         b = pickle.loads(pickle.dumps(a))
@@ -500,7 +701,6 @@ def test_pickle(self):
         self.assertEqual(a(None, None), 1)
         self.assertEqual(b(None, None), 1)
 
-        m.x = Var([1,2,3])
         a = Initializer(_init_indexed)
         b = pickle.loads(pickle.dumps(a))
         self.assertIsNot(a, b)