Wishing to do a harder problem, I tried to attack it from a simpler point of view with the following toy minimization equation
which does not have a trivial solution.
In order to do that, I need to use an augmented lagrangian / dual function 1 with its gradient 2, and the equilibrium point 3.
The augmented lagrangian version of the previous problem:
The point of a Lagrange multiplier is to optimize over mu in [0,inf] in order to take into account the weird constraints of our problem.
Running the following code
a = 1
nbtests = 5 ; minmu = 0 ; maxmu = 5
def dual(mu) :
x = spo.fsolve(lambda x : 2*x - mu * (np.exp(x)+1) , 1)
return (- (x**2 - mu *(np.exp(x) + x -a )) ,- (np.exp(x) + x-a))
pl = np.empty((nbtests,2))
for i,nu in enumerate(np.linspace(minmu,maxmu,pl.shape[0])) :
res = spo.fmin_l_bfgs_b(dual,nu,bounds=[(0,None)],factr=1e6)
print(nu,res[0],res[2]['task'])
pl[i] = [nu,spo.fsolve(lambda x : 2*x - res[0] * (np.exp(x)+1), 1)]
plt.plot(pl[:,0],pl[:,1],label="L-BFGS-B")
plt.axhline(y=spo.fsolve(lambda x : np.exp(x)+x-a, 1)[0], label="Target",color="red")
plt.legend()
plt.show()
plt.plot(pl[:,0],pl[:,1],label="L-BFGS-B")
plt.axhline(y=spo.fsolve(lambda x : np.exp(x)+x-a, 1)[0], label="Target",color="red")
plt.legend()
plt.show()
Here, we are trying to L-BFGS-B optimizer in Python (which is the fastest one, since we have access to the gradient) from the dual problem, then revert to the original solution with fsolve. Note that the - signs inside the function and gradient are because the minimisation of the primal problem is equal to the maximistation of the dual problem, so they are for the actual maximisation of the equations.
I plot here the result of the optimizer with respect to the initial guess of mu, and shows it's very very dependent on the initialisation, not robust at all. When changing the parameter a to other values, the convergence of the method is even worse.
The plot of the optimizer result with respesct to mu (for a bigger nbtests)
and the output of the program
0.0 [0.] b'ABNORMAL_TERMINATION_IN_LNSRCH'
0.5555555555555556 [0.55555556] b'ABNORMAL_TERMINATION_IN_LNSRCH'
1.1111111111111112 [3.52870269] b'ABNORMAL_TERMINATION_IN_LNSRCH'
1.6666666666666667 [3.52474085] b'ABNORMAL_TERMINATION_IN_LNSRCH'
2.2222222222222223 [3.5243099] b'ABNORMAL_TERMINATION_IN_LNSRCH'
2.7777777777777777 [3.49601967] b'CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH'
3.3333333333333335 [3.52020875] b'CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH'
3.8888888888888893 [3.88888889] b'ABNORMAL_TERMINATION_IN_LNSRCH'
4.444444444444445 [4.44444354] b'ABNORMAL_TERMINATION_IN_LNSRCH'
5.0 [5.] b'ABNORMAL_TERMINATION_IN_LNSRCH'
The first column is the initial guess, and the second one is the estimated guess after the optimzer, and we see that it does not optimize at all for most of the values.
For a <= 0 , the domain of the function is x < 0, which gives a trivial minimisation x^2 = 0, for mu = 0. So all the solutions should give 0 at the return of the optimizer.
The error b'ABNORMAL_TERMINATION_IN_LNSRCH comes from a bad gradient, but here, is it the real gradient of the function...
What did I miss?
There are multiple wrong signs in your code, which, by the way, violates the DRY principle. It should be x**2 + mu * (np.exp(x) + x - a) instead of x**2 - mu * (np.exp(x) + x - a) and similar for the derivative. IMHO, something like
from scipy.optimize import fsolve, fmin_l_bfgs_b
a = 1
nbtests = 5
minmu = 0
maxmu = 5
def lagrange(x, mu):
return x**2 + mu * (np.exp(x) + x - a)
def lagrange_grad(x, mu):
grad_x = 2*x + mu * (np.exp(x) + 1)
grad_mu = np.exp(x) + x - a
return grad_x, grad_mu
def dual(mu):
x = fsolve(lambda x: lagrange_grad(x, mu)[0], x0=1)
obj_val = lagrange(x, mu)
grad = lagrange_grad(x, mu)[1]
return -1.0*obj_val, -1.0*grad
pl = np.empty((nbtests, 2))
for i, nu in enumerate(np.linspace(minmu,maxmu,nbtests)):
res = fmin_l_bfgs_b(dual, x0=nu, bounds=[(0,None)], factr=1e6)
mu_opt = res[0]
x_opt = fsolve(lambda x: lagrange_grad(x, mu_opt)[0], x0=1)
pl[i] = [nu, *x_opt]
is much cleaner. This gives me
array([[0. , 0. ],
[1.25, 0. ],
[2.5 , 0. ],
[3.75, 0. ],
[5. , 0. ]])
as desired.
I am trying to use scipy.optimize.fsolve to work out the x-intercept(s):
from scipy.optimize import fsolve
from numpy import array, empty
counter = 0
def f(x_):
global counter
counter += 1
return pow(x_, 3) * 3 - 9.5 * pow(x_, 2) + 10 * x_
x0_ = empty(2)
x0_[0] = 1
x0_[1] = 6
res = fsolve(f, x0=x0_)
print(counter)
print(res)
the function f(x): https://www.desmos.com/calculator/8j8djr01da
the result of this code is:
74
[0. 0.]
I expect the result to be
[0, 1.575, 3.175]
Can someone please offer some help.
Plus:
I can't understand the documentation of fsolve(x0), is that just a guess? I will be so appreciated if you can explain.
Plus Plus:
I will be working with lots of linear equations with unknown expressions and exponential, I am really looking for a way to work out the x-intercepts, in other words, the roots by the expression of f(x).I would be so glad if you can help.
You get the set of all roots for a polynomial by
numpy.roots([3, -9.5, +10, 0])
array([1.58333333+0.90905934j, 1.58333333-0.90905934j,
0. +0.j ])
It is not clear what your other expected real roots are, fsolve will only find the real root 0.
Of course, if you take the coefficients that you used in the Desmos graphing tool
numpy.roots([2, -9.5, +10, 0])
you will actually get the expected
array([3.17539053, 1.57460947, 0. ])
For scalar non-polynomial functions the interface scipy.optimize.find_root is perhaps more suitable, especially if you can provide a bracketing interval.
I just want to say that at the first step you define your function wrong:
it should be
def f(x_):
# global counter
# counter += 1
return pow(x_, 3) * 2 - 9.5 * pow(x_, 2) + 10 * x_
but notpow(x_, 3) * 3 - 9.5 * pow(x_, 2) + 10 * x_
If you then set x0_ precisely:
x0_=[0,1,3] # according to intersection on graph
res=fsolve(f, x0=x0_)
Give you the anticipated output:
[0. 1.57460947 3.17539053]
Sometimes you just have to be more careful :)
I'm looking to convert a series of ordered (pretty dense) 2D points describing arbitrary curves into a NURBS representation, which can be written into an IGES file.
I'm using scipy.interpolate's splprep to get a B-spline representation of the given series of points, and then I had presumed the NURBS definition would essentially be this plus saying all weights are equal to 1. However I think I am fundamentally misinterpreting the output of splprep, specifically the relation between 'B-spline coefficients' and the control points needed to manually recreate the spline in some CAD package (I am using Siemens NX11).
I've tried a simple example of approximating the function y = x^3 from a sparse set of points:
import scipy.interpolate as si
import numpy as np
import matplotlib.pyplot as plt
# Sparse points defining cubic
x = np.linspace(-1,1,7)
y = x**3
# Get B-spline representation
tck, u = si.splprep([x,y],s=0.0)
# Get (x,y) coordinates of control points
c_x = tck[1][0]
c_y = tck[1][1]
# Plotting
u_fine = np.linspace(0,1,1000)
x_fine, y_fine = si.splev(u_fine, tck)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'o', x_fine, y_fine)
ax.axis('equal')
plt.show()
Which gives the following parameters:
>>> t
array([ 0. , 0. , 0. , 0. , 0.39084883,
0.5 , 0.60915117, 1. , 1. , 1. , 1. ])
>>> c_x
array([ -1.00000000e+00, -9.17992269e-01, -6.42403598e-01,
-2.57934892e-16, 6.42403598e-01, 9.17992269e-01,
1.00000000e+00])
>>> c_y
array([ -1.00000000e+00, -7.12577481e-01, -6.82922469e-03,
-1.00363771e-18, 6.82922469e-03, 7.12577481e-01,
1.00000000e+00])
>>> k
3
>>> u
array([ 0. , 0.25341516, 0.39084883, 0.5 , 0.60915117,
0.74658484, 1. ])
>>>
I've assumed that the two sets of coefficients (c_x, c_y) describe the (x,y) coordinates of poles needed to construct the spline. Trying this manually in NX gives a similar spline, though not quite the same, with other points in the interval being evaluated differently than in Python. When I export this manual spline to IGES format, NX changes the knots to the below (while obviously keeping the same control points/poles and setting all weights = 1).
t_nx = np.array([0.0, 0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0])
Going the other way and writing the splprep knots (t) into the IGES definition (with said 'control points' and weights = 1) does not seem to give a valid spline. NX and at least one other package cannot evaluate it, citing 'invalid trim or parametric values for B-spline curve'.
There seem to me to be at least three possibilities:
A non-trivial conversion is necessary to go from non-rational to rational B-splines
There is an application-specific interpretation of IGES splines (i.e. my interpretation of splprep output is correct, but this is simplified/approximated by NX when manually drawn/during the IGES conversion routine). Seems unlikely.
The coefficients from splprep cannot be interpreted as control points in the manner I've described
I had written off the first possibility by comparing the equations for a scipy B-spline (link) and an IGES NURBS spline with all weights = 1 (link, page 14). They look identical, and it was this that led me to believe splprep coefficients = control points.
Any help clarifying any of the above points would be very much appreciated!
NB, I would like the possibility of representing closed curves, so want to stick to splprep if possible.
EDIT:
I thought it would be simpler to try this process first using splrep, as the outputs seemed more intuitive to me. I assumed the coefficients returned were the y-values of the control points, but didn't know to what x position they corresponded. I therefore tried to calculate them from the spline definition and input data using this matrix approach. The C matrix is just the input data. The N matrix is the evaluation of each basis function for each x-value, I did this using the (slightly modified) recursive functions shown here. Then all that remains is to invert N, and pre-multiply C by it to get the control points. The code and result is below:
import numpy as np
import scipy.interpolate as si
# Functions to evaluate B-spline basis functions
def B(x, k, i, t):
if k == 0:
return 1.0 if t[i] <= x < t[i+1] else 0.0
if t[i+k] == t[i]:
c1 = 0.0
else:
c1 = (x - t[i])/(t[i+k] - t[i]) * B(x, k-1, i, t)
if t[i+k+1] == t[i+1]:
c2 = 0.0
else:
c2 = (t[i+k+1] - x)/(t[i+k+1] - t[i+1]) * B(x, k-1, i+1, t)
return c1 + c2
def bspline(x, t, c, k):
n = len(t) - k - 1
assert (n >= k+1) and (len(c) >= n)
cont = []
for i in range(n):
res = B(x, k, i, t)
cont.append(res)
return cont
# Input data
x = np.linspace(-1,1,7)
y = x**3
# B-spline definition
t, c, k = si.splrep(x,y)
# Number of knots = m + 1 = n + k + 2
m = len(t) - 1
# Number of kth degree basis fcns
n = m - k - 1
# Define C and initialise N matrix
C_mat = np.column_stack((x,y))
N_mat = np.zeros(((n+1),(n+1)))
# Calculate basis functions for each x, store in matrix
for i, xs in enumerate(x):
row = bspline(xs, t, c, k)
N_mat[i,:] = row
# Last value must be one...
N_mat[-1,-1] = 1.0
# Invert the matrix
N_inv = np.linalg.inv(N_mat)
# Now calculate control points
P = np.dot(N_inv, C_mat)
Resulting in:
>>> P
array([[ -1.00000000e+00, -1.00000000e+00],
[ -7.77777778e-01, -3.33333333e-01],
[ -4.44444444e-01, -3.29597460e-17],
[ -3.12250226e-17, 8.67361738e-18],
[ 4.44444444e-01, -2.77555756e-17],
[ 7.77777778e-01, 3.33333333e-01],
[ 1.00000000e+00, 1.00000000e+00]])
I think it's correct because the y-values of P match the coefficients from splrep, c. Interestingly the x-values seem to be the knot averages (which could be separately calculated as below). Perhaps this result is obvious to someone very familiar with the maths, it certainly wasn't to me.
def knot_average(knots, degree):
"""
Determines knot average vector from knot vector.
:knots: A 1D numpy array describing knots of B-spline.
(NB expected from scipy.interpolate.splrep)
:degree: Integer describing degree of B-spline basis fcns
"""
# Chop first and last vals off
knots_to_average = knots[1:-1]
num_averaged_knots = len(knots_to_average) - degree + 1
knot_averages = np.zeros((num_averaged_knots,))
for i in range(num_averaged_knots):
avg = np.average(knots_to_average[i: i + degree])
knot_averages[i] = avg
return(knot_averages)
Now, to convert these to IGES NURBS I thought it was a case of defining the normalised knot vector, setting the weights all equal to one, and including the P control points from above. I normalised it as below, and have included the IGES file below that.
However when I try to import the file into NX, it again fails stating invalid trim parameters in the definition. Can anyone tell me if this is a valid NURBS definition?
Or perhaps this is some limitation with NX? For instance, I noticed when interactively drawing studio splines the knot vector was forced to be (clamped) uniform (as alluded to by fang). This constraint (and weights all = 1) must be required to uniquely define the curve. Interestingly if I force splrep to return a spline representation using a uniform knot vector (that is, clamped but otherwise uniform), the IGES is read in. I shouldn't think this is necessary though from NXs point of view - it defeats the purpose of having a NURBS in the first place. So it doesn't seem likely and I loop round wondering if my interpretation of the output of splrep is correct...can someone please point out where I've gone wrong?
# Original knot vector
>>> t
array([-1. , -1. , -1. , -1. , -0.33333333,
0. , 0.33333333, 1. , 1. , 1. , 1. ])
mini = min(t)
maxi = max(t)
r = maxi - mini
norm_t = (t-mini)/r
# Giving:
>>> norm_t
array([ 0. , 0. , 0. , 0. , 0.33333333,
0.5 , 0.66666667, 1. , 1. , 1. , 1. ])
IGES definition:
S 1
,,11Hspline_test,13Hsome_path.igs,19HSpline to iges v1.0,4H 0.1,,,,,,, G 1
1.0, 2,2HMM,,,8H 8:58:19,,,,; G 2
126 1 1 1 0 0 0D 1
126 27 4 0 Spline1 1D 2
126,6,3,0,0,1,0,0.0,0.0,0.0,0.0,0.33333,0.5,0.6666666,1.0,1.0,1.0,1.0, 1P 1
1.0,1.0,1.0,1.0,1.0,1.0,1.0,-1.0,-1.0,0.0,-0.7777,-0.33333,0.0, 1P 2
-0.444444,0.0,0.0,0.0,0.0,0.0,0.4444444,0.0,0.0,0.777777777,0.33333, 1P 3
0.0,1.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,0; 1P 4
S 1G 2D 2P 4 T 1
On the off chance this niche query helps anyone else- it turns out the problem was incorrect formatting of the parameter data section in the IGES. The data describing the spline can't take up > 64 characters per line. The interpretation of splprep output was correct, the (c_x, c_y) arrays describe the (x,y) coordinates of successive poles. The equivalent NURBS definition just requires specification of all weights = 1.
I want to slove a set of linear equation of 10 variable.
I created the first array like this:
A=np.random.random_integers(15, size=(10,10))
and i want the values after the equal to be 0
(A.x + d.y + .... + N = 0)
so i did something like that:
b=np.zeros(shape=(10))
but when i apply the linear algebra function
print linalg.solve(A, b)
i just get as a result an array of 10 zeros.
[ 0. 0. 0. 0. -0. -0. -0. -0. 0. 0.]
anyone can help??
I do not understand the meaning of the second line of code.
Though, with this:
A=np.random.random_integers(15, size=(10,10))
b=np.zeros(shape=(10))
you are solving the system:
A * x = b
which means that you have:
A[1,1] * x_1 + A[1,2] * x_2 + ... + A[1,10] * x_10 = 0
A[2,1] * x_1 + A[2,2] * x_2 + ... + A[2,10] * x_10 = 0
...
So that the x = zero vector is always a perfect solution = you are looking for such x that A x = 0, so x is zero. Try
b = np.random.random_integers(15, size=(10,1))
and x resulting from linalg.solve(A,b) will specify a linear combination of columns from A to sum up to the random b vector.
In https://stackoverflow.com/questions/12910513/how-to-verify-the-results-of-a-linear-equation-system you tried numpy.svd (which is singular value decmposition, which I think you do not want) and numpy.lstsq which tries to find inexact solution that minimizes the least square distance (e.g. for overdetermined matrices).
I might not have understood what you are looking for - please clarify the line specifying what exactly are you looking for.