Example #1
0
def feasible_plan(world, robot, qtarget):
    """Plans for some number of iterations from the robot's current configuration to
    configuration qtarget.  Returns the first path found.

    Returns None if no path was found, otherwise returns the plan.
    """
    t0 = time.time()

    moving_joints = [1, 2, 3, 4, 5, 6, 7]
    # space = robotplanning.makeSpace(world=world,robot=robot,edgeCheckResolution=1e-2,movingSubset=moving_joints)
    plan = robotplanning.planToConfig(world,
                                      robot,
                                      qtarget,
                                      movingSubset=moving_joints)
    #TODO: maybe you should use planToConfig?
    numIters = 100
    path = plan.getPath()
    t1 = time.time()
    while path == None and t1 - t0 <= 10:
        plan.planMore(numIters)
        t1 = time.time()
        path = plan.getPath()
    print("Planning time,", numIters, "iterations", t1 - t0)

    #to be nice to the C++ module, do this to free up memory
    plan.space.close()
    plan.close()
    return path
Example #2
0
def optimizing_plan(world, robot, qtarget):
    """Plans for some number of iterations from the robot's current configuration to
    configuration qtarget.

    Returns None if no path was found, otherwise returns the best plan found.
    """
    #TODO: copy what's in feasible_plan, but change the way in which you to terminate
    t0 = time.time()

    moving_joints = [1, 2, 3, 4, 5, 6, 7]
    # space = robotplanning.makeSpace(world=world,robot=robot,edgeCheckResolution=1e-2,movingSubset=moving_joints)
    plan = robotplanning.planToConfig(world,
                                      robot,
                                      qtarget,
                                      movingSubset=moving_joints,
                                      type='sbl',
                                      perturbationRadius=0.5)
    if plan is None:
        return None
    #TODO: maybe you should use planToConfig?
    numIters = 100
    t1 = time.time()
    while t1 - t0 <= 15:
        plan.planMore(numIters)
        t1 = time.time()

    path = plan.getPath()
    print("Planning time,", numIters, "iterations", t1 - t0)
    # debug_plan_results(plan, robot)
    #to be nice to the C++ module, do this to free up memory
    plan.space.close()
    plan.close()
    return path
 def keyboardfunc(self, c, x, y):
     #Put your keyboard handler here
     #the current example prints the config when [space] is pressed
     if c == ' ':
         config = self.robotWidget.get()
         subconfig = self.subrobots[0].fromfull(config)
         print("Config:", subconfig)
         self.subrobots[0].setConfig(subconfig)
     elif c == 'r':
         self.subrobots[0].randomizeConfig()
         self.robotWidget.set(self.robot.getConfig())
     elif c == 'p':
         config = self.robotWidget.get()
         subconfig = self.subrobots[0].fromfull(config)
         self.subrobots[0].setConfig(self.subrobots[0].fromfull(
             self.startConfig))
         settings = {
             'type': "sbl",
             'perturbationRadius': 0.5,
             'bidirectional': 1,
             'shortcut': 1,
             'restart': 1,
             'restartTermCond': "{foundSolution:1,maxIters:1000}"
         }
         plan = robotplanning.planToConfig(self.world,
                                           self.subrobots[0],
                                           subconfig,
                                           movingSubset='all',
                                           **settings)
         plan.planMore(1000)
         print(plan.getPath())
     else:
         GLWidgetPlugin.keyboardfunc(self, c, x, y)
Example #4
0
def baxter_planner(qi, q, qSubset, settings):
    global world
    global robot

    t0 = time.time()
    print "Creating plan..."
    #this code uses the robotplanning module's convenience functions
    robot.setConfig(qi)
    print 'len(q)', len(q)
    plan = robotplanning.planToConfig(world,
                                      robot,
                                      q,
                                      movingSubset=qSubset,
                                      **settings)

    if plan is None:
        print 'plan is None...'
        return None

    print "Planner creation time", time.time() - t0
    t0 = time.time()
    plan.space.cspace.enableAdaptiveQueries(True)
    print "Planning..."
    for round in range(10):
        plan.planMore(50)
    print "Planning time, 500 iterations", time.time() - t0

    #this code just gives some debugging information. it may get expensive
    #V,E = plan.getRoadmap()
    #print len(V),"feasible milestones sampled,",len(E),"edges connected"
    path = plan.getPath()
    if path is None or len(path) == 0:
        print "Failed to plan path between configuration"
        print qi
        print "and"
        print q
        # #debug some sampled configurations
        # print V[0:min(10,len(V))]
    """
        print "Constraint testing order:"
        print plan.space.cspace.feasibilityQueryOrder()
        print "Manually optimizing constraint testing order..."
        plan.space.cspace.optimizeQueryOrder()
        print "Optimized constraint testing order:"
        print plan.space.cspace.feasibilityQueryOrder()

        print "Plan stats:"
        print plan.getStats()

        print "CSpace stats:"
        print plan.space.getStats()
    """
    #to be nice to the C++ module, do this to free up memory
    plan.space.close()
    plan.close()

    return path
Example #5
0
 def keyboardfunc(self, c, x, y):
     #Put your keyboard handler here
     #the current example prints the config when [space] is pressed
     if c == 'h' or c == '?':
         print("Controls:")
         print("- [space]: print the widget's sub-robot configuration")
         print("- r: randomize the sub-robot configuration")
         print("- p: plan to the widget's sub-robot configuration")
         print("- i: test the IK functions")
     elif c == ' ':
         config = self.robotWidget.get()
         subconfig = self.subrobots[0].fromfull(config)
         print("Config:", subconfig)
         self.subrobots[0].setConfig(subconfig)
     elif c == 'r':
         self.subrobots[0].randomizeConfig()
         self.robotWidget.set(self.robot.getConfig())
     elif c == 'p':
         config = self.robotWidget.get()
         subconfig = self.subrobots[0].fromfull(config)
         self.subrobots[0].setConfig(self.subrobots[0].fromfull(
             self.startConfig))
         settings = {
             'type': "sbl",
             'perturbationRadius': 0.5,
             'bidirectional': 1,
             'shortcut': 1,
             'restart': 1,
             'restartTermCond': "{foundSolution:1,maxIters:1000}"
         }
         plan = robotplanning.planToConfig(self.world,
                                           self.subrobots[0],
                                           subconfig,
                                           movingSubset='all',
                                           **settings)
         plan.planMore(1000)
         print(plan.getPath())
     elif c == 'i':
         link = self.subrobots[0].link(self.subrobots[0].numLinks() - 1)
         print("IK solve for ee to go 10cm upward...")
         obj = ik.objective(link,
                            local=[0, 0, 0],
                            world=vectorops.add(
                                link.getWorldPosition([0, 0, 0]),
                                [0, 0, 0.1]))
         solver = ik.solver(obj)
         res = solver.solve()
         print("  result", res)
         print("  residual", solver.getResidual())
         print(self.robotWidget.set(self.robot.getConfig()))
     else:
         GLWidgetPlugin.keyboardfunc(self, c, x, y)
Example #6
0
def feasible_plan(world, robot, object, qtarget):
    """Plans for some number of iterations from the robot's current configuration to
    configuration qtarget.  Returns the first path found.

    Returns None if no path was found, otherwise returns the plan.
    """
    t0 = time.time()

    moving_joints = [1, 2, 3, 4, 5, 6, 7]

    kwargs = {'type':'sbl','perturbationRadius':0.5, 'bidirectional': 1, 'shortcut':True,'restart':True,
              'restartTermCond':"{foundSolution:1,maxIters:100}"}
    def check_collision(q):
        robot.setConfig(q)
        return is_collision_free_grasp(world, robot, object)
    planner = robotplanning.planToConfig(world=world, robot=robot, target=qtarget, edgeCheckResolution=0.1,
                                         movingSubset=moving_joints, extraConstraints=[check_collision], **kwargs)
    # qtarget is not feasible
    if planner is None:
        return None
    path = None
    numIters = 0
    increment = 100  # there is a little overhead for each planMore call, so it is best not to set the increment too low
    t0 = time.time()
    while time.time() - t0 < 100:  # max 20 seconds of planning
        planner.planMore(increment)
        numIters = numIters + 1
        path = planner.getPath()
        if path is not None:
            if len(path) > 0:
                print("Solved, path has", len(path), "milestones")
                break

    t1 = time.time()
    print("Planning time,", numIters, "iterations", t1 - t0)

    # to be nice to the C++ module, do this to free up memory
    planner.space.close()
    planner.close()

    return path
def planOptimizedTrajectory(world,
                            robot,
                            target,
                            maxTime=float('inf'),
                            numRestarts=1,
                            plannerSettings={
                                'type': 'sbl',
                                'shortcut': True
                            },
                            plannerMaxIters=3000,
                            plannerMaxTime=10.0,
                            plannerContinueOnRestart=False,
                            optimizationMaxIters=200,
                            kinematicsCache=None,
                            settings=None,
                            logFile=None):
    """
    Args:
        world (WorldModel): contains all objects
        robot (RobotModel): the moving robot
        target: a target configuration
        maxTime (float, optional): a total max time for all planning / optimization calls
        numRestarts (int): number of restarts of the randomized planner
        plannerSettings (dict): a Klamp't planner settings dictionary
        plannerMaxIters (int): max number of iterations to perform each planner call
        plannerMaxTime (float): max time for each planner call
        plannerContinueOnRestart (bool): if true, the planner doesn't get reset every restart
        optimizationMaxIters (int): max number of total optimization iterations
        kinematicsCache (RobotKinematicsCache, optional): modifies the kinematics cache.
        settings (SemiInfiniteOptimizationSettings, optional): modifies the optimizer setting
        logFile (file, optional): if given, this is a CSV file that logs the output

    Returns a RobotTrajectory that (with more iterations) optimizes path length
    """
    bestPath = None
    bestPathCost = float('inf')
    best_instantiated_params = None
    t0 = time.time()
    if kinematicsCache is None:
        kinematicsCache = geometryopt.RobotKinematicsCache(robot)
    if settings is None:
        settings = geometryopt.SemiInfiniteOptimizationSettings()
        settings.minimum_constraint_value = -0.02
    optimizing = False
    if 'optimizing' in plannerSettings:
        optimizing = plannerSettings['optimizing']
        plannerSettings = plannerSettings.copy()
        del plannerSettings['optimizing']
    obstacles = [
        world.rigidObject(i) for i in xrange(world.numRigidObjects())
    ] + [world.terrain(i) for i in xrange(world.numTerrains())]
    obstaclegeoms = [
        geometryopt.PenetrationDepthGeometry(obs.geometry(), None, 0.04)
        for obs in obstacles
    ]
    t1 = time.time()
    print("Preprocessing took time", t1 - t0)
    if logFile:
        logFile.write(
            "numRestarts,plannerSettings,plannerMaxIters,plannerMaxTime,optimizationMaxIters\n"
        )
        logFile.write("%d,%s,%d,%g,%d\n" %
                      (numRestarts, str(plannerSettings), plannerMaxIters,
                       plannerMaxTime, optimizationMaxIters))
        logFile.write("restart,iterations,time,method,path length\n")

    tstart = time.time()
    startConfig = robot.getConfig()
    for restart in xrange(numRestarts):
        print(
            "***************************************************************")
        #optimize existing best path, if available
        if bestPath is not None and optimizationMaxIters > 0:
            t0 = time.time()
            #optimize the best path
            settings.max_iters = optimizationMaxIters // (numRestarts * 2)
            settings.instantiated_params = best_instantiated_params
            print("Optimizing current best path with", settings.max_iters,
                  "iters over", len(bestPath.milestones), "milestones")
            if logFile:
                logFile.write("%d,0,%g,optimize-best,%g\n" %
                              (restart, t0 - tstart, bestPathCost))
            trajCache = geometryopt.RobotTrajectoryCache(
                kinematicsCache,
                len(bestPath.milestones) - 2,
                qstart=startConfig,
                qend=target)
            trajCache.tstart = 0
            trajCache.tend = bestPath.times[-1]
            obj = geometryopt.TrajectoryLengthObjective(trajCache)
            constraint = geometryopt.RobotTrajectoryCollisionConstraint(
                obstaclegeoms, trajCache)
            #constraint = geometryopt.MultiSemiInfiniteConstraint([geometryopt.RobotLinkTrajectoryCollisionConstraint(robot.link(i),obstaclegeoms[0],trajCache) for i in range(robot.numLinks())])
            traj, traj_trace, traj_times, constraintPts = geometryopt.optimizeCollFreeTrajectory(
                trajCache,
                bestPath,
                obstacles,
                constraints=[constraint],
                settings=settings)
            if logFile:
                #write the whole trace
                for i, (traji, ti) in enumerate(zip(traj_trace, traj_times)):
                    if i == 0: continue
                    logFile.write("%d,%d,%g,optimize-best,%g\n" %
                                  (restart, i, t0 + ti - tstart,
                                   min(bestPathCost, traji.length())))
            xtraj = trajCache.trajectoryToState(traj)
            #cost = obj.value(xtraj)
            cost = traj.length()
            constraint.setx(xtraj)
            residual = constraint.minvalue(xtraj)
            constraint.clearx()
            print("Optimized cost", cost, "and residual", residual)
            if cost < bestPathCost and residual[0] >= 0:
                print("Got a better cost path by optimizing current best",
                      cost, "<", bestPathCost)
                bestPathCost = cost
                bestPath = traj
                best_instantiated_params = constraintPts
            t1 = time.time()
            if t1 - tstart > maxTime:
                break

        #do sampling-based planningif bestPath is not None:
        if restart == 0 or (not plannerContinueOnRestart
                            and planner.getPath()):
            robot.setConfig(startConfig)
            planner = robotplanning.planToConfig(world, robot, target,
                                                 **plannerSettings)
        t0 = time.time()
        if logFile:
            logFile.write("%d,0,%g,sample,%g\n" %
                          (restart, t0 - tstart, bestPathCost))
        path = None
        oldpath = None
        for it in xrange(0, plannerMaxIters, 50):
            oldpath = path
            planner.planMore(50)
            t1 = time.time()
            path = planner.getPath()
            if path:
                if oldpath is None:
                    print("Found a feasible path on restart", restart,
                          "iteration", it)
                path = RobotTrajectory(robot, range(len(path)), path)
                cost = path.length()
                if cost < bestPathCost:
                    print("Got a better cost path from planner", cost, "<",
                          bestPathCost)
                    bestPathCost = cost
                    bestPath = path
                    if logFile:
                        if oldpath is not None:
                            logFile.write(
                                "%d,%d,%g,sample-optimize,%g\n" %
                                (restart, it + 50, t1 - tstart, bestPathCost))
                        else:
                            logFile.write(
                                "%d,%d,%g,sample,%g\n" %
                                (restart, it + 50, t1 - tstart, bestPathCost))
                if not optimizing:
                    break
            if t1 - t0 > plannerMaxTime:
                break
            if t1 - tstart > maxTime:
                break
        if logFile:
            if oldpath is not None:
                logFile.write("%d,%d,%g,sample-optimize,%g\n" %
                              (restart, it + 50, t1 - tstart, bestPathCost))
            else:
                logFile.write("%d,%d,%g,sample,%g\n" %
                              (restart, it + 50, t1 - tstart, bestPathCost))
        if t1 - tstart > maxTime:
            break

        #optimize new planned path, if available
        if path and len(path.milestones) == 2:
            #straight line path works, this is certainly optimal
            return path
        elif path and len(path.milestones) > 2:
            #found a feasible path
            t0 = time.time()
            if len(path.milestones) < 30:
                traj0 = arc_length_refine(
                    path, min(30 - len(path.milestones), len(path.milestones)))
            else:
                traj0 = path
            traj0.times = [
                float(i) / (len(traj0.milestones) - 1)
                for i in xrange(len(traj0.times))
            ]
            trajCache = geometryopt.RobotTrajectoryCache(
                kinematicsCache,
                len(traj0.milestones) - 2,
                qstart=startConfig,
                qend=target)
            trajCache.tstart = 0
            trajCache.tend = traj0.times[-1]

            print()
            print("Planned trajectory with", len(traj0.milestones),
                  "milestones")

            obj = geometryopt.TrajectoryLengthObjective(trajCache)
            constraint = geometryopt.RobotTrajectoryCollisionConstraint(
                obstaclegeoms, trajCache)
            xtraj = trajCache.trajectoryToState(traj0)
            #cost0 = obj.value(xtraj)
            cost0 = traj0.length()
            constraint.setx(xtraj)
            residual0 = constraint.minvalue(xtraj)
            constraint.clearx()
            print("Planned cost", cost0, "and residual", residual0)
            if bestPath:
                settings.max_iters = optimizationMaxIters // (numRestarts * 2)
            else:
                settings.max_iters = optimizationMaxIters // (numRestarts)
            settings.instantiated_params = None
            if residual0[0] < 0:
                print(
                    "Warning, the planned path has a negative constraint residual?",
                    residual0[0])
                print("  Residual met @ parameter", residual0[1:])
                #raw_input("Warning, the planned path has a negative constraint residual?")
            if optimizationMaxIters > 0:
                if logFile:
                    logFile.write("%d,0,%g,optimize,%g\n" %
                                  (restart, t0 - tstart, bestPathCost))
                print("Optimizing planned path with", settings.max_iters,
                      "iters")
                traj, traj_trace, traj_times, constraintPts = geometryopt.optimizeCollFreeTrajectory(
                    trajCache,
                    traj0,
                    obstacles,
                    constraints=[constraint],
                    settings=settings)
                if logFile:
                    #write the whole trace
                    for i, (traji,
                            ti) in enumerate(zip(traj_trace, traj_times)):
                        if i == 0: continue
                        xtraj = trajCache.trajectoryToState(traji)
                        constraint.setx(xtraj)
                        residual = constraint.minvalue(xtraj)
                        constraint.clearx()
                        if residual[0] >= 0:
                            logFile.write("%d,%d,%g,optimize,%g\n" %
                                          (restart, i, t0 + ti - tstart,
                                           min(bestPathCost, traji.length())))
                xtraj = trajCache.trajectoryToState(traj)
                #cost = obj.value(xtraj)
                cost = traj.length()
                constraint.setx(xtraj)
                residual = constraint.minvalue(xtraj)
                constraint.clearx()
                print("Optimized cost", cost, "and residual", residual)
                if cost < bestPathCost and residual[0] >= 0:
                    print("Got a better cost path from optimizer", cost, "<",
                          bestPathCost)
                    bestPathCost = cost
                    bestPath = traj
                    best_instantiated_params = constraintPts
                else:
                    print("Optimizer produced a worse cost (", cost, "vs",
                          bestPathCost, ") or negative residual, skipping")
                if residual[0] < 0:
                    #raw_input("Warning, the optimized path has a negative constraint residual...")
                    print(
                        "Warning, the optimized path has a negative constraint residual..."
                    )
                t1 = time.time()
                #if logFile:
                #    logFile.write("%d,%d,%g,optimize,%g\n"%(restart,settings.max_iters,t1-tstart,bestPathCost))
                if t1 - tstart > maxTime:
                    break
        else:
            print("No feasible path was found by the planner on restart",
                  restart)
    return bestPath
     robot.setConfig(configs[i + 1])
     obj = ik.fixed_objective(robot.link(robot.numLinks() - 1),
                              local=[0, 0, 0])
     #start from end of previous path
     robot.setConfig(wholepath[-1])
     plan = robotplanning.planToCartesianObjective(world,
                                                   robot,
                                                   obj,
                                                   movingSubset='all',
                                                   **settings)
 else:
     #this code uses the robotplanning module's convenience functions
     robot.setConfig(configs[i])
     plan = robotplanning.planToConfig(world,
                                       robot,
                                       configs[i + 1],
                                       movingSubset='all',
                                       **settings)
 if plan is None:
     break
 print("Planner creation time", time.time() - t0)
 t0 = time.time()
 plan.space.cspace.enableAdaptiveQueries(True)
 print("Planning...")
 for round in range(10):
     plan.planMore(50)
 print("Planning time, 500 iterations", time.time() - t0)
 #this code just gives some debugging information. it may get expensive
 V, E = plan.getRoadmap()
 print(len(V), "feasible milestones sampled,", len(E), "edges connected")
 path = plan.getPath()
Example #9
0
def transfer_plan(world, robot, qtarget, object, Tobject_gripper):
    """Plans for some number of iterations from the robot's current configuration to
    configuration qtarget, assuming the object is fixed to the gripper. 
    Returns the first path found.

    Returns None if no path was found, otherwise returns the plan.
    """
    moving_joints = [1, 2, 3, 4, 5, 6, 7]
    gripper_link = 9

    # create a new copy of the world
    world1 = world.copy()
    # remove the hammer object from the world
    world1.remove(world1.rigidObject(object.index))
    # find the robot in world1
    robot1 = world1.robot(0)
    robot1.setConfig(robot.getConfig())
    # find the gripper link in new world????
    gripper_link1 = robot1.link(gripper_link)

    #kwargs = {'type':'rrt*', 'bidirectional':1}
    kwargs = {
        'type': 'sbl',
        'perturbationRadius': 0.5,
        'bidirectional': 1,
        'shortcut': True,
        'restart': True,
        'restartTermCond': "{foundSolution:1,maxIters:100}"
    }
    t0 = time.time()

    # collision check function
    finger_pad_links = ['gripper:Link_4', 'gripper:Link_6']

    def check_collision(q):
        # set robot to current config
        robot1.setConfig(q)

        finger_pad_links_idx = []
        for i in finger_pad_links:
            idx = robot1.link(i).getIndex()
            finger_pad_links_idx.append(idx)

        Tworld_gripper = gripper_link1.getTransform()
        Tworld_object = se3.mul(Tworld_gripper, Tobject_gripper)
        object.setTransform(Tworld_object[0], Tworld_object[1])

        # self collide
        if robot1.selfCollides():
            print('self collides')
            return False
        # collision between object and robot
        for i in range(robot1.numLinks()):
            if i in finger_pad_links_idx:
                continue
            if robot1.link(i).geometry().collides(object.geometry()):
                print('hammer and robot', i)
                return False

        # collision between robot and world environment
        for i in range(world1.numTerrains()):
            for j in range(robot1.numLinks()):
                if robot1.link(j).geometry().collides(
                        world1.terrain(i).geometry()):
                    print('robot and world')
                    return False
            if object.geometry().collides(world1.terrain(i).geometry()):
                print('hammer and world', i, world1.terrain(i).getName())
                return False

        # collision between robot and objects in world
        for i in range(world1.numRigidObjects()):
            for j in range(robot1.numLinks()):
                if world1.rigidObject(
                        i) == object and j in finger_pad_links_idx:
                    continue
                if robot1.link(j).geometry().collides(
                        world1.rigidObject(i).geometry()):
                    print('robot and other objects')
                    return False
            if object.geometry().collides(world1.rigidObject(i).geometry()):
                print('hammer and other objects')
                return False
        return True

    planner = robotplanning.planToConfig(world=world1,
                                         robot=robot1,
                                         target=qtarget,
                                         edgeCheckResolution=0.1,
                                         movingSubset=moving_joints,
                                         extraConstraints=[check_collision],
                                         **kwargs)

    if planner is None:
        return None

    # plan for 10 seconds
    path = None
    increment = 100  # there is a little overhead for each planMore call, so it is best not to set the increment too low
    t0 = time.time()
    while time.time() - t0 < 10:  # max 10 seconds of planning
        planner.planMore(increment)

        path = planner.getPath()
        if path is not None:
            if len(path) > 0:
                print("Solved, path has", len(path), "milestones")
                break

    t1 = time.time()
    print("Planning time,", t1 - t0)

    # to be nice to the C++ module, do this to free up memory
    planner.space.close()
    planner.close()
    print('path:', path)

    return path
Example #10
0
def optimizing_plan(world,robot,qtarget):
    """Plans for some number of iterations from the robot's current configuration to
    configuration qtarget.

    Returns None if no path was found, otherwise returns the best plan found.
    """
    t0 = time.time()

    moving_joints = [1, 2, 3, 4, 5, 6, 7]

    arg_list = [{'type':'sbl','shortcut':True,'perturbationRadius':0.5},
                {'type':'sbl','perturbationRadius':0.5,'shortcut':True,'restart':True,'restartTermCond':"{foundSolution:1,maxIters:100}"},
                {'type':'rrt*'},
                {'type':'lazyrrg*'}]
    planner_names = ['sbl1', 'sbl2', 'rrt*', 'lazyrrg']
    success = [0, 0, 0, 0]
    path_len = [[], [], [], []]

    best_path = None
    best_path_len = np.inf
    qinit = robot.getConfig()
    for i, kwarg in enumerate(arg_list):
        robot.setConfig(qinit)
        print('')
        print(planner_names[i])
        planner = robotplanning.planToConfig(world=world, robot=robot, target=qtarget, edgeCheckResolution=0.01,
                                             movingSubset=moving_joints, **kwarg)
        if planner is None:
            return None
        # there are 18 dimensions in config space, but we only allow the first 7 joints to move

        path = None
        numIters = 0
        increment = 100  # there is a little overhead for each planMore call, so it is best not to set the increment too low
        for j in range(10):
            t0 = time.time()
            while time.time() - t0 < 10:  # max 20 seconds of planning
                planner.planMore(increment)
                numIters = numIters + 1
                path = planner.getPath()
                if path is not None:
                    if len(path) > 0:
                        print("Solved, path has", len(path), "milestones")
                        success[i] = success[i] + 1
                        path_len[i].append(len(path))
                        # update best_path if a better path is found
                        if len(path) < best_path_len:
                            best_path = copy.deepcopy(path)
                            best_path_len = len(path)
                        break


        # to be nice to the C++ module, do this to free up memory
        planner.space.close()
        planner.close()

        print('success rate:', success[i]/10.)
        print('optimal path len:', min(path_len[i]), 'mean:', np.mean(path_len[i]), 'std:', np.std(path_len[i]))



    return best_path