PINN: physics informed neural network to predict motion of 1D simple harmonic oscillator​
​by Shrey Arora​
Student
We attempted to incorporate physical differential equations to training of a neural network, so that we may be able to make predictions over a domain larger than the training data domain. The main challenge was “how to use the latest state of the net being trained, take its derivatives, and add those to the loss term.” Many in-built functions, and compositions of in-built functions were tried. Finally, we devised a way to use the the net for building custom loss functions. Next, we will try to tweak and improve the performance of the eventual net.

Analytical Solution to generate our data

In the first step, we use the already known physics equations to generate training and testing data for our model. The equation for the 1D damped harmonic oscillator is:
in the underdamped state :

Oscillator function

This function takes in two parameters δ and
ω
0
, and an x position, and outputs the y position for the same.​
In[]:=
oscillator[d_,w0_,x_]:=Module[{w,phi,A,cos,exp,y},​​w=Sqrt[w0^2-d^2];​​phi=ArcTan[-d/w];​​A=1/(2*Cos[phi]);​​cos=Cos[w0*x+phi];​​exp=Exp[-d*x];​​y=exp*2*A*cos;​​y//N​​]

Generate Data

In[]:=
data=Table[{x,oscillator[2,20,x]},{x,0,0.5,0.01}];​​traindata=Table[{data[[i]][[1]]}->{data[[i]][[2]]},{i,1,Length[data]}];​​traindata=RandomChoice[traindata,Length[traindata]];​​testdata=Table[{data[[i]][[1]]}->{data[[i]][[2]]},{i,1,Length[data]}];​​testdata=RandomChoice[testdata,Length[testdata]];

Training on a traditional neural network

Define a simple net class

net function that takes 4 arguments: no. of inputs, outputs, hidden neurons per layer, and number of layers
In[]:=
net[ninput_,noutput_,nhidden_,nlayers_]:=​​NetChain[{​​LinearLayer[nhidden],​​FunctionLayer[Tanh],​​Table[{​​LinearLayer[nhidden],Tanh},​​{nlayers}],​​LinearLayer[noutput]​​},"Input"->{ninput}]
Create a net with the required parameters
In[]:=
firstnet=net[1,1,10,2]
Out[]=
NetChain
uniniti
aliz
ed
Inputport:
vector(size: 1)
Outputport:
vector(size: 1)

training this net to get a baseline
In[]:=
firstnettrained=NetTrain[firstnet,traindata,{"TrainedNet","ResultsObject"},ValidationSettestdata]​​Show[​​Plot[oscillator[2,20,x],{x,0,1},ColorFunctionHue],​​Plot[firstnettrained[[1]][i],{i,0,1},PlotStyle->Gray],​​Graphics@Line[{{0.5,-1},{0.5,1}}]​​]
Out[]=
NetChain
Inputport:
vector(size: 1)
Outputport:
vector(size: 1)
,NetTrainResultsObject
NetTrain Results
summary
batches:
757,
rounds:
757,
time:
1.7s
,
examples/s:
23088
data
training examples:
51,
validation examples:
51,
processed examples:
38607,
skipped examples:
0
method
ADAM
optimizer
,
batch size
51,
CPU
round
loss:
1.94×
-3
10
validation
loss:
2.64×
-3
10
​
rounds
loss
training set
validation set

Out[]=

Structuring the Physics Informed Net

To calculate the first and second derivative, we use their formulae derived from first principles :
y’(x)=
lim
h->0
y(x+h)-y(x)
h
,and​​y’’(x)=
lim
h->0
(y(x+2h)-2y(x+h)+f(x))
2
h
To be able to do this, we need to be able to evaluate the latest iteration of the target net, as Wolfram Language doesn’t have an in-built equivalent to AutoGrad.
For doing that, we need to share the parameters from our base model, using the “NetInsertSharedArrays” function, and Initialize the model.
In[]:=
secondmodel=net[1,1,10,2]
Out[]=
NetChain
uniniti
aliz
ed
Inputport:
vector(size: 1)
Outputport:
vector(size: 1)

In[]:=
secondmodel=NetInsertSharedArrays[secondmodel]​​secondmodel=NetInitialize[secondmodel]
Out[]=
NetChain
uniniti
aliz
ed
Inputport:
vector(size: 1)
Outputport:
vector(size: 1)

Out[]=
NetChain
Inputport:
vector(size: 1)
Outputport:
vector(size: 1)

“ploss” is a NetGraph that finds the first and second derivative
In[]:=
ploss=NetGraph[​​<|​​"d1"->FunctionLayer[Apply[4*(#1-#2)/10^-4&]],​​"d2"->FunctionLayer[Apply[(#1-2*#2+#3)/((10^-4)^2)&]],​​"total"->TotalLayer[],​​"sum"->SummationLayer[],​​"scale"->FunctionLayer[Apply[#*400&]]​​|>,​​{​​NetPort["net(x+h)"]->"d1",​​NetPort["net(x)"]->"d1",​​NetPort["net(x+2h)"]->"d2",​​NetPort["net(x+h)"]->"d2",​​NetPort["net(x)"]->"d2",​​"d1"->"total",​​"d2"->"total",​​"total"->"sum",​​NetPort["net(x)"]->"scale"->"total"​​}​​]​​
To be able to use “ploss”, we need to give it the relevant inputs, we define “NetPhys”

Concluding remarks

While this doesn’t work as well as we had hoped it would, we have made progress in the coding problem this project presented, and have got results a bit better than a plain net. What remains to be done is to train the network over more iterations and test out various tweaks like changing the scaling factors, and weight of the loss terms.

Keywords

◼
  • PINN
  • ◼
  • Physics
  • ◼
  • Classical mechanics
  • ◼
  • Harmonic motion
  • ◼
  • Machine learning
  • ◼
  • Neural networks
  • Acknowledgment

    My mentor Mr. Julian Laverde, TA Mr. Rahul Sharma and Mr. Mike Yeh were instrumental

    References

    ◼
  • https://github.com/benmoseley/harmonic-oscillator-pinn/blob/main/Harmonic%20 oscillator %20 PINN.ipynb
  • ◼
  • https://qph.fs.quoracdn.net/main-qimg-52fb863d4b7fa63f2df2a01c08140422
  • ◼
  • https://benmoseley.blog/my-research/so-what-is-a-physics-informed-neural-network/