Skip to content

Commit 7101350

Browse files
authored
More tolerant scalar check (#348)
* more tolerant scalar check * refactor broadcast of value/lower/upper etc. * isort * change allow_none to default False * add missing backtick, good job flake8 * version bump
1 parent c7b3784 commit 7101350

File tree

5 files changed

+70
-109
lines changed

5 files changed

+70
-109
lines changed

pyoptsparse/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "2.10.0"
1+
__version__ = "2.10.1"
22

33
from .pyOpt_history import History
44
from .pyOpt_variable import Variable

pyoptsparse/pyOpt_constraint.py

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
# Local modules
1010
from .pyOpt_error import Error, pyOptSparseWarning
11-
from .pyOpt_utils import INFINITY, convertToCOO
11+
from .pyOpt_utils import INFINITY, _broadcast_to_array, convertToCOO
1212
from .types import Dict1DType
1313

1414

@@ -43,41 +43,9 @@ def __init__(
4343
# Before we can do the processing below we need to have lower
4444
# and upper arguments expanded:
4545

46-
if lower is None:
47-
lower = [None for i in range(self.ncon)]
48-
elif np.isscalar(lower):
49-
lower = lower * np.ones(self.ncon)
50-
elif len(lower) == self.ncon:
51-
pass # Some iterable object
52-
else:
53-
raise Error(
54-
"The 'lower' argument to addCon or addConGroup is invalid. "
55-
+ f"It must be None, a scalar, or a list/array or length nCon={nCon}."
56-
)
57-
58-
if upper is None:
59-
upper = [None for i in range(self.ncon)]
60-
elif np.isscalar(upper):
61-
upper = upper * np.ones(self.ncon)
62-
elif len(upper) == self.ncon:
63-
pass # Some iterable object
64-
else:
65-
raise Error(
66-
"The 'upper' argument to addCon or addConGroup is invalid. "
67-
+ f"It must be None, a scalar, or a list/array or length nCon={nCon}."
68-
)
69-
70-
# ------ Process the scale argument
71-
scale = np.atleast_1d(scale)
72-
if len(scale) == 1:
73-
scale = scale[0] * np.ones(nCon)
74-
elif len(scale) == nCon:
75-
pass
76-
else:
77-
raise Error(
78-
f"The length of the 'scale' argument to addCon or addConGroup is {len(scale)}, "
79-
+ f"but the number of constraints is {nCon}."
80-
)
46+
lower = _broadcast_to_array("lower", lower, nCon, allow_none=True)
47+
upper = _broadcast_to_array("upper", upper, nCon, allow_none=True)
48+
scale = _broadcast_to_array("scale", scale, nCon)
8149

8250
# Save lower and upper...they are only used for printing however
8351
self.lower = lower

pyoptsparse/pyOpt_optimization.py

Lines changed: 17 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@
1515
from .pyOpt_constraint import Constraint
1616
from .pyOpt_error import Error
1717
from .pyOpt_objective import Objective
18-
from .pyOpt_utils import ICOL, IDATA, INFINITY, IROW, convertToCOO, convertToCSR, mapToCSR, scaleColumns, scaleRows
18+
from .pyOpt_utils import (
19+
ICOL,
20+
IDATA,
21+
INFINITY,
22+
IROW,
23+
_broadcast_to_array,
24+
convertToCOO,
25+
convertToCSR,
26+
mapToCSR,
27+
scaleColumns,
28+
scaleRows,
29+
)
1930
from .pyOpt_variable import Variable
2031
from .types import Dict1DType, Dict2DType, NumpyType
2132

@@ -229,71 +240,11 @@ def addVarGroup(
229240
if varType not in ["c", "i", "d"]:
230241
raise Error("Type must be one of 'c' for continuous, 'i' for integer or 'd' for discrete.")
231242

232-
# ------ Process the value argument
233-
value = np.atleast_1d(value).real
234-
if len(value) == 1:
235-
value = value[0] * np.ones(nVars)
236-
elif len(value) == nVars:
237-
pass
238-
else:
239-
raise Error(
240-
f"The length of the 'value' argument to addVarGroup is {len(value)}, "
241-
+ f"but the number of variables in nVars is {nVars}."
242-
)
243-
244-
if lower is None:
245-
lower = [None for i in range(nVars)]
246-
elif np.isscalar(lower):
247-
lower = lower * np.ones(nVars)
248-
elif len(lower) == nVars:
249-
lower = np.atleast_1d(lower).real
250-
else:
251-
raise Error(
252-
"The 'lower' argument to addVarGroup is invalid. "
253-
+ f"It must be None, a scalar, or a list/array or length nVars={nVars}."
254-
)
255-
256-
if upper is None:
257-
upper = [None for i in range(nVars)]
258-
elif np.isscalar(upper):
259-
upper = upper * np.ones(nVars)
260-
elif len(upper) == nVars:
261-
upper = np.atleast_1d(upper).real
262-
else:
263-
raise Error(
264-
"The 'upper' argument to addVarGroup is invalid. "
265-
+ f"It must be None, a scalar, or a list/array or length nVars={nVars}."
266-
)
267-
268-
# ------ Process the scale argument
269-
if scale is None:
270-
scale = np.ones(nVars)
271-
else:
272-
scale = np.atleast_1d(scale)
273-
if len(scale) == 1:
274-
scale = scale[0] * np.ones(nVars)
275-
elif len(scale) == nVars:
276-
pass
277-
else:
278-
raise Error(
279-
f"The length of the 'scale' argument to addVarGroup is {len(scale)}, "
280-
+ f"but the number of variables in nVars is {nVars}."
281-
)
282-
283-
# ------ Process the offset argument
284-
if offset is None:
285-
offset = np.ones(nVars)
286-
else:
287-
offset = np.atleast_1d(offset)
288-
if len(offset) == 1:
289-
offset = offset[0] * np.ones(nVars)
290-
elif len(offset) == nVars:
291-
pass
292-
else:
293-
raise Error(
294-
f"The length of the 'offset' argument to addVarGroup is {len(offset)}, "
295-
+ f"but the number of variables is {nVars}."
296-
)
243+
value = _broadcast_to_array("value", value, nVars)
244+
lower = _broadcast_to_array("lower", lower, nVars, allow_none=True)
245+
upper = _broadcast_to_array("upper", upper, nVars, allow_none=True)
246+
scale = _broadcast_to_array("scale", scale, nVars)
247+
offset = _broadcast_to_array("offset", offset, nVars)
297248

298249
# Determine if scalar i.e. it was called from addVar():
299250
scalar = kwargs.pop("scalar", False)

pyoptsparse/pyOpt_utils.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
# Local modules
2222
from .pyOpt_error import Error
23+
from .types import ArrayType
2324

2425
# Define index mnemonics
2526
IROW = 0
@@ -529,3 +530,43 @@ def _csc_to_coo(mat: dict) -> dict:
529530
coo_data = np.array(data)
530531

531532
return {"coo": [coo_rows, coo_cols, coo_data], "shape": mat["shape"]}
533+
534+
535+
def _broadcast_to_array(name: str, value: ArrayType, n_values: int, allow_none: bool = False):
536+
"""
537+
Broadcast an input to an array with a specified length
538+
539+
Parameters
540+
----------
541+
name : str
542+
The name of the input. This is only used in the error message emitted.
543+
value : float, list[float], numpy array
544+
The input value
545+
n_values : int
546+
The number of values
547+
allow_none : bool, optional
548+
Whether to allow `None` in the input/output, by default False
549+
550+
Returns
551+
-------
552+
NDArray
553+
An array with the shape ``(n_values)``
554+
555+
Raises
556+
------
557+
Error
558+
If either the input is not broadcastable, or if the input contains None and ``allow_none=False``.
559+
560+
Warnings
561+
--------
562+
Note that the default value for ``allow_none`` is False.
563+
"""
564+
try:
565+
value = np.broadcast_to(value, n_values)
566+
except ValueError:
567+
raise Error(
568+
f"The '{name}' argument is invalid. It must be None, a scalar, or a list/array or length {n_values}."
569+
)
570+
if not allow_none and any([i is None for i in value]):
571+
raise Error(f"The {name} argument cannot be 'None'.")
572+
return value

pyoptsparse/types.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Standard Python modules
2-
from typing import Dict, List, Union
2+
from typing import Dict, Sequence, Union
33

44
# External modules
5-
from numpy import ndarray
5+
import numpy as np
6+
import numpy.typing as npt
67

78
# Either ndarray or scalar
8-
NumpyType = Union[float, ndarray]
9+
NumpyType = Union[float, npt.NDArray[np.float_]]
910
# ndarray, list of numbers, or scalar
10-
ArrayType = Union[float, List[float], ndarray]
11+
ArrayType = Union[NumpyType, Sequence[float]]
1112
# funcs
12-
Dict1DType = Dict[str, ndarray]
13+
Dict1DType = Dict[str, npt.NDArray[np.float_]]
1314
# funcsSens
14-
Dict2DType = Dict[str, Dict[str, ndarray]]
15+
Dict2DType = Dict[str, Dict[str, npt.NDArray[np.float_]]]

0 commit comments

Comments
 (0)