def _CalculateLeastUpperBoundInoperativeInterval(x0, x1, v0, v1, vm, am): # All input must already be of mp.mpf type d = x1 - x0 temp1 = Prod([ number('2'), Neg(Sqr(am)), Sub(Prod([number('2'), am, d]), Add(Sqr(v0), Sqr(v1))) ]) if temp1 < zero: T0 = number('-1') T1 = number('-1') else: term1 = mp.fdiv(Add(v0, v1), am) term2 = mp.fdiv(mp.sqrt(temp1), Sqr(am)) T0 = Add(term1, term2) T1 = Sub(term1, term2) temp2 = Prod([ number('2'), Sqr(am), Add(Prod([number('2'), am, d]), Add(Sqr(v0), Sqr(v1))) ]) if temp2 < zero: T2 = number('-1') T3 = number('-1') else: term1 = Neg(mp.fdiv(Add(v0, v1), am)) term2 = mp.fdiv(mp.sqrt(temp2), Sqr(am)) T2 = Add(term1, term2) T3 = Sub(term1, term2) newDuration = max(max(T0, T1), max(T2, T3)) if newDuration > zero: dStraight = Prod([pointfive, Add(v0, v1), newDuration]) if Sub(d, dStraight) > 0: amNew = am vmNew = vm else: amNew = -am vmNew = -vm # import IPython; IPython.embed() vp = Mul(pointfive, Sum([Mul(newDuration, amNew), v0, v1])) # the peak velocity if (Abs(vp) > vm): dExcess = mp.fdiv(Sqr(Sub(vp, vmNew)), am) assert (dExcess > 0) deltaTime = mp.fdiv(dExcess, vm) newDuration = Add(newDuration, deltaTime) newDuration = Mul(newDuration, number('1.01')) # add 1% safety bound return newDuration else: log.debug('Unable to calculate the least upper bound: T0 = {0}; T1 = {1}; T2 = {2}; T3 = {3}'.\ format(mp.nstr(T0, n=_prec), mp.nstr(T1, n=_prec), mp.nstr(T2, n=_prec), mp.nstr(T3, n=_prec))) return number('-1')
def _SolveForT0(A, B, t, tInterval): """There are four solutions to the equation (including complex ones). Here we use x instead of t0 for convenience. """ """ SolveQuartic(2*A, -4*A*T + 2*B, 3*A*T**2 - 3*B*T, -A*T**3 + 3*B*T**2, -B*T**3) """ if (Abs(A) < epsilon): def f(x): return Mul(number('2'), B) * x * x * x - Prod( [number('3'), B, t]) * x * x + Prod( [number('3'), B, t, t]) * x - Mul(B, mp.power(t, 3)) sols = [mp.findroot(f, x0=0.5 * t)] else: sols = SolveQuartic( Add(A, A), Add(Prod([number('-4'), A, t]), Mul(number('2'), B)), Sub(Prod([number('3'), A, t, t]), Prod([number('3'), B, t])), Sub(Prod([number('3'), B, t, t]), Mul(A, mp.power(t, 3))), Neg(Mul(B, mp.power(t, 3)))) realSols = [ sol for sol in sols if type(sol) is mp.mpf and sol in tInterval ] if len(realSols) > 1: # I think this should not happen. We should either have one or no solution. raise NotImplementedError elif len(realSols) == 0: return None else: return realSols[0]
def _BrakeAccel(x, v, xbound): coeff0 = Mul(number('2'), Sub(xbound, x)) coeff1 = Sqr(v) [result, a] = _SolveAXMB(coeff0, Neg(coeff1), epsilon, -inf, inf) if not result: log.debug("Cannot solve for braking acceleration from the equation {0}*a + {1} = 0".\ format(mp.nstr(coeff0, n=_prec), mp.nstr(coeff1, n=_prec))) return 0 return a
def _Interpolate1DNoVelocityLimit(x0, x1, v0, v1, am): # Check types if type(x0) is not mp.mpf: x0 = mp.mpf("{:.15e}".format(x0)) if type(x1) is not mp.mpf: x1 = mp.mpf("{:.15e}".format(x1)) if type(v0) is not mp.mpf: v0 = mp.mpf("{:.15e}".format(v0)) if type(v1) is not mp.mpf: v1 = mp.mpf("{:.15e}".format(v1)) if type(am) is not mp.mpf: am = mp.mpf("{:.15e}".format(am)) # Check inputs assert (am > zero) # Check for an appropriate acceleration direction of the first ramp d = Sub(x1, x0) dv = Sub(v1, v0) difVSqr = Sub(v1**2, v0**2) if Abs(dv) < epsilon: if Abs(d) < epsilon: # Stationary ramp ramp0 = Ramp(zero, zero, zero, x0) return ParabolicCurve([ramp0]) else: dStraight = zero else: dStraight = mp.fdiv(difVSqr, Prod([2, mp.sign(dv), am])) if IsEqual(d, dStraight): # With the given distance, v0 and v1 can be directly connected using max/min # acceleration. Here the resulting profile has only one ramp. a0 = mp.sign(dv) * am ramp0 = Ramp(v0, a0, mp.fdiv(dv, a0), x0) return ParabolicCurve([ramp0]) sumVSqr = Add(v0**2, v1**2) sigma = mp.sign(Sub(d, dStraight)) a0 = sigma * am # acceleration of the first ramp vp = sigma * mp.sqrt(Add(Mul(pointfive, sumVSqr), Mul(a0, d))) t0 = mp.fdiv(Sub(vp, v0), a0) t1 = mp.fdiv(Sub(vp, v1), a0) ramp0 = Ramp(v0, a0, t0, x0) assert (IsEqual(ramp0.v1, vp)) # check soundness ramp1 = Ramp(vp, Neg(a0), t1) curve = ParabolicCurve([ramp0, ramp1]) assert (IsEqual(curve.d, d)) # check soundness return curve
def Interpolate1DFixedDuration(x0, x1, v0, v1, newDuration, vm, am): x0 = ConvertFloatToMPF(x0) x1 = ConvertFloatToMPF(x1) v0 = ConvertFloatToMPF(v0) v1 = ConvertFloatToMPF(v1) vm = ConvertFloatToMPF(vm) am = ConvertFloatToMPF(am) newDuration = ConvertFloatToMPF(newDuration) log.debug("\nx0 = {0}; x1 = {1}; v0 = {2}; v1 = {3}; vm = {4}; am = {5}; newDuration = {6}".\ format(mp.nstr(x0, n=_prec), mp.nstr(x1, n=_prec), mp.nstr(v0, n=_prec), mp.nstr(v1, n=_prec), mp.nstr(vm, n=_prec), mp.nstr(am, n=_prec), mp.nstr(newDuration, n=_prec))) # Check inputs assert (vm > zero) assert (am > zero) if (newDuration < -epsilon): return ParabolicCurve() if (newDuration <= epsilon): # Check if this is a stationary trajectory if (FuzzyEquals(x0, x1, epsilon) and FuzzyEquals(v0, v1, epsilon)): ramp0 = Ramp(v0, 0, 0, x0) newCurve = ParabolicCurve(ramp0) return newCurve else: # newDuration is too short to any movement to be made return ParabolicCurve() d = Sub(x1, x0) # First assume no velocity bound -> re-interpolated trajectory will have only two ramps. # Solve for a0 and a1 (the acceleration of the first and the last ramps). # a0 = A + B/t0 # a1 = A + B/(t - t0) # where t is the (new) total duration, t0 is the (new) duration of the first ramp, and # A = (v1 - v0)/t # B = (2d/t) - (v0 + v1). newDurInverse = mp.fdiv(one, newDuration) A = Mul(Sub(v1, v0), newDurInverse) B = Sub(Prod([mp.mpf('2'), d, newDurInverse]), Add(v0, v1)) interval0 = iv.mpf([zero, newDuration]) # initial interval for t0 # Now consider the interval(s) computed from a0's constraints sum1 = Neg(Add(am, A)) sum2 = Sub(am, A) C = mp.fdiv(B, sum1) D = mp.fdiv(B, sum2) log.debug("\nA = {0}; \nB = {1}; \nC = {2}; \nD = {3}; \nsum1 = {4}; \nsum2 = {5};".\ format(mp.nstr(A, n=_prec), mp.nstr(B, n=_prec), mp.nstr(C, n=_prec), mp.nstr(D, n=_prec), mp.nstr(sum1, n=_prec), mp.nstr(sum2, n=_prec))) if (sum1 > zero): # This implied that the duration is too short log.debug("the given duration ({0}) is too short.".format(newDuration)) return ParabolicCurve() if (sum2 < zero): # This implied that the duration is too short log.debug("the given duration ({0}) is too short.".format(newDuration)) return ParabolicCurve() if IsEqual(sum1, zero): raise NotImplementedError # not yet considered elif sum1 > epsilon: log.debug("sum1 > 0. This implies that newDuration is too short.") return ParabolicCurve() else: interval1 = iv.mpf([C, inf]) if IsEqual(sum2, zero): raise NotImplementedError # not yet considered elif sum2 > epsilon: interval2 = iv.mpf([D, inf]) else: log.debug("sum2 < 0. This implies that newDuration is too short.") return ParabolicCurve() if Sub(interval2.a, interval1.b) > epsilon or Sub(interval1.a, interval2.b) > epsilon: # interval1 and interval2 do not intersect each other return ParabolicCurve() # interval3 = interval1 \cap interval2 : valid interval for t0 computed from a0's constraints interval3 = iv.mpf( [max(interval1.a, interval2.a), min(interval1.b, interval2.b)]) # Now consider the interval(s) computed from a1's constraints if IsEqual(sum1, zero): raise NotImplementedError # not yet considered elif sum1 > epsilon: log.debug("sum1 > 0. This implies that newDuration is too short.") return ParabolicCurve() else: interval4 = iv.mpf([Neg(inf), Add(C, newDuration)]) if IsEqual(sum2, zero): raise NotImplementedError # not yet considered elif sum2 > epsilon: interval5 = iv.mpf([Neg(inf), Add(D, newDuration)]) else: log.debug("sum2 < 0. This implies that newDuration is too short.") return ParabolicCurve() if Sub(interval5.a, interval4.b) > epsilon or Sub(interval4.a, interval5.b) > epsilon: log.debug("interval4 and interval5 do not intersect each other") return ParabolicCurve() # interval6 = interval4 \cap interval5 : valid interval for t0 computed from a1's constraints interval6 = iv.mpf( [max(interval4.a, interval5.a), min(interval4.b, interval5.b)]) # import IPython; IPython.embed() if Sub(interval3.a, interval6.b) > epsilon or Sub(interval6.a, interval3.b) > epsilon: log.debug("interval3 and interval6 do not intersect each other") return ParabolicCurve() # interval7 = interval3 \cap interval6 interval7 = iv.mpf( [max(interval3.a, interval6.a), min(interval3.b, interval6.b)]) if Sub(interval0.a, interval7.b) > epsilon or Sub(interval7.a, interval0.b) > epsilon: log.debug("interval0 and interval7 do not intersect each other") return ParabolicCurve() # interval8 = interval0 \cap interval7 : valid interval of t0 when considering all constraints (from a0 and a1) interval8 = iv.mpf( [max(interval0.a, interval7.a), min(interval0.b, interval7.b)]) # import IPython; IPython.embed() # We choose the value t0 (the duration of the first ramp) by selecting the mid point of the # valid interval of t0. t0 = _SolveForT0(A, B, newDuration, interval8) if t0 is None: # The fancy procedure fails. Now consider no optimization whatsoever. # TODO: Figure out why solving fails. t0 = mp.convert(interval8.mid) # select the midpoint # return ParabolicCurve() t1 = Sub(newDuration, t0) a0 = Add(A, Mul(mp.fdiv(one, t0), B)) if (Abs(t1) < epsilon): a1 = zero else: a1 = Add(A, Mul(mp.fdiv(one, Neg(t1)), B)) assert (Sub(Abs(a0), am) < epsilon ) # check if a0 is really below the bound assert (Sub(Abs(a1), am) < epsilon ) # check if a1 is really below the bound # import IPython; IPython.embed() # Check if the velocity bound is violated vp = Add(v0, Mul(a0, t0)) if Abs(vp) > vm: vmnew = Mul(mp.sign(vp), vm) D2 = Prod([ pointfive, Sqr(Sub(vp, vmnew)), Sub(mp.fdiv(one, a0), mp.fdiv(one, a1)) ]) # print "D2", # mp.nprint(D2, n=_prec) # print "vmnew", # mp.nprint(vmnew, n=_prec) A2 = Sqr(Sub(vmnew, v0)) B2 = Neg(Sqr(Sub(vmnew, v1))) t0trimmed = mp.fdiv(Sub(vmnew, v0), a0) t1trimmed = mp.fdiv(Sub(v1, vmnew), a1) C2 = Sum([ Mul(t0trimmed, Sub(vmnew, v0)), Mul(t1trimmed, Sub(vmnew, v1)), Mul(mp.mpf('-2'), D2) ]) log.debug("\nA2 = {0}; \nB2 = {1}; \nC2 = {2}; \nD2 = {3};".format( mp.nstr(A2, n=_prec), mp.nstr(B2, n=_prec), mp.nstr(C2, n=_prec), mp.nstr(D2, n=_prec))) temp = Prod([A2, B2, B2]) initguess = mp.sign(temp) * (Abs(temp)**(1. / 3.)) root = mp.findroot(lambda x: Sub(Prod([x, x, x]), temp), x0=initguess) # import IPython; IPython.embed() log.debug("root = {0}".format(mp.nstr(root, n=_prec))) a0new = mp.fdiv(Add(A2, root), C2) if (Abs(a0new) > Add(am, epsilon)): if FuzzyZero(Sub(Mul(C2, a0new), A2), epsilon): # The computed a0new is exceeding the bound and its corresponding a1new is # zero. Therefore, there is no other way to fix this. This is probably because the # given newDuration is less than the minimum duration (x0, x1, v0, v1, vm, am) can # get. log.debug( "abs(a0new) > am and a1new = 0; Cannot fix this case. This happens probably because the given newDuration is too short." ) return ParabolicCurve() a0new = Mul(mp.sign(a0new), am) if (Abs(a0new) < epsilon): a1new = mp.fdiv(B2, C2) if (Abs(a1new) > Add(am, epsilon)): # Similar to the case above log.debug( "a0new = 0 and abs(a1new) > am; Cannot fix this case. This happens probably because the given newDuration is too short." ) return ParabolicCurve() else: if FuzzyZero(Sub(Mul(C2, a0new), A2), epsilon): # import IPython; IPython.embed() a1new = 0 else: a1new = Mul(mp.fdiv(B2, C2), Add(one, mp.fdiv(A2, Sub(Mul(C2, a0new), A2)))) if (Abs(a1new) > Add(am, epsilon)): a1new = Mul(mp.sign(a1new), am) a0new = Mul(mp.fdiv(A2, C2), Add(one, mp.fdiv(B2, Sub(Mul(C2, a1new), B2)))) if (Abs(a0new) > Add(am, epsilon)) or (Abs(a1new) > Add(am, epsilon)): log.warn("Cannot fix acceleration bounds violation") return ParabolicCurve() log.debug( "\na0 = {0}; \na0new = {1}; \na1 = {2}; \na1new = {3};".format( mp.nstr(a0, n=_prec), mp.nstr(a0new, n=_prec), mp.nstr(a1, n=_prec), mp.nstr(a1new, n=_prec))) if (Abs(a0new) < epsilon) and (Abs(a1new) < epsilon): log.warn("Both accelerations are zero. Should we allow this case?") return ParabolicCurve() if (Abs(a0new) < epsilon): # This is likely because v0 is at the velocity bound t1new = mp.fdiv(Sub(v1, vmnew), a1new) assert (t1new > 0) ramp2 = Ramp(v0, a1new, t1new) t0new = Sub(newDuration, t1new) assert (t0new > 0) ramp1 = Ramp(v0, zero, t0new, x0) newCurve = ParabolicCurve([ramp1, ramp2]) return newCurve elif (Abs(a1new) < epsilon): t0new = mp.fdiv(Sub(vmnew, v0), a0new) assert (t0new > 0) ramp1 = Ramp(v0, a0new, t0new, x0) t1new = Sub(newDuration, t0new) assert (t1new > 0) ramp2 = Ramp(ramp1.v1, zero, t1new) newCurve = ParabolicCurve([ramp1, ramp2]) return newCurve else: # No problem with those new accelerations # import IPython; IPython.embed() t0new = mp.fdiv(Sub(vmnew, v0), a0new) if (t0new < 0): log.debug( "t0new < 0. The given newDuration not achievable with the given bounds" ) return ParabolicCurve() t1new = mp.fdiv(Sub(v1, vmnew), a1new) if (t1new < 0): log.debug( "t1new < 0. The given newDuration not achievable with the given bounds" ) return ParabolicCurve() if (Add(t0new, t1new) > newDuration): # Final fix. Since we give more weight to acceleration bounds, we make the velocity # bound saturated. Therefore, we set vp to vmnew. # import IPython; IPython.embed() if FuzzyZero(A, epsilon): log.warn( "(final fix) A is zero. Don't know how to fix this case" ) return ParabolicCurve() t0new = mp.fdiv(Sub(Sub(vmnew, v0), B), A) if (t0new < 0): log.debug("(final fix) t0new is negative") return ParabolicCurve() t1new = Sub(newDuration, t0new) a0new = Add(A, Mul(mp.fdiv(one, t0new), B)) a1new = Add(A, Mul(mp.fdiv(one, Neg(t1new)), B)) ramp1 = Ramp(v0, a0new, t0new, x0) ramp2 = Ramp(ramp1.v1, a1new, t1new) newCurve = ParabolicCurve([ramp1, ramp2]) else: ramp1 = Ramp(v0, a0new, t0new, x0) ramp3 = Ramp(ramp1.v1, a1new, t1new) ramp2 = Ramp(ramp1.v1, zero, Sub(newDuration, Add(t0new, t1new))) newCurve = ParabolicCurve([ramp1, ramp2, ramp3]) # import IPython; IPython.embed() return newCurve else: ramp1 = Ramp(v0, a0, t0, x0) ramp2 = Ramp(ramp1.v1, a1, t1) newCurve = ParabolicCurve([ramp1, ramp2]) return newCurve
def SolveQuartic(a, b, c, d, e): """ SolveQuartic solves a quartic (fouth order) equation of the form ax^4 + bx^3 + cx^2 + dx + e = 0. For the detail of formulae presented here, see https://en.wikipedia.org/wiki/Quartic_function """ # Check types if type(a) is not mp.mpf: a = mp.mpf("{:.15e}".format(a)) if type(b) is not mp.mpf: b = mp.mpf("{:.15e}".format(b)) if type(c) is not mp.mpf: c = mp.mpf("{:.15e}".format(c)) if type(d) is not mp.mpf: d = mp.mpf("{:.15e}".format(d)) if type(e) is not mp.mpf: e = mp.mpf("{:.15e}".format(e)) """ # Working code (more readable but probably less precise) p = (8*a*c - 3*b*b)/(8*a*a) q = (b**3 - 4*a*b*c + 8*a*a*d)/(8*a*a*a) delta0 = c*c - 3*b*d + 12*a*e delta1 = 2*(c**3) - 9*b*c*d + 27*b*b*e + 27*a*d*d - 72*a*c*e Q = mp.nthroot(pointfive*(delta1 + mp.sqrt(delta1*delta1 - 4*mp.power(delta0, 3))), 3) S = pointfive*mp.sqrt(-mp.fdiv(mp.mpf('2'), mp.mpf('3'))*p + (one/(3*a))*(Q + delta0/Q)) x1 = -b/(4*a) - S + pointfive*mp.sqrt(-4*S*S - 2*p + q/S) x2 = -b/(4*a) - S - pointfive*mp.sqrt(-4*S*S - 2*p + q/S) x3 = -b/(4*a) + S + pointfive*mp.sqrt(-4*S*S - 2*p - q/S) x4 = -b/(4*a) + S - pointfive*mp.sqrt(-4*S*S - 2*p - q/S) """ p = mp.fdiv( Sub(Prod([number('8'), a, c]), Mul(number('3'), mp.power(b, 2))), Mul(number('8'), mp.power(a, 2))) q = mp.fdiv( Sum([ mp.power(b, 3), Prod([number('-4'), a, b, c]), Prod([number('8'), mp.power(a, 2), d]) ]), Mul(8, mp.power(a, 3))) delta0 = Sum([ mp.power(c, 2), Prod([number('-3'), b, d]), Prod([number('12'), a, e]) ]) delta1 = Sum([ Mul(2, mp.power(c, 3)), Prod([number('-9'), b, c, d]), Prod([number('27'), mp.power(b, 2), e]), Prod([number('27'), a, mp.power(d, 2)]), Prod([number('-72'), a, c, e]) ]) Q = mp.nthroot( Mul( pointfive, Add( delta1, mp.sqrt( Add(mp.power(delta1, 2), Mul(number('-4'), mp.power(delta0, 3)))))), 3) S = Mul( pointfive, mp.sqrt( Mul(mp.fdiv(mp.mpf('-2'), mp.mpf('3')), p) + Mul(mp.fdiv(one, Mul(number('3'), a)), Add(Q, mp.fdiv(delta0, Q)))) ) # log.debug("p = {0}".format(mp.nstr(p, n=_prec))) # log.debug("q = {0}".format(mp.nstr(q, n=_prec))) # log.debug("delta0 = {0}".format(mp.nstr(delta0, n=_prec))) # log.debug("delta1 = {0}".format(mp.nstr(delta1, n=_prec))) # log.debug("Q = {0}".format(mp.nstr(Q, n=_prec))) # log.debug("S = {0}".format(mp.nstr(S, n=_prec))) x1 = Sum([ mp.fdiv(b, Mul(number('-4'), a)), Neg(S), Mul( pointfive, mp.sqrt( Sum([ Mul(number('-4'), mp.power(S, 2)), Mul(number('-2'), p), mp.fdiv(q, S) ]))) ]) x2 = Sum([ mp.fdiv(b, Mul(number('-4'), a)), Neg(S), Neg( Mul( pointfive, mp.sqrt( Sum([ Mul(number('-4'), mp.power(S, 2)), Mul(number('-2'), p), mp.fdiv(q, S) ])))) ]) x3 = Sum([ mp.fdiv(b, Mul(number('-4'), a)), S, Mul( pointfive, mp.sqrt( Sum([ Mul(number('-4'), mp.power(S, 2)), Mul(number('-2'), p), Neg(mp.fdiv(q, S)) ]))) ]) x4 = Sum([ mp.fdiv(b, Mul(number('-4'), a)), S, Neg( Mul( pointfive, mp.sqrt( Sum([ Mul(number('-4'), mp.power(S, 2)), Mul(number('-2'), p), Neg(mp.fdiv(q, S)) ])))) ]) return [x1, x2, x3, x4]