You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The error is in control/tests/optimal_test and it looks like some of the optimization checks are no longer converging to the expected answer. Looks OK in the runs that use slycot from conda, so perhaps something different in the build dependencies?
It looks like I'm getting a couple of different types of errors:
In some tests, I get res.success == False from scipy.optimize.minimize with message "Inequality constraints incompatible". I tried changing method to 'coblya' on one of these that I ran by hand and the error message went away.
In other tests, I get res.success == True but the optimal result doesn't match what it is expected to be. I tried setting tol=1e-12 and those error messages went away.
This gets rid of 13 of the 14 errors. Unfortunately, if I try to use coblya (or other solvers that can handle constraints) as the default method and I still get failures, mainly due to lack of convergence to the expected optimal values (not sure why yet).
@ilayn: Any insights into why the new C version might have differences in the way that constraints are handled and/or tolerances/convergence criteria? Is there a set of parameters I can pass via the options keyword that might recover the previous behavior?
I am a bit puzzled since in Ubuntu systems we have started seeing diverging results compared to the other architectures and I still don't have a good answer to these strange deviations. Is it also possible that the desired results are obtained by the F77 algorithm itself hence creating self-referencing results?
In any case, is it possible that we can isolate a simple case arguments that goes into scipy.optimize so I can try to emulate in a virtual box?
In the python-control context, here's an example of old versus new results:
import numpy as np
import scipy
import control as ct
import control.optimal as opt
# Define a (discrete-time) linear system with constraints
# Source: https://www.mpt3.org/UI/RegulationProblem
# LTI prediction model (discrete time)
sys = ct.ss([[1, 1], [0, 1]], [[1], [0.5]], np.eye(2), 0, 1)
# State and input constraints
constraints = [
scipy.optimize.LinearConstraint(np.eye(3), [-5, -5, -1], [5, 5, 1]),
]
# Quadratic state and input penalty
Q = [[1, 0], [0, 1]]
R = [[1]]
cost = opt.quadratic_cost(sys, Q, R)
# Set up the optimal control problem
time = np.arange(0, 5, 1)
x0 = [4, 0]
# Retrieve the full open-loop predictions
res = opt.solve_optimal_trajectory(
sys, time, x0, cost, constraints, squeeze=True,
trajectory_method='shooting',
terminal_cost=cost) # include to match MPT3 formulation
print("\nMPT inputs: [-1, -1, 0.1393, 0.3361, -5.204e-16]")
print("python-control: ", res.inputs)
print("\nMPT cost: 32.4898")
print("python-control: ", res.cost)
Since the cost is greater in the new case, it would seem that at the least it has different convergence criteria (or multiple minima, but I don't think that is the case here).
When I get a chance, I'll see if I can "unwrap" the optimization that python-control is setting up and come up with a simple case for the arguments that go directly into scipy.optimize.minimize.
Does this look meaningful to you? I could not dive into the problem setup so I can't quite see what the function is but it seems like the function evaluation is not the same between two versions. This might be completely SciPy's fault in its clamping behavior change but any feedback would be really nice.
From the inspect module I can see the function is the same to both versions so I'm not sure I can explain why they are giving different results yet
def_cost_function(self, coeffs):
ifself.log:
start_time=time.process_time()
logging.info("_cost_function called at: %g", start_time)
# Compute the states and inputsstates, inputs=self._compute_states_inputs(coeffs)
# Trajectory costifct.isctime(self.system):
# Evaluate the costscosts= [self.integral_cost(states[:, i], inputs[:, i]) foriinrange(self.timepts.size)]
# Compute the time intervalsdt=np.diff(self.timepts)
# Integrate the costcosts=np.array(costs)
# Approximate the integral using trapezoidal rulecost=np.sum(0.5* (costs[:-1] +costs[1:]) *dt)
else:
# Sum the integral cost over the time (second) indices# cost += self.integral_cost(states[:,i], inputs[:,i])cost=sum(map(
self.integral_cost, states[:, :-1].transpose(),
inputs[:, :-1].transpose()))
# Terminal costifself.terminal_costisnotNone:
cost+=self.terminal_cost(states[:, -1], inputs[:, -1])
# Update statisticsself.cost_evaluations+=1ifself.log:
stop_time=time.process_time()
self.cost_process_time+=stop_time-start_timelogging.info(
"_cost_function returning %g; elapsed time: %g",
cost, stop_time-start_time)
# Return the total cost for this input sequencereturncost
I'll have a look. For this particular use case, what _cost_function expects as inputs is the values of input vector over time (SISO system => coeffs = (u[t], u[t+1], u[t+2], u[t+3], u[t+4]), as an ndarray). From those values, the system state over the horizon is generated via simulation (using odeint, I think).
The fact that the two versions are giving different costs is a great clue and it should help track down what is different.
So from this it looks like something is wrong with the computation of the states, inside the cost function. The initial state (first column) is the same in both cases and the inputs are the same, but for "SciPy 1.16.0" the input is somehow not getting applied.
Hard to imagine that is something in scipy.optimize.minimize, so it must be something else (maybe associated with SciPy 1.16.0 or maybe not...).
Found the problem in optimal_test.py. Nothing to do with SciPy. Dumb coding error in saving the last value of a computation: needed to save a contents of an array, not just the reference to the array object. Not sure why it just started failing now...
Next problem comes up in lti_test.py, where ct.c2d() is throwing errors due to complex coefficients when creating a transfer function. Might be something different between NumPy 2.2.6 (old) and NumPy 2.3.1 (new).
Found the second set of errors. Looks like there is a difference in the way rounding was handled in NumPy 2.3.1 that wasn't showing up previously and arose when multiplying complex conjugates to get a real number.
PR coming next. I'll go ahead and merge once it comes through, since it is breaking CI and doesn't change functionality.
Activity
murrayrm commentedon Jun 25, 2025
NumPy 2.3 was released on 6 Jun 2025, so I'm guessing that is the culprit (somehow). Release notes:
https://numpy.org/doc/stable/release/2.3.0-notes.html
murrayrm commentedon Jun 26, 2025
I've been able to recreate the problem locally. The problem is not NumPy 2.3, but SciPy 1.16.0:
https://docs.scipy.org/doc/scipy-1.16.0/release/1.16.0-notes.html
Working to figure out what the issue is ...
ilayn commentedon Jun 26, 2025
If I read the errors correctly, this is SLSQP output, we converted the F77 solver to C.
murrayrm commentedon Jun 28, 2025
It looks like I'm getting a couple of different types of errors:
In some tests, I get
res.success == Falsefromscipy.optimize.minimizewith message "Inequality constraints incompatible". I tried changingmethodto 'coblya' on one of these that I ran by hand and the error message went away.In other tests, I get
res.success == Truebut the optimal result doesn't match what it is expected to be. I tried settingtol=1e-12and those error messages went away.This gets rid of 13 of the 14 errors. Unfortunately, if I try to use
coblya(or other solvers that can handle constraints) as the default method and I still get failures, mainly due to lack of convergence to the expected optimal values (not sure why yet).@ilayn: Any insights into why the new C version might have differences in the way that constraints are handled and/or tolerances/convergence criteria? Is there a set of parameters I can pass via the
optionskeyword that might recover the previous behavior?ilayn commentedon Jun 28, 2025
I am a bit puzzled since in Ubuntu systems we have started seeing diverging results compared to the other architectures and I still don't have a good answer to these strange deviations. Is it also possible that the
desiredresults are obtained by the F77 algorithm itself hence creating self-referencing results?In any case, is it possible that we can isolate a simple case arguments that goes into
scipy.optimizeso I can try to emulate in a virtual box?murrayrm commentedon Jun 28, 2025
In the python-control context, here's an example of old versus new results:
Results with SciPy 1.15.1:
The results from the MPT toolbox were created under MATLAB (though might still be using F77 SLSQP under the hood?).
Results with SciPy 1.16.0:
Since the cost is greater in the new case, it would seem that at the least it has different convergence criteria (or multiple minima, but I don't think that is the case here).
When I get a chance, I'll see if I can "unwrap" the optimization that python-control is setting up and come up with a simple case for the arguments that go directly into
scipy.optimize.minimize.ilayn commentedon Jun 30, 2025
Thank you for the extra information, will be very useful. I'll also try to build SciPy with logging so we can catch it on the other side too.
ilayn commentedon Jun 30, 2025
I just did a very crude
printf()comparison. Even in the beginning when the function evaluation is requested at the point starts to disagree alreadyThis is 1.15.1 output
and in the new version this gives
Does this look meaningful to you? I could not dive into the problem setup so I can't quite see what the function is but it seems like the function evaluation is not the same between two versions. This might be completely SciPy's fault in its clamping behavior change but any feedback would be really nice.
ilayn commentedon Jun 30, 2025
From the
inspectmodule I can see the function is the same to both versions so I'm not sure I can explain why they are giving different results yetmurrayrm commentedon Jun 30, 2025
I'll have a look. For this particular use case, what
_cost_functionexpects as inputs is the values of input vector over time (SISO system => coeffs = (u[t], u[t+1], u[t+2], u[t+3], u[t+4]), as an ndarray). From those values, the system state over the horizon is generated via simulation (usingodeint, I think).The fact that the two versions are giving different costs is a great clue and it should help track down what is different.
murrayrm commentedon Jul 1, 2025
(Mainly notes for me...)
I put in some statements in
_cost_function()to show the inputs arguments (coeffs) as well as the states, inputs, costs. Here's what I get.SciPy 1.15.1:
Scipy 1.16.0
So from this it looks like something is wrong with the computation of the states, inside the cost function. The initial state (first column) is the same in both cases and the inputs are the same, but for "SciPy 1.16.0" the input is somehow not getting applied.
Hard to imagine that is something in
scipy.optimize.minimize, so it must be something else (maybe associated with SciPy 1.16.0 or maybe not...).ilayn commentedon Jul 1, 2025
If SciPy is the only moving piece it is probably somewhere else but still in SciPy if I hazard a guess,
odeintorsolve_ivp.murrayrm commentedon Jul 4, 2025
Found the problem in
optimal_test.py. Nothing to do with SciPy. Dumb coding error in saving the last value of a computation: needed to save a contents of an array, not just the reference to the array object. Not sure why it just started failing now...murrayrm commentedon Jul 4, 2025
Next problem comes up in
lti_test.py, wherect.c2d()is throwing errors due to complex coefficients when creating a transfer function. Might be something different between NumPy 2.2.6 (old) and NumPy 2.3.1 (new).murrayrm commentedon Jul 4, 2025
Found the second set of errors. Looks like there is a difference in the way rounding was handled in NumPy 2.3.1 that wasn't showing up previously and arose when multiplying complex conjugates to get a real number.
PR coming next. I'll go ahead and merge once it comes through, since it is breaking CI and doesn't change functionality.
add bug fix associated with python-controlgh-1161
ilayn commentedon Jul 5, 2025
Ah OK, at least this time we dodged the bullet so that we don't need to play with SciPy version exceptions. Thank you for the extended investigation.
murrayrm commentedon Jul 5, 2025
Fixed in #1163.