This commit is contained in:
Michael Fogleman 2017-01-05 14:50:33 -05:00
parent aaa86ebafa
commit e31a9cfa1e
4 changed files with 62 additions and 59 deletions

View File

@ -1,5 +1,6 @@
from __future__ import division from __future__ import division
from bisect import bisect
from collections import namedtuple from collections import namedtuple
from itertools import groupby from itertools import groupby
from math import sqrt, hypot from math import sqrt, hypot
@ -77,7 +78,16 @@ def jerk_duration(s, vi, ai, j):
# TODO: remove numpy dependency? # TODO: remove numpy dependency?
roots = numpy.roots([j / 6, ai / 2, vi, -s]) roots = numpy.roots([j / 6, ai / 2, vi, -s])
roots = roots.real[abs(roots.imag) < EPS] roots = roots.real[abs(roots.imag) < EPS]
return min(x for x in roots if x > 0) return float(min(x for x in roots if x > 0))
def jerk_factor(a, j, t):
# compute a jerk factor based on desired jerk, 0 < jf <= 0.5
a = abs(a)
jt = j * t
r = jt * (jt - 4 * a)
if r < EPS:
return 0.5
return (jt - sqrt(r)) / (2 * jt)
def corner_velocity(s1, s2, vmax, a, delta): def corner_velocity(s1, s2, vmax, a, delta):
# compute a maximum velocity at the corner of two segments # compute a maximum velocity at the corner of two segments
@ -93,17 +103,37 @@ def corner_velocity(s1, s2, vmax, a, delta):
Instant = namedtuple('Instant', ['t', 'p', 's', 'v', 'a', 'j']) Instant = namedtuple('Instant', ['t', 'p', 's', 'v', 'a', 'j'])
class Plan(object):
# a complete motion profile
def __init__(self, blocks):
self.blocks = blocks
self.duration = sum(b.t for b in blocks)
self.length = sum(b.s for b in blocks)
self.times = [] # start time of each block
t = 0
for b in blocks:
self.times.append(t)
t += b.t
def instant(self, t):
t = max(0, t)
i = bisect(self.times, t) - 1
b = self.blocks[i]
bt = t - self.times[i]
return b.instant(bt)
class Block(object): class Block(object):
# a constant jerk for a duration of time # a constant jerk for a duration of time
def __init__(self, j, t, vi, ai, p1, p2): def __init__(self, j, t, vi, ai, p1, p2):
# TODO: track total time and distance for entire path or do in post?
self.j = j self.j = j
self.t = t self.t = t
# TODO: si?
self.vi = vi self.vi = vi
self.ai = ai self.ai = ai
self.p1 = p1 self.p1 = p1 # TODO: rename pi?
self.p2 = p2 self.p2 = p2 # TODO: rename pf?
self.s = p1.distance(p2) self.s = p1.distance(p2)
# TODO: support providing vf, af when known
self.vf = vi + ai * t + j * t * t / 2 self.vf = vi + ai * t + j * t * t / 2
self.af = ai + j * t self.af = ai + j * t
@ -113,14 +143,6 @@ class Block(object):
b2 = Block(self.j, self.t - t, x.v, x.a, x.p, self.p2) b2 = Block(self.j, self.t - t, x.v, x.a, x.p, self.p2)
return b1, b2 return b1, b2
@property
def initial(self):
return self.instant(0)
@property
def final(self):
return self.instant(self.t)
def instant(self, t): def instant(self, t):
t2 = t * t t2 = t * t
t3 = t2 * t t3 = t2 * t
@ -149,11 +171,11 @@ class Segment(object):
self.blocks = [] self.blocks = []
class Planner(object): class Planner(object):
def __init__(self, acceleration, max_velocity, corner_factor, jerk_factor): def __init__(self, acceleration, max_velocity, corner_factor, jerk):
self.acceleration = acceleration self.acceleration = acceleration
self.max_velocity = max_velocity self.max_velocity = max_velocity
self.corner_factor = corner_factor self.corner_factor = corner_factor
self.jerk_factor = jerk_factor self.jerk = jerk
def plan(self, points): def plan(self, points):
a = self.acceleration a = self.acceleration
@ -162,9 +184,8 @@ class Planner(object):
return constant_acceleration_plan(points, a, vmax, cf) return constant_acceleration_plan(points, a, vmax, cf)
def jerk_plan(self, points): def jerk_plan(self, points):
blocks = self.plan(points) plan = self.plan(points)
jf = self.jerk_factor return constant_jerk_plan(plan, self.jerk)
return constant_jerk_plan(blocks, jf)
def constant_acceleration_plan(points, a, vmax, cf): def constant_acceleration_plan(points, a, vmax, cf):
# make sure points are Point objects # make sure points are Point objects
@ -197,6 +218,9 @@ def constant_acceleration_plan(points, a, vmax, cf):
# determine which profile to use for this segment # determine which profile to use for this segment
# TODO: rearrange these cases for better flow? # TODO: rearrange these cases for better flow?
# TODO: ensure acceleration blocks are long enough to jerk
# min_acceleration_duration = 2 * a / j
# accelerate? / # accelerate? /
vf = sqrt(vi * vi + 2 * a * s) vf = sqrt(vi * vi + 2 * a * s)
if vf <= vexit: if vf <= vexit:
@ -241,25 +265,26 @@ def constant_acceleration_plan(points, a, vmax, cf):
blocks.extend(segment.blocks) blocks.extend(segment.blocks)
# filter out zero-duration blocks and return # filter out zero-duration blocks and return
blocks = [x for x in blocks if x.t > EPS] blocks = [b for b in blocks if b.t > EPS]
return blocks return Plan(blocks)
def constant_jerk_plan(blocks, jf): def constant_jerk_plan(plan, j):
# TODO: ignore blocks that already have a jerk? # TODO: ignore blocks that already have a jerk?
result = [] blocks = []
for a, g in groupby(blocks, key=lambda x: x.ai): for a, g in groupby(plan.blocks, key=lambda b: b.ai):
result.extend(_constant_jerk_plan(list(g), jf, a)) blocks.extend(_constant_jerk_plan(list(g), j, a))
return result return Plan(blocks)
def _constant_jerk_plan(blocks, jf, a): def _constant_jerk_plan(blocks, j, a):
if abs(a) < EPS: if abs(a) < EPS:
return blocks return blocks
result = [] result = []
duration = sum(x.t for x in blocks) duration = sum(b.t for b in blocks)
jf = jerk_factor(a, j, duration)
t1 = duration * jf t1 = duration * jf
t2 = duration - 2 * t1 t2 = duration - 2 * t1
amax = a / (1 - jf) amax = a / (1 - jf)
j = amax / t1 j = amax / t1 # actual jerk may exceed desired jerk
vi = blocks[0].vi vi = blocks[0].vi
ai = 0 ai = 0
s1 = vi * t1 + ai * t1 * t1 / 2 + j * t1 * t1 * t1 / 6 s1 = vi * t1 + ai * t1 * t1 / 2 + j * t1 * t1 * t1 / 6
@ -317,22 +342,3 @@ def split_blocks(blocks, s):
# af = ai + j * t # af = ai + j * t
# vf = vi + ai * t + j * t * t / 2 # vf = vi + ai * t + j * t * t / 2
# sf = si + vi * t + ai * t * t / 2 + j * t * t * t / 6 # sf = si + vi * t + ai * t * t / 2 + j * t * t * t / 6
# def chop_block(p, dt):
# result = []
# t = 0
# while t < p.t:
# t1 = t
# t2 = min(t + dt, p.t)
# p1 = p.point(t1)
# p2 = p.point(t2)
# v = (p.velocity(t1) + p.velocity(t2)) / 2
# result.append(accelerate(0, t2 - t1, v, p1, p2))
# t += dt
# return result
# def chop_blocks(blocks, dt):
# result = []
# for block in blocks:
# result.extend(chop_block(block, dt))
# return result

View File

@ -2,8 +2,7 @@ from axi import Planner
def main(): def main():
planner = Planner( planner = Planner(
acceleration=100, max_velocity=200, corner_factor=0.1, jerk_factor=0.1) acceleration=100, max_velocity=200, corner_factor=0.1, jerk=5000)
print 'var PIECES = ['
draws = list(PATHS) draws = list(PATHS)
jogs = [] jogs = []
for p1, p2 in zip(draws, draws[1:]): for p1, p2 in zip(draws, draws[1:]):
@ -11,10 +10,11 @@ def main():
paths = draws + jogs paths = draws + jogs
paths[::2] = draws paths[::2] = draws
paths[1::2] = jogs paths[1::2] = jogs
print 'var PIECES = ['
for i, path in enumerate(paths): for i, path in enumerate(paths):
print '[' print '['
blocks = planner.jerk_plan(path) plan = planner.jerk_plan(path)
for b in blocks: for b in plan.blocks:
record = (b.p1.x, b.p1.y, b.p2.x, b.p2.y, b.j, b.t, i) record = (b.p1.x, b.p1.y, b.p2.x, b.p2.y, b.j, b.t, i)
print '[%s],' % ','.join(map(str, record)) print '[%s],' % ','.join(map(str, record))
print '],' print '],'

View File

@ -16,10 +16,10 @@ def main():
for r in range(20, 100, 20): for r in range(20, 100, 20):
points = circle(0, 0, r, 90) + points points = circle(0, 0, r, 90) + points
planner = Planner( planner = Planner(
acceleration=100, max_velocity=200, corner_factor=1, jerk_factor=0.25) acceleration=100, max_velocity=200, corner_factor=1, jerk=5000)
blocks = planner.jerk_plan(points) plan = planner.jerk_plan(points)
print 'var PIECES = [' print 'var PIECES = ['
for b in blocks: for b in plan.blocks:
record = (b.p1.x, b.p1.y, b.p2.x, b.p2.y, b.j, b.t) record = (b.p1.x, b.p1.y, b.p2.x, b.p2.y, b.j, b.t)
print '[%s],' % ','.join(map(str, record)) print '[%s],' % ','.join(map(str, record))
print '];' print '];'

View File

@ -16,16 +16,13 @@ def main():
for r in range(20, 100, 20): for r in range(20, 100, 20):
points = circle(0, 0, r, 90) + points points = circle(0, 0, r, 90) + points
planner = Planner( planner = Planner(
acceleration=50, max_velocity=200, corner_factor=1, jerk_factor=0.5) acceleration=50, max_velocity=200, corner_factor=1, jerk=5000)
blocks = planner.plan(points) plan = planner.plan(points)
print 'var PIECES = [' print 'var PIECES = ['
for b in blocks: for b in plan.blocks:
record = (b.p1.x, b.p1.y, b.p2.x, b.p2.y, b.ai, b.t) record = (b.p1.x, b.p1.y, b.p2.x, b.p2.y, b.ai, b.t)
print '[%s],' % ','.join(map(str, record)) print '[%s],' % ','.join(map(str, record))
print '];' print '];'
# blocks = planner.smooth(blocks)
# for b in blocks:
# print b.t, b.t
if __name__ == '__main__': if __name__ == '__main__':
main() main()