Repository: Pyomo/pyomo

Issue:
validate does not raise Exception
When `AbstractModel.create_instance` is called, if the `validate` function of a Parameter returns `False`, the value is not assigned but no Exception is raised.

Example:
```python
from pyomo import environ as pe

m = pe.AbstractModel()
    
m.p = pe.Param(validate = lambda m,v: False)
    
data = {None:{
    'p': {None: 0.2}}}

cm = m.create_instance(data=data)
```

does not raise any error. When one tries to retrieve the value though, `ValueError` is raised because the value has not been set.

```python
>>> cm.p.value
ValueError: Error retrieving immutable Param value (p):
	The Param value is undefined and no default value is specified.
```

There might be reasons not to raise an exception, but failing to set a value silently can create a lot of confusion.

Besides, the [docs](https://pyomo.readthedocs.io/en/latest/pyomo_modeling_components/Parameters.html?#parameters) clearly say:
> If a value is provided that is less than that, the model instantation would be terminated and an error message issued

Imho, validation is a great feature but I understand it introduces a lot of challenges.
If the functionality does not behave as expected, it should at least be (temporarily, hopefully) deprecated, or issue a warning.
validate does not raise Exception
When `AbstractModel.create_instance` is called, if the `validate` function of a Parameter returns `False`, the value is not assigned but no Exception is raised.

Example:
```python
from pyomo import environ as pe

m = pe.AbstractModel()
    
m.p = pe.Param(validate = lambda m,v: False)
    
data = {None:{
    'p': {None: 0.2}}}

cm = m.create_instance(data=data)
```

does not raise any error. When one tries to retrieve the value though, `ValueError` is raised because the value has not been set.

```python
>>> cm.p.value
ValueError: Error retrieving immutable Param value (p):
	The Param value is undefined and no default value is specified.
```

There might be reasons not to raise an exception, but failing to set a value silently can create a lot of confusion.

Besides, the [docs](https://pyomo.readthedocs.io/en/latest/pyomo_modeling_components/Parameters.html?#parameters) clearly say:
> If a value is provided that is less than that, the model instantation would be terminated and an error message issued

Imho, validation is a great feature but I understand it introduces a lot of challenges.
If the functionality does not behave as expected, it should at least be (temporarily, hopefully) deprecated, or issue a warning.


Discussion:
same happens with `ConcreteModel`, so it's not an `AbstracModel` specific issue.
It apparently also applies to `within`, so it's probably an issue with validation in general (which makes sense, since the underlying mechanism is the same).
@blnicho is this something you have seen in any tests / use cases?
I can confirm that something strange is happening here. At a minimum, the error message raised when a parameter value is invalid doesn't give any useful information for debugging:

```
from pyomo.environ import *
m = ConcreteModel()
m.p = Param(initialize=-3, within=PositiveReals)
```
```
ERROR: Constructing component 'p' from data=None failed: ValueError: Error
    retrieving immutable Param value (p):
    	The Param value is undefined and no default value is specified.
Traceback (most recent call last):
  File "weird_error.py", line 5, in <module>
    m.p = Param(initialize=-3, within=PositiveReals)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/block.py", line 568, in __setattr__
    self.add_component(name, val)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/block.py", line 1008, in add_component
    val.construct(data)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/param.py", line 869, in construct
    self._initialize_from(self._rule)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/param.py", line 818, in _initialize_from
    _init = self[idx]
  File "/home/blnicho/Research/pyomo/pyomo/core/base/indexed_component.py", line 348, in __getitem__
    return self._getitem_when_not_present(index)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/param.py", line 482, in _getitem_when_not_present
    % ( idx_str,) )
ValueError: Error retrieving immutable Param value (p):
	The Param value is undefined and no default value is specified.

```

My organization is using currently using both Pyomo 5.2 and 5.6.x, and this does not happen when running using 5.2 but does happen under 5.6.x. Not sure if that's helpful information.
I've tracked down this issue (for Bethany's example) to [line 591 in param.py](https://github.com/Pyomo/pyomo/blob/66874abedc5dcb53ccafd319f1925e71cd6029b2/pyomo/core/base/param.py#L591).

The exception is properly thrown in `self.set_value()` but the exception ends up being caught and silenced by this try/except block, and the value is deleted. Eliminating the try/except logic fixed this issue, causing the more helpful error in Param._validate_value() to be raised.

I'm not certain what the try/except block here is trying to accomplish, so I doubt eliminating it entirely is the correct solution.
Hi @bknueven and @blnicho, are there any updates on addressing this bug? Thanks!

We have run into several issues with production code that relied on the validate functionality to work as-documented but no longer does due to this bug.

Is there any way that @jsiirola, who seems to have last touched that part of the code, could answer the question about the try/except?
same happens with `ConcreteModel`, so it's not an `AbstracModel` specific issue.
It apparently also applies to `within`, so it's probably an issue with validation in general (which makes sense, since the underlying mechanism is the same).
@blnicho is this something you have seen in any tests / use cases?
I can confirm that something strange is happening here. At a minimum, the error message raised when a parameter value is invalid doesn't give any useful information for debugging:

```
from pyomo.environ import *
m = ConcreteModel()
m.p = Param(initialize=-3, within=PositiveReals)
```
```
ERROR: Constructing component 'p' from data=None failed: ValueError: Error
    retrieving immutable Param value (p):
    	The Param value is undefined and no default value is specified.
Traceback (most recent call last):
  File "weird_error.py", line 5, in <module>
    m.p = Param(initialize=-3, within=PositiveReals)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/block.py", line 568, in __setattr__
    self.add_component(name, val)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/block.py", line 1008, in add_component
    val.construct(data)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/param.py", line 869, in construct
    self._initialize_from(self._rule)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/param.py", line 818, in _initialize_from
    _init = self[idx]
  File "/home/blnicho/Research/pyomo/pyomo/core/base/indexed_component.py", line 348, in __getitem__
    return self._getitem_when_not_present(index)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/param.py", line 482, in _getitem_when_not_present
    % ( idx_str,) )
ValueError: Error retrieving immutable Param value (p):
	The Param value is undefined and no default value is specified.

```

My organization is using currently using both Pyomo 5.2 and 5.6.x, and this does not happen when running using 5.2 but does happen under 5.6.x. Not sure if that's helpful information.
I've tracked down this issue (for Bethany's example) to [line 591 in param.py](https://github.com/Pyomo/pyomo/blob/66874abedc5dcb53ccafd319f1925e71cd6029b2/pyomo/core/base/param.py#L591).

The exception is properly thrown in `self.set_value()` but the exception ends up being caught and silenced by this try/except block, and the value is deleted. Eliminating the try/except logic fixed this issue, causing the more helpful error in Param._validate_value() to be raised.

I'm not certain what the try/except block here is trying to accomplish, so I doubt eliminating it entirely is the correct solution.
Hi @bknueven and @blnicho, are there any updates on addressing this bug? Thanks!

We have run into several issues with production code that relied on the validate functionality to work as-documented but no longer does due to this bug.

Is there any way that @jsiirola, who seems to have last touched that part of the code, could answer the question about the try/except?

Resolving patch:
diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py
--- a/pyomo/core/base/param.py
+++ b/pyomo/core/base/param.py
@@ -601,6 +601,7 @@ def _setitem_when_not_present(self, index, value, _check_domain=True):
                 return value
         except:
             del self._data[index]
+            raise
 
 
     def _validate_value(self, index, value, validate_domain=True):


Test patch:
diff --git a/pyomo/core/tests/unit/test_param.py b/pyomo/core/tests/unit/test_param.py
--- a/pyomo/core/tests/unit/test_param.py
+++ b/pyomo/core/tests/unit/test_param.py
@@ -1169,6 +1169,58 @@ def rule(model, i):
             return 0.0
         model.p = Param(model.A, initialize=rule)
 
+    def test_param_validate(self):
+        """Test Param `validate` and `within` throw ValueError when not valid.
+
+        The `within` argument will catch the ValueError, log extra information
+        with of an "ERROR" message, and reraise the ValueError.
+
+        1. Immutable Param (unindexed)
+        2. Immutable Param (indexed)
+        3. Immutable Param (arbitrary validation rule)
+        4. Mutable Param (unindexed)
+        5. Mutable Param (indexed)
+        6. Mutable Param (arbitrary validation rule)
+        """
+        def validation_rule(model, value):
+            """Arbitrary validation rule that always returns False."""
+            return False
+
+        # 1. Immutable Param (unindexed)
+        with self.assertRaisesRegex(ValueError, "Value not in parameter domain"):
+            m = ConcreteModel()
+            m.p1 = Param(initialize=-3, within=NonNegativeReals)
+
+        # 2. Immutable Param (indexed)
+        with self.assertRaisesRegex(ValueError, "Value not in parameter domain"):
+            m = ConcreteModel()
+            m.A = RangeSet(1, 2)
+            m.p2 = Param(m.A, initialize=-3, within=NonNegativeReals)
+
+        # 3. Immutable Param (arbitrary validation rule)
+        with self.assertRaisesRegex(ValueError, "Invalid parameter value"):
+            m = ConcreteModel()
+            m.p5 = Param(initialize=1, validate=validation_rule)
+
+        # 4. Mutable Param (unindexed)
+        with self.assertRaisesRegex(ValueError, "Value not in parameter domain"):
+            m = ConcreteModel()
+            m.p3 = Param(within=NonNegativeReals, mutable=True)
+            m.p3 = -3
+
+        # 5. Mutable Param (indexed)
+        with self.assertRaisesRegex(ValueError, "Value not in parameter domain"):
+            m = ConcreteModel()
+            m.A = RangeSet(1, 2)
+            m.p4 = Param(m.A, within=NonNegativeReals, mutable=True)
+            m.p4[1] = -3
+
+        # 6. Mutable Param (arbitrary validation rule)
+        with self.assertRaisesRegex(ValueError, "Invalid parameter value"):
+            m = ConcreteModel()
+            m.p6 = Param(mutable=True, validate=validation_rule)
+            m.p6 = 1
+
     def test_get_uninitialized(self):
         model=AbstractModel()
         model.a = Param()