示例#1
0
    def test_explicit_steppers(self, explicit_stepper):
        collective_system = ScalarExponentialDampedHarmonicOscillatorCollectiveSystem(
        )
        final_time = 1.0
        n_steps = 500
        stepper = explicit_stepper()

        dt = np.float64(float(final_time) / n_steps)
        time = np.float64(0.0)
        tol = Tolerance.atol()

        # Before stepping, let's extend the interface of the stepper
        # while providing memory slots
        from elastica.systems import make_memory_for_explicit_stepper

        memory_collection = make_memory_for_explicit_stepper(
            stepper, collective_system)
        from elastica.timestepper import extend_stepper_interface

        extend_stepper_interface(stepper, collective_system)

        while np.abs(final_time - time) > 1e5 * tol:
            time = stepper.do_step(collective_system, memory_collection, time,
                                   dt)

        for system in collective_system:
            assert_allclose(
                system.state,
                system.analytical_solution(final_time),
                rtol=Tolerance.rtol(),
                atol=Tolerance.atol(),
            )
示例#2
0
    def test_symplectic_stepper_throws_for_bad_stepper(self,
                                                       stepper_and_interface):
        system = ScalarExponentialDecaySystem()
        (stepper_cls, interface_cls) = stepper_and_interface
        stepper = stepper_cls()

        assert interface_cls not in stepper.__class__.__bases__

        with pytest.raises(NotImplementedError) as excinfo:
            extend_stepper_interface(stepper, system)
        assert "steppers are supported" in str(excinfo.value)
示例#3
0
    def test_symplectic_stepper_interface_for_collective_systems(
            self, stepper_and_interface):
        system = SymplecticUndampedHarmonicOscillatorCollectiveSystem()
        (stepper_cls, interface_cls) = stepper_and_interface
        stepper = stepper_cls()

        assert interface_cls not in stepper.__class__.__bases__

        extend_stepper_interface(stepper, system)

        assert interface_cls in stepper.__class__.__bases__
示例#4
0
    def test_symplectic_stepper_interface_for_simple_systems(
            self, stepper_and_interface):
        system = ScalarExponentialDecaySystem()
        (stepper_cls, interface_cls) = stepper_and_interface
        stepper = stepper_cls()

        assert interface_cls not in stepper.__class__.__bases__

        extend_stepper_interface(stepper, system)

        assert interface_cls in stepper.__class__.__bases__
示例#5
0
    def test_symplectic_stepper_interface_for_simple_systems(
            self, stepper_and_interface):
        system = ScalarExponentialDecaySystem()
        (stepper_cls, interface_cls) = stepper_and_interface
        stepper = stepper_cls()

        stepper_methods = None
        assert stepper_methods is None

        _, stepper_methods = extend_stepper_interface(stepper, system)

        assert stepper_methods
示例#6
0
    def test_symplectic_stepper_interface_for_collective_systems(
            self, stepper_and_interface):
        system = SymplecticUndampedHarmonicOscillatorCollectiveSystem()
        (stepper_cls, interface_cls) = stepper_and_interface
        stepper = stepper_cls()

        stepper_methods = None
        assert stepper_methods is None

        _, stepper_methods = extend_stepper_interface(stepper, system)

        assert stepper_methods
示例#7
0
    def test_explicit_steppers(self, explicit_stepper):
        collective_system = ScalarExponentialDampedHarmonicOscillatorCollectiveSystem(
        )
        final_time = 1.0
        if explicit_stepper == EulerForward:
            # Euler requires very small time-steps and in order not to slow down test,
            # we are scaling the difference between analytical and numerical solution.
            n_steps = 25000
            scale = 1e3
        else:
            n_steps = 500
            scale = 1

        stepper = explicit_stepper()

        dt = np.float64(float(final_time) / n_steps)
        time = np.float64(0.0)
        tol = Tolerance.atol()

        # Before stepping, let's extend the interface of the stepper
        # while providing memory slots
        from elastica.systems import make_memory_for_explicit_stepper

        memory_collection = make_memory_for_explicit_stepper(
            stepper, collective_system)
        from elastica.timestepper import extend_stepper_interface

        do_step, stagets_and_updates = extend_stepper_interface(
            stepper, collective_system)

        while np.abs(final_time - time) > 1e5 * tol:
            time = do_step(
                stepper,
                stagets_and_updates,
                collective_system,
                memory_collection,
                time,
                dt,
            )

        for system in collective_system:
            assert_allclose(
                system.state,
                system.analytical_solution(final_time),
                rtol=Tolerance.rtol() * scale,
                atol=Tolerance.atol() * scale,
            )
    def reset(self):
        """

        This class method, resets and creates the simulation environment. First,
        Elastica rod (or arm) is initialized and boundary conditions acting on the rod defined.
        Second, target and if there are obstacles are initialized and append to the
        simulation. Finally, call back functions are set for Elastica rods and rigid bodies.

        Returns
        -------

        """
        self.simulator = BaseSimulator()

        # setting up test params
        n_elem = self.n_elem
        start = np.zeros((3, ))
        start[1] = self.target_position[1]
        direction = np.array([0.0, 0.0,
                              1.0])  # rod direction: pointing upwards
        normal = np.array([0.0, 1.0, 0.0])
        binormal = np.cross(direction, normal)

        density = 1000
        nu = self.NU  # dissipation coefficient
        E = self.E  # Young's Modulus
        poisson_ratio = 0.5

        # Set the arm properties after defining rods
        base_length = 1.0  # rod base length
        radius_tip = 0.05  # radius of the arm at the tip
        radius_base = 0.05  # radius of the arm at the base

        radius_along_rod = np.linspace(radius_base, radius_tip, n_elem)

        # Arm is shearable Cosserat rod
        self.shearable_rod = CosseratRod.straight_rod(
            n_elem,
            start,
            direction,
            normal,
            base_length,
            base_radius=radius_along_rod,
            density=density,
            nu=nu,
            youngs_modulus=E,
            poisson_ratio=poisson_ratio,
        )

        # Now rod is ready for simulation, append rod to simulation
        self.simulator.append(self.shearable_rod)

        if self.mode != 2:
            # fixed target position to reach
            target_position = self.target_position

        if self.mode == 2 or self.mode == 4:
            # random target position to reach with boundary
            t_x = np.random.uniform(self.boundary[0], self.boundary[1])
            t_y = np.random.uniform(self.boundary[2], self.boundary[3])
            if self.dim == 2.0 or self.dim == 2.5:
                t_z = np.random.uniform(self.boundary[4], self.boundary[5]) * 0
            elif self.dim == 3.0 or self.dim == 3.5:
                t_z = np.random.uniform(self.boundary[4], self.boundary[5])

            print("Target position:", t_x, t_y, t_z)
            target_position = np.array([t_x, t_y, t_z])

        # initialize target sphere
        self.sphere = Sphere(
            center=target_position,  # initialize target position of the ball
            base_radius=0.05,
            density=1000,
        )

        if self.mode == 3:
            self.dir_indicator = 1
            self.sphere_initial_velocity = self.target_v
            self.sphere.velocity_collection[..., 0] = [
                self.sphere_initial_velocity,
                0.0,
                0.0,
            ]

        if self.mode == 4:

            self.rand_direction_1 = np.pi * np.random.uniform(0, 2)
            if self.dim == 2.0 or self.dim == 2.5:
                self.rand_direction_2 = np.pi / 2.0
            elif self.dim == 3.0 or self.dim == 3.5:
                self.rand_direction_2 = np.pi * np.random.uniform(0, 2)

            self.v_x = (self.target_v * np.cos(self.rand_direction_1) *
                        np.sin(self.rand_direction_2))
            self.v_y = (self.target_v * np.sin(self.rand_direction_1) *
                        np.sin(self.rand_direction_2))
            self.v_z = self.target_v * np.cos(self.rand_direction_2)

            self.sphere.velocity_collection[..., 0] = [
                self.v_x,
                self.v_y,
                self.v_z,
            ]
            self.boundaries = np.array(self.boundary)

        # Set rod and sphere directors to each other.
        self.sphere.director_collection[
            ..., 0] = self.shearable_rod.director_collection[..., 0]
        self.simulator.append(self.sphere)

        class WallBoundaryForSphere(FreeRod):
            """

            This class generates a bounded space that sphere can move inside. If sphere
            hits one of the boundaries (walls) of this space, it is reflected in opposite direction
            with the same velocity magnitude.

            """
            def __init__(self, boundaries):
                self.x_boundary_low = boundaries[0]
                self.x_boundary_high = boundaries[1]
                self.y_boundary_low = boundaries[2]
                self.y_boundary_high = boundaries[3]
                self.z_boundary_low = boundaries[4]
                self.z_boundary_high = boundaries[5]

            def constrain_values(self, sphere, time):
                pos_x = sphere.position_collection[0]
                pos_y = sphere.position_collection[1]
                pos_z = sphere.position_collection[2]

                radius = sphere.radius

                vx = sphere.velocity_collection[0]
                vy = sphere.velocity_collection[1]
                vz = sphere.velocity_collection[2]

                if (pos_x - radius) < self.x_boundary_low:
                    sphere.velocity_collection[:] = np.array([-vx, vy, vz])

                if (pos_x + radius) > self.x_boundary_high:
                    sphere.velocity_collection[:] = np.array([-vx, vy, vz])

                if (pos_y - radius) < self.y_boundary_low:
                    sphere.velocity_collection[:] = np.array([vx, -vy, vz])

                if (pos_y + radius) > self.y_boundary_high:
                    sphere.velocity_collection[:] = np.array([vx, -vy, vz])

                if (pos_z - radius) < self.z_boundary_low:
                    sphere.velocity_collection[:] = np.array([vx, vy, -vz])

                if (pos_z + radius) > self.z_boundary_high:
                    sphere.velocity_collection[:] = np.array([vx, vy, -vz])

            def constrain_rates(self, sphere, time):
                pass

        if self.mode == 4:
            self.simulator.constrain(self.sphere).using(
                WallBoundaryForSphere, boundaries=self.boundaries)

        # Add boundary constraints as fixing one end
        self.simulator.constrain(self.shearable_rod).using(
            OneEndFixedRod,
            constrained_position_idx=(0, ),
            constrained_director_idx=(0, ))

        # Add muscle torques acting on the arm for actuation
        # MuscleTorquesWithVaryingBetaSplines uses the control points selected by RL to
        # generate torques along the arm.
        self.torque_profile_list_for_muscle_in_normal_dir = defaultdict(list)
        self.spline_points_func_array_normal_dir = []
        # Apply torques
        self.simulator.add_forcing_to(self.shearable_rod).using(
            MuscleTorquesWithVaryingBetaSplines,
            base_length=base_length,
            number_of_control_points=self.number_of_control_points,
            points_func_array=self.spline_points_func_array_normal_dir,
            muscle_torque_scale=self.alpha,
            direction=str("normal"),
            step_skip=self.step_skip,
            torque_profile_recorder=self.
            torque_profile_list_for_muscle_in_normal_dir,
        )

        self.torque_profile_list_for_muscle_in_binormal_dir = defaultdict(list)
        self.spline_points_func_array_binormal_dir = []
        # Apply torques
        self.simulator.add_forcing_to(self.shearable_rod).using(
            MuscleTorquesWithVaryingBetaSplines,
            base_length=base_length,
            number_of_control_points=self.number_of_control_points,
            points_func_array=self.spline_points_func_array_binormal_dir,
            muscle_torque_scale=self.alpha,
            direction=str("binormal"),
            step_skip=self.step_skip,
            torque_profile_recorder=self.
            torque_profile_list_for_muscle_in_binormal_dir,
        )

        self.torque_profile_list_for_muscle_in_tangent_dir = defaultdict(list)
        self.spline_points_func_array_tangent_dir = []
        # Apply torques
        self.simulator.add_forcing_to(self.shearable_rod).using(
            MuscleTorquesWithVaryingBetaSplines,
            base_length=base_length,
            number_of_control_points=self.number_of_control_points,
            points_func_array=self.spline_points_func_array_tangent_dir,
            muscle_torque_scale=self.beta,
            direction=str("tangent"),
            step_skip=self.step_skip,
            torque_profile_recorder=self.
            torque_profile_list_for_muscle_in_tangent_dir,
        )

        # Call back function to collect arm data from simulation
        class ArmMuscleBasisCallBack(CallBackBaseClass):
            """
            Call back function for Elastica rod
            """
            def __init__(
                self,
                step_skip: int,
                callback_params: dict,
            ):
                CallBackBaseClass.__init__(self)
                self.every = step_skip
                self.callback_params = callback_params

            def make_callback(self, system, time, current_step: int):
                if current_step % self.every == 0:
                    self.callback_params["time"].append(time)
                    self.callback_params["step"].append(current_step)
                    self.callback_params["position"].append(
                        system.position_collection.copy())
                    self.callback_params["radius"].append(system.radius.copy())
                    self.callback_params["com"].append(
                        system.compute_position_center_of_mass())
                    self.callback_params["directors"].append(
                        system.director_collection.copy())

                    return

        # Call back function to collect target sphere data from simulation
        class RigidSphereCallBack(CallBackBaseClass):
            """
            Call back function for target sphere
            """
            def __init__(self, step_skip: int, callback_params: dict):
                CallBackBaseClass.__init__(self)
                self.every = step_skip
                self.callback_params = callback_params

            def make_callback(self, system, time, current_step: int):
                if current_step % self.every == 0:
                    self.callback_params["time"].append(time)
                    self.callback_params["step"].append(current_step)
                    self.callback_params["position"].append(
                        system.position_collection.copy())
                    self.callback_params["radius"].append(
                        copy.deepcopy(system.radius))
                    self.callback_params["com"].append(
                        system.compute_position_center_of_mass())
                    self.callback_params["directors"].append(
                        system.director_collection.copy())

                    return

        # Call back function to collect obstacle data from simulation
        class RigidCylinderCallBack(CallBackBaseClass):
            """
            Call back function for rigid cylinders or obstacles.
            """
            def __init__(self, step_skip: int, callback_params: dict):
                CallBackBaseClass.__init__(self)
                self.every = step_skip
                self.callback_params = callback_params

            def make_callback(self, system, time, current_step: int):
                if current_step % self.every == 0:
                    self.callback_params["time"].append(time)
                    self.callback_params["step"].append(current_step)
                    self.callback_params["position"].append(
                        system.position_collection.copy())
                    self.callback_params["com"].append(
                        system.compute_position_center_of_mass())

                    return

        """ Add Obstacles to the environment """

        self.obstacle = [None for _ in range(self.N_OBSTACLE)]
        self.obstacle_histories = [
            defaultdict(list) for _ in range(self.N_OBSTACLE)
        ]
        self.obstacle_radii = [0.1 for _ in range(self.N_OBSTACLE)]
        self.obstacle_length = [1.0 for _ in range(self.N_OBSTACLE)]
        obstacle_direction = np.array([0.0, 1.0, 0.0])
        self.obstacle_states = np.zeros(
            (self.number_of_points_on_cylinder * self.N_OBSTACLE, 3))

        if self.N_OBSTACLE > 0:

            com_target = self.sphere.position_collection[..., -1]

            start_position_array = np.zeros((3, self.N_OBSTACLE))

            # first obstacle
            start_position_array[
                0, 0] = com_target[0] + 0.1 + self.obstacle_radii[0]
            start_position_array[
                1, 0] = com_target[1] - 0.5 * self.obstacle_length[0]
            start_position_array[
                2, 0] = com_target[2] - 0.3 * self.obstacle_radii[0]

            # second obstacle
            start_position_array[0, 1] = (com_target[0] + 0.1 +
                                          3 * self.obstacle_radii[1] + 0.2)
            start_position_array[
                1, 1] = com_target[1] - 0.5 * self.obstacle_length[1]
            start_position_array[
                2, 1] = com_target[2] - 0.3 * self.obstacle_radii[1]

            # third obstacle
            start_position_array[0, 2] = (com_target[0] + 0.1 +
                                          2 * self.obstacle_radii[2] + 0.1)
            start_position_array[
                1, 2] = com_target[1] - 0.5 * self.obstacle_length[2]
            start_position_array[
                2, 2] = com_target[2] + 2.2 * self.obstacle_radii[2]

            # Forth obstacle underneath the third obstacle
            start_position_array[0, 3] = start_position_array[0, 2]
            start_position_array[1, 3] = start_position_array[1, 2]
            start_position_array[2, 3] = (start_position_array[2, 1] -
                                          0.2 * self.obstacle_radii[1] -
                                          self.obstacle_radii[3])

            # Fifth obstacle top of the  third obstacle
            start_position_array[0, 4] = start_position_array[0, 2]
            start_position_array[1, 4] = start_position_array[1, 2]
            start_position_array[2, 4] = (start_position_array[2, 3] -
                                          self.obstacle_radii[1] -
                                          self.obstacle_radii[4])

            # Sixth obstacle top of the fifth obstacle
            start_position_array[0, 5] = start_position_array[0, 4]
            start_position_array[1, 5] = start_position_array[1, 4]
            start_position_array[2, 5] = (start_position_array[2, 4] -
                                          self.obstacle_radii[1] -
                                          self.obstacle_radii[5])

            # Seventh obstacle top of the third obstacle
            start_position_array[0, 6] = start_position_array[0, 2]
            start_position_array[1, 6] = start_position_array[1, 2]
            start_position_array[2, 6] = (start_position_array[2, 2] +
                                          self.obstacle_radii[1] +
                                          self.obstacle_radii[6])

            # Eight obstacle top of the third obstacle
            start_position_array[0, 7] = start_position_array[0, 6]
            start_position_array[1, 7] = start_position_array[1, 6]
            start_position_array[2, 7] = (start_position_array[2, 6] +
                                          self.obstacle_radii[6] +
                                          self.obstacle_radii[7])

            for i in range(self.N_OBSTACLE):

                start = start_position_array[..., i]

                self.obstacle[i] = Cylinder(
                    start=start,
                    direction=obstacle_direction,
                    normal=normal,
                    base_length=self.obstacle_length[i],
                    base_radius=self.obstacle_radii[i],
                    density=1000,
                )

                # Obstacle information for state
                # obstacle_states(idx_on_obstacle, coordinates_x_y_z)
                self.obstacle_states[
                    i * self.number_of_points_on_cylinder:(i + 1) *
                    self.number_of_points_on_cylinder, :, ] = (
                        start.reshape(3, 1) +
                        obstacle_direction.reshape(3, 1) *
                        np.linspace(0, self.obstacle_length[i],
                                    self.number_of_points_on_cylinder)).T

                self.obstacle_histories[i]["radius"] = self.obstacle_radii[i]
                self.obstacle_histories[i]["height"] = self.obstacle_length[i]
                self.obstacle_histories[i][
                    "direction"] = obstacle_direction.copy()

                # We use scatter plot to plot obstacles, thus we need to discretize the obstacle and
                # save the position of the discretized elements. This is only used for plotting and
                # rendering purposes.

                n_elem_for_plotting = 40
                position_collection_for_plotting = np.zeros(
                    (3, n_elem_for_plotting))
                end = (start_position_array[..., i] +
                       obstacle_direction * self.obstacle_length[i])
                for k in range(0, 3):
                    position_collection_for_plotting[k, ...] = np.linspace(
                        start_position_array[..., i][k], end[k],
                        n_elem_for_plotting)

                self.obstacle_histories[i][
                    "position_plotting"] = position_collection_for_plotting.copy(
                    )

                self.simulator.append(self.obstacle[i])

                # Constraint obstacle positions
                self.simulator.constrain(self.obstacle[i]).using(
                    OneEndFixedRod,
                    constrained_position_idx=(0, ),
                    constrained_director_idx=(0, ),
                )

                # Add external contact
                self.simulator.connect(
                    self.shearable_rod, self.obstacle[i]).using(
                        ExternalContact, k=8e4,
                        nu=4.0)  # for rendering and plotting k=2*8e4, nu=4.0
        """ Add Obstacles to the environment """

        if self.COLLECT_DATA_FOR_POSTPROCESSING:
            # Collect data using callback function for postprocessing
            self.post_processing_dict_rod = defaultdict(list)
            # list which collected data will be append
            # set the diagnostics for rod and collect data
            self.simulator.collect_diagnostics(self.shearable_rod).using(
                ArmMuscleBasisCallBack,
                step_skip=self.step_skip,
                callback_params=self.post_processing_dict_rod,
            )

            self.post_processing_dict_sphere = defaultdict(list)
            # list which collected data will be append
            # set the diagnostics for cylinder and collect data
            self.simulator.collect_diagnostics(self.sphere).using(
                RigidSphereCallBack,
                step_skip=self.step_skip,
                callback_params=self.post_processing_dict_sphere,
            )

            for i in range(self.N_OBSTACLE):
                self.simulator.collect_diagnostics(self.obstacle[i]).using(
                    RigidCylinderCallBack,
                    step_skip=self.step_skip,
                    callback_params=self.obstacle_histories[i],
                )

        # Finalize simulation environment. After finalize, you cannot add
        # any forcing, constrain or call back functions
        self.simulator.finalize()

        # do_step, stages_and_updates will be used in step function
        self.do_step, self.stages_and_updates = extend_stepper_interface(
            self.StatefulStepper, self.simulator)

        # set state
        state = self.get_state()

        # reset on_goal
        self.on_goal = 0
        # reset current_step
        self.current_step = 0
        # reset time_tracker
        self.time_tracker = np.float64(0.0)
        # reset previous_action
        self.previous_action = None

        # After resetting the environment return state information
        return state
示例#9
0
    def reset(self):
        """

        This class method, resets and creates the simulation environment. First,
        Elastica rod (or arm) is initialized and boundary conditions acting on the rod defined.
        Second, target and if there are obstacles are initialized and append to the
        simulation. Finally, call back functions are set for Elastica rods and rigid bodies.

        Returns
        -------

        """
        self.simulator = BaseSimulator()

        # setting up test params
        n_elem = self.n_elem
        start = np.zeros((3,))
        direction = np.array([0.0, 1.0, 0.0])  # rod direction: pointing upwards
        normal = np.array([0.0, 0.0, 1.0])
        binormal = np.cross(direction, normal)

        density = 1000
        nu = self.NU  # dissipation coefficient
        E = self.E  # Young's Modulus
        poisson_ratio = 0.5

        # Set the arm properties after defining rods
        base_length = 1.0  # rod base length
        radius_tip = 0.05  # radius of the arm at the tip
        radius_base = 0.05  # radius of the arm at the base

        radius_along_rod = np.linspace(radius_base, radius_tip, n_elem)

        # Arm is shearable Cosserat rod
        self.shearable_rod = CosseratRod.straight_rod(
            n_elem,
            start,
            direction,
            normal,
            base_length,
            base_radius=radius_along_rod,
            density=density,
            nu=nu,
            youngs_modulus=E,
            poisson_ratio=poisson_ratio,
        )

        # Now rod is ready for simulation, append rod to simulation
        self.simulator.append(self.shearable_rod)
        # self.mode = 4
        if self.mode != 2:
            # fixed target position to reach
            target_position = self.target_position

        if self.mode == 2 or self.mode == 4:
            # random target position to reach with boundary
            t_x = np.random.uniform(self.boundary[0], self.boundary[1])
            t_y = np.random.uniform(self.boundary[2], self.boundary[3])
            if self.dim == 2.0 or self.dim == 2.5:
                t_z = np.random.uniform(self.boundary[4], self.boundary[5]) * 0
            elif self.dim == 3.0 or self.dim == 3.5:
                t_z = np.random.uniform(self.boundary[4], self.boundary[5])

            print("Target position:", t_x, t_y, t_z)
            target_position = np.array([t_x, t_y, t_z])

        # initialize sphere
        self.sphere = Sphere(
            center=target_position,  # initialize target position of the ball
            base_radius=0.05,
            density=1000,
        )

        if self.mode == 3:
            self.dir_indicator = 1
            self.sphere_initial_velocity = self.target_v
            self.sphere.velocity_collection[..., 0] = [
                self.sphere_initial_velocity,
                0.0,
                0.0,
            ]

        if self.mode == 4:

            self.trajectory_iteration = 0  # for changing directions
            self.rand_direction_1 = np.pi * np.random.uniform(0, 2)
            if self.dim == 2.0 or self.dim == 2.5:
                self.rand_direction_2 = np.pi / 2.0
            elif self.dim == 3.0 or self.dim == 3.5:
                self.rand_direction_2 = np.pi * np.random.uniform(0, 2)

            self.v_x = (
                self.target_v
                * np.cos(self.rand_direction_1)
                * np.sin(self.rand_direction_2)
            )
            self.v_y = (
                self.target_v
                * np.sin(self.rand_direction_1)
                * np.sin(self.rand_direction_2)
            )
            self.v_z = self.target_v * np.cos(self.rand_direction_2)

            self.sphere.velocity_collection[..., 0] = [
                self.v_x,
                self.v_y,
                self.v_z,
            ]
            self.boundaries = np.array(self.boundary)

        # Set rod and sphere directors to each other.
        self.sphere.director_collection[
            ..., 0
        ] = self.shearable_rod.director_collection[..., 0]
        self.simulator.append(self.sphere)

        class WallBoundaryForSphere(FreeRod):
            """

            This class generates a bounded space that sphere can move inside. If sphere
            hits one of the boundaries (walls) of this space, it is reflected in opposite direction
            with the same velocity magnitude.

            """

            def __init__(self, boundaries):
                self.x_boundary_low = boundaries[0]
                self.x_boundary_high = boundaries[1]
                self.y_boundary_low = boundaries[2]
                self.y_boundary_high = boundaries[3]
                self.z_boundary_low = boundaries[4]
                self.z_boundary_high = boundaries[5]

            def constrain_values(self, sphere, time):
                pos_x = sphere.position_collection[0]
                pos_y = sphere.position_collection[1]
                pos_z = sphere.position_collection[2]

                radius = sphere.radius

                vx = sphere.velocity_collection[0]
                vy = sphere.velocity_collection[1]
                vz = sphere.velocity_collection[2]

                if (pos_x - radius) < self.x_boundary_low:
                    sphere.velocity_collection[:] = np.array([-vx, vy, vz])

                if (pos_x + radius) > self.x_boundary_high:
                    sphere.velocity_collection[:] = np.array([-vx, vy, vz])

                if (pos_y - radius) < self.y_boundary_low:
                    sphere.velocity_collection[:] = np.array([vx, -vy, vz])

                if (pos_y + radius) > self.y_boundary_high:
                    sphere.velocity_collection[:] = np.array([vx, -vy, vz])

                if (pos_z - radius) < self.z_boundary_low:
                    sphere.velocity_collection[:] = np.array([vx, vy, -vz])

                if (pos_z + radius) > self.z_boundary_high:
                    sphere.velocity_collection[:] = np.array([vx, vy, -vz])

            def constrain_rates(self, sphere, time):
                pass

        if self.mode == 4:
            self.simulator.constrain(self.sphere).using(
                WallBoundaryForSphere, boundaries=self.boundaries
            )

        # Add boundary constraints as fixing one end
        self.simulator.constrain(self.shearable_rod).using(
            OneEndFixedRod, constrained_position_idx=(0,), constrained_director_idx=(0,)
        )

        # Add muscle torques acting on the arm for actuation
        # MuscleTorquesWithVaryingBetaSplines uses the control points selected by RL to
        # generate torques along the arm.
        self.torque_profile_list_for_muscle_in_normal_dir = defaultdict(list)
        self.spline_points_func_array_normal_dir = []
        # Apply torques
        self.simulator.add_forcing_to(self.shearable_rod).using(
            MuscleTorquesWithVaryingBetaSplines,
            base_length=base_length,
            number_of_control_points=self.number_of_control_points,
            points_func_array=self.spline_points_func_array_normal_dir,
            muscle_torque_scale=self.alpha,
            direction=str("normal"),
            step_skip=self.step_skip,
            max_rate_of_change_of_activation=self.max_rate_of_change_of_activation,
            torque_profile_recorder=self.torque_profile_list_for_muscle_in_normal_dir,
        )

        self.torque_profile_list_for_muscle_in_binormal_dir = defaultdict(list)
        self.spline_points_func_array_binormal_dir = []
        # Apply torques
        self.simulator.add_forcing_to(self.shearable_rod).using(
            MuscleTorquesWithVaryingBetaSplines,
            base_length=base_length,
            number_of_control_points=self.number_of_control_points,
            points_func_array=self.spline_points_func_array_binormal_dir,
            muscle_torque_scale=self.alpha,
            direction=str("binormal"),
            step_skip=self.step_skip,
            max_rate_of_change_of_activation=self.max_rate_of_change_of_activation,
            torque_profile_recorder=self.torque_profile_list_for_muscle_in_binormal_dir,
        )

        self.torque_profile_list_for_muscle_in_twist_dir = defaultdict(list)
        self.spline_points_func_array_twist_dir = []
        # Apply torques
        self.simulator.add_forcing_to(self.shearable_rod).using(
            MuscleTorquesWithVaryingBetaSplines,
            base_length=base_length,
            number_of_control_points=self.number_of_control_points,
            points_func_array=self.spline_points_func_array_twist_dir,
            muscle_torque_scale=self.beta,
            direction=str("tangent"),
            step_skip=self.step_skip,
            max_rate_of_change_of_activation=self.max_rate_of_change_of_activation,
            torque_profile_recorder=self.torque_profile_list_for_muscle_in_twist_dir,
        )

        # Call back function to collect arm data from simulation
        class ArmMuscleBasisCallBack(CallBackBaseClass):
            """
            Call back function for Elastica rod
            """

            def __init__(
                self, step_skip: int, callback_params: dict,
            ):
                CallBackBaseClass.__init__(self)
                self.every = step_skip
                self.callback_params = callback_params

            def make_callback(self, system, time, current_step: int):
                if current_step % self.every == 0:
                    self.callback_params["time"].append(time)
                    self.callback_params["step"].append(current_step)
                    self.callback_params["position"].append(
                        system.position_collection.copy()
                    )
                    self.callback_params["radius"].append(system.radius.copy())
                    self.callback_params["com"].append(
                        system.compute_position_center_of_mass()
                    )

                    return

        # Call back function to collect target sphere data from simulation
        class RigidSphereCallBack(CallBackBaseClass):
            """
            Call back function for target sphere
            """

            def __init__(self, step_skip: int, callback_params: dict):
                CallBackBaseClass.__init__(self)
                self.every = step_skip
                self.callback_params = callback_params

            def make_callback(self, system, time, current_step: int):
                if current_step % self.every == 0:
                    self.callback_params["time"].append(time)
                    self.callback_params["step"].append(current_step)
                    self.callback_params["position"].append(
                        system.position_collection.copy()
                    )
                    self.callback_params["radius"].append(copy.deepcopy(system.radius))
                    self.callback_params["com"].append(
                        system.compute_position_center_of_mass()
                    )

                    return

        if self.COLLECT_DATA_FOR_POSTPROCESSING:
            # Collect data using callback function for postprocessing
            self.post_processing_dict_rod = defaultdict(list)
            # list which collected data will be append
            # set the diagnostics for rod and collect data
            self.simulator.collect_diagnostics(self.shearable_rod).using(
                ArmMuscleBasisCallBack,
                step_skip=self.step_skip,
                callback_params=self.post_processing_dict_rod,
            )

            self.post_processing_dict_sphere = defaultdict(list)
            # list which collected data will be append
            # set the diagnostics for cyclinder and collect data
            self.simulator.collect_diagnostics(self.sphere).using(
                RigidSphereCallBack,
                step_skip=self.step_skip,
                callback_params=self.post_processing_dict_sphere,
            )

        # Finalize simulation environment. After finalize, you cannot add
        # any forcing, constrain or call back functions
        self.simulator.finalize()

        # do_step, stages_and_updates will be used in step function
        self.do_step, self.stages_and_updates = extend_stepper_interface(
            self.StatefulStepper, self.simulator
        )

        # set state
        state = self.get_state()

        # reset on_goal
        self.on_goal = 0
        # reset current_step
        self.current_step = 0
        # reset time_tracker
        self.time_tracker = np.float64(0.0)
        # reset previous_action
        self.previous_action = None

        # After resetting the environment return state information
        return state