简体   繁体   中英

Finding the location of a given arc distance along a parameterized curve in python

I have a parameterized 2D curve: (x,y) = f(t)

The function f is arbitrary but differentiable and hence I can figure out the differential arc length ds along the curve at any point using standard formulas. I can also get the total arc length S(t) from the beginning to any point on the curve by integrating the differential arc length formula numerically. I can control the accuracy of the calculation.

I want to find locate the point (x,y) that has a total arc length S = D from the beginning of the curve. Even better if the implementation were in python. I will be doing this many times, and it is part of a computational application where I need tight control of accuracy and some confidence of convergence.

I don't know if root finding is the best approach, but my question is the equivalent of a root finding problem for g(t) = S(t) - D where the function g(t) is not evaluated exactly because S(t) isn't. Inexact function evaluation messes not only with accuracy but also the monotonicity of g(t). I tried doing tight numerical integration from the outset but it takes forever. I'm pretty sure to converge to my required tolerance the root finding algorithm would have to lazily control the integration accuracy as it proceeded, demanding sloppy evaluation at the outset and increasing accuracy as the root algorithm converges.

Is there such a thing readily available? Is there an alternative clever way to do it?

Appreciate the help

Can you post some code, and tell us what's wrong with it?

Here is my version that calculate the t where S(t) == D:

from scipy.integrate import quad
from scipy.optimize import fsolve
from math import cos, sin, sqrt, pi

def circle_diff(t):
    dx = -sin(t)
    dy = cos(t)
    return sqrt(dx*dx+dy*dy)

def sin_diff(t):
    dx = 1
    dy = cos(t)
    return sqrt(dx*dx+dy*dy)

def curve_length(t0, S, length):
    return quad(S, 0, t0)[0] - length

def solve_t(curve_diff, length):    
    return fsolve(curve_length, 0.0, (curve_diff, length))[0]

print solve_t(circle_diff, 2*pi)
print solve_t(sin_diff, 7.640395578)

OK, @HYRY, here is a code snippet largely based on yours. You gave me the hint I needed to succeed: use "quad" instead of "quadrature". So I will at least vote for your answer, but I want to add to the story.

First, your code ran fast, but about five places short of the precision I was after. I added quadtol and opttol to your example to try to illustrate the interplay of the quadrature and root finding accuracy. I also added a loop based on default high tolerances to expose the speed differences.

The sin example is much more sensitive than the circle on accuracy. I also added a paremerized curve whose arc length is given by a hypergeometric function and the the "brentq" option commented out because fsolve fails on this example and in any even brentq is equal or better on this task.

"quadrature" is slow, but exhibits the expected behavior: the root finding speed, accuracy and success change with quadrature tolerance.

In contrast, "quad" seems to ignore the requested tolerance and produce a more accurate answer always. This unrequested accuracy would be annoying or invite explanation, except it also works so fast on the examples that I am not sure my question is interesting any more. Thanks!

from scipy.integrate import quad, quadrature
from scipy.optimize import fsolve, brentq 
from math import cos, sin, sqrt, pi, pow

def circle_diff(t): 
    dx = -sin(t) 
    dy = cos(t) 
    return sqrt(dx*dx+dy*dy) 

def sin_diff(t): 
    dx = 1 
    dy = cos(t) 
    return sqrt(dx*dx+dy*dy) 

def hypergeom_diff(t):
    """ y = t^5 x = t^3 """
    dx = 3*t*t
    dy = 5*pow(t,4)
    return sqrt(dx*dx+dy*dy)

def curve_length(t0, S, length,quadtol): 
    integral = quad(S, 0, t0,epsabs=quadtol,epsrel=quadtol)
    #integral = quadrature(S, 0, t0,tol=quadtol,rtol=quadtol, vec_func = False)
    return integral[0] - length 

def solve_t(curve_diff, length,opttol=1.e-15,quadtol=1e-10):
    return fsolve(curve_length, 0.0, (curve_diff, length,quadtol), xtol = opttol)[0] 
    #return brentq(curve_length, 0.0, 3.2*pi,(curve_diff, length, quadtol), rtol = opttol)

for i in range(1000):
    y = solve_t(circle_diff, 2*pi)

print 2*pi
print solve_t(circle_diff, 2*pi)
print solve_t(sin_diff, 7.640395578)
print solve_t(circle_diff, 2*pi,opttol=1e-5,quadtol=1e-3)
print solve_t(sin_diff, 7.640395578,opttol = 1e-12,quadtol=1e-6)
print "hypergeom"
print solve_t(hypergeom_diff, 2.0,opttol = 1e-12,quadtol=1e-12)
print solve_t(hypergeom_diff, 2.0,opttol = 1e-12,quadtol=1e-6)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM