def __init__(self, fn=None, size=1000):
     self.fn = fn
     self.T = None
     HRR.reset_kernel()
     if size is not None:
         HRR.set_size(size)
     else:
         HRR.set_size(HRR.size)
    def learn(self, input_range, output_range, n_samples=200, fn=None, stddev=0.03, use_incremental=True):
        if fn is not None:
            self.fn = fn
            HRR.reset_kernel()
        #HRR.input_range = np.array([in_range[0], in_range[1]])
        HRR.stddev = stddev
        #if isinstance(n_samples, float) or isinstance(n_samples, numbers.Integral):
        #    n_samples = np.tuple(n_samples)
        #if isinstance(input_range[0], float) or isinstance(input_range[0], numbers.Integral):
        #    input_range[0] = np.tuple(input_range[0])
        #if isinstance(input_range[1], float) or isinstance(input_range[1], numbers.Integral):
        #    input_range[1] = np.tuple(input_range[1])
        #if isinstance(output_range[0], float) or isinstance(output_range[0], numbers.Integral):
        #    output_range[0] = np.tuple(output_range[0])
        #if isinstance(output_range[1], float) or isinstance(output_range[1], numbers.Integral):
        #    output_range[1] = np.tuple(output_range[1])
        #assert(len(input_range[0]) == len(input_range[1]) == len(output_range[0]) == len(output_range[1]) == len(n_samples))

        #if len(n_samples) == 1:
        if isinstance(n_samples, float) or isinstance(n_samples, numbers.Integral):
            # 1D function
            # create n_samples evenly spaced sampling points for input space
            A = np.linspace(float(input_range[0]), float(input_range[1]), n_samples)
            if use_incremental:
                # initialize T
                B_0 = self.fn(A[0])
                self.T = HRR(B_0, valid_range=output_range) % HRR(A[0], valid_range=input_range)
                for A_i in A[1:]:
                    B = self.fn(A_i)
                    self.T = self.T ** (HRR(B, valid_range=output_range) % HRR(A, valid_range=input_range)) # update T
            else:
                samples = np.empty((n_samples, HRR.size), dtype=float)
                for i, A_i in enumerate(A):
                    B_i = self.fn(A_i)  # evaluate ith sample
                    HRR_A = HRR(A_i, valid_range=input_range)
                    HRR_B = HRR(B_i, valid_range=output_range)
                    samples[i] = (HRR_B % HRR_A).memory  # probe HRR
                    #HRR_A.plot(HRR_A.reverse_permute(HRR_A.memory))
                    #HRR_B.plot(HRR_B.reverse_permute(HRR_B.memory))
                    #HRR_B.plot(HRR_B.reverse_permute(samples[i]))
                self.T = HRR(0, generator=samples)
        elif len(n_samples) == 2:
            # 2D function
            A_x = np.linspace(float(input_range[0][0]), float(input_range[0][1]), n_samples[0]) # samples for X-Axis
            A_y = np.linspace(float(input_range[1][0]), float(input_range[1][1]), n_samples[1]) # samples for Y-axis
            if use_incremental:
                # initialize T
                B_0 = self.fn(A_x[0], A_y[0])
                self.T = HRR(B_0, valid_range=output_range[0]) % HRR((A_x[0], A_y[0]), valid_range=input_range[0])
                for A_x_i in A_x[1:]: # iterate over X
                    for A_y_i in A_y[1:]: # iterate over Y
                        B = self.fn(A_x_i, A_y_i)
                        self.T = self.T ** (HRR(B, valid_range=output_range) % HRR((A_x_i, A_y_i), valid_range=input_range)) # update T
            else:
                samples = np.empty((n_samples[0] * n_samples[1], HRR.size), dtype=float)
                for i, A_x_i in enumerate(A_x):
                    print("{}".format(i))
                    for j, A_y_i in enumerate(A_y):
                        idx = j * len(A_x) + i
                        B_i = self.fn(A_x_i, A_y_i)  # evaluate ith sample
                        HRR_A = HRR((A_x_i, A_y_i), valid_range=input_range)
                        HRR_B = HRR(B_i, valid_range=output_range)
                        samples[idx] = (HRR_B % HRR_A).memory  # probe HRR
                        #print("Probe sample {} for ({}, {}) and ({}, {}):".format(idx, A_x_i, A_y_i, -1.0, -1.0))
                        #probe1 = HRR_A * HRR('', memory=samples[idx])
                        #probe2 = HRR((-1.0, -1.0), valid_range=input_range) * HRR('', memory=samples[idx])
                        #probe1.plot(probe1.reverse_permute(probe1.memory))
                        #probe2.plot(probe2.reverse_permute(probe2.memory))
                        if Approximation.verbose_learn:
                            print("learning f({}, {}) = {}".format(A_x_i, A_y_i, B_i))
                            HRR_A.plot(HRR_A.reverse_permute(HRR_A.memory))
                            HRR_B.plot(HRR_B.reverse_permute(HRR_B.memory))
                            temp_B = HRR_A * HRR('', memory=samples[idx])
                            print("probed sample {}:".format(idx))
                            temp_B.plot(temp_B.reverse_permute(temp_B.memory))
                            print("sample mem:")
                            HRR_B.plot(HRR_B.reverse_permute(samples[idx]))
                self.T = HRR(0, generator=samples)
                #print("final T:")
                #self.T.plot(self.T.reverse_permute(self.T.memory))
                #print("probe (-1.0, -1.0):")
                #probe1 = HRR((-1.0, -1.0), valid_range=input_range) * self.T
                #probe1.plot(probe1.reverse_permute(probe1.memory))
                #print("probe (-1.0, 1.0):")
                #probe2 = HRR((-1.0, -1.0), valid_range=input_range) * self.T
                #probe2.plot(probe2.reverse_permute(probe2.memory))

        else:
            raise ValueError("Dimensions > 2 not implemented yet")