PINN解偏微分方程实例3(Allen-Cahn方程)

PINN解偏微分方程实例3之Allen-Cahn方程

  • 1. Allen-Cahn方程
  • 2. 损失函数如下定义
  • 3. 代码
  • 4. 实验细节及复现结果
  • 参考资料

1. Allen-Cahn方程

   考虑偏微分方程如下:
u t − 0.0001 u x x + 5 u 3 − 5 u = 0 u ( 0 , x ) = x 2 c o s ( π x ) u ( t , − 1 ) = u ( t , 1 ) u x ( t , − 1 ) = u x ( t , 1 ) \begin{align} \begin{aligned} & u_t - 0.0001u_{xx} + 5u^3 -5u = 0 \\ & u(0,x) = x^2cos(\pi x) \\ & u(t,-1) = u(t,1) \\ & u_x(t,-1) = u_x(t,1) \end{aligned} \end{align} ut0.0001uxx+5u35u=0u(0,x)=x2cos(πx)u(t,1)=u(t,1)ux(t,1)=ux(t,1)
其中 x ∈ [ − 1 , 1 ] , t ∈ [ 0 , 1 ] . x\in[-1,1],t\in[0,1]. x[1,1],t[0,1].这是一个带有周期性边界条件,初始条件的偏微分方程。这个方程主要用 P I N N [ 1 ] PINN^{[1]} PINN[1]论文中正向问题的离散时间模型求解。

2. 损失函数如下定义

S S E = S S E n + S S E b \begin{align} \begin{aligned} SSE = SSE_n + SSE_b \\ \end{aligned} \end{align} SSE=SSEn+SSEb
其中
S S E n = ∑ j = 1 q + 1 ∑ i = 1 N n ∣ u j n ( x n , i ) − u n , i ∣ 2 S S E b = ∑ i = 1 q ∣ u n + c i ( − 1 ) − u n + c i ( 1 ) ∣ 2 + ∣ u n + 1 ( − 1 ) − u n + 1 ( 1 ) ∣ 2 + ∑ i = 1 q ∣ u x n + c i ( − 1 ) − u x n + c i ( 1 ) ∣ 2 + ∣ u x n + 1 ( − 1 ) − u x n + 1 ( 1 ) ∣ 2 \begin{align} \begin{aligned} SSE_n &= \sum_{j=1}^{q+1}\sum_{i=1}^{N_n}|u_j^{n}(x^{n,i})-u^{n,i}|^2 \\ SSE_b &= \sum_{i=1}^{q} |u^{n+c_i}(-1)-u^{n+c_i}(1)|^2+ |u^{n+1}(-1)-u^{n+1}(1)|^2 \\ &+\sum_{i=1}^{q} |u_x^{n+c_i}(-1)-u_x^{n+c_i}(1)|^2+ |u_x^{n+1}(-1)-u_x^{n+1}(1)|^2 \\ \end{aligned} \end{align} SSEnSSEb=j=1q+1i=1Nnujn(xn,i)un,i2=i=1qun+ci(1)un+ci(1)2+un+1(1)un+1(1)2+i=1quxn+ci(1)uxn+ci(1)2+uxn+1(1)uxn+1(1)2
这里 S S E b SSE_b SSEb是周期性边界损失, S S E n SSE_n SSEn可以理解为PDE损失, { x n , i , u n , i } ∣ i = 1 N n \{x^{n,i},u^{n,i}\}|_{i=1}^{N_n} {xn,i,un,i}i=1Nn t n t^n tn时刻相应的数据点和真解。 u j n ( x n , i ) u_j^{n}(x^{n,i}) ujn(xn,i)利用公式(4)、(5)计算得到。
u n + c i = u n − Δ t ∑ j = 1 q a i j N [ u n + c j ] , i = 1 , 2 , . . . , q u n + 1 = u n − Δ t ∑ j = 1 q b j N [ u n + c j ] . \begin{align} \begin{aligned} u^{n+c_i} &= u^n - \Delta t \sum_{j=1}^q a_{ij} \mathcal{N}[u^{n+c_j}], \quad i=1,2,...,q \\ u^{n+1} &= u^n - \Delta t \sum_{j=1}^q b_{j} \mathcal{N}[u^{n+c_j}]. \end{aligned} \end{align} un+ciun+1=unΔtj=1qaijN[un+cj],i=1,2,...,q=unΔtj=1qbjN[un+cj].
公式(4)中 N [ u n + c j ] \mathcal{N}[u^{n+c_j}] N[un+cj]表达式如公式(5)所示。
N [ u n + c j ] = − 0.0001 u x x n + c j + 5 ( u n + c j ) 3 − 5 u n + c j \begin{align} \begin{aligned} \mathcal{N}[u^{n+c_j}] = -0.0001u_{xx}^{n+c_j} + 5(u^{n+c_j})^3 - 5u^{n+c_j} \end{aligned} \end{align} N[un+cj]=0.0001uxxn+cj+5(un+cj)35un+cj
   这里 N n = 200 , q = 100 , Δ t = 0.8 N_n=200,q=100,\Delta t=0.8 Nn=200q=100Δt=0.8。神经网络模型输入层包括一个神经元,四个100神经元的隐藏层,101个神经元的输出层。

3. 代码

  代码参考下图进行理解。
在这里插入图片描述
  代码参考https://github.com/maziarraissi/PINNs,原代码运行框架tensorflow1,这里将其改为tensorflow2上运行,代码如下:

"""
@author: Maziar Raissi
@Annotator:ST
计算t*x为[0,1]*[-1,1]区域上的真解,真解个数t*x为201*256
"""import sys
sys.path.insert(0, '../../Utilities/')import tensorflow.compat.v1 as tf   # tensorflow1.0代码迁移到2.0上运行,加上这两行
tf.disable_v2_behavior()# import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import time
import scipy.io
from plotting import newfig, savefig
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1 import make_axes_locatablenp.random.seed(1234)
tf.set_random_seed(1234)class PhysicsInformedNN:# Initialize the classdef __init__(self, x0, u0, x1, layers, dt, lb, ub, q):"""input: 200个t=0.1时x坐标,x=-1,1 输入202个样本output: 每个坐标输出这个坐标在未来q个时间的解和t+dt时刻的解,输出维度分别为(200*101)(2*101):param x0: 空间选定的200个点的x坐标值:param u0: 200个x在t=0.1时对应的u的精确解:param x1: 空间边界[[-1],[1]]:param layers: 神经网络各层神经元列表:param dt: 时间步长 0.8:param lb: -1:param ub: 1:param q: q阶龙格库达,即t方向取q个点的斜率的加权平均作为龙格库达法的平均斜率"""self.lb = lbself.ub = ubself.x0 = x0self.x1 = x1self.u0 = u0self.layers = layersself.dt = dtself.q = max(q,1)# Initialize NNself.weights, self.biases = self.initialize_NN(layers)# Load IRK weightstmp = np.float32(np.loadtxt('../../Utilities/IRK_weights/Butcher_IRK%d.txt' % (q), ndmin = 2))self.IRK_weights = np.reshape(tmp[0:q**2+q], (q+1,q))self.IRK_times = tmp[q**2+q:]# tf placeholders and graphself.sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True,log_device_placement=True))self.x0_tf = tf.placeholder(tf.float32, shape=(None, self.x0.shape[1]))self.x1_tf = tf.placeholder(tf.float32, shape=(None, self.x1.shape[1]))self.u0_tf = tf.placeholder(tf.float32, shape=(None, self.u0.shape[1]))self.dummy_x0_tf = tf.placeholder(tf.float32, shape=(None, self.q)) # dummy variable for fwd_gradientsself.dummy_x1_tf = tf.placeholder(tf.float32, shape=(None, self.q+1)) # dummy variable for fwd_gradientsself.U0_pred = self.net_U0(self.x0_tf) # N(200) x (q+1)  200个x内部点输入网络训练self.U1_pred, self.U1_x_pred= self.net_U1(self.x1_tf) # N1(=2) x (q+1)  x=-1,1 输入网络得到边界# self.U1_pred (2*101) 分别对应x=-1,1时的预测的dt内q个真解和一个u^{n+1}时的真解self.loss = tf.reduce_sum(tf.square(self.u0_tf - self.U0_pred)) + \tf.reduce_sum(tf.square(self.U1_pred[0,:] - self.U1_pred[1,:])) + \tf.reduce_sum(tf.square(self.U1_x_pred[0,:] - self.U1_x_pred[1,:]))                     # self.optimizer = tf.contrib.opt.ScipyOptimizerInterface(self.loss,#                                                         method = 'L-BFGS-B',#                                                         options = {'maxiter': 50000,#                                                                    'maxfun': 50000,#                                                                    'maxcor': 50,#                                                                    'maxls': 50,#                                                                    'ftol' : 1.0 * np.finfo(float).eps})self.optimizer_Adam = tf.train.AdamOptimizer()self.train_op_Adam = self.optimizer_Adam.minimize(self.loss)init = tf.global_variables_initializer()self.sess.run(init)def initialize_NN(self, layers):        weights = []biases = []num_layers = len(layers) for l in range(0,num_layers-1):W = self.xavier_init(size=[layers[l], layers[l+1]])b = tf.Variable(tf.zeros([1,layers[l+1]], dtype=tf.float32), dtype=tf.float32)weights.append(W)biases.append(b)        return weights, biasesdef xavier_init(self, size):in_dim = size[0]out_dim = size[1]        xavier_stddev = np.sqrt(2/(in_dim + out_dim))return tf.Variable(tf.truncated_normal([in_dim, out_dim], stddev=xavier_stddev), dtype=tf.float32)def neural_net(self, X, weights, biases):num_layers = len(weights) + 1H = 2.0*(X - self.lb)/(self.ub - self.lb) - 1.0for l in range(0,num_layers-2):W = weights[l]b = biases[l]H = tf.tanh(tf.add(tf.matmul(H, W), b))W = weights[-1]b = biases[-1]Y = tf.add(tf.matmul(H, W), b)return Ydef fwd_gradients_0(self, U, x):        g = tf.gradients(U, x, grad_ys=self.dummy_x0_tf)[0]return tf.gradients(g, self.dummy_x0_tf)[0]def fwd_gradients_1(self, U, x):        g = tf.gradients(U, x, grad_ys=self.dummy_x1_tf)[0]return tf.gradients(g, self.dummy_x1_tf)[0]def net_U0(self, x):U1 = self.neural_net(x, self.weights, self.biases)U = U1[:,:-1]U_x = self.fwd_gradients_0(U, x)U_xx = self.fwd_gradients_0(U_x, x)F = 5.0*U - 5.0*U**3 + 0.0001*U_xxU0 = U1 - self.dt*tf.matmul(F, self.IRK_weights.T)    # IRK_weights(101*100)  包括了Runde-Kutta方法参数a,breturn U0def net_U1(self, x):U1 = self.neural_net(x, self.weights, self.biases)U1_x = self.fwd_gradients_1(U1, x)return U1, U1_x # N x (q+1)def callback(self, loss):print('Loss:', loss)def train(self, nIter):tf_dict = {self.x0_tf: self.x0, self.u0_tf: self.u0, self.x1_tf: self.x1,self.dummy_x0_tf: np.ones((self.x0.shape[0], self.q)),self.dummy_x1_tf: np.ones((self.x1.shape[0], self.q+1))}start_time = time.time()for it in range(nIter):self.sess.run(self.train_op_Adam, tf_dict)# Printif it % 10 == 0:elapsed = time.time() - start_timeloss_value = self.sess.run(self.loss, tf_dict)print('It: %d, Loss: %.3e, Time: %.2f' % (it, loss_value, elapsed))start_time = time.time()# self.optimizer.minimize(self.sess,#                         feed_dict = tf_dict,#                         fetches = [self.loss],#                         loss_callback = self.callback)def predict(self, x_star):U1_star = self.sess.run(self.U1_pred, {self.x1_tf: x_star})return U1_starif __name__ == "__main__": q = 100layers = [1, 200, 200, 200, 200, q+1]lb = np.array([-1.0])ub = np.array([1.0])N = 200data = scipy.io.loadmat('../Data/AC.mat')t = data['tt'].flatten()[:,None]  # T(201) x 1  精确解时间坐标节点x = data['x'].flatten()[:,None]  # N(512) x 1  精确解空间坐标节点Exact = np.real(data['uu']).T  # T x N 精确解idx_t0 = 20idx_t1 = 180dt = t[idx_t1] - t[idx_t0]    # 时间步长0.8# Initial datanoise_u0 = 0.0idx_x = np.random.choice(Exact.shape[1], N, replace=False)    # 随机选择空间200个点的下标索引x0 = x[idx_x,:]    # 空间200个点的x坐标值u0 = Exact[idx_t0:idx_t0+1,idx_x].T    # t=0.10时200个精确解u0 = u0 + noise_u0*np.std(u0)*np.random.randn(u0.shape[0], u0.shape[1])# Boudanry datax1 = np.vstack((lb,ub))# Test datax_star = xmodel = PhysicsInformedNN(x0, u0, x1, layers, dt, lb, ub, q)model.train(2)    # 10000U1_pred = model.predict(x_star)    # (512,101)error = np.linalg.norm(U1_pred[:,-1] - Exact[idx_t1,:], 2)/np.linalg.norm(Exact[idx_t1,:], 2)print('Error: %e' % (error))  # sqrt(sum_{i=1}^512 (u-u_{ext})) / sqrt(sum_{i=1}^512 (u_{ext}))  相对误差################################################################################################### Plotting #####################################################################################################    fig, ax = newfig(1.0, 1.2)ax.axis('off')####### Row 0: h(t,x) ##################    gs0 = gridspec.GridSpec(1, 2)gs0.update(top=1-0.06, bottom=1-1/2 + 0.1, left=0.15, right=0.85, wspace=0)ax = plt.subplot(gs0[:, :])h = ax.imshow(Exact.T, interpolation='nearest', cmap='seismic', extent=[t.min(), t.max(), x_star.min(), x_star.max()], origin='lower', aspect='auto')divider = make_axes_locatable(ax)cax = divider.append_axes("right", size="5%", pad=0.05)fig.colorbar(h, cax=cax)line = np.linspace(x.min(), x.max(), 2)[:,None]ax.plot(t[idx_t0]*np.ones((2,1)), line, 'w-', linewidth = 1)ax.plot(t[idx_t1]*np.ones((2,1)), line, 'w-', linewidth = 1)ax.set_xlabel('$t$')ax.set_ylabel('$x$')leg = ax.legend(frameon=False, loc = 'best')ax.set_title('$u(t,x)$', fontsize = 10)####### Row 1: h(t,x) slices ##################    gs1 = gridspec.GridSpec(1, 2)gs1.update(top=1-1/2-0.05, bottom=0.15, left=0.15, right=0.85, wspace=0.5)ax = plt.subplot(gs1[0, 0])ax.plot(x,Exact[idx_t0,:], 'b-', linewidth = 2) ax.plot(x0, u0, 'rx', linewidth = 2, label = 'Data')      ax.set_xlabel('$x$')ax.set_ylabel('$u(t,x)$')    ax.set_title('$t = %.2f$' % (t[idx_t0]), fontsize = 10)ax.set_xlim([lb-0.1, ub+0.1])ax.legend(loc='upper center', bbox_to_anchor=(0.8, -0.3), ncol=2, frameon=False)ax = plt.subplot(gs1[0, 1])ax.plot(x,Exact[idx_t1,:], 'b-', linewidth = 2, label = 'Exact') ax.plot(x_star, U1_pred[:,-1], 'r--', linewidth = 2, label = 'Prediction')      ax.set_xlabel('$x$')ax.set_ylabel('$u(t,x)$')    ax.set_title('$t = %.2f$' % (t[idx_t1]), fontsize = 10)    ax.set_xlim([lb-0.1, ub+0.1])    ax.legend(loc='upper center', bbox_to_anchor=(0.1, -0.3), ncol=2, frameon=False)savefig('./figures/retest/reAC')

4. 实验细节及复现结果

  这里使用4层全连接神经网络,输入层和输出层各两个神经元,输入层一个神经元代表 x x x坐标值,输出层101个神经元分别代表 [ u n + c 1 ( x ) , ⋯ , u n + c q ( x ) , u n + 1 ( x ) ] [u^{n+c_1}(x),\cdots,u^{n+c_q}(x),u^{n+1}(x)] [un+c1(x),,un+cq(x),un+1(x)],隐藏层每层100个神经元。为了计算误差,作者提供了使用谱方法计算的 ( 256 ∗ 201 ) (256*201) (256201)个真解,其中第一维度代表空间 x x x,第二维度代表时间 t t t. 训练10000次之后输出结果如下:

It: 9970, Loss: 5.127e+00, Time: 0.08
It: 9980, Loss: 1.335e+01, Time: 0.07
It: 9990, Loss: 2.095e+01, Time: 0.07
Error: 2.470155e-01

这里误差是相对误差,计算公式如下:
E r r o r = ∣ ∣ U − U e x t ∣ ∣ 2 ∣ ∣ U e x t ∣ ∣ 2 \begin{align} \begin{aligned} Error = \frac{||U-U_{ext}||_2}{||U_{ext}||_2} \end{aligned} \end{align} Error=∣∣Uext2∣∣UUext2
其中 U ∈ R 512 × 1 , U e x t ∈ R 512 × 1 U \in \mathbb{R}^{512 \times 1},U_{ext}\in \mathbb{R}^{512 \times 1} UR512×1,UextR512×1,前者为 t = 0.9 t=0.9 t=0.9时PINN预测的解,后者为 t = 0.9 t=0.9 t=0.9时计算的精确解。复现结果图如下:
在这里插入图片描述
论文中结果如下:
在这里插入图片描述

参考资料

[1]. Physics-informed machine learning

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/43981.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chatgpt赋能python:用Python解方程

用Python解方程 介绍 解方程是数学中最基础的技能之一,也是很多实际问题中必须掌握的技能。Python是一种功能强大的编程语言,通过它,我们可以编写程序来解方程。在本篇文章中,我们将介绍如何使用Python来解方程。 Python中的方…

PINN解偏微分方程--程函方程

目录 前言 一、什么是程函方程? 二、配置环境及库的导入 三、构建训练数据集 四、用Pytorch搭建PINN网络 1.网络搭建 2.一些基本参数变量的确定以及数据格式的转换 五、用Pytorch搭建PINN网络 六、查看loss下降情况 七、导入网络模型,输入验证数据&#…

【免费下载】2023年1月份热门报告合集(附下载链接)

省时查报告-专业、及时、全面的报告库 省时查方案-专业、及时、全面的方案库 2023年1月份省时查报告平台十大热门报告新鲜出炉,本期的热门报告关键词有:2023、趋势、投资、房地产、展望、消费、短视频、抖音、直播电商、零售等;快来看看都谁上…

【免费下载】2023年2月份热门报告合集(附下载链接)

省时查报告-专业、及时、全面的报告库 省时查方案-专业、及时、全面的方案库 2023年2月份省时查报告平台十大热门报告新鲜出炉,本期的热门报告关键词有:ChatGPT、AIGC、人工智能、情人节、营销、直播电商、跨境电商、数字化等;快来看看都谁上…

【免费下载】2023年3月份热门报告合集(附下载链接)

省时查报告-专业、及时、全面的报告库 省时查方案-专业、及时、全面的方案库 【限时免费】无需翻墙,ChatGPT4直接使用 2023年2月十大热门报告盘点 2023年3月份省时查报告平台十大热门报告新鲜出炉,本期的热门报告关键词有:ChatGPT、GPT4、小红…

初学Python到月入过万最快的兼职途径(纯干货)

不错过任何一次干赚钱干货 1.兼职薪资,附行哥工资单 2.兼职门槛,附学习知识清单 3.兼职途径,附入职考核过程 4.行哥的兼职感受 答应行友的第一篇赚钱干货推文来啦,行哥第一个在读书期间通过兼职赚到的10w收入,这也…

AIGC|我让AI来写今年高考作文

作者:谢凯 | 神州数码云基地-需求分析师 目录 一、人工智能究竟强在哪 //以ChatGPT为例,人工智能其优势何在? 二、BingAI如何处理高考作文 三、总结 一、人工智能究竟强在哪 随着ChatGPT(Chat Generative Pre-trained Transfo…

ChatGPT|谷歌首席决策科学家Cassie Kozyrkov介绍 ChatGPT

文章目录 介绍 ChatGPT!对抗网络GANs使用 ChatGPT 编写代码 大揭秘一些自动生成的废话 介绍 ChatGPT! 原文:地址 作者:Cassie Kozyrkov 谷歌首席决策科学家。 ❤️ 统计、ML/AI、数据、双关语、艺术、戏剧、决策科学。 有句话介绍…

新媒体如何借势进行热点营销

互联网营销时代,眼花缭乱的信息在消费者眼中总是昙花一现,碎片化的信息分散着当代人的注意力。为了聚焦用户碎片化的注意力,吸引消费者眼球,“蹭热点”就成为了各大品牌方“借势”惯用的方法。“蹭热点”其实就是借势营销&#xf…

2022年的告别

契子 2022年即将过去,没想到年末成了杨过。坐在家里电脑前,看了看窗外,回想了一整年的时光,虽然很多时刻有过彷徨迷茫,但总归都是成长。今年看了不少人性和哲学相关的书籍,其主要原因是因为在管理方面&…

基于Qt的网络音乐播放器(五)实现歌词滚动显示

2020博客之星年度总评选进行中:请为74号的狗子投上宝贵的一票! 我的投票地址:点击为我投票 文章目录 1.思路和效果图2.歌词的解析与存储3.onDurationChanged()4.总结 网络播放器系列: qt 布局和样式表基于Qt的网络音乐播放器&am…

为什么停更ROS2机器人课程-2023-

机器人工匠阿杰肺腑之言: 我放弃了ROS2课程 真正的危机不是同行竞争,比如教育从业者相互竞争不会催生ChatGPT…… 技术变革的突破式发展通常是新势力带来的而非传统行业的升级改革。 2013年也就是10年前在当时主流视频网站开启分享: 比如 …

chatgpt赋能Python-mac上的python

在Mac上使用Python编程的好处 作为一名有10年Python编程经验的工程师,我必须说Mac是一个出色的编程工作台。Mac操作系统本质上就是一个整合了各种开发工具的平台,为Python编程提供了很好的支持。这篇文章将介绍在Mac上使用Python编程的好处。 简单易用…

chatgpt赋能Python-python_bonjour

Python Bonjour: 通过Python实现Bonjour协议 介绍 Bonjour是苹果公司推出的一种新型、免配置的网络协议,它可以使局域网中的电脑、打印机、电话等各种设备自动发现及配置,从而促进了网络设备的普及。Python作为一种高效、简洁、易学的语言&…

chatgpt赋能Python-python_heic

Python处理HEIC文件-从未如此容易 如果你是摄影师或者只是经常在移动设备上拍照的人,你可能已经遇到过HEIC文件的问题。HEIC是苹果公司最新的图像格式,它在保存高质量图像的同时节省了存储空间。但是,许多人在处理HEIC文件时遇到了问题&…

chatgpt赋能Python-python_imessage

Python iMessage – 给你的消息通讯锦上添花 作为一款风靡全球的通讯工具,iMessage的使用率在近年来不断攀升。iMessage的简洁易用性以及其在安全性和隐私方面的优势,经常使得其成为个人和商业用户的首选。而Python iMessage,则为这项通讯工…

视频剪辑 图文教程

一米剪辑图文教程 AppStore 搜索:一米剪辑 可下载该APP,免费无广告。 一、提取视频 功能说明:提取视频是根据抖音视频链接地址提取无水印视频,能无痕提取抖音视频,并保存到自己的相册。 图文教程: 1、…

手把手教你批量剪辑视频

1、去蓝奏云获取CRVideoMate 2、双击软件安装 3、 主界面分为视频队列区、操作面板区、处理记录区与状态栏。 用户导入或拖视频至视频列表中,设置转换参数,点击[开始处理],静待处理即可。 右下角为功能菜单,选项、背景、参数、帮…

分享三个视频剪辑软件APP给你

每次在社交平台上刷到好看的视频VLOG,相信大家应该和我的内心一样,开始蠢蠢欲动了,想要自己制作一段短视频,但是自己却不会视频剪辑,不知道怎么才能将视频更好的展示给大家看,其实我们可以借助一些好用的视…

使用HTML版制作个人简历制作,非常好看的模板!!!

代码 <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,…