diff --git a/src/examodels/model.py b/src/examodels/model.py index 8cbe76c..bb29bf7 100644 --- a/src/examodels/model.py +++ b/src/examodels/model.py @@ -119,8 +119,11 @@ def parameters(self, block): def set_parameters(self, block, values): """Change a parameter block's values in place; the model is reused as is.""" + # Place the values where the model's arrays live, like every other + # setter: writing a host array into device memory falls into a scalar + # path the backend disallows. _b.guard(_b.EM.set_value_b, self._jl, block._jl, - np.asarray(values, dtype=np.float64).ravel()) + _b.upload(self._jl, np.asarray(values, dtype=np.float64).ravel())) return self def solve(self, solver=None, **options): diff --git a/tests/test_device_parameters.py b/tests/test_device_parameters.py new file mode 100644 index 0000000..323502b --- /dev/null +++ b/tests/test_device_parameters.py @@ -0,0 +1,29 @@ +"""`set_parameters` on a device model — needs hardware. + +Every other setter places its values where the model's arrays live +(`_b.upload`); `set_parameters` passed the host array straight through, which +on a device model drops into the scalar path GPUArrays forbids and raised +`ModelError: Scalar indexing is disallowed`. The scalar-indexing guard only +exists on a real device array, so this cannot be pinned by the stubbed-device +tests in `test_device_interop.py`. +""" +import numpy as np +import pytest + +import examodels as exa + +pytestmark = pytest.mark.skipif( + not __import__("shutil").which("nvidia-smi"), reason="no GPU on this machine") + + +def test_set_parameters_takes_effect_on_a_device_model(): + core = exa.Core(backend="cuda") + x = core.add_var(4, start=0.0) + p = core.add_par([1.0, 2.0, 3.0, 4.0]) + core.add_obj(lambda i: (x[i] - p[i]) ** 2, over=range(4)) + m = exa.Model(core) + + assert m.objective(np.zeros(4)) == pytest.approx(1.0 + 4.0 + 9.0 + 16.0) + + m.set_parameters(p, [5.0, 6.0, 7.0, 8.0]) + assert m.objective(np.zeros(4)) == pytest.approx(25.0 + 36.0 + 49.0 + 64.0)