Пример #1
0
    def run_test(self,
                 n_neurons=1000,
                 n_ensembles=1,
                 decimal_scale=1,
                 test_name="adaptive_training",
                 session=None,
                 run=None,
                 weights_file=None,
                 pes_learning_rate=1e-6,
                 backend=None,
                 autoload=False,
                 time_limit=30,
                 target_type='single',
                 offset=None,
                 avoid_limits=False,
                 additional_mass=0,
                 kp=20,
                 kv=6,
                 ki=0,
                 integrate_err=False,
                 ee_adaptation=False,
                 joint_adaptation=False,
                 simulate_wear=False,
                 probe_weights=False,
                 seed=None,
                 friction_bootstrap=False,
                 redis_adaptation=False,
                 SCALES=None,
                 MEANS=None):
        """
        The test script used to collect data for training. The use of the
        adaptive controller and the type of backend can be specified. The script is
        also automated to use the correct weights files for continuous learning.
        The standard naming convention used is runs are consecutive tests where the previous
        learned weights are used. The whole of these runs are a single session.
        Multiple sessions of these runs can then be used for averaging and
        statictical purposes.

        The adaptive controller uses a 6 dimensional input and returns a 3 dimensional
        output. The base, shoulder and elbow joints' positions and velocities are
        used as the input, and the control signal for the corresponding joints gets
        returned.

        Parameters
        ----------
        n_neurons: int, Optional (Default: 1000)
            the number of neurons in the adaptive population
        n_ensembles: int, Optional (Default: 1)
            the number of ensembles of n_neurons number of neurons
        decimal_scale: int, Optional (Default: 1)
            used for scaling the spinnaker input. Due to precision limit, spinnaker
            input gets scaled to allow for more decimals, then scaled back once
            extracted. Also can be used to account for learning speed since
            spinnaker runs in real time
        test_name: string, Optional (Default: "dewolf_2017_data")
            folder name where data is saved
        session: int, Optional (Default: None)
            The current session number, if left to None then it will be automatically
            updated based on the current session. If the next session is desired then
            this will need to be updated.
        run: int, Optional (Default: None)
            The current nth run that specifies to use the weights from the n-1 run.
            If set to None if will automatically increment based off the last saved
            weights.
        weights_file: string, Optional (Default: None)
            the path to the desired saved weights to use. If None will
            automatically take the most recent weights saved in the 'test_name'
            directory
        pes_learning_rate: float, Optional (Default: 1e-6)
            the learning rate for the adaptive population
        backend: string
            None: non adaptive control, Optional (Default: None)
            'nengo': use nengo as the backend for the adaptive population
            'spinnaker': use spinnaker as the adaptive population
        autoload: boolean, Optional (Default: False)
            True: used the specified weights, or the last set of learned weights if
                  not specified
            False: use the specified weights, if not specified start learning from
                   zero
        time_limit: float, Optional (Default: 30)
            the time limit for each run in seconds
        target_type: string, Optional (Default: single)
            single: one target
            multi: five targets
            figure8: move in figure8 pattern
            vision: get target from vision system
        offset: float array, Optional (Default: None)
            Set the offset to the end effector if something other than the default
            is desired. Use the form [x_offset, y_offset, z_offset]
        avoid_limits: boolean, Optional (Default: False)
            set true if there are limits you would like to avoid
        additional_mass: float, Optional (Default: 0)
            any extra mass added to the EE if known [kg]
        kp: float, Optional (Default: 20)
            proportional gain term
        kv: float, Optional (Default: 6)
            derivative gain term
        ki: float, Optional (Default: 0)
            integral gain term
        integrate_err: Boolean, Optional (Default: False)
            True to add I term to PD control (PD -> PID)
        ee_adaptation: Booleanm Optional (Default: False)
            True if doing neural adaptation in task space
        joint_adaptation: Boolean Optional (Default: False)
            True if doing neural adaptation in joint space
        simulate_wear: Boolean Optional (Default: False)
            True to add a simulated wearing of the joints that mimics friction
            by opposing motion
        probe_weights: Boolean Optional (Default: False)
            True to probe adaptive population for decoders
        seed: int Optional, (Default: None)
            seed used for random number generation in adaptive population
        friction_bootstrap: Boolean Optional (Default: False)
            True if want to pass an estimate of the friction signal as starting
            point for adaptive population
        redis_adaptation: Boolean Optional (Default: False)
            True to send adaptive inputs to redis and read outputs back, allows
            user to use any backend they like as long as it can read/write from
            redis

        Attributes
        ----------
        TARGET_XYZ: array of floats
            the goal target, used for calculating error
        target: array of floats [x, y, z, vx, vy, vz]
            the filtered target, used for calculating error for figure8
            test since following trajectory and not moving to a single final
            point
        """
        print("RUN PASSED IN IS: ", run)

        if redis_adaptation:
            try:
                import redis
                redis_server = redis.StrictRedis(host='localhost')
            except ImportError:
                print(
                    "You must install redis to do adaptation through a redis" +
                    " server")

        # Set the target based on the source and type of test
        PRESET_TARGET = np.array([[.57, 0.03, .87]])

        if target_type == 'multi':
            PRESET_TARGET = np.array([[.56, -.09, .95], [.12, .15, .80],
                                      [.80, .26, .61], [.38, .46, .81],
                                      [.0, .0, 1.15]])
            time_limit /= len(PRESET_TARGET)

        elif target_type == 'vision':
            # try to setup redis server if vision targets are desired
            try:
                import redis
                redis_server = redis.StrictRedis(host='localhost')
                self.redis_server = redis_server
            except ImportError:
                print(
                    'ERROR: Install redis to use vision targets, using preset targets'
                )

        elif target_type == 'figure8':
            try:
                import pydmps
            except ImportError:
                print('\npydmps library required, see ' +
                      'https://github.com/studywolf/pydmps\n')

            # create a dmp that traces a figure8
            x = np.linspace(0, np.pi * 2, 100)
            dmps_traj = np.array([0.2 * np.sin(x), 0.2 * np.sin(2 * x) + 0.7])
            dmps = pydmps.DMPs_rhythmic(n_dmps=2, n_bfs=50, dt=0.001)
            dmps.imitate_path(dmps_traj)

        # get averages and scale of target locations for scaling into network
        x_avg = np.mean(PRESET_TARGET[:, 0])
        y_avg = np.mean(PRESET_TARGET[:, 1])
        z_avg = np.mean(PRESET_TARGET[:, 2])

        x_scale = np.max(PRESET_TARGET[:, 0]) - np.min(PRESET_TARGET[:, 0])
        y_scale = np.max(PRESET_TARGET[:, 1]) - np.min(PRESET_TARGET[:, 1])
        z_scale = np.max(PRESET_TARGET[:, 2]) - np.min(PRESET_TARGET[:, 2])

        # initialize our robot config
        robot_config = abr_jaco2.Config(use_cython=True,
                                        hand_attached=True,
                                        SCALES=SCALES,
                                        MEANS=MEANS)
        self.robot_config = robot_config

        zeros = np.zeros(robot_config.N_JOINTS)

        # get Jacobians to each link for calculating perturbation
        self.J_links = [
            robot_config._calc_J('link%s' % ii, x=[0, 0, 0])
            for ii in range(robot_config.N_LINKS)
        ]

        self.JEE = robot_config._calc_J('EE', x=[0, 0, 0])

        # account for wrist to fingers offset
        R_func = robot_config._calc_R('EE')

        # Use user defined offset if one is specified
        if offset is None:
            OFFSET = robot_config.OFFSET
        else:
            OFFSET = offset

        robot_config.Tx('EE', q=zeros, x=OFFSET)

        # temporarily set backend to nengo if non adaptive if selected so we can
        # still use the weight saving function in dynamics_adaptation.py in the
        # abr_control repo
        if backend is None:
            adapt_backend = 'nengo'
        else:
            adapt_backend = backend

        # create our adaptive controller
        if ee_adaptation and joint_adaptation:
            n_input = 4
            n_output = 3
        elif ee_adaptation and not joint_adaptation:
            n_input = 3
            n_output = 3
            pes_learning_rate /= ki
        elif joint_adaptation and not ee_adaptation:
            n_input = 4
            n_output = 2
        else:
            n_input = 1
            n_output = 1

        # for use with weight estimation, is the number of joint angles being
        # passed to the adaptive controller (not including velocities)
        self.adapt_dim = n_output
        self.decimal_scale = decimal_scale

        # if user specifies an additional mass, use the estimation function to
        # give the adaptive controller a better starting point
        self.additional_mass = additional_mass
        if self.additional_mass != 0 and weights_file is None:
            # if the user knows about the mass at the EE, try and improve
            # our starting point
            self.fake_gravity = np.array(
                [[0, 0, -9.81 * self.additional_mass, 0, 0, 0]]).T
            print('Using mass estimate of %f kg as starting point' %
                  self.additional_mass)
            adapt = signals.DynamicsAdaptation(
                n_input=n_input,
                n_output=n_output,
                n_neurons=n_neurons,
                n_ensembles=n_ensembles,
                pes_learning_rate=pes_learning_rate,
                intercepts=(-0.5, -0.2),
                weights_file=weights_file,
                backend=adapt_backend,
                session=session,
                run=run,
                test_name=test_name,
                autoload=autoload,
                function=self.gravity_estimate,
                probe_weights=probe_weights,
                seed=seed)

        elif friction_bootstrap and weights_file is None:
            # if the user knows about the friction, try and improve
            # our starting point
            print('Using friction estimate as starting point')
            adapt = signals.DynamicsAdaptation(
                n_input=n_input,
                n_output=n_output,
                n_neurons=n_neurons,
                n_ensembles=n_ensembles,
                pes_learning_rate=pes_learning_rate,
                intercepts=(-0.5, -0.2),
                weights_file=weights_file,
                backend=adapt_backend,
                session=session,
                run=run,
                test_name=test_name,
                autoload=autoload,
                function=lambda x: self.friction(x) * -1,
                probe_weights=probe_weights,
                seed=seed)
        else:
            adapt = signals.DynamicsAdaptation(
                n_input=n_input,
                n_output=n_output,
                n_neurons=n_neurons,
                n_ensembles=n_ensembles,
                pes_learning_rate=pes_learning_rate,
                intercepts=(-0.5, -0.2),
                weights_file=weights_file,
                backend=adapt_backend,
                session=session,
                run=run,
                test_name=test_name,
                autoload=autoload,
                probe_weights=probe_weights,
                seed=seed)

        # get save location of saved data
        [location, run_num] = adapt.weights_location(test_name=test_name,
                                                     run=run,
                                                     session=session)
        # if using integral term for PID control, check if previous weights
        # have been saved
        if integrate_err:
            if run_num < 1:
                run_num = 0
                int_err_prev = [0, 0, 0]
            else:
                int_err_prev = np.squeeze(
                    np.load(location + '/run%i_data/int_err%i.npz' %
                            (run_num - 1, run_num - 1))['int_err'])[-1]
        else:
            int_err_prev = None

        # instantiate operational space controller
        print('using int err of: ', int_err_prev)
        ctrlr = OSC(robot_config,
                    kp=kp,
                    kv=kv,
                    ki=ki,
                    vmax=1,
                    null_control=True,
                    int_err=int_err_prev)

        # add joint avoidance controller if specified to do so
        if avoid_limits:
            avoid = signals.AvoidJointLimits(
                robot_config,
                # joint 4 flipped because does not cross 0-2pi line
                min_joint_angles=[None, 1.1, 0.5, 3.5, 2.0, 1.6],
                max_joint_angles=[None, 3.65, 6.25, 6.0, 5.0, 4.6],
                max_torque=[4] * robot_config.N_JOINTS,
                cross_zero=[True, False, False, False, True, False],
                gradient=[False, True, True, True, True, False])
        # if not planning the trajectory (figure8 target) then use a second
        # order filter to smooth out the trajectory to target(s)
        if target_type != 'figure8':
            path = path_planners.SecondOrder(robot_config)
            n_timesteps = 4000
            w = 1e4 / n_timesteps
            zeta = 2
            dt = 0.003

        # run controller once to generate functions / take care of overhead
        # outside of the main loop, because force mode auto-exits after 200ms
        ctrlr.generate(zeros, zeros, np.zeros(3), offset=OFFSET)

        interface = abr_jaco2.Interface(robot_config)

        # connect to and initialize the arm
        interface.connect()
        interface.init_position_mode()
        interface.send_target_angles(robot_config.INIT_TORQUE_POSITION)

        # set up lists for tracking data
        time_track = []
        q_track = []
        dq_track = []
        u_track = []
        adapt_track = []
        error_track = []
        training_track = []
        target_track = []
        ee_track = []
        input_signal = []
        if integrate_err:
            int_err_track = []
        if simulate_wear:
            self.F_prev = np.array([0, 0])
            friction_track = []

        try:
            interface.init_force_mode()
            for ii in range(0, len(PRESET_TARGET)):
                # counter for print statements
                count = 0

                # track loop_time for stopping test
                loop_time = 0

                # get joint angle and velocity feedback
                feedback = interface.get_feedback()
                q = feedback['q']
                dq = feedback['dq']
                dq[abs(dq) < 0.05] = 0
                # calculate end-effector position
                ee_xyz = robot_config.Tx('EE', q=q, x=OFFSET)

                # last three terms used as started point for target EE velocity
                target = np.concatenate((ee_xyz, np.array([0, 0, 0])), axis=0)

                # M A I N   C O N T R O L   L O O P
                while loop_time < time_limit:
                    start = timeit.default_timer()
                    prev_xyz = ee_xyz

                    if target_type == 'vision':
                        TARGET_XYZ = self.get_target_from_camera()
                        TARGET_XYZ = self.normalize_target(TARGET_XYZ)
                        target = path.step(y=target[:3],
                                           dy=target[3:],
                                           target=TARGET_XYZ,
                                           w=w,
                                           zeta=zeta,
                                           dt=dt,
                                           threshold=0.05)

                    elif target_type == 'figure8':
                        y, dy, ddy = dmps.step()
                        target = [0.65, y[0], y[1], 0, dy[0], dy[1]]
                        TARGET_XYZ = target[:3]

                    else:
                        TARGET_XYZ = PRESET_TARGET[ii]
                        target = path.step(y=target[:3],
                                           dy=target[3:],
                                           target=TARGET_XYZ,
                                           w=w,
                                           zeta=zeta,
                                           dt=dt,
                                           threshold=0.05)

                    # calculate euclidean distance to target
                    error = np.sqrt(np.sum((ee_xyz - TARGET_XYZ)**2))

                    # get joint angle and velocity feedback
                    feedback = interface.get_feedback()
                    q = feedback['q']
                    dq = feedback['dq']
                    dq[abs(dq) < 0.05] = 0

                    # calculate end-effector position
                    ee_xyz = robot_config.Tx('EE', q=q, x=OFFSET)

                    if ee_adaptation and joint_adaptation:
                        # adaptive control in state space
                        training_signal = TARGET_XYZ - ee_xyz
                        # calculate the adaptive control signal
                        adapt_input = np.array([
                            robot_config.scaledown('q', q)[1],
                            robot_config.scaledown('q', q)[2],
                            robot_config.scaledown('dq', dq)[1],
                            robot_config.scaledown('dq', dq)[2]
                        ])
                        u_adapt = adapt.generate(
                            input_signal=adapt_input,
                            training_signal=training_signal)
                        u_adapt /= decimal_scale

                    elif ee_adaptation and not joint_adaptation:
                        # adaptive control in state space
                        training_signal = TARGET_XYZ - ee_xyz
                        # scale inputs
                        adapt_input = np.array([(ee_xyz[0] - x_avg) / x_scale,
                                                (ee_xyz[1] - y_avg) / y_scale,
                                                (ee_xyz[2] - z_avg) / z_scale])

                        u_adapt = adapt.generate(
                            input_signal=adapt_input,
                            training_signal=training_signal)
                        u_adapt /= decimal_scale

                    else:
                        u_adapt = None

                    # Need to create controller here, in case using ee
                    # adaptation, it needs to be passed to the OSC controller
                    # calculate the base operation space control signal
                    u_base = ctrlr.generate(q=q,
                                            dq=dq,
                                            target_pos=target[:3],
                                            target_vel=target[3:],
                                            offset=OFFSET,
                                            ee_adapt=u_adapt)

                    # account for stiction in jaco2 base
                    if u_base[0] > 0:
                        u_base[0] *= 3.0
                    else:
                        u_base[0] *= 2.0

                    if joint_adaptation and not ee_adaptation:
                        training_signal = np.array([
                            ctrlr.training_signal[1], ctrlr.training_signal[2]
                        ])
                        # calculate the adaptive control signal
                        adapt_input = np.array([
                            robot_config.scaledown('q', q)[1],
                            robot_config.scaledown('q', q)[2],
                            robot_config.scaledown('dq', dq)[1],
                            robot_config.scaledown('dq', dq)[2]
                        ])
                        u_adapt = adapt.generate(
                            input_signal=adapt_input,
                            training_signal=training_signal)

                        # add adaptive signal to base controller
                        u_adapt = np.array([
                            0, u_adapt[0] / decimal_scale,
                            u_adapt[1] / decimal_scale, 0, 0, 0
                        ])
                        u = u_base + u_adapt

                    elif redis_adaptation:
                        training_signal = np.array([
                            ctrlr.training_signal[1], ctrlr.training_signal[2]
                        ])
                        adapt_input = np.array([
                            robot_config.scaledown('q', q)[1],
                            robot_config.scaledown('q', q)[2],
                            robot_config.scaledown('dq', dq)[1],
                            robot_config.scaledown('dq', dq)[2]
                        ])
                        # send input information to redis
                        redis_server.set(
                            'input_signal', '%.3f %.3f %.3f %.3f' %
                            (adapt_input[0], adapt_input[1], adapt_input[2],
                             adapt_input[3]))
                        redis_server.set(
                            'training_signal', '%.3f %.3f' %
                            (training_signal[0], training_signal[1]))
                        # get adaptive output back
                        u_adapt = redis_server.get('u_adapt').decode('ascii')
                        u_adapt = np.array(
                            [float(val) for val in u_adapt.split()])
                        u_adapt = np.array(
                            [0, u_adapt[0], u_adapt[1], 0, 0, 0])
                        u = u_base + u_adapt

                    else:
                        u = u_base
                        u_adapt = None
                        training_signal = np.zeros(3)
                        adapt_input = np.zeros(3)

                    # add limit avoidance if True
                    if avoid_limits:
                        u += avoid.generate(q)

                    if simulate_wear:
                        u_friction = self.friction(dq[1:3])
                        u += [0, u_friction[0], u_friction[1], 0, 0, 0]
                        friction_track.append(np.copy(u_friction))

                    # send forces
                    interface.send_forces(np.array(u, dtype='float32'))

                    # track data
                    q_track.append(np.copy(q))
                    dq_track.append(np.copy(dq))
                    u_track.append(np.copy(u))
                    adapt_track.append(np.copy(u_adapt))
                    error_track.append(np.copy(error))
                    training_track.append(np.copy(training_signal))
                    end = timeit.default_timer() - start
                    loop_time += end
                    time_track.append(np.copy(end))
                    target_track.append(np.copy(TARGET_XYZ))
                    input_signal.append(np.copy(adapt_input))
                    ee_track.append(np.copy(ee_xyz))
                    if integrate_err:
                        int_err_track.append(np.copy(ctrlr.int_err))

                    if count % 1000 == 0:
                        print('error: ', error)
                        print('dt: ', end)
                        #print('adapt: ', u_adapt)
                        #print('int_err/ki: ', ctrlr.int_err)
                        #print('q: ', q)
                        #print('hand: ', ee_xyz)
                        #print('target: ', target)
                        #print('control: ', u_base)

                    count += 1

        except:
            print(traceback.format_exc())

        finally:
            # close the connection to the arm
            interface.init_position_mode()

            print("RUN IN IS: ", run)
            # get save location of weights to save tracked data in same directory
            [location, run_num] = adapt.weights_location(test_name=test_name,
                                                         run=run,
                                                         session=session)
            print("RUN OUT IS: ", run_num)
            if backend != None:
                run_num += 1

                # Save the learned weights
                adapt.save_weights(test_name=test_name,
                                   session=session,
                                   run=run)

            interface.send_target_angles(robot_config.INIT_TORQUE_POSITION)
            interface.disconnect()

            print('**** RUN STATS ****')
            print('Average loop speed: ', sum(time_track) / len(time_track))
            print('AVG Error/Step: ', sum(error_track) / len(error_track))
            print('Run number ', run_num)
            print('Saving tracked data to ',
                  location + '/run%i_data' % (run_num))
            print('*******************')

            time_track = np.array(time_track)
            q_track = np.array(q_track)
            u_track = np.array(u_track)
            adapt_track = np.array(adapt_track)
            error_track = np.array(error_track)
            training_track = np.array(training_track)
            input_signal = np.array(input_signal)
            ee_track = np.array(ee_track)
            dq_track = np.array(dq_track)
            if integrate_err:
                int_err_track = np.array(int_err_track)
            if simulate_wear:
                friction_track = np.array(friction_track)

            if not os.path.exists(location + '/run%i_data' % (run_num)):
                os.makedirs(location + '/run%i_data' % (run_num))

            np.savez_compressed(location + '/run%i_data/q%i' %
                                (run_num, run_num),
                                q=[q_track])
            np.savez_compressed(location + '/run%i_data/time%i' %
                                (run_num, run_num),
                                time=[time_track])
            np.savez_compressed(location + '/run%i_data/u%i' %
                                (run_num, run_num),
                                u=[u_track])
            np.savez_compressed(location + '/run%i_data/adapt%i' %
                                (run_num, run_num),
                                adapt=[adapt_track])
            np.savez_compressed(location + '/run%i_data/target%i' %
                                (run_num, run_num),
                                target=[target_track])
            np.savez_compressed(location + '/run%i_data/error%i' %
                                (run_num, run_num),
                                error=[error_track])
            np.savez_compressed(location + '/run%i_data/training%i' %
                                (run_num, run_num),
                                training=[training_track])
            np.savez_compressed(location + '/run%i_data/input_signal%i' %
                                (run_num, run_num),
                                input_signal=[input_signal])
            np.savez_compressed(location + '/run%i_data/ee_xyz%i' %
                                (run_num, run_num),
                                ee_xyz=[ee_track])
            np.savez_compressed(location + '/run%i_data/dq%i' %
                                (run_num, run_num),
                                dq=[dq_track])
            if probe_weights:
                np.savez_compressed(
                    location + '/run%i_data/probe%i' % (run_num, run_num),
                    probe=[adapt.sim.data[adapt.nengo_model.weights_probe]])
            if integrate_err:
                np.savez_compressed(location + '/run%i_data/int_err%i' %
                                    (run_num, run_num),
                                    int_err=[int_err_track])
            if simulate_wear:
                np.savez_compressed(location + '/run%i_data/friction%i' %
                                    (run_num, run_num),
                                    friction=[friction_track])

            if backend != 'nengo_spinnaker':
                # give user time to pause before the next run starts, only
                # works if looping through tests using a bash script
                import time
                print('2 Seconds to pause: Hit ctrl z to pause, fg to resume')
                time.sleep(1)
                print('1 Second to pause')
                time.sleep(1)
                print('Starting next test...')
Пример #2
0
try:
    import pydmps
except ImportError:
    print("\npydmps library required, see " +
          "https://github.com/studywolf/pydmps\n")

from abr_control.arms import threejoint as arm

# from abr_control.arms import twojoint as arm
from abr_control.interfaces.pygame import PyGame
from abr_control.controllers import OSC, Damping

# create a dmp that traces a circle
x = np.linspace(0, np.pi * 2, 100)
dmps_traj = np.array([np.cos(x) * 0.5, np.sin(x) * 0.5 + 2])
dmps = pydmps.DMPs_rhythmic(n_dmps=2, n_bfs=50, dt=0.01)
dmps.imitate_path(dmps_traj)

# initialize our robot config
robot_config = arm.Config()
# create our arm simulation
arm_sim = arm.ArmSim(robot_config)

# damp the movements of the arm
damping = Damping(robot_config, kv=10)
# create an operational space controller
ctrlr = OSC(
    robot_config,
    kp=500,
    null_controllers=[damping],
    vmax=[20, 0],  # [m/s, rad/s]