Example #1
0
def basis_vectors(N, equation):
    xx = legslbndm(N)
    lepolys = gen_lepolys(N, xx)
    lepoly_x = dx(N, xx, lepolys)
    lepoly_xx = dxx(N, xx, lepolys)
    phi = basis(N, lepolys, equation)
    phi_x = basis_x(N, phi, lepoly_x, equation)
    phi_xx = basis_xx(N, phi_x, lepoly_xx, equation)
    return xx, lepolys, lepoly_x, lepoly_xx, phi, phi_x, phi_xx
Example #2
0
def diff(N, T, D):
    x = legslbndm(N + 1)
    # D = torch.from_numpy(legslbdiff(N+1, x)).to(device).float()
    T_ = T.clone()
    for i in range(T.shape[0]):
        element = torch.mm(D, T[i, :].reshape(T.shape[1],
                                              1)).reshape(T.shape[1], )
        T_[i, :] = element
    T = T_.clone()
    del T_
    return T
Example #3
0
def weak_form(eps,
              N,
              f,
              u,
              alphas,
              lepolys,
              phi,
              phi_x,
              equation,
              nbfuncs,
              index=0):
    B, i, j = u.shape
    N -= 1
    LHS = torch.zeros((B, 1, 1)).to(device).float()
    RHS = torch.zeros((B, 1, 1)).to(device).float()
    phi = torch.transpose(phi, 0, 1)
    denom = torch.square(torch.from_numpy(lepolys[N]).to(device).float())
    denom = torch.transpose(denom, 0, 1)
    diffusion = 6 * eps * alphas[:, :, index]
    if equation == 'Standard':
        u_x = reconstruct(alphas, phi_x)
        ux_phi = u_x * phi[:, index]
        convection = torch.sum(ux_phi * 2 / (N * (N + 1)) / denom, axis=2)
        LHS[:, 0] = diffusion - convection
        RHS[:, 0] = torch.sum(2 * f * phi[:, index] / (N * (N + 1)) / denom,
                              axis=2)
    elif equation == 'Burgers':
        phi_x = torch.transpose(phi_x, 0, 1)
        convection = torch.sum(u**2 * phi_x[:, index] / (N * (N + 1)) / denom,
                               axis=2)
        LHS[:, 0] = diffusion - convection
        RHS[:, 0] = torch.sum(2 * f * phi[:, index] / (N * (N + 1)) / denom,
                              axis=2)
    elif equation == 'Helmholtz':
        ku = 3.5
        x = legslbndm(N + 1)
        D_ = torch.from_numpy(legslbdiff(N + 1, x)).to(device).float()
        D = torch.zeros((B, N + 1, N + 1)).to(device).float()
        D[:, :, :] = D_
        phi_x = torch.transpose(phi_x, 0, 1)
        u_ = torch.transpose(u, 1, 2)
        temp = torch.bmm(D, u_)
        temp = torch.transpose(temp, 1, 2)
        diffusion = torch.sum(2 * temp * phi_x[:, index] / (N * (N + 1)) /
                              denom,
                              axis=2)
        reaction = ku * torch.sum(
            2 * u * phi[:, index] / (N * (N + 1)) / denom, axis=2)
        LHS[:, 0] = -diffusion + reaction
        RHS[:, 0] = torch.sum(2 * f * phi[:, index] / (N * (N + 1)) / denom,
                              axis=2)
    return LHS, RHS
Example #4
0
				LHS, RHS = 0, 0
				for _ in range(10):
					phi_0 = lepolys[_] - lepolys[_+2]
					phi_x = D@phi_0
					diffusion = -epsilon*(4*_+6)*(-1)*alphas[_]
					denom = lepolys[N]**2
					convection = np.sum(u**2*phi_x/(N*(N+1))/denom)
					LHS += diffusion - convection
					RHS += np.sum(2*f*phi_0/(N*(N+1))/denom)
		else:
			u, f, alphas, params = generate(x, D, a, b, lepolys, epsilon, equation, sd, forcing)
		data.append([u, f, alphas, params, epsilon])
	return data


x = sem.legslbndm(N+1)
D = sem.legslbdiff(N+1, x)
lepolys = gen_lepolys(N, x)
# pprint(lepolys)
if equation == 'Standard2D':
	lepolysx = gen_lepolysx(N, x, lepolys)
# pprint(lepolysx)

data = create_fast(N, epsilon, size, eps_flag, equation, sd, forcing)
data = np.array(data, dtype=object)



def save_obj(data, name, equation, kind):
	cwd = os.getcwd()
	path = os.path.join(cwd,'data', equation, kind)
Example #5
0
def create_fast(N:int, epsilon:float, size:int, eps_flag=False):
	def func(t: float) -> float:
		# Random force
		m = 2*np.random.rand(4) - 1
		f = m[0]*np.sin(m[1]*np.pi*t) + m[2]*np.cos(m[3]*np.pi*t)
		return f, m

	def gen_lepolys(N, x):
		lepolys = {}
		for i in range(N+3):
			lepolys[i] = sem.lepoly(i, x)
		return lepolys

	def generate(x, D, a, b, lepolys, epsilon):
		f, params = func(x)
		s_diag = np.zeros((N-1,1))
		M = np.zeros((N-1,N-1))
		for ii in range(1, N):
			k = ii - 1
			s_diag[ii-1] = -(4*k+6)*b
			phi_k_M = D@(lepolys[k] + a*lepolys[k+1] + b*lepolys[k+2])
			for jj in range(1,N):
				if abs(ii-jj) <=2:
					l = jj-1
					psi_l_M = lepolys[l] + a*lepolys[l+1] + b*lepolys[l+2]
					M[jj-1,ii-1] = np.sum((psi_l_M*phi_k_M)*2/(N*(N+1))/(lepolys[N]**2))

		S = s_diag*np.eye(N-1)
		g = np.zeros((N+1,))
		for i in range(1,N+1):
			k = i-1
			g[i-1] = (2*k+1)/(N*(N+1))*np.sum(f*(lepolys[k])/(lepolys[N]**2))
		g[N-1] = 1/(N+1)*np.sum(f/lepolys[N])

		bar_f = np.zeros((N-1,))
		for i in range(1,N):
			k = i-1
			bar_f[i-1] = g[i-1]/(k+1/2) + a*g[i]/(k+3/2) + b*g[i+1]/(k+5/2)

		Mass = epsilon*S-M
		u = np.linalg.solve(Mass, bar_f)
		alphas = np.copy(u)
		g[0], g[1] = u[0], u[1] + a*u[0]

		for i in range(3, N):
			k = i - 1
			g[i-1] = u[i-1] + a*u[i-2] + b*u[i-3]

		g[N-1] = a*u[N-2] + b*u[N-3]
		g[N] = b*u[N-2]
		u = np.zeros((N+1,))
		for i in range(1,N+2):
			_ = 0
			for j in range(1, N+2):
				k = j-1
				L = lepolys[k]
				_ += g[j-1]*L[i-1]
			_ = _[0]
			u[i-1] = _
		return u, f, alphas, params

	def loop(N, epsilon, size, lepolys, eps_flag):
		if eps_flag == True:
			epsilons = np.random.uniform(1E0, 1E-6, SIZE)
		data = []
		U, F, ALPHAS, PARAMS = [], [], [], []
		for n in tqdm(range(size)):
			if eps_flag == True:
				epsilon = epsilons[n]
			u, f, alphas, params = generate(x, D, a, b, lepolys, epsilon)
			data.append([u, f, alphas, params, epsilon])
		return data


	x = sem.legslbndm(N+1)
	D = sem.legslbdiff(N+1, x)
	a, b = 0, -1
	lepolys = gen_lepolys(N, x)
	return loop(N, epsilon, size, lepolys, eps_flag)
Example #6
0
def create_fast(N: int,
                epsilon: float,
                size: int,
                eps_flag=False,
                equation='Standard',
                sd=1,
                forcing='uniform'):
    def func(x: np.ndarray, equation: str, sd: float,
             forcing: str) -> np.ndarray:
        if forcing == 'uniform':
            m = 3 + 2 * np.random.rand(2)
            n = np.pi * (1 + 2 * np.random.rand(2))
            f = m[0] * np.sin(n[0] * x) + m[1] * np.cos(n[1] * x)
            m = np.array([m[0], m[1], n[0], n[1]])
        elif forcing == 'normal':
            m = np.random.normal(0, sd, 4)
            f = m[0] * np.sin(m[1] * np.pi * x) + m[2] * np.cos(
                m[3] * np.pi * x)
        return f, m

    def gen_lepolys(N, x):
        lepolys = {}
        for i in range(N + 3):
            lepolys[i] = sem.lepoly(i, x)
        return lepolys

    def generate(x, D, a, b, lepolys, epsilon, equation, sd, forcing):
        f, params = func(x, equation, sd, forcing)
        s_diag = np.zeros((N - 1, 1))
        if equation == 'Standard':
            M = np.zeros((N - 1, N - 1))
            for ii in range(1, N):
                k = ii - 1
                s_diag[ii - 1] = -(4 * k + 6) * b
                phi_k_M = D @ (lepolys[k] + a * lepolys[k + 1] +
                               b * lepolys[k + 2])
                for jj in range(1, N):
                    if np.abs(ii - jj) <= 2:
                        l = jj - 1
                        psi_l_M = lepolys[l] + a * lepolys[
                            l + 1] + b * lepolys[l + 2]
                        M[jj - 1,
                          ii - 1] = np.sum((psi_l_M * phi_k_M) * 2 /
                                           (N * (N + 1)) / (lepolys[N]**2))

            S = s_diag * np.eye(N - 1)
            g = np.zeros((N + 1, ))
            for i in range(1, N + 1):
                k = i - 1
                g[i - 1] = (2 * k + 1) / (N *
                                          (N + 1)) * np.sum(f * (lepolys[k]) /
                                                            (lepolys[N]**2))
            g[N - 1] = 1 / (N + 1) * np.sum(f / lepolys[N])

            bar_f = np.zeros((N - 1, ))
            for i in range(1, N):
                k = i - 1
                bar_f[i - 1] = g[i - 1] / (k + 1 / 2) + a * g[i] / (
                    k + 3 / 2) + b * g[i + 1] / (k + 5 / 2)

            Mass = epsilon * S - M
            u = np.linalg.solve(Mass, bar_f)
            alphas = np.copy(u)
            g[0], g[1] = u[0], u[1] + a * u[0]

            for i in range(3, N):
                k = i - 1
                g[i - 1] = u[i - 1] + a * u[i - 2] + b * u[i - 3]

            g[N - 1] = a * u[N - 2] + b * u[N - 3]
            g[N] = b * u[N - 2]
            u = np.zeros((N + 1, ))
            for i in range(1, N + 2):
                _ = 0
                for j in range(1, N + 2):
                    k = j - 1
                    L = lepolys[k]
                    _ += g[j - 1] * L[i - 1]
                u[i - 1] = _[0]

        elif equation == 'Burgers':
            for ii in range(1, N):
                k = ii - 1
                s_diag[k] = -(4 * k + 6) * b
            S = s_diag * np.eye(N - 1)
            Mass = epsilon * S
            error, tolerance, u_old, force = 1, 1E-9, 0 * f.copy(), f.copy()
            iterations = 0
            while error > tolerance:
                f_ = force - u_old * (D @ u_old)
                g = np.zeros((N + 1, ))
                for i in range(1, N + 1):
                    k = i - 1
                    g[k] = (2 * k + 1) / (N *
                                          (N + 1)) * np.sum(f_ * (lepolys[k]) /
                                                            (lepolys[N]**2))
                g[N - 1] = 1 / (N + 1) * np.sum(f_ / lepolys[N])

                bar_f = np.zeros((N - 1, ))
                for i in range(1, N):
                    k = i - 1
                    bar_f[k] = g[k] / (k + 1 / 2) + a * g[k + 1] / (
                        k + 3 / 2) + b * g[k + 2] / (k + 5 / 2)

                alphas = np.linalg.solve(Mass, bar_f)
                u_sol = np.zeros((N + 1, 1))
                for ij in range(1, N):
                    i_ind = ij - 1
                    u_sol += alphas[i_ind] * (lepolys[i_ind] +
                                              a * lepolys[i_ind + 1] +
                                              b * lepolys[i_ind + 2])

                error = np.max(u_sol - u_old)
                u_old = u_sol
                iterations += 1
            u = u_sol

        elif equation == 'BurgersT':
            M = np.zeros((N - 1, N - 1))
            tol, T, dt = 1E-9, 5E-4, 1E-4
            t_f = int(T / dt)
            u_pre, u_ans, f_ans, alphas_ans = np.sin(np.pi * x), [], [], []
            for ii in range(1, N):
                k = ii - 1
                s_diag[k] = -(4 * k + 6) * b
                phi_k_M = lepolys[k] + a * lepolys[k + 1] + b * lepolys[k + 2]
                for jj in range(1, N):
                    if np.abs(ii - jj) <= 2:
                        l = jj - 1
                        psi_l_M = lepolys[l] + a * lepolys[
                            l + 1] + b * lepolys[l + 2]
                        entry = psi_l_M * phi_k_M * 2 / (N *
                                                         (N + 1)) / (lepolys[N]
                                                                     **2)
                        M[l, k] = np.sum(entry)

            S = s_diag * np.eye(N - 1)
            Mass = epsilon * S + (1 / dt) * M

            for t_idx in np.linspace(1, t_f, t_f, endpoint=True):
                error, tolerance, u_old, force = 1, tol, u_pre, np.cos(
                    t_idx * dt) * f

                iterations = 0
                while error > tolerance:
                    f_ = force - u_old * (D @ u_old) + (1 / dt) * u_pre
                    g = np.zeros((N + 1, ))
                    for i in range(1, N + 1):
                        k = i - 1
                        g[k] = (2 * k + 1) / (N * (N + 1)) * np.sum(
                            f_ * (lepolys[k]) / (lepolys[N]**2))
                    g[N - 1] = 1 / (N + 1) * np.sum(f_ / lepolys[N])

                    bar_f = np.zeros((N - 1, ))
                    for i in range(1, N):
                        k = i - 1
                        bar_f[k] = g[k] / (k + 1 / 2) + a * g[k + 1] / (
                            k + 3 / 2) + b * g[k + 2] / (k + 5 / 2)

                    alphas = np.linalg.solve(Mass, bar_f)
                    u_sol = np.zeros((N + 1, 1))
                    for ij in range(1, N):
                        i_ind = ij - 1
                        u_sol += alphas[i_ind] * (lepolys[i_ind] +
                                                  a * lepolys[i_ind + 1] +
                                                  b * lepolys[i_ind + 2])

                    error = np.max(u_sol - u_old)
                    u_old = u_sol.copy()
                    iterations += 1

                u_ans.append(u_sol)
                f_ans.append(force)
                alphas_ans.append(alphas)
                u_pre = u_sol
            u, f, alphas = u_ans, f_ans, alphas_ans

        elif equation == 'Helmholtz':
            ku = 3.5
            M = np.zeros((N - 1, N - 1))
            for ii in range(1, N):
                k = ii - 1
                s_diag[k] = -(4 * k + 6) * b[k]
                phi_k_M = lepolys[k] + a[k] * lepolys[k + 1] + b[k] * lepolys[
                    k + 2]
                for jj in range(1, N):
                    if np.abs(ii - jj) <= 2:
                        l = jj - 1
                        psi_l_M = lepolys[l] + a[l] * lepolys[
                            l + 1] + b[l] * lepolys[l + 2]
                        entry = psi_l_M * phi_k_M * 2 / (N *
                                                         (N + 1)) / (lepolys[N]
                                                                     **2)
                        M[l, k] = np.sum(entry)

            S = s_diag * np.eye(N - 1)
            g = np.zeros((N + 1, ))
            for i in range(1, N + 1):
                k = i - 1
                g[k] = (2 * k + 1) / (N * (N + 1)) * np.sum(f * (lepolys[k]) /
                                                            (lepolys[N]**2))
            g[N] = 1 / (N + 1) * np.sum(f / lepolys[N])

            bar_f = np.zeros((N - 1, ))
            for i in range(1, N):
                k = i - 1
                bar_f[k] = g[k] / (k + 1 / 2) + a[k] * g[k + 1] / (
                    k + 3 / 2) + b[k] * g[k + 2] / (k + 5 / 2)

            Mass = -S + ku * M
            alphas = np.linalg.solve(Mass, bar_f)

            u = np.zeros((N + 1, 1))
            for ij in range(1, N):
                i_ind = ij - 1
                u += alphas[i_ind] * (lepolys[i_ind] +
                                      a[i_ind] * lepolys[i_ind + 1] +
                                      b[i_ind] * lepolys[i_ind + 2])
        return u, f, alphas, params

    def loop(N, epsilon, size, lepolys, eps_flag, equation, a, b, forcing):
        if eps_flag == True:
            epsilons = np.random.uniform(1E0, 1E-6, SIZE)
        data = []
        U, F, ALPHAS, PARAMS = [], [], [], []
        for n in tqdm(range(size)):
            if eps_flag == True:
                epsilon = epsilons[n]
            if equation == 'BurgersT':
                u, f, alphas, params = generate(x, D, a, b, lepolys, epsilon,
                                                equation, sd, forcing)
                for i, u_ in enumerate(u):
                    if i < len(u):
                        data.append([u[i], f[i], alphas[i], params, epsilon])
            elif equation == 'Burgers':
                u, f, alphas, params = generate(x, D, a, b, lepolys, epsilon,
                                                equation, sd, forcing)
                LHS, RHS = 0, 0
                for _ in range(10):
                    phi_0 = lepolys[_] - lepolys[_ + 2]
                    phi_x = D @ phi_0
                    diffusion = -epsilon * (4 * _ + 6) * (-1) * alphas[_]
                    denom = lepolys[N]**2
                    convection = np.sum(u**2 * phi_x / (N * (N + 1)) / denom)
                    LHS += diffusion - convection
                    RHS += np.sum(2 * f * phi_0 / (N * (N + 1)) / denom)
                while np.abs(LHS - RHS) > 1E-5 and np.linalg.norm(
                        u, ord=2) < 1E-2:
                    u, f, alphas, params = generate(x, D, a, b, lepolys,
                                                    epsilon, equation, sd,
                                                    forcing)
                    LHS, RHS = 0, 0
                    for _ in range(10):
                        phi_0 = lepolys[_] - lepolys[_ + 2]
                        phi_x = D @ phi_0
                        diffusion = -epsilon * (4 * _ + 6) * (-1) * alphas[_]
                        denom = lepolys[N]**2
                        convection = np.sum(u**2 * phi_x / (N * (N + 1)) /
                                            denom)
                        LHS += diffusion - convection
                        RHS += np.sum(2 * f * phi_0 / (N * (N + 1)) / denom)
            else:
                u, f, alphas, params = generate(x, D, a, b, lepolys, epsilon,
                                                equation, sd, forcing)
            data.append([u, f, alphas, params, epsilon])
        return data

    x = sem.legslbndm(N + 1)
    D = sem.legslbdiff(N + 1, x)
    lepolys = gen_lepolys(N, x)
    if equation == 'Helmholtz':
        a, b = np.zeros((N + 1, )), np.zeros((N + 1, ))
        for i in range(1, N + 2):
            k = i - 1
            b[k] = -k * (k + 1) / ((k + 2) * (k + 3))
    else:
        a, b = 0, -1
    return loop(N, epsilon, size, lepolys, eps_flag, equation, a, b, forcing)
Example #7
0
                           z,
                           cmap=cm.coolwarm,
                           linewidth=0,
                           antialiased=False)
    plt.xlim(-1, 1)
    plt.ylim(-1, 1)
    plt.grid(alpha=0.618)
    plt.xlabel('$x$')
    plt.ylabel('$y$')
    plt.show()
    # exit()


if __name__ == '__main__':
    start = time()
    x = legslbndm(N + 1)
    y = x.copy()
    D = legslbdiff(N + 1, x)
    D2 = D @ D
    D2 = D2[1:-1, 1:-1]
    lepolys = gen_lepolys(N, x)
    lepolysx = gen_lepolysx(N, x, lepolys)
    I = np.eye(N - 1)
    L = np.kron(I, D2) + np.kron(D2, I)
    x1, y1 = np.meshgrid(x, x)
    f = f2D(x1, y1)
    f_ = f[1:-1, 1:-1]
    f_ = f_.ravel()
    u = np.linalg.solve(-L, f_)
    xx, yy = np.meshgrid(x[1:-1], x[1:-1])
    uu = np.zeros_like(x1)
Example #8
0
def plotter(xx, sample, epoch, a=None, u=None, DE=None, title='alpha', ks=7):
    def relative_l2(measured, theoretical):
        return np.linalg.norm(measured - theoretical, ord=2) / np.linalg.norm(
            theoretical, ord=2)

    def relative_linf(measured, theoretical):
        return np.linalg.norm(measured - theoretical,
                              ord=np.inf) / np.linalg.norm(theoretical,
                                                           ord=np.inf)

    def mae(measured, theoretical):
        return np.linalg.norm(measured - theoretical, ord=1) / len(theoretical)

    aa = sample['a'][0, 0, :].to('cpu').detach().numpy()
    uu = sample['u'][0, 0, :].to('cpu').detach().numpy()
    ff = sample['f'][0, 0, :].to('cpu').detach().numpy()
    x_ = legslbndm(len(xx) - 2)
    xxx = np.linspace(-1, 1, len(ff), endpoint=True)
    if a is not None:
        ahat = a[0, :].to('cpu').detach().numpy()
        mae_error_a = mae(ahat, aa)
        l2_error_a = relative_l2(ahat, aa)
        linf_error_a = relative_linf(ahat, aa)
        plt.figure(1, figsize=(10, 6))
        plt.title(f'Alphas Example Epoch {epoch}\n'\
               f'Alphas MAE Error: {np.round(mae_error_a, 6)}\n'\
               f'Alphas Rel. $L_2$ Error: {np.round(float(l2_error_a), 6)}\n'\
               f'Alphas Rel. $L_\\infty$ Error: {np.round(float(linf_error_a), 6)}')
        plt.plot(x_, aa, 'r-', mfc='none', label='$\\alpha$')
        plt.plot(x_, ahat, 'bo', mfc='none', label='$\\hat{\\alpha}$')
        # plt.plot(xxx, ff, 'g-', label='$f$')
        plt.xlim(-1, 1)
        plt.grid(alpha=0.618)
        plt.xlabel('$x$')
        plt.ylabel('$y$')
        plt.legend(shadow=True)
        plt.savefig(
            f'./pics/{title}_ks{ks}_epoch{str(epoch).zfill(5)}_alphas.png',
            bbox_inches='tight')
        plt.close(1)
    if u is not None:
        uhat = u[0, :].to('cpu').detach().numpy()
        mae_error_u = mae(uhat, uu)
        l2_error_u = relative_l2(uhat, uu)
        linf_error_u = relative_linf(uhat, uu)
        plt.figure(2, figsize=(10, 6))
        plt.title(f'Reconstruction Example Epoch {epoch}\n'\
               f'Reconstruction MAE Error: {np.round(mae_error_u, 6)}\n'\
               f'Reconstruction Rel. $L_2$ Error: {np.round(float(l2_error_u), 6)}\n'\
               f'Reconstruction Rel. $L_\\infty$ Error: {np.round(float(linf_error_u), 6)}')
        plt.plot(xx, uu, 'r-', mfc='none', label='$u$')
        plt.plot(xx, uhat.T, 'bo', mfc='none', label='$\\hat{u}$')
        plt.xlim(-1, 1)
        plt.grid(alpha=0.618)
        plt.xlabel('$x$')
        plt.ylabel('$y$')
        plt.legend(shadow=True)
        plt.savefig(
            f'./pics/{title}_ks{ks}_epoch{str(epoch).zfill(5)}_reconstruction.png',
            bbox_inches='tight')
        # plt.show()
        plt.close(2)
    if DE is not None:
        de = DE[0, :].to('cpu').detach().numpy()
        plt.figure(3, figsize=(10, 6))
        mae_error_de = mae(de, ff)
        l2_error_de = relative_l2(de, ff)
        linf_error_de = relative_linf(de, ff)
        plt.title(f'DE Example Epoch {epoch}\n'\
               f'DE MAE Error: {np.round(mae_error_de, 6)}\n'\
               f'DE Rel. $L_2$ Error: {np.round(float(l2_error_de), 6)}\n'\
               f'DE Rel. $L_\\infty$ Error: {np.round(float(linf_error_de), 6)}')
        plt.plot(xxx, ff, 'g-', label='$f$')
        plt.plot(xxx, de, 'co', mfc='none', label='ODE')
        plt.xlim(-1, 1)
        plt.grid(alpha=0.618)
        plt.xlabel('$x$')
        plt.ylabel('$y$')
        plt.legend(shadow=True)
        plt.savefig(f'./pics/{title}_ks{ks}_epoch{str(epoch).zfill(5)}_DE.png',
                    bbox_inches='tight')
        # plt.show()
        plt.close(3)