Repository: Pyomo/pyomo

Issue:
Are expressions of units with different SI-prefixes supported
In the [docs](https://pyomo.readthedocs.io/en/stable/advanced_topics/units_container.html) it is stated

> The module also supports conversion of units within expressions to support construction of constraints that contain embedded unit conversions.

Therefore, my expectation was that the creation of expressions containing units with different SI-prefixes is supported.

Example:

```python
from pyomo.environ import Param, units
from pyomo.util.check_units import assert_units_consistent

param_kW = Param(initialize=2, units=units.kW)
param_MW = Param(initialize=1, units=units.MW)

assert_units_consistent(param_kW + param_MW)
```

However this raises an exception
```
pyomo.core.base.units_container.InconsistentUnitsError: Error in units found in expression: SimpleParam + SimpleParam: kilowatt not compatible with megawatt.
```

Pint supports these conversions
```python
import pint

ureg = pint.UnitRegistry()
print(2 * ureg.kW + 1 * ureg.MW)
# 1002.0 kilowatt
```

Are expressions which have units with different SI-prefixes supported?

PS thanks a lot for implementing unit functionality in 5.7.
This is really a convenient feature because modelling mistakes can be found much easier!


Discussion:
The short answer is "no".  You must write your expressions to be unit-consistent.  We should probably clarify the documentation to read:
> The module also supports conversion of units within expressions **using the convert() method** to support construction of constraints that contain embedded unit conversions.

The reason for this is that for optimization problems, scaling matters.  If you were to write:
```python
m.x = Var(units=units.uW)
m.y = Var(units=units.MW)
m.c = Constraint(expr=m.x >= m.y)
```
We could conceivably resolve those units for you.  However; there are many possible solutions: 
- convert `m.x` to MW
- convert `m.y` to uW
- convert *both* to W

The problem is that each of those would give a constraint with different scaling.  We decided that we weren't going to be able to automatically pick the "right" scaling for your problem, and that the best solution was to have the user handle all the unit conversions explicitly.  So for your example, you could do the following:
```python
assert_units_consistent(param_kW + units.convert(param_MW, units.kW))
```
or even
```python
assert_units_consistent(param_kW + units.convert(param_MW, param_kW.get_units()))
```
Thanks a lot for your comprehensive answer, this answers my question.

> The problem is that each of those would give a constraint with different scaling. We decided that we weren't going to be able to automatically pick the "right" scaling for your problem, and that the best solution was to have the user handle all the unit conversions explicitly.

Yes this makes sense and in particular in optimization the scaling is a tricky topic.
Pint seems to scale all terms of a expression to the smallest unit, therefore, I thought pyomo does maybe the same.

> We should probably clarify the documentation to read...

I can open a pull request which includes your proposed changes in https://github.com/Pyomo/pyomo/blob/master/pyomo/core/base/units_container.py

Resolving patch:
diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py
--- a/pyomo/core/base/units_container.py
+++ b/pyomo/core/base/units_container.py
@@ -15,8 +15,8 @@
 This module provides support for including units within Pyomo expressions. This module
 can be used to define units on a model, and to check the consistency of units
 within the underlying constraints and expressions in the model. The module also
-supports conversion of units within expressions to support construction of constraints
-that contain embedded unit conversions.
+supports conversion of units within expressions using the `convert` method to support
+construction of constraints that contain embedded unit conversions.
 
 To use this package within your Pyomo model, you first need an instance of a
 PyomoUnitsContainer. You can use the module level instance already defined as


Test patch: