Solving this system of nonlinear equations with Python - python

Problem.
Consider the following problem: An experiment consists of three receivers, i,j and k, with known coordinates (xi,yi,zi), (xj,yi... in 3D space. A stationary transmitter, with unknown coordinates (x,y,z), emits a signal with known velocity v. The time of arrival of this signal at each receiver is recorded, giving ti,tj,tk. The signal emission time, t, is unknown.
Using only the coordinates of the receivers and the signal arrival times, I wish to determine the location of the transmitter.
In general, to solve for the coordinates of such a transmitter in N-dimensional space, N+1 receivers are required. Hence, in this case, a single, unique solution is unobtainable. A small number of finite solutions should be obtainable via numerical methods, however.
We may write the following system of equations to model the problem:
Sqrt[(x-xi)^2 + (y-yi)^2 + (z-zi)^2] + s(tj-ti) = Sqrt[(x-xj)^2 + (y-yj)^2 + (z-zj)^2]
Sqrt[(x-xj)^2 + (y-yj)^2 + (z-zj)^2] + s(tk-tj) = Sqrt[(x-xk)^2 + (y-yk)^2 + (z-zk)^2]
Sqrt[(x-xi)^2 + (y-yi)^2 + (z-zi)^2] + s(tk-ti) = Sqrt[(x-xk)^2 + (y-yk)^2 + (z-zk)^2]
Each equation gives a hyperboloid. Under ideal conditions, these three hyperboloids will intersect at precisely two points— one being the "true" solution, and the other being a reflection of that solution about the plane defined by the three receivers. In practice, given sufficiently accurate measurements, numerical solvers should be able to approximate these points of intersection.
My goal is to determine both solutions. Although it is impossible to determine which is the "true" transmitter location, for my purposes this will be sufficient.
Implementation.
I wish to implement a solution in Python. I'm not terribly familiar with NumPy or SciPy, however I've done a fair bit of work in SymPy, so I began there.
SymPy offers a variety of solvers, most of which focus on obtaining solutions symbolically. Not surprisingly, solve() and the like failed to find a solution, even under simulated "ideal" conditions (picking a random point, calculating the time taken for a signal originating from that point to arrive at each receiver, and feeding this to the algorithm).
SymPy also offers a numerical solver, nsolve(). I gave this a go, using the approach given below, however (not surpisingly) I got the error ZeroDivisionError: Matrix is numerically singular.
f = sym.Eq(sym.sqrt((x - x_i)**2 + (y - y_i)**2 + (z - z_i)**2) - sym.sqrt((x - x_j)**2 + (y - y_j)**2 + (z - z_j)**2), D_ij)
g = sym.Eq(sym.sqrt((x - x_i)**2 + (y - y_i)**2 + (z - z_i)**2) - sym.sqrt((x - x_k)**2 + (y - y_k)**2 + (z - z_k)**2), D_ik)
h = sym.Eq(sym.sqrt((x - x_j)**2 + (y - y_j)**2 + (z - z_j)**2) - sym.sqrt((x - x_k)**2 + (y - y_k)**2 + (z - z_k)**2), D_jk)
print("Soln. ", sym.nsolve((f,g,h),(x,y,z), (1,1,1)))
As I understand it, nsolve() relies on "matrix" techniques. A singular matrix is one which may not be inverted (or, equivalently, which has determinant zero), hence SymPy is unable to solve the system in question.
My understanding of matrices and nonlinear system solving techniques is a bit lacking, however my understanding is that a singular matrix occurs when there are infinitely many solutions, no solutions, or more than one solution. Given that I know there to be exactly two solutions, I believe this is the issue.
What Python solvers are available can be used so solve a nonlinear system with multiple solutions? Or, alternatively, is there a way to modify this such that it is digestible by SymPy?
Browsing the Numpy and SciPy docs, it seems that their solvers are effectively identical to what SymPy offers.
The (crude) test code mentioned below:
from random import randrange
import math
# SIMPLE CODE FOR SIMULATING A SIGNAL
# Set range
N=10
P=100
# Pick nodes to be at random locations
x_1 = randrange(N); y_1 = randrange(N); z_1 = randrange(N)
x_2 = randrange(N); y_2 = randrange(N); z_2 = randrange(N)
x_3 = randrange(N); y_3 = randrange(N); z_3 = randrange(N)
# Pick source to be at random location
x = randrange(P); y = randrange(P); z = randrange(P)
# Set velocity
c = 299792 # km/ns
# Generate simulated source
t_1 = math.sqrt( (x - x_1)**2 + (y - y_1)**2 + (z - z_1)**2 ) / c
t_2 = math.sqrt( (x - x_2)**2 + (y - y_2)**2 + (z - z_2)**2 ) / c
t_3 = math.sqrt( (x - x_3)**2 + (y - y_3)**2 + (z - z_3)**2 ) / c
# Normalize times to remove information about 'true' emission time
earliest = min(t_1, t_2, t_3)
t_1 = t_1 - earliest; t_2 = t2 - earliest; t_3 = t_3 = earliest

Related

Is there any way to choose how many features are selected in Binary Particle Swarm Optimization?

I implemented BPSO as a feature selection approach using the pyswarms library. I followed this tutorial.
Is there a way to limit the maximum number of features? If not, are there other particle swarm (or genetic/simulated annealing) python-implementations that have this functionality?
An easy way is to introduce a penalty for using any number of features. The in the following code a objective i defined
# Perform classification and store performance in P
classifier.fit(X_subset, y)
P = (classifier.predict(X_subset) == y).mean()
# Compute for the objective function
j = (alpha * (1.0 - P)
+ (1.0 - alpha) * (1 - (X_subset.shape[1] / total_features)))
return j
What you could do, is add a penalty if the number of features is about max_num_features, e.g.
features_count = np.count_nonzero(m)
features_overflow = np.clip( max_num_features - features_count, 0, 10)
feature_overflow_penalty = (features_overflow / 10)
and define a new objective with:
j = (alpha * (1.0 - P)
+ (1.0 - alpha) * (1 - (X_subset.shape[1] / total_features))) - feature_overflow_penalty
This is not tested, and there is work to do to find the right penalty. A alternative is to never suggest/try features above certain threshold.

Minimizing this error function, using NumPy

Background
I've been working for some time on attempting to solve the (notoriously painful) Time Difference of Arrival (TDoA) multi-lateration problem, in 3-dimensions and using 4 nodes. If you're unfamiliar with the problem, it is to determine the coordinates of some signal source (X,Y,Z), given the coordinates of n nodes, the time of arrival of the signal at each node, and the velocity of the signal v.
My solution is as follows:
For each node, we write (X-x_i)**2 + (Y-y_i)**2 + (Z-z_i)**2 = (v(t_i - T)**2
Where (x_i, y_i, z_i) are the coordinates of the ith node, and T is the time of emission.
We have now 4 equations in 4 unknowns. Four nodes are obviously insufficient. We could try to solve this system directly, however that seems next to impossible given the highly nonlinear nature of the problem (and, indeed, I've tried many direct techniques... and failed). Instead, we simplify this to a linear problem by considering all i/j possibilities, subtracting equation i from equation j. We obtain (n(n-1))/2 =6 equations of the form:
2*(x_j - x_i)*X + 2*(y_j - y_i)*Y + 2*(z_j - z_i)*Z + 2 * v**2 * (t_i - t_j) = v**2 ( t_i**2 - t_j**2) + (x_j**2 + y_j**2 + z_j**2) - (x_i**2 + y_i**2 + z_i**2)
Which look like Xv_1 + Y_v2 + Z_v3 + T_v4 = b. We try now to apply standard linear least squares, where the solution is the matrix vector x in A^T Ax = A^T b. Unfortunately, if you were to try feeding this into any standard linear least squares algorithm, it'll choke up. So, what do we do now?
...
The time of arrival of the signal at node i is given (of course) by:
sqrt( (X-x_i)**2 + (Y-y_i)**2 + (Z-z_i)**2 ) / v
This equation implies that the time of arrival, T, is 0. If we have that T = 0, we can drop the T column in matrix A and the problem is greatly simplified. Indeed, NumPy's linalg.lstsq() gives a surprisingly accurate & precise result.
...
So, what I do is normalize the input times by subtracting from each equation the earliest time. All I have to do then is determine the dt that I can add to each time such that the residual of summed squared error for the point found by linear least squares is minimized.
I define the error for some dt to be the squared difference between the arrival time for the point predicted by feeding the input times + dt to the least squares algorithm, minus the input time (normalized), summed over all 4 nodes.
for node, time in nodes, times:
error += ( (sqrt( (X-x_i)**2 + (Y-y_i)**2 + (Z-z_i)**2 ) / v) - time) ** 2
My problem:
I was able to do this somewhat satisfactorily by using brute-force. I started at dt = 0, and moved by some step up to some maximum # of iterations OR until some minimum RSS error is reached, and that was the dt I added to the normalized times to obtain a solution. The resulting solutions were very accurate and precise, but quite slow.
In practice, I'd like to be able to solve this in real time, and therefore a far faster solution will be needed. I began with the assumption that the error function (that is, dt vs error as defined above) would be highly nonlinear-- offhand, this made sense to me.
Since I don't have an actual, mathematical function, I can automatically rule out methods that require differentiation (e.g. Newton-Raphson). The error function will always be positive, so I can rule out bisection, etc. Instead, I try a simple approximation search. Unfortunately, that failed miserably. I then tried Tabu search, followed by a genetic algorithm, and several others. They all failed horribly.
So, I decided to do some investigating. As it turns out the plot of the error function vs dt looks a bit like a square root, only shifted right depending upon the distance from the nodes that the signal source is:
Where dt is on horizontal axis, error on vertical axis
And, in hindsight, of course it does!. I defined the error function to involve square roots so, at least to me, this seems reasonable.
What to do?
So, my issue now is, how do I determine the dt corresponding to the minimum of the error function?
My first (very crude) attempt was to get some points on the error graph (as above), fit it using numpy.polyfit, then feed the results to numpy.root. That root corresponds to the dt. Unfortunately, this failed, too. I tried fitting with various degrees, and also with various points, up to a ridiculous number of points such that I may as well just use brute-force.
How can I determine the dt corresponding to the minimum of this error function?
Since we're dealing with high velocities (radio signals), it's important that the results be precise and accurate, as minor variances in dt can throw off the resulting point.
I'm sure that there's some infinitely simpler approach buried in what I'm doing here however, ignoring everything else, how do I find dt?
My requirements:
Speed is of utmost importance
I have access only to pure Python and NumPy in the environment where this will be run
EDIT:
Here's my code. Admittedly, a bit messy. Here, I'm using the polyfit technique. It will "simulate" a source for you, and compare results:
from numpy import poly1d, linspace, set_printoptions, array, linalg, triu_indices, roots, polyfit
from dataclasses import dataclass
from random import randrange
import math
#dataclass
class Vertexer:
receivers: list
# Defaults
c = 299792
# Receivers:
# [x_1, y_1, z_1]
# [x_2, y_2, z_2]
# [x_3, y_3, z_3]
# Solved:
# [x, y, z]
def error(self, dt, times):
solved = self.linear([time + dt for time in times])
error = 0
for time, receiver in zip(times, self.receivers):
error += ((math.sqrt( (solved[0] - receiver[0])**2 +
(solved[1] - receiver[1])**2 +
(solved[2] - receiver[2])**2 ) / c ) - time)**2
return error
def linear(self, times):
X = array(self.receivers)
t = array(times)
x, y, z = X.T
i, j = triu_indices(len(x), 1)
A = 2 * (X[i] - X[j])
b = self.c**2 * (t[j]**2 - t[i]**2) + (X[i]**2).sum(1) - (X[j]**2).sum(1)
solved, residuals, rank, s = linalg.lstsq(A, b, rcond=None)
return(solved)
def find(self, times):
# Normalize times
times = [time - min(times) for time in times]
# Fit the error function
y = []
x = []
dt = 1E-10
for i in range(50000):
x.append(self.error(dt * i, times))
y.append(dt * i)
p = polyfit(array(x), array(y), 2)
r = roots(p)
return(self.linear([time + r for time in times]))
# SIMPLE CODE FOR SIMULATING A SIGNAL
# Pick nodes to be at random locations
x_1 = randrange(10); y_1 = randrange(10); z_1 = randrange(10)
x_2 = randrange(10); y_2 = randrange(10); z_2 = randrange(10)
x_3 = randrange(10); y_3 = randrange(10); z_3 = randrange(10)
x_4 = randrange(10); y_4 = randrange(10); z_4 = randrange(10)
# Pick source to be at random location
x = randrange(1000); y = randrange(1000); z = randrange(1000)
# Set velocity
c = 299792 # km/ns
# Generate simulated source
t_1 = math.sqrt( (x - x_1)**2 + (y - y_1)**2 + (z - z_1)**2 ) / c
t_2 = math.sqrt( (x - x_2)**2 + (y - y_2)**2 + (z - z_2)**2 ) / c
t_3 = math.sqrt( (x - x_3)**2 + (y - y_3)**2 + (z - z_3)**2 ) / c
t_4 = math.sqrt( (x - x_4)**2 + (y - y_4)**2 + (z - z_4)**2 ) / c
print('Actual:', x, y, z)
myVertexer = Vertexer([[x_1, y_1, z_1],[x_2, y_2, z_2],[x_3, y_3, z_3],[x_4, y_4, z_4]])
solution = myVertexer.find([t_1, t_2, t_3, t_4])
print(solution)
It seems like the Bancroft method applies to this problem? Here's a pure NumPy implementation.
# Implementation of the Bancroft method, following
# https://gssc.esa.int/navipedia/index.php/Bancroft_Method
M = np.diag([1, 1, 1, -1])
def lorentz_inner(v, w):
return np.sum(v * (w # M), axis=-1)
B = np.array(
[
[x_1, y_1, z_1, c * t_1],
[x_2, y_2, z_2, c * t_2],
[x_3, y_3, z_3, c * t_3],
[x_4, y_4, z_4, c * t_4],
]
)
one = np.ones(4)
a = 0.5 * lorentz_inner(B, B)
B_inv_one = np.linalg.solve(B, one)
B_inv_a = np.linalg.solve(B, a)
for Lambda in np.roots(
[
lorentz_inner(B_inv_one, B_inv_one),
2 * (lorentz_inner(B_inv_one, B_inv_a) - 1),
lorentz_inner(B_inv_a, B_inv_a),
]
):
x, y, z, c_t = M # np.linalg.solve(B, Lambda * one + a)
print("Candidate:", x, y, z, c_t / c)
My answer might have mistakes (glaring) as I had not heard the TDOA term before this afternoon. Please double check if the method is right.
I could not find solution to your original problem of finding dt corresponding to the minimum error. My answer also deviates from the requirement that other than numpy no third party library had to be used (I used Sympy and largely used the code from here). However I am still posting this thinking that somebody someday might find it useful if all one is interested in ... is to find X,Y,Z of the source emitter. This method also does not take into account real-life situations where white noise or errors might be present or curvature of the earth and other complications.
Your initial test conditions are as below.
from random import randrange
import math
# SIMPLE CODE FOR SIMULATING A SIGNAL
# Pick nodes to be at random locations
x_1 = randrange(10); y_1 = randrange(10); z_1 = randrange(10)
x_2 = randrange(10); y_2 = randrange(10); z_2 = randrange(10)
x_3 = randrange(10); y_3 = randrange(10); z_3 = randrange(10)
x_4 = randrange(10); y_4 = randrange(10); z_4 = randrange(10)
# Pick source to be at random location
x = randrange(1000); y = randrange(1000); z = randrange(1000)
# Set velocity
c = 299792 # km/ns
# Generate simulated source
t_1 = math.sqrt( (x - x_1)**2 + (y - y_1)**2 + (z - z_1)**2 ) / c
t_2 = math.sqrt( (x - x_2)**2 + (y - y_2)**2 + (z - z_2)**2 ) / c
t_3 = math.sqrt( (x - x_3)**2 + (y - y_3)**2 + (z - z_3)**2 ) / c
t_4 = math.sqrt( (x - x_4)**2 + (y - y_4)**2 + (z - z_4)**2 ) / c
print('Actual:', x, y, z)
My solution is as below.
import sympy as sym
X,Y,Z = sym.symbols('X,Y,Z', real=True)
f = sym.Eq((x_1 - X)**2 +(y_1 - Y)**2 + (z_1 - Z)**2 , (c*t_1)**2)
g = sym.Eq((x_2 - X)**2 +(y_2 - Y)**2 + (z_2 - Z)**2 , (c*t_2)**2)
h = sym.Eq((x_3 - X)**2 +(y_3 - Y)**2 + (z_3 - Z)**2 , (c*t_3)**2)
i = sym.Eq((x_4 - X)**2 +(y_4 - Y)**2 + (z_4 - Z)**2 , (c*t_4)**2)
print("Solved coordinates are ", sym.solve([f,g,h,i],X,Y,Z))
print statement from your initial condition gave.
Actual: 111 553 110
and the solution that almost instantly came out was
Solved coordinates are [(111.000000000000, 553.000000000000, 110.000000000000)]
Sorry again if something is totally amiss.

Intersections for 3D lines

I have written a function that should calculate all intersectionpoints between a line and all lines that are given to it, and this in 3D. I have these lines parametrized because that seemed to be the easiest way to work with things. The problem is that when I input the variables "t1" and "t2" back into the functions of the lines, there seems to be an inaccuracy that is too big to be acceptable for the thing that I need.
t1 is the parameter for the line of which you would like to know all intersections, so it's written in this form:
x = xo + t1 * dx
y = yo + t1 * dy
z = zo + t1 * dz
Where [xo, yo, zo] represent a point on the line that I call the "origin" and [dx, dy, dz] represents the direction of that line. The other lines are given in the same form and the function I wrote basically solves the following equation:
xo1 + t1 * dx1 = xo2 + t2 * dx2
yo1 + t1 * dy1 = yo2 + t2 * dy2
zo1 + t1 * dz1 = zo2 + t2 * dz2
Where everything is given except for t1 and t2, that's what I'm looking for here. However, I don't think actually finding t1 and t2 is the problem, I do have a solution that gives me some kind of result. As mentioned earlier, the problem is really that when I feed t1 and t2 back into these formulas to get the actual intersectionpoints, that they differ slightly from eachother. I'm talking about differences that are mostly 0.005-0.05 away from eachother in euclidean distance. But in extreme cases it could be up to 0.5 inaccuracy. I am aware that most lines in 3D do not intersect and therefore do not have a solution to these equations, but for the tests that I'm doing right now, I am 100% sure that all of the lines are within the same plane, but some might be parallel to each other. However, these inaccuracies occur for all lines, and I'm really just looking for a solution that gets it accuratly when they do intersect.
Here's the code I have for this:
def lineIntersection(self, lines):
origins = np.zeros((len(lines), 3), dtype=np.float32)
directions = np.zeros((len(lines), 3), dtype=np.float32)
for i in range(0, len(lines)):
origins[i] = lines[i].origin
directions[i] = lines[i].direction
ox = origins[:, 0]
oy = origins[:, 1]
dx = self.origin[0]
dy = self.origin[1]
x1 = directions[:, 0]
y1 = directions[:, 1]
x2 = self.direction[0]
y2 = self.direction[1]
t2 = np.divide((np.subtract(np.add(oy, np.multiply(np.divide(np.subtract(dx, ox), x1), y1)), dy)), np.subtract(y2, np.multiply(np.divide(x2, x1), y1)))
t1 = np.divide((np.add(dx, np.subtract(np.multiply(t2, x2), ox))), x1)
testx1 = np.add(ox, np.multiply(t1, x1))
testx2 = np.add(dx, np.multiply(t2, x2))
testy1 = np.add(oy, np.multiply(t1, y1))
testy2 = np.add(dy, np.multiply(t2, y2))
testz1 = np.add(origins[:, 2], np.multiply(t1, directions[:, 2]))
testz2 = np.add(self.origin[2], np.multiply(t2, self.direction[2]))
arr1 = np.array([testx1, testy1, testz1]).T
arr2 = np.array([testx2, testy2, testz2]).T
diff = np.linalg.norm(np.subtract(arr1, arr2), axis=1)
narr = arr1[diff < 0.05] #Filtering out points that aren't actually intersecting
nt2 = t2[diff < 0.05]
return narr, nt2
This function is located in the "Line" class and has an origin and direction as explained earlier. The input it takes, is an array of objects from the "Line" class.
So to be clear, I'm asking why this doesn't seem to work as precise as I want it to be and how I can fix it. Or, if there are alternatives to calculating intersectionpoints that are really accurate, I would love to hear about it.
Inaccuracy is common case for intersection of lines forming small angle.
I did not checked your algo correctness, but seems you just solve system of three equation with linalg solver.
In case of almost parallel lines intermediate values (determinant) might be small causing significant errors.
Have you tried more robust numeric algorithms like SVD?
But perhaps you really don't need them:
Note that when you are sure that all lines lie in the same plane, you can exploit 2D algorithm - just check what component of dx,dy,dz have the smallest magnitude (check for some distinct lines) and ignore corresponding component - it is similar to projecting of lines onto OXY or OXZ or OYZ plane. 2D code should be much simpler.
For true 3D case there is well-tested vector approach intended to find distance (the shortest line segment) between two skew lines - it is just zero length for intersecting ones. Example here.
Note that det (determinant) magnitude is evaluated to check for parallel (and almost parallel) lines too.

FiPy - Domain stretches / Frame growth

I'm trying to solve a simple diffusion equation (dT/dt = K d2T/dx2 ) on a domain whose depth ( h(t) ) changes in time. The resulting equation is therefore:
dT/dt = K/h^2 d2T/dx2 + z/h dh/dt dT/dz
where z is now a fixed 0->1 domain.
The new term is frame advection and I'm trying to include it as such but I'm struggling with the spatially dependent coefficient.
When I include it outside the convection term:
mesh.cellCenters[0]*PowerLawConvectionTerm(...)
I get this error:
TermMultiplyError: Must multiply terms by int or float
But if I reorganise the equation so the spatial dependence is inside the convection term:
PowerLawConvectionTerm(coeff=(mesh.cellCenters[0]**2,),...)
I get a different error when solving the equation:
AssertionError: assert( len(id1) == len(id2) == len(vector) )
What is the correct way to include these terms? Is there a silly mistake I'm making somewhere?
The best way to solve this might be to split the last term into two parts so that the equation in FiPy is written
fipy.TransientTerm() == fipy.DiffusionTerm(K / h**2) \
+ fipy.ConvectionTerm(z * z_hat * h_t / h) \
- h_t / h * T
In FiPy there can't be multipliers outside of the term's derivative so an extra source term is required. Here it is assumed that
K = 1. ## some constant
h = fipy.Variable(...) ## variable that is continuously updated
h_old = fipy.Variable(...) ## variable that is continuously updated
h_t = (h - h_old) / dt ## variable dependent on h and h_old
T = fipy.CellVariable(...)
z_hat = [0, 1] ## vector required for convection term coefficient
T is the variable being solved for, h and h_old are explicilty updated at every sweep or time step using setValue based on some formula. Additionally, the last term can be split into an explicit and an implicit source
- h_t / h * T -> - fipy.ImplicitSourceTerm(1 / dt) + h_old / h / dt * T
depending on how the h_t is evaluated. The implicit source should make the solution very stable.

Euler method (explicit and implicit)

I'd like to implement Euler's method (the explicit and the implicit one)
(https://en.wikipedia.org/wiki/Euler_method) for the following model:
x(t)' = q(x_M -x(t))x(t)
x(0) = x_0
where q, x_M and x_0 are real numbers.
I know already the (theoretical) implementation of the method. But I couldn't figure out where I can insert / change the model.
Could anybody help?
EDIT: You were right. I didn't understand correctly the method. Now, after a few hours, I think that I really got it! With the explicit method, I'm pretty sure (nevertheless: could anybody please have a look at my code? )
With the implicit implementation, I'm not very sure if it's correct. Could please anyone have a look at the implementation of the implicit method and give me a feedback what's correct / not good?
def explizit_euler():
''' x(t)' = q(xM -x(t))x(t)
x(0) = x0'''
q = 2.
xM = 2
x0 = 0.5
T = 5
dt = 0.01
N = T / dt
x = x0
t = 0.
for i in range (0 , int(N)):
t = t + dt
x = x + dt * (q * (xM - x) * x)
print '%6.3f %6.3f' % (t, x)
def implizit_euler():
''' x(t)' = q(xM -x(t))x(t)
x(0) = x0'''
q = 2.
xM = 2
x0 = 0.5
T = 5
dt = 0.01
N = T / dt
x = x0
t = 0.
for i in range (0 , int(N)):
t = t + dt
x = (1.0 / (1.0 - q *(xM + x) * x))
print '%6.3f %6.3f' % (t, x)
Pre-emptive note: Although the general idea should be correct, I did all the algebra in place in the editor box so there might be mistakes there. Please, check it yourself before using for anything really important.
I'm not sure how you come to the "implicit" formula
x = (1.0 / (1.0 - q *(xM + x) * x))
but this is wrong and you can check it by comparing your "explicit" and "implicit" results: they should slightly diverge but with this formula they will diverge drastically.
To understand the implicit Euler method, you should first get the idea behind the explicit one. And the idea is really simple and is explained at the Derivation section in the wiki: since derivative y'(x) is a limit of (y(x+h) - y(x))/h, you can approximate y(x+h) as y(x) + h*y'(x) for small h, assuming our original differential equation is
y'(x) = F(x, y(x))
Note that the reason this is only an approximation rather than exact value is that even over small range [x, x+h] the derivative y'(x) changes slightly. It means that if you want to get a better approximation of y(x+h), you need a better approximation of "average" derivative y'(x) over the range [x, x+h]. Let's call that approximation just y'. One idea of such improvement is to find both y' and y(x+h) at the same time by saying that we want to find such y' and y(x+h) that y' would be actually y'(x+h) (i.e. the derivative at the end). This results in the following system of equations:
y'(x+h) = F(x+h, y(x+h))
y(x+h) = y(x) + h*y'(x+h)
which is equivalent to a single "implicit" equation:
y(x+h) - y(x) = h * F(x+h, y(x+h))
It is called "implicit" because here the target y(x+h) is also a part of F. And note that quite similar equation is mentioned in the Modifications and extensions section of the wiki article.
So now going to your case that equation becomes
x(t+dt) - x(t) = dt*q*(xM -x(t+dt))*x(t+dt)
or equivalently
dt*q*x(t+dt)^2 + (1 - dt*q*xM)*x(t+dt) - x(t) = 0
This is a quadratic equation with two solutions:
x(t+dt) = [(dt*q*xM - 1) ± sqrt((dt*q*xM - 1)^2 + 4*dt*q*x(t))]/(2*dt*q)
Obviously we want the solution that is "close" to the x(t) which is the + solution. So the code should be something like:
b = (q * xM * dt - 1)
x(t+h) = (b + (b ** 2 + 4 * q * x(t) * dt) ** 0.5) / 2 / q / dt
(editor note:) Applying the binomial complement, this formula has the numerically more stable form for small dt, where then b < 0,
x(t+h) = (2 * x(t)) / ((b ** 2 + 4 * q * x(t) * dt) ** 0.5 - b)

Categories

Resources