def __init__(self, model, numIntType, energy, dim):
     self.model = MultiLayerNet(model[0], model[1], model[2])
     self.model = self.model.to(dev)
     self.intLoss = IntegrationLoss(numIntType, dim)
     self.energy = energy
     self.lossArray = []
     self.dim = dim
 def __init__(self, model, numIntType, energy, dim):
     # self.data = data
     self.model = MultiLayerNet(model[0], model[1], model[2])
     self.model = self.model.to(dev)
     self.intLoss = IntegrationLoss(numIntType, dim)
     self.energy = energy
     # self.post = PostProcessing(energy, dim)
     self.dim = dim
class DeepEnergyMethod:
    # Instance attributes
    def __init__(self, model, numIntType, energy, dim):
        # self.data = data
        self.model = MultiLayerNet(model[0], model[1], model[2])
        self.model = self.model.to(dev)
        self.intLoss = IntegrationLoss(numIntType, dim)
        self.energy = energy
        self.lossArray = []
        self.dim = dim

    def train_model(self, shape, dxdydz, data, bForce, neumannBC, dirichletBC,
                    iteration, learning_rate):
        x = torch.from_numpy(data).float()
        x = x.to(dev)
        x.requires_grad_(True)
        # get tensor inputs and outputs for boundary conditions
        # -------------------------------------------------------------------------------
        #                             Dirichlet BC
        # -------------------------------------------------------------------------------
        dirBC_coordinates = {}  # declare a dictionary
        dirBC_values = {}  # declare a dictionary
        dirBC_penalty = {}
        for i, keyi in enumerate(dirichletBC):
            dirBC_coordinates[i] = torch.from_numpy(
                dirichletBC[keyi]['coord']).float().to(dev)
            dirBC_values[i] = torch.from_numpy(
                dirichletBC[keyi]['known_value']).float().to(dev)
            dirBC_penalty[i] = torch.tensor(
                dirichletBC[keyi]['penalty']).float().to(dev)
        # -------------------------------------------------------------------------------
        #                           Neumann BC
        # -------------------------------------------------------------------------------
        neuBC_coordinates = {}  # declare a dictionary
        neuBC_values = {}  # declare a dictionary
        neuBC_penalty = {}
        for i, keyi in enumerate(neumannBC):
            neuBC_coordinates[i] = torch.from_numpy(
                neumannBC[keyi]['coord']).float().to(dev)
            neuBC_coordinates[i].requires_grad_(True)
            neuBC_values[i] = torch.from_numpy(
                neumannBC[keyi]['known_value']).float().to(dev)
            neuBC_penalty[i] = torch.tensor(
                neumannBC[keyi]['penalty']).float().to(dev)
        # ----------------------------------------------------------------------------------
        # Minimizing loss function (energy and boundary conditions)
        # ----------------------------------------------------------------------------------
        optimizer = torch.optim.LBFGS(self.model.parameters(),
                                      lr=learning_rate,
                                      max_iter=20)
        start_time = time.time()
        energy_loss_array = []
        boundary_loss_array = []
        loss_array = []
        for t in range(iteration):
            # Zero gradients, perform a backward pass, and update the weights.
            def closure():
                it_time = time.time()
                # ----------------------------------------------------------------------------------
                # Internal Energy
                # ----------------------------------------------------------------------------------
                u_pred = self.getU(x)
                u_pred.double()
                storedEnergy = self.energy.getStoredEnergy(u_pred, x)
                internal2 = self.intLoss.lossInternalEnergy(storedEnergy,
                                                            dx=dxdydz[0],
                                                            dy=dxdydz[1],
                                                            dz=dxdydz[2],
                                                            shape=shape)
                body_force = torch.from_numpy(bForce).float().to(dev)
                bF = torch.bmm((u_pred + x).unsqueeze(1),
                               body_force.unsqueeze(2))
                body_force_crit = self.intLoss.approxIntegration(bF,
                                                                 dx=dxdydz[0],
                                                                 dy=dxdydz[1],
                                                                 dz=dxdydz[2],
                                                                 shape=shape)
                external2 = torch.zeros(len(neuBC_coordinates))
                for i, vali in enumerate(neuBC_coordinates):
                    neu_u_pred = self.getU(neuBC_coordinates[i])
                    fext = torch.bmm(
                        (neu_u_pred + neuBC_coordinates[i]).unsqueeze(1),
                        neuBC_values[i].unsqueeze(2))
                    if i == 0:
                        hx, hy = (dxdydz[0], dxdydz[2])
                        Nx, Ny = (shape[0], shape[2])
                        external2[i] = self.intLoss.lossExternalEnergy(
                            fext, dx=hx, dy=hy, shape=[Nx, Ny])
                    elif i == 1:
                        hx, hy = (dxdydz[0], dxdydz[1])
                        Nx, Ny = (shape[0], shape[1])
                        external2[i] = self.intLoss.lossExternalEnergy(
                            fext, dx=hx, dy=hy, shape=[Nx, Ny])
                    elif i == 2:
                        hx, hy = (dxdydz[0], dxdydz[2])
                        Nx, Ny = (shape[0], shape[2])
                        external2[i] = self.intLoss.lossExternalEnergy(
                            fext, dx=hx, dy=hy, shape=[Nx, Ny])
                    elif i == 3:
                        hx, hy = (dxdydz[0], dxdydz[1])
                        Nx, Ny = (shape[0], shape[1])
                        external2[i] = self.intLoss.lossExternalEnergy(
                            fext, dx=hx, dy=hy, shape=[Nx, Ny])
                    else:
                        print(
                            "Not yet implemented !!! Please contact the author to ask !!!"
                        )
                        exit()

                bc_u_crit = torch.zeros((len(dirBC_coordinates)))
                for i, vali in enumerate(dirBC_coordinates):
                    dir_u_pred = self.getU(dirBC_coordinates[i])
                    bc_u_crit[i] = self.loss_squared_sum(
                        dir_u_pred, dirBC_values[i]) * dirBC_penalty[i]
                energy_loss = internal2 - body_force_crit - torch.sum(
                    external2)
                boundary_loss = torch.tensor([0])
                loss = energy_loss
                optimizer.zero_grad()
                loss.backward()
                print(
                    'Iter: %d Loss: %.9e Energy: %.9e Boundary: %.9e Time: %.3e'
                    % (t + 1, loss.item(), energy_loss.item(),
                       boundary_loss.item(), time.time() - it_time))
                energy_loss_array.append(energy_loss.data)
                boundary_loss_array.append(boundary_loss.data)
                self.lossArray.append(loss.data)
                return loss

            optimizer.step(closure)
        elapsed = time.time() - start_time
        print('Training time: %.4f' % elapsed)

    def getU(self, x):
        u = self.model(x)
        PI = torch.from_numpy(np.array(np.pi)).float()
        angle = PI / 3
        factor = 0.5
        length = 1.25
        Ux_known = 0
        Uy_known = factor * (0.5 + (x[:, 1] - 0.5) * torch.cos(angle) -
                             (x[:, 2] - 0.5) * torch.sin(angle) - x[:, 1])
        Uz_known = factor * (0.5 + (x[:, 1] - 0.5) * torch.sin(angle) +
                             (x[:, 2] - 0.5) * torch.cos(angle) - x[:, 2])
        Ux = Ux_known + (x[:, 0] - length) * x[:, 0] * u[:, 0]
        Uy = (Uy_known * x[:, 0] /
              length) + (x[:, 0] - length) * x[:, 0] * u[:, 1]
        Uz = (Uz_known * x[:, 0] /
              length) + (x[:, 0] - length) * x[:, 0] * u[:, 2]
        Ux = Ux.reshape(Ux.shape[0], 1)
        Uy = Uy.reshape(Uy.shape[0], 1)
        Uz = Uz.reshape(Uz.shape[0], 1)
        u_pred = torch.cat((Ux, Uy, Uz), -1)
        return u_pred

    # --------------------------------------------------------------------------------
    # Evaluate model to obtain:
    # 1. U - Displacement
    # 2. E - Green Lagrange Strain
    # 3. S - 2nd Piola Kirchhoff Stress
    # 4. F - Deformation Gradient
    # Date implement: 20.06.2019
    # --------------------------------------------------------------------------------
    def evaluate_model(self, x, y, z):
        energy_type = self.energy.type
        mu = self.energy.mu
        lmbda = self.energy.lam
        dim = self.dim
        if dim == 2:
            Nx = len(x)
            Ny = len(y)
            xGrid, yGrid = np.meshgrid(x, y)
            x1D = xGrid.flatten()
            y1D = yGrid.flatten()
            xy = np.concatenate((np.array([x1D]).T, np.array([y1D]).T),
                                axis=-1)
            xy_tensor = torch.from_numpy(xy).float()
            xy_tensor = xy_tensor.to(dev)
            xy_tensor.requires_grad_(True)
            # u_pred_torch = self.model(xy_tensor)
            u_pred_torch = self.getU(xy_tensor)
            duxdxy = grad(u_pred_torch[:, 0].unsqueeze(1),
                          xy_tensor,
                          torch.ones(xy_tensor.size()[0], 1, device=dev),
                          create_graph=True,
                          retain_graph=True)[0]
            duydxy = grad(u_pred_torch[:, 1].unsqueeze(1),
                          xy_tensor,
                          torch.ones(xy_tensor.size()[0], 1, device=dev),
                          create_graph=True,
                          retain_graph=True)[0]
            F11 = duxdxy[:, 0].unsqueeze(1) + 1
            F12 = duxdxy[:, 1].unsqueeze(1) + 0
            F21 = duydxy[:, 0].unsqueeze(1) + 0
            F22 = duydxy[:, 1].unsqueeze(1) + 1
            detF = F11 * F22 - F12 * F21
            invF11 = F22 / detF
            invF22 = F11 / detF
            invF12 = -F12 / detF
            invF21 = -F21 / detF
            C11 = F11**2 + F21**2
            C12 = F11 * F12 + F21 * F22
            C21 = F12 * F11 + F22 * F21
            C22 = F12**2 + F22**2
            E11 = 0.5 * (C11 - 1)
            E12 = 0.5 * C12
            E21 = 0.5 * C21
            E22 = 0.5 * (C22 - 1)
            if energy_type == 'neohookean' and dim == 2:
                P11 = mu * F11 + (lmbda * torch.log(detF) - mu) * invF11
                P12 = mu * F12 + (lmbda * torch.log(detF) - mu) * invF21
                P21 = mu * F21 + (lmbda * torch.log(detF) - mu) * invF12
                P22 = mu * F22 + (lmbda * torch.log(detF) - mu) * invF22
            else:
                print("This energy model will be implemented later !!!")
                exit()
            S11 = invF11 * P11 + invF12 * P21
            S12 = invF11 * P12 + invF12 * P22
            S21 = invF21 * P11 + invF22 * P21
            S22 = invF21 * P12 + invF22 * P22
            u_pred = u_pred_torch.detach().cpu().numpy()
            F11_pred = F11.detach().cpu().numpy()
            F12_pred = F12.detach().cpu().numpy()
            F21_pred = F21.detach().cpu().numpy()
            F22_pred = F22.detach().cpu().numpy()
            E11_pred = E11.detach().cpu().numpy()
            E12_pred = E12.detach().cpu().numpy()
            E21_pred = E21.detach().cpu().numpy()
            E22_pred = E22.detach().cpu().numpy()
            S11_pred = S11.detach().cpu().numpy()
            S12_pred = S12.detach().cpu().numpy()
            S21_pred = S21.detach().cpu().numpy()
            S22_pred = S22.detach().cpu().numpy()
            surUx = u_pred[:, 0].reshape(Ny, Nx, 1)
            surUy = u_pred[:, 1].reshape(Ny, Nx, 1)
            surUz = np.zeros([Nx, Ny, 1])
            surE11 = E11_pred.reshape(Ny, Nx, 1)
            surE12 = E12_pred.reshape(Ny, Nx, 1)
            surE13 = np.zeros([Nx, Ny, 1])
            surE21 = E21_pred.reshape(Ny, Nx, 1)
            surE22 = E22_pred.reshape(Ny, Nx, 1)
            surE23 = np.zeros([Nx, Ny, 1])
            surE33 = np.zeros([Nx, Ny, 1])
            surS11 = S11_pred.reshape(Ny, Nx, 1)
            surS12 = S12_pred.reshape(Ny, Nx, 1)
            surS13 = np.zeros([Nx, Ny, 1])
            surS21 = S21_pred.reshape(Ny, Nx, 1)
            surS22 = S22_pred.reshape(Ny, Nx, 1)
            surS23 = np.zeros([Nx, Ny, 1])
            surS33 = np.zeros([Nx, Ny, 1])
            SVonMises = np.float64(
                np.sqrt(0.5 * ((surS11 - surS22)**2 + (surS22)**2 +
                               (-surS11)**2 + 6 * (surS12**2))))
            U = (np.float64(surUx), np.float64(surUy), np.float64(surUz))
            return U, np.float64(surS11), np.float64(surS12), np.float64(surS13), np.float64(surS22), np.float64(
                surS23), \
                   np.float64(surS33), np.float64(surE11), np.float64(surE12), \
                   np.float64(surE13), np.float64(surE22), np.float64(surE23), np.float64(surE33), np.float64(
                SVonMises), \
                   np.float64(F11_pred), np.float64(F12_pred), np.float64(F21_pred), np.float64(F22_pred)
        else:
            Nx = len(x)
            Ny = len(y)
            Nz = len(z)
            xGrid, yGrid, zGrid = np.meshgrid(x, y, z)
            x1D = xGrid.flatten()
            y1D = yGrid.flatten()
            z1D = zGrid.flatten()
            xyz = np.concatenate(
                (np.array([x1D]).T, np.array([y1D]).T, np.array([z1D]).T),
                axis=-1)
            xyz_tensor = torch.from_numpy(xyz).float()
            xyz_tensor = xyz_tensor.to(dev)
            xyz_tensor.requires_grad_(True)
            # u_pred_torch = self.model(xyz_tensor)
            u_pred_torch = self.getU(xyz_tensor)
            duxdxyz = grad(u_pred_torch[:, 0].unsqueeze(1),
                           xyz_tensor,
                           torch.ones(xyz_tensor.size()[0], 1, device=dev),
                           create_graph=True,
                           retain_graph=True)[0]
            duydxyz = grad(u_pred_torch[:, 1].unsqueeze(1),
                           xyz_tensor,
                           torch.ones(xyz_tensor.size()[0], 1, device=dev),
                           create_graph=True,
                           retain_graph=True)[0]
            duzdxyz = grad(u_pred_torch[:, 2].unsqueeze(1),
                           xyz_tensor,
                           torch.ones(xyz_tensor.size()[0], 1, device=dev),
                           create_graph=True,
                           retain_graph=True)[0]
            F11 = duxdxyz[:, 0].unsqueeze(1) + 1
            F12 = duxdxyz[:, 1].unsqueeze(1) + 0
            F13 = duxdxyz[:, 2].unsqueeze(1) + 0
            F21 = duydxyz[:, 0].unsqueeze(1) + 0
            F22 = duydxyz[:, 1].unsqueeze(1) + 1
            F23 = duydxyz[:, 2].unsqueeze(1) + 0
            F31 = duzdxyz[:, 0].unsqueeze(1) + 0
            F32 = duzdxyz[:, 1].unsqueeze(1) + 0
            F33 = duzdxyz[:, 2].unsqueeze(1) + 1
            detF = F11 * (F22 * F33 - F23 * F32) - F12 * (
                F21 * F33 - F23 * F31) + F13 * (F21 * F32 - F22 * F31)
            invF11 = (F22 * F33 - F23 * F32) / detF
            invF12 = -(F12 * F33 - F13 * F32) / detF
            invF13 = (F12 * F23 - F13 * F22) / detF
            invF21 = -(F21 * F33 - F23 * F31) / detF
            invF22 = (F11 * F33 - F13 * F31) / detF
            invF23 = -(F11 * F23 - F13 * F21) / detF
            invF31 = (F21 * F32 - F22 * F31) / detF
            invF32 = -(F11 * F32 - F12 * F31) / detF
            invF33 = (F11 * F22 - F12 * F21) / detF
            C11 = F11**2 + F21**2 + F31**2
            C12 = F11 * F12 + F21 * F22 + F31 * F32
            C13 = F11 * F13 + F21 * F23 + F31 * F33
            C21 = F12 * F11 + F22 * F21 + F32 * F31
            C22 = F12**2 + F22**2 + F32**2
            C23 = F12 * F13 + F22 * F23 + F32 * F33
            C31 = F13 * F11 + F23 * F21 + F33 * F31
            C32 = F13 * F12 + F23 * F22 + F33 * F32
            C33 = F13**2 + F23**2 + F33**2
            E11 = 0.5 * (C11 - 1)
            E12 = 0.5 * C12
            E13 = 0.5 * C13
            E21 = 0.5 * C21
            E22 = 0.5 * (C22 - 1)
            E23 = 0.5 * C23
            E31 = 0.5 * C31
            E32 = 0.5 * C32
            E33 = 0.5 * (C33 - 1)
            if energy_type == 'neohookean' and dim == 3:
                P11 = mu * F11 + (lmbda * torch.log(detF) - mu) * invF11
                P12 = mu * F12 + (lmbda * torch.log(detF) - mu) * invF21
                P13 = mu * F13 + (lmbda * torch.log(detF) - mu) * invF31
                P21 = mu * F21 + (lmbda * torch.log(detF) - mu) * invF12
                P22 = mu * F22 + (lmbda * torch.log(detF) - mu) * invF22
                P23 = mu * F23 + (lmbda * torch.log(detF) - mu) * invF32
                P31 = mu * F31 + (lmbda * torch.log(detF) - mu) * invF13
                P32 = mu * F32 + (lmbda * torch.log(detF) - mu) * invF23
                P33 = mu * F33 + (lmbda * torch.log(detF) - mu) * invF33
            else:
                print("This energy model will be implemented later !!!")
                exit()
            S11 = invF11 * P11 + invF12 * P21 + invF13 * P31
            S12 = invF11 * P12 + invF12 * P22 + invF13 * P32
            S13 = invF11 * P13 + invF12 * P23 + invF13 * P33
            S21 = invF21 * P11 + invF22 * P21 + invF23 * P31
            S22 = invF21 * P12 + invF22 * P22 + invF23 * P32
            S23 = invF21 * P13 + invF22 * P23 + invF23 * P33
            S31 = invF31 * P11 + invF32 * P21 + invF33 * P31
            S32 = invF31 * P12 + invF32 * P22 + invF33 * P32
            S33 = invF31 * P13 + invF32 * P23 + invF33 * P33
            u_pred = u_pred_torch.detach().cpu().numpy()
            F11_pred = F11.detach().cpu().numpy()
            F12_pred = F12.detach().cpu().numpy()
            F13_pred = F13.detach().cpu().numpy()
            F21_pred = F21.detach().cpu().numpy()
            F22_pred = F22.detach().cpu().numpy()
            F23_pred = F23.detach().cpu().numpy()
            F31_pred = F31.detach().cpu().numpy()
            F32_pred = F32.detach().cpu().numpy()
            F33_pred = F33.detach().cpu().numpy()
            E11_pred = E11.detach().cpu().numpy()
            E12_pred = E12.detach().cpu().numpy()
            E13_pred = E13.detach().cpu().numpy()
            E21_pred = E21.detach().cpu().numpy()
            E22_pred = E22.detach().cpu().numpy()
            E23_pred = E23.detach().cpu().numpy()
            E31_pred = E31.detach().cpu().numpy()
            E32_pred = E32.detach().cpu().numpy()
            E33_pred = E33.detach().cpu().numpy()
            S11_pred = S11.detach().cpu().numpy()
            S12_pred = S12.detach().cpu().numpy()
            S13_pred = S13.detach().cpu().numpy()
            S21_pred = S21.detach().cpu().numpy()
            S22_pred = S22.detach().cpu().numpy()
            S23_pred = S23.detach().cpu().numpy()
            S31_pred = S31.detach().cpu().numpy()
            S32_pred = S32.detach().cpu().numpy()
            S33_pred = S33.detach().cpu().numpy()
            surUx = u_pred[:, 0].reshape(Ny, Nx, Nz)
            surUy = u_pred[:, 1].reshape(Ny, Nx, Nz)
            surUz = u_pred[:, 2].reshape(Ny, Nx, Nz)
            surE11 = E11_pred.reshape(Ny, Nx, Nz)
            surE12 = E12_pred.reshape(Ny, Nx, Nz)
            surE13 = E13_pred.reshape(Ny, Nx, Nz)
            surE21 = E21_pred.reshape(Ny, Nx, Nz)
            surE22 = E22_pred.reshape(Ny, Nx, Nz)
            surE23 = E23_pred.reshape(Ny, Nx, Nz)
            surE31 = E31_pred.reshape(Ny, Nx, Nz)
            surE32 = E32_pred.reshape(Ny, Nx, Nz)
            surE33 = E33_pred.reshape(Ny, Nx, Nz)
            surS11 = S11_pred.reshape(Ny, Nx, Nz)
            surS12 = S12_pred.reshape(Ny, Nx, Nz)
            surS13 = S13_pred.reshape(Ny, Nx, Nz)
            surS21 = S21_pred.reshape(Ny, Nx, Nz)
            surS22 = S22_pred.reshape(Ny, Nx, Nz)
            surS23 = S23_pred.reshape(Ny, Nx, Nz)
            surS31 = S31_pred.reshape(Ny, Nx, Nz)
            surS32 = S32_pred.reshape(Ny, Nx, Nz)
            surS33 = S33_pred.reshape(Ny, Nx, Nz)
            SVonMises = np.float64(
                np.sqrt(0.5 * ((surS11 - surS22)**2 + (surS22 - surS33)**2 +
                               (surS33 - surS11)**2 + 6 *
                               (surS12**2 + surS23**2 + surS31**2))))
            U = (np.float64(surUx), np.float64(surUy), np.float64(surUz))
            S1 = (np.float64(surS11), np.float64(surS12), np.float64(surS13))
            S2 = (np.float64(surS21), np.float64(surS22), np.float64(surS23))
            S3 = (np.float64(surS31), np.float64(surS32), np.float64(surS33))
            E1 = (np.float64(surE11), np.float64(surE12), np.float64(surE13))
            E2 = (np.float64(surE21), np.float64(surE22), np.float64(surE23))
            E3 = (np.float64(surE31), np.float64(surE32), np.float64(surE33))
            return U, np.float64(surS11), np.float64(surS12), np.float64(surS13), np.float64(surS22), np.float64(surS23), \
                   np.float64(surS33), np.float64(surE11), np.float64(surE12), \
                   np.float64(surE13), np.float64(surE22), np.float64(surE23), np.float64(surE33), np.float64(SVonMises), \
                   np.float64(F11_pred), np.float64(F12_pred), np.float64(F13_pred), \
                   np.float64(F21_pred), np.float64(F22_pred), np.float64(F23_pred), \
                   np.float64(F31_pred), np.float64(F32_pred), np.float64(F33_pred)

    # --------------------------------------------------------------------------------
    # method: loss sum for the energy part
    # --------------------------------------------------------------------------------
    @staticmethod
    def loss_sum(tinput):
        return torch.sum(tinput) / tinput.data.nelement()

    # --------------------------------------------------------------------------------
    # purpose: loss square sum for the boundary part
    # --------------------------------------------------------------------------------
    @staticmethod
    def loss_squared_sum(tinput, target):
        row, column = tinput.shape
        loss = 0
        for j in range(column):
            loss += torch.sum((tinput[:, j] - target[:, j])**
                              2) / tinput[:, j].data.nelement()
        return loss
 def __init__(self, model, numIntType, energy, dim):
     # self.data = data
     self.model = MultiLayerNet(model[0], model[1], model[2])
     self.model = self.model.to(dev)
     self.intLoss = IntegrationLoss(numIntType, dim)
     self.energy = EnergyModel(energy, dim)
class DeepEnergyMethod:
    # Instance attributes
    def __init__(self, model, numIntType, energy, dim):
        # self.data = data
        self.model = MultiLayerNet(model[0], model[1], model[2])
        self.model = self.model.to(dev)
        self.intLoss = IntegrationLoss(numIntType, dim)
        self.energy = EnergyModel(energy, dim)

    def train_model(self, shape, dxdydz, data, neumannBC, dirichletBC, LHD,
                    iteration, type_energy):
        x = torch.from_numpy(data).float()
        x = x.to(dev)
        x.requires_grad_(True)
        # get tensor inputs and outputs for boundary conditions
        # -------------------------------------------------------------------------------
        #                             Dirichlet BC
        # -------------------------------------------------------------------------------
        dirBC_coordinates = {}  # declare a dictionary
        dirBC_values = {}  # declare a dictionary
        dirBC_penalty = {}
        for i, keyi in enumerate(dirichletBC):
            dirBC_coordinates[i] = torch.from_numpy(
                dirichletBC[keyi]['coord']).float().to(dev)
            dirBC_values[i] = torch.from_numpy(
                dirichletBC[keyi]['known_value']).float().to(dev)
            dirBC_penalty[i] = torch.tensor(
                dirichletBC[keyi]['penalty']).float().to(dev)
        # -------------------------------------------------------------------------------
        #                           Neumann BC
        # -------------------------------------------------------------------------------
        neuBC_coordinates = {}  # declare a dictionary
        neuBC_values = {}  # declare a dictionary
        neuBC_penalty = {}
        for i, keyi in enumerate(neumannBC):
            neuBC_coordinates[i] = torch.from_numpy(
                neumannBC[keyi]['coord']).float().to(dev)
            neuBC_coordinates[i].requires_grad_(True)
            neuBC_values[i] = torch.from_numpy(
                neumannBC[keyi]['known_value']).float().to(dev)
            neuBC_penalty[i] = torch.tensor(
                neumannBC[keyi]['penalty']).float().to(dev)

        # ----------------------------------------------------------------------------------
        # Minimizing loss function (energy and boundary conditions)
        # ----------------------------------------------------------------------------------
        optimizer = torch.optim.LBFGS(self.model.parameters(),
                                      lr=0.5,
                                      max_iter=20)
        start_time = time.time()
        energy_loss_array = []
        boundary_loss_array = []
        loss_array = []
        for t in range(iteration):
            # Zero gradients, perform a backward pass, and update the weights.
            def closure():
                it_time = time.time()
                # ----------------------------------------------------------------------------------
                # Internal Energy
                # ----------------------------------------------------------------------------------
                u_pred = self.model(x)  # prediction of primary variables
                u_pred.double()
                # Strain energy equations = Internal Energy
                storedEnergy = self.energy.getStoredEnergy(u_pred, x)
                dim = len(dxdydz)
                volume = LHD[0] * LHD[1] * LHD[2]

                dom_crit = volume * self.loss_sum(storedEnergy)
                if dim == 2:
                    internal2 = self.intLoss.approxIntegration(storedEnergy,
                                                               x,
                                                               shape=shape)
                elif dim == 3:
                    internal2 = self.trapz3D(storedEnergy,
                                             dx=dxdydz[0],
                                             dy=dxdydz[1],
                                             dz=dxdydz[2],
                                             shape=shape)
                # ----------------------------------------------------------------------------------
                # External Energy
                # ----------------------------------------------------------------------------------
                bc_n_crit = torch.zeros(len(neuBC_coordinates))
                external = torch.zeros(len(neuBC_coordinates))
                external2 = torch.zeros(len(neuBC_coordinates))
                for i, vali in enumerate(neuBC_coordinates):
                    if i == 0:
                        neu_u_pred = self.model(neuBC_coordinates[i])
                        area = LHD[1] * LHD[2]
                        fext = torch.bmm(
                            (neu_u_pred + neuBC_coordinates[i]).unsqueeze(1),
                            neuBC_values[i].unsqueeze(2))
                        bc_n_crit[i] = area * self.loss_sum(
                            fext) * neuBC_penalty[i]
                        if dim == 2:
                            external[i] = self.trapz1D(
                                fext, neuBC_coordinates[i][:, 1])
                            external2[i] = self.trapz1D(fext, dx=dxdydz[1])
                        elif dim == 3:
                            external2[i] = self.trapz2D(
                                fext,
                                dx=dxdydz[1],
                                dy=dxdydz[2],
                                shape=[shape[1], shape[2]])
                    else:
                        print(
                            "Not yet implemented !!! Please contact the author to ask !!!"
                        )
                        exit()
                # ----------------------------------------------------------------------------------
                # Dirichlet boundary conditions
                # ----------------------------------------------------------------------------------
                # boundary 1 x - direction
                bc_u_crit = torch.zeros((len(dirBC_coordinates)))
                for i, vali in enumerate(dirBC_coordinates):
                    dir_u_pred = self.model(dirBC_coordinates[i])
                    bc_u_crit[i] = self.loss_squared_sum(
                        dir_u_pred, dirBC_values[i]) * dirBC_penalty[i]
                # ----------------------------------------------------------------------------------
                # Compute and print loss
                # ----------------------------------------------------------------------------------
                energy_loss = internal2 - torch.sum(external2)
                boundary_loss = torch.sum(bc_u_crit)
                loss = energy_loss + boundary_loss
                optimizer.zero_grad()
                loss.backward()
                print(
                    'Iter: %d Loss: %.9e Energy: %.9e Boundary: %.9e Time: %.3e'
                    % (t + 1, loss.item(), energy_loss.item(),
                       boundary_loss.item(), time.time() - it_time))
                energy_loss_array.append(energy_loss.data)
                boundary_loss_array.append(boundary_loss.data)
                loss_array.append(loss.data)
                return loss

            optimizer.step(closure)
        elapsed = time.time() - start_time
        print('Training time: %.4f' % elapsed)

    def __trapz(self, y, x=None, dx=1.0, axis=-1):
        # y = np.asanyarray(y)
        if x is None:
            d = dx
        else:
            d = x[1:] - x[0:-1]
            # reshape to correct shape
            shape = [1] * y.ndimension()
            shape[axis] = d.shape[0]
            d = d.reshape(shape)
        nd = y.ndimension()
        slice1 = [slice(None)] * nd
        slice2 = [slice(None)] * nd
        slice1[axis] = slice(1, None)
        slice2[axis] = slice(None, -1)
        ret = torch.sum(d * (y[tuple(slice1)] + y[tuple(slice2)]) / 2.0, axis)
        return ret

    def trapz1D(self, y, x=None, dx=1.0, axis=-1):
        y1D = y.flatten()
        if x is not None:
            x1D = x.flatten()
            return self.__trapz(y1D, x1D, dx=dx, axis=axis)
        else:
            return self.__trapz(y1D, dx=dx)

    def trapz2D(self, f, xy=None, dx=None, dy=None, shape=None):
        f2D = f.reshape(shape[0], shape[1])
        if dx is None and dy is None:
            x = xy[:, 0].flatten().reshape(shape[0], shape[1])
            y = xy[:, 1].flatten().reshape(shape[0], shape[1])
            return self.__trapz(self.__trapz(f2D, y[0, :]), x[:, 0])
        else:
            return self.__trapz(self.__trapz(f2D, dx=dy), dx=dx)

    def trapz3D(self, f, xyz=None, dx=None, dy=None, dz=None, shape=None):
        f3D = f.reshape(shape[0], shape[1], shape[2])
        if dx is None and dy is None and dz is None:
            print("dxdydz - trapz3D - Need to implement !!!")
        else:
            return self.__trapz(self.__trapz(self.__trapz(f3D, dx=dz), dx=dy),
                                dx=dx)

    # --------------------------------------------------------------------------------
    # Purpose: After training model, predict solutions with some data input
    # --------------------------------------------------------------------------------
    def evaluate_model(self, x_space, y_space, z_space):
        surfaceUx = np.zeros([len(y_space), len(x_space), len(z_space)])
        surfaceUy = np.zeros([len(y_space), len(x_space), len(z_space)])
        surfaceUz = np.zeros([len(y_space), len(x_space), len(z_space)])
        for i, y in enumerate(y_space):
            for j, x in enumerate(x_space):
                for k, z in enumerate(z_space):
                    t_tensor = torch.tensor([x, y, z]).unsqueeze(0)
                    tRes = self.model(t_tensor).detach().cpu().numpy()[0]
                    surfaceUx[i][j][k] = tRes[0]
                    surfaceUy[i][j][k] = tRes[1]
                    surfaceUz[i][j][k] = tRes[2]
        return surfaceUx, surfaceUy, surfaceUz

    # --------------------------------------------------------------------------------
    # Purpose: After training model, predict solutions with some data input in 2D
    # --------------------------------------------------------------------------------
    def evaluate_model2d(self, x_space, y_space):
        z_space = np.array([0])
        surfaceUx = np.zeros([len(y_space), len(x_space), len(z_space)])
        surfaceUy = np.zeros([len(y_space), len(x_space), len(z_space)])
        surfaceUz = np.zeros([len(y_space), len(x_space), len(z_space)])
        for i, y in enumerate(y_space):
            for j, x in enumerate(x_space):
                for k, z in enumerate(z_space):
                    t_tensor = torch.tensor([x, y]).unsqueeze(0)
                    tRes = self.model(t_tensor).detach().cpu().numpy()[0]
                    surfaceUx[i][j][k] = tRes[0]
                    surfaceUy[i][j][k] = tRes[1]
                    surfaceUz[i][j][k] = 0
        return surfaceUx, surfaceUy, surfaceUz

    # --------------------------------------------------------------------------------
    # Purpose: Evaluate data
    # --------------------------------------------------------------------------------
    def evaluate_data(self, data):
        new_position = np.zeros(np.shape(data))
        disp = np.zeros(np.shape(data))
        for i, vali in enumerate(data):
            t_tensor = torch.tensor([vali[0], vali[1]]).unsqueeze(0)
            tRes = self.model(t_tensor).detach().cpu().numpy()[0]
            disp[i, :] = np.copy(tRes)
            new_position[i, :] = vali + tRes
        return new_position, disp

    # --------------------------------------------------------------------------------
    # method: loss sum for the energy part
    # --------------------------------------------------------------------------------
    @staticmethod
    def loss_sum(tinput):
        return torch.sum(tinput) / tinput.data.nelement()

    # --------------------------------------------------------------------------------
    # purpose: loss square sum for the boundary part
    # --------------------------------------------------------------------------------
    @staticmethod
    def loss_squared_sum(tinput, target):
        row, column = tinput.shape
        loss = 0
        for j in range(column):
            loss += torch.sum((tinput[:, j] - target[:, j])**
                              2) / tinput[:, j].data.nelement()
        return loss