5 "execution_count": null,
11 "import reproducibility"
15 "cell_type": "markdown",
20 "# Inside Neural Networks for Fuel Moisture\n",
21 "## Jan Mandel, University of Colorado Denver\n"
25 "cell_type": "markdown",
30 "## Building and evaluating RNN"
34 "cell_type": "markdown",
39 "A recurrent neural network (RNN) has a similar information flow but it can be more flexible and look for the best model automatically, i.e., build the model from data. \n",
41 "We'll start by how to evaluate the map, then actually create it later.\n",
43 "The following is based on https://machinelearningmastery.com/understanding-simple-recurrent-neural-networks-in-keras/\n"
48 "execution_count": null,
54 "import numpy as np\n",
55 "import tensorflow as tf\n",
56 "from keras.models import Sequential\n",
57 "from keras.layers import Dense, SimpleRNN\n",
58 "from keras.utils.vis_utils import plot_model\n",
59 "from sklearn.preprocessing import MinMaxScaler\n",
60 "from sklearn.metrics import mean_squared_error\n",
62 "import matplotlib.pyplot as plt\n",
63 "import tensorflow as tf\n",
64 "import keras.backend as K"
69 "execution_count": null,
75 "def create_RNN(hidden_units, dense_units, input_shape, activation):\n",
76 " inputs = tf.keras.Input(shape=input_shape)\n",
77 " # https://stackoverflow.com/questions/43448029/how-can-i-print-the-values-of-keras-tensors\n",
78 " # inputs2 = K.print_tensor(inputs, message='inputs = ') # change allso inputs to inputs2 below, must be used\n",
79 " x = tf.keras.layers.SimpleRNN(hidden_units, input_shape=input_shape,\n",
80 " activation=activation[0])(inputs)\n",
81 " outputs = tf.keras.layers.Dense(dense_units, activation=activation[1])(x)\n",
82 " model = tf.keras.Model(inputs=inputs, outputs=outputs)\n",
83 " model.compile(loss='mean_squared_error', optimizer='adam')\n",
89 "execution_count": null,
99 "demo_model = create_RNN(hidden_units=hidden, dense_units=1, \n",
100 " input_shape=(timesteps,features), \n",
101 " activation=['linear', 'linear'])\n",
102 "print(demo_model.summary())\n",
103 "w = demo_model.get_weights()\n",
104 "#print(len(w),' weight arrays:',w)\n",
105 "wname=('wx','wh','bh','wy','by','wz','bz')\n",
106 "for i in range(len(w)):\n",
107 " print(i,':',wname[i],'shape=',w[i].shape)\n",
108 "wx, wh, bh, wy, by = w\n",
109 "plot_model(demo_model, to_file='model_plot.png', \n",
110 " show_shapes=True, show_layer_names=True,\n",
111 " expand_nested=True,)"
115 "cell_type": "markdown",
120 "The input layer here is just a formality. The input of the hidden layer `simple_rnn` consist of vector passed by the input layer, followed by its own output from the previous time step.\n",
122 "Now let’s do a simple experiment to see how the layers from a SimpleRNN and Dense layer produce an output. Keep this figure in view.\n",
123 "<img src=\"https://machinelearningmastery.com/wp-content/uploads/2021/09/rnnCode1.png\">"
127 "cell_type": "markdown",
132 "We’ll input x for three time steps and let the network generate an output. The values of the hidden units at time steps 1, 2 and 3 will be computed. $h(0)$ is initialized to the zero vector. The output $o(3)$ is computed from $h(3)$ and $w(3)$. An activation function is linear, $f(x)=x$, so the update of $h(k)$ and the output $o(k)$ are given by\n",
134 "h\\left( 0\\right) = &0 \\\\\n",
135 "h\\left( k+1\\right) =& \n",
136 "x\\left( k\\right) w_{x}\n",
137 " +h(k) w_{h} + b_{h}\\\\\n",
138 "o(k+1)=& h(k+1)w_{y} + b_y\n",
144 "execution_count": null,
150 "# Reshape the input to sample_size x time_steps x features \n",
151 "samples=4 # number of samples\n",
152 "outputs = 1 # number of outputs\n",
153 "x_tf = tf.reshape(tf.range(samples*timesteps*features),[samples,timesteps,features]) \n",
154 "# print('test input x=',x)\n",
155 "print('model.predict start')\n",
156 "y_pred_model = demo_model.predict(x_tf)\n",
157 "print('model.predict end')\n",
158 "x = x_tf.numpy() # without converting x from tensor we get in loops zero\n",
160 "o3=np.zeros([samples,outputs])\n",
161 "h_3 = np.zeros(hidden)\n",
162 "for i in range(samples):\n",
163 " h_0 = np.zeros(hidden)\n",
164 " h_1 = np.dot(x[i,0,:], wx) + np.dot(h_0,wh) + bh\n",
165 " h_2 = np.dot(x[i,1,:], wx) + np.dot(h_1,wh) + bh\n",
166 " h_3 = np.dot(x[i,2,:], wx) + np.dot(h_2,wh) + bh\n",
167 " o3[i,:] = np.dot(h_3, wy) + by\n",
171 "o = np.zeros([samples,outputs])\n",
172 "for i in range(samples):\n",
173 " h = np.zeros(hidden)\n",
174 " for j in range(timesteps):\n",
175 " hout = np.zeros(hidden)\n",
176 " for k in range(hidden):\n",
177 " hout[k] = bh[k]\n",
178 " for l in range(features):\n",
179 " hout[k] += x[i,j,l]*wx[l,k] \n",
180 " for l in range(hidden):\n",
181 " hout[k] += h[l]*wh[l,k]\n",
182 " h = hout.copy()\n",
184 " for l in range(outputs):\n",
185 " for k in range(hidden):\n",
186 " # print('i=',i,'k=',k,'l=',l)\n",
187 " o[i,l] += h[k]*wy[k,l]\n",
189 "print(\"Prediction from network \", y_pred_model)\n",
190 "print(\"Prediction from our computation \", o3)\n",
191 "print(\"Prediction computed by loops \", o)\n",
192 "if np.allclose(y_pred_model,o3) and np.allclose(y_pred_model,o):\n",
193 " print('The result is the same.')"
198 "execution_count": null,
202 "input(\"Press Enter to continue...\")"
206 "cell_type": "markdown",
211 "## Fuel moisture models\n",
217 "cell_type": "markdown",
222 "### A simple fuel moisture model"
226 "cell_type": "markdown",
231 "First consider a simplified fuel moisture model without considering the effect of rain.\n",
232 "The evolution of fuel moisture content $m(t)$ is modeled by the time-lag differential equation on interval $\\left[\n",
233 "t_{0},t_{1}\\right] $,\n",
235 "\\frac{dm}{dt}=\\frac{E-m(t)}{T},\\quad m(t_{0})=m_{0}.\n",
237 "where the initial fuel moisture content $m_{0}=m\\left( t_{0}\\right) $ is the\n",
238 "input, and $m_{1}=m(t_{1})$ is the output. Tnus, $m_1=F(m_0)$. The parameters of the model are the\n",
239 "fuel moisture equilibrium $E$, assumed to be constant over the interval $\\left[\n",
240 "t_{0},t_{1}\\right] $, NS the characteristic decay time $T$. \n",
242 "We can build the general model later by calling this simple model with different\n",
243 "equilibria and time constants (drying, wetting, rain).\n",
245 "Since $E$ is constant in time, the solution can be found\n",
248 "m\\left( t\\right) =E+\\left( m_{0}-E\\right) e^{-t/T}%\n",
250 "For convenience, we use $T_{1}=1/T$ instead of $T$, and the model becomes\n",
252 "m_{1}=E+\\left( m_{0}-E\\right) e^{-\\left( t_{1}-t_{0}\\right) T_{1}}%\n",
254 "In the extended Kalman filter, we will need the partial derivatives of $m_{1}$\n",
255 "with respect to the input and the parameters. Compute\n",
257 "\\frac{dm_{1}}{d_{m0}}=e^{-\\left( t_{1}-t_{0}\\right) T_{1}}\n",
260 "\\frac{dm_{1}}{dE}=1-e^{-\\left( t_{1}-t_{0}\\right) T_{1}}\n",
263 "\\frac{dm_{1}}{dT_{1}}=-\\left( m_{0}-E\\right) \\left( t_{1}-t_{0}\\right)\n",
264 "e^{-\\left( t_{1}-t_{0}\\right) T_{1}}\n",
266 "At the moment, we need only ${dm_{1}}/{dm_{0}}$ but we put in the code all partials for possible use in future.\n"
271 "execution_count": null,
277 "import numpy as np\n",
278 "def model_decay(m0,E,partials=0,T1=0.1,tlen=1): \n",
280 " # m0 fuel moisture content at start dimensionless, unit (1)\n",
281 " # E fuel moisture eqilibrium (1)\n",
282 " # partials=0: return m1 = fuel moisture contents after time tlen (1)\n",
283 " # =1: return m1, dm0/dm0 \n",
284 " # =2: return m1, dm1/dm0, dm1/dE\n",
285 " # =3: return m1, dm1/dm0, dm1/dE dm1/dT1 \n",
286 " # T1 1/T, where T is the time constant approaching the equilibrium\n",
287 " # default 0.1/hour\n",
288 " # tlen the time interval length, default 1 hour\n",
290 " exp_t = np.exp(-tlen*T1) # compute this subexpression only once\n",
291 " m1 = E + (m0 - E)*exp_t # the solution at end\n",
292 " if partials==0:\n",
294 " dm1_dm0 = exp_t\n",
295 " if partials==1:\n",
296 " return m1, dm1_dm0 # return value and Jacobian\n",
297 " dm1_dE = 1 - exp_t \n",
298 " if partials==2:\n",
299 " return m1, dm1_dm0, dm1_dE \n",
300 " dm1_dT1 = -(m0 - E)*tlen*exp_t # partial derivative dm1 / dT1\n",
301 " if partials==3:\n",
302 " return m1, dm1_dm0, dm1_dE, dm1_dT1 # return value and all partial derivatives wrt m1 and parameters\n",
303 " raise('Bad arg partials')\n",
308 "cell_type": "markdown",
313 "### Fuel moisture model with drying equilibrium, wetting equilibrium, and rain"
317 "cell_type": "markdown",
322 "Here is a little more realistic fuel moisture model from Mandel et al. (2004). A rain-wetting lag time $t_{\\mathrm{r}}$ is reached for heavy rain only\n",
323 "asymptotically, when the rain intensity $r$ (mm/h) is\n",
326 "\\frac{\\mathrm{d}m}{\\mathrm{d}t}=\\frac{S-m}{t_{\\mathrm{r}}}\\left(1-\\exp\\left(-\\frac{r-r_0}{r_{\\mathrm{s}}}\n",
327 "\\right) \\right),\\ \\text{if}\\ r>r_0, \n",
329 "where $r_0$ is the threshold rain intensity below which no perceptible\n",
330 "wetting occurs, and $r_{\\mathrm{s}}$ is the saturation rain\n",
331 "intensity. At the saturation rain intensity, $1-1/e\\approx 0.63$ of\n",
332 "the maximal rain-wetting rate is achieved. For 10h fuel, the model takes $S=250\\,{\\%}$,\n",
333 "$t_{\\mathrm{r}}=14$h, $r_0=0.05$mm/h and\n",
334 "$r_{\\mathrm{s}}=8$mm/h. "
339 "execution_count": null,
345 "### Define model function with drying, wetting, and rain equilibria\n",
348 "r0 = 0.05 # threshold rainfall [mm/h]\n",
349 "rs = 8.0 # saturation rain intensity [mm/h]\n",
350 "Tr = 14.0 # time constant for rain wetting model [h]\n",
351 "S = 250 # saturation intensity [dimensionless]\n",
352 "T = 10.0 # time constant for wetting/drying\n",
354 "def model_moisture(m0,Eqd,Eqw,r,t,partials=0,T=10.0,tlen=1.0):\n",
356 " # m0 starting fuel moistureb (%s\n",
357 " # Eqd drying equilibrium (%) \n",
358 " # Eqw wetting equilibrium (%)\n",
359 " # r rain intensity (mm/h)\n",
361 " # partials = 0, 1, 2\n",
362 " # returns: same as model_decay\n",
363 " # if partials==0: m1 = fuel moisture contents after time 1 hour\n",
364 " # ==1: m1, dm1/dm0 \n",
365 " # ==2: m1, dm1/dm0, dm1/dE \n",
368 " # print('raining')\n",
370 " T1 = (1.0 - np.exp(- (r - r0) / rs)) / Tr\n",
371 " elif m0 <= Eqw: \n",
372 " # print('wetting')\n",
375 " elif m0 >= Eqd:\n",
376 " # print('drying')\n",
379 " else: # no change'\n",
382 " exp_t = np.exp(-tlen*T1)\n",
383 " m1 = E + (m0 - E)*exp_t \n",
384 " dm1_dm0 = exp_t\n",
385 " dm1_dE = 1 - exp_t\n",
386 " #if t>=933 and t < 940:\n",
387 " # print('t,Eqw,Eqd,r,T1,E,m0,m1,dm1_dm0,dm1_dE',\n",
388 " # t,Eqw,Eqd,r,T1,E,m0,m1,dm1_dm0,dm1_dE) \n",
389 " if partials==0: \n",
391 " if partials==1:\n",
392 " return m1, dm1_dm0\n",
393 " if partials==2:\n",
394 " return m1, dm1_dm0, dm1_dE\n",
395 " raise('bad partials')"
399 "cell_type": "markdown",
404 "#### Training and forecasting with the RNN"
408 "cell_type": "markdown",
413 "We are given a sequence `x` of inputs size `[train_steps+forecast_steps,features]` and want to train a model so that at step `i` in `range(train_steps)`, the model returns close to `features[i,:]`. The trained model then returns for `i` in `range(train_steps,train_steps+forecast_steps)` a forecast `features[i,:]`."
418 "execution_count": null,
424 "def staircase(x,y,timesteps,trainsteps,return_sequences=False):\n",
425 " # x [trainsteps+forecaststeps,features] all inputs\n",
426 " # y [trainsteps,outputs]\n",
427 " # timesteps: split x and y into samples length timesteps, shifted by 1\n",
428 " # trainsteps: number of timesteps to use for training, no more than y.shape[0]\n",
429 " print('shape x = ',x.shape)\n",
430 " print('shape y = ',y.shape)\n",
431 " print('timesteps=',timesteps)\n",
432 " print('trainsteps=',trainsteps)\n",
433 " outputs = y.shape[1]\n",
434 " features = x.shape[1]\n",
435 " forecaststeps = x.shape[0]-trainsteps\n",
436 " samples = trainsteps-timesteps+1\n",
437 " print('staircase: samples=',samples,'timesteps=',timesteps,'features=',features)\n",
438 " x_train = np.empty([samples, timesteps, features])\n",
439 " print('return_sequences=',return_sequences)\n",
440 " if return_sequences:\n",
441 " print('returning all timesteps in a sample')\n",
442 " y_train = np.empty([samples, timesteps, outputs]) # all\n",
443 " for i in range(samples):\n",
444 " for k in range(timesteps):\n",
445 " for j in range(features):\n",
446 " x_train[i,k,j] = x[i+k,j]\n",
447 " for j in range(outputs):\n",
448 " y_train[i,k,j] = y[i+k,j]\n",
450 " print('returning only the last timestep in a sample')\n",
451 " y_train = np.empty([samples, outputs])\n",
452 " for i in range(samples):\n",
453 " for j in range(features):\n",
454 " for k in range(timesteps):\n",
455 " x_train[i,k,j] = x[i+k,j]\n",
456 " for j in range(outputs):\n",
457 " y_train[i,j] = y[i+timesteps-1,j]\n",
459 " return x_train, y_train"
464 "execution_count": null,
470 "def seq2batches(x,y,timesteps,trainsteps):\n",
471 " # x [trainsteps+forecaststeps,features] all inputs\n",
472 " # y [trainsteps,outputs]\n",
473 " # timesteps: split x and y into samples length timesteps, shifted by 1\n",
474 " # trainsteps: number of timesteps to use for training, no more than y.shape[0]\n",
475 " print('shape x = ',x.shape)\n",
476 " print('shape y = ',y.shape)\n",
477 " print('timesteps=',timesteps)\n",
478 " print('trainsteps=',trainsteps)\n",
479 " outputs = y.shape[1]\n",
480 " features = x.shape[1]\n",
481 " samples= trainsteps - timesteps + 1\n",
482 " print('samples=',samples)\n",
483 " x_train = np.empty([samples, timesteps, features])\n",
484 " y_train = np.empty([samples, timesteps, outputs]) # only the last\n",
485 " print('samples=',samples,' timesteps=',timesteps,\n",
486 " ' features=',features,' outputs=',outputs)\n",
487 " for i in range(samples):\n",
488 " for k in range(timesteps):\n",
489 " for j in range(features):\n",
490 " x_train[i,k,j] = x[i+k,j]\n",
491 " for j in range(outputs):\n",
492 " y_train[i,k,j] = y[i+k,j] # return sequences\n",
493 " return x_train, y_train"
498 "execution_count": null,
504 "print('test preprocessing for RNN')\n",
509 "x = tf.reshape(tf.range(trainsteps*features),[trainsteps,features])\n",
510 "y = tf.reshape(tf.range(trainsteps*outputs),[trainsteps,outputs])\n",
513 "x_train, y_train = staircase(x,y,timesteps,trainsteps)\n",
514 "print('x_train=',x_train)\n",
515 "print('y_train=',y_train)\n",
516 "x_train, y_train = seq2batches(x,y,timesteps,trainsteps)\n",
517 "print('x_train=',x_train)\n",
518 "print('y_train=',y_train)"
523 "execution_count": null,
529 "E,m_f,data,hour,h2,DeltaE = create_synthetic_data(days=20,power=4,data_noise=0.01,process_noise=0.0,DeltaE=0.1) "
534 "execution_count": null,
541 "# transform as 2D, (timesteps, features) and (timesteps, outputs)\n",
542 "Et = np.reshape(E,[E.shape[0],1])\n",
543 "datat = np.reshape(data,[data.shape[0],1])\n",
545 " scalerx = MinMaxScaler()\n",
546 " scalerx.fit(Et)\n",
547 " Et = scalerx.transform(Et)\n",
548 " scalery = MinMaxScaler()\n",
549 " scalery.fit(datat)\n",
550 " datat = scalery.transform(datat)"
555 "execution_count": null,
561 "def create_RNN_2(hidden_units, dense_units, activation, stateful=False, \n",
562 " batch_shape=None, input_shape=None, dense_layers=1,\n",
563 " rnn_layers=1,return_sequences=False,\n",
564 " initial_state=None):\n",
566 " inputs = tf.keras.Input(batch_shape=batch_shape)\n",
568 " inputs = tf.keras.Input(shape=input_shape)\n",
569 " # https://stackoverflow.com/questions/43448029/how-can-i-print-the-values-of-keras-tensors\n",
570 " # inputs2 = K.print_tensor(inputs, message='inputs = ') # change allso inputs to inputs2 below, must be used\n",
572 " for i in range(rnn_layers):\n",
573 " x = tf.keras.layers.SimpleRNN(hidden_units,activation=activation[0],\n",
574 " stateful=stateful,return_sequences=return_sequences)(x\n",
575 " # ,initial_state=initial_state\n",
577 " # x = tf.keras.layers.Dense(hidden_units, activation=activation[1])(x)\n",
578 " for i in range(dense_layers):\n",
579 " x = tf.keras.layers.Dense(dense_units, activation=activation[1])(x)\n",
580 " model = tf.keras.Model(inputs=inputs, outputs=x)\n",
581 " model.compile(loss='mean_squared_error', optimizer='adam')\n",
587 "execution_count": null,
594 "return_sequences=False\n",
596 "print('shifting inputs by',shift)\n",
597 "x_train, y_train = staircase(Et+shift,datat+shift,timesteps=5,trainsteps=h2,\n",
598 " return_sequences=return_sequences)\n",
599 "print('x_train shape=',x_train.shape)\n",
600 "samples, timesteps, features = x_train.shape\n",
601 "print('y_train shape=',y_train.shape)\n",
602 "# the simplest model possible\n",
603 "activation=['linear','linear']\n",
608 "hours=Et.shape[0]\n",
609 "h0 = tf.convert_to_tensor(datat[:samples],dtype=tf.float32)\n",
610 "# print('initial state=',h0)\n",
611 "# statefull model version for traning\n",
612 "import moisture_rnn\n",
613 "# model_fit=create_RNN_2(hidden_units=hidden_units, \n",
614 "model_fit=moisture_rnn.create_RNN_2(hidden_units=hidden_units, \n",
615 " dense_units=dense_units, \n",
616 " batch_shape=(samples,timesteps,features),\n",
618 " return_sequences=return_sequences,\n",
619 " # initial_state=h0,\n",
620 " activation=activation,\n",
621 " dense_layers=dense_layers)\n",
622 "# same model stateless for prediction on the entire dataset - to start onlg\n",
623 "# the real application will switch to prediction after training data end\n",
624 "# and start from the state there\n",
625 "print('model_fit input shape',x_train.shape,'output shape',model_fit(x_train).shape)\n",
626 "from keras.utils.vis_utils import plot_model\n",
627 "plot_model(model_fit, to_file='model_plot.png', \n",
628 " show_shapes=True, show_layer_names=True)"
633 "execution_count": null,
639 "model_predict=create_RNN_2(hidden_units=hidden_units, dense_units=dense_units, \n",
640 " input_shape=(hours,features),stateful = False,\n",
641 " return_sequences=True,\n",
642 " activation=activation,dense_layers=dense_layers)\n",
643 "# model_predict=create_RNN_sequences(hidden_units=1, dense_units=1, input_shape=(hours,1), \n",
644 "# activation=['linear', 'linear'])\n",
645 "print('model_predict input shape',Et.shape,'output shape',model_predict(Et).shape)\n",
646 "print(model_predict.summary())\n",
647 "from keras.utils.vis_utils import plot_model\n",
648 "plot_model(model_predict, to_file='model_plot.png', \n",
649 " show_shapes=True, show_layer_names=True)"
654 "execution_count": null,
661 "w_exact= [np.array([[1.-np.exp(-0.1)]]), np.array([[np.exp(-0.1)]]), np.array([0.]),np.array([[1.0]]),np.array([-1.*DeltaE])]\n",
662 "w_initial=[np.array([[1.-np.exp(-0.1)]]), np.array([[np.exp(-0.1)]]), np.array([0.]),np.array([[1.0]]),np.array([0.*DeltaE])]\n",
663 "model_fit.set_weights(w_initial)\n",
664 "model_fit.fit(x_train, y_train, epochs=1000, verbose=0,batch_size=samples)\n",
665 "w_fitted=model_fit.get_weights()\n",
666 "for i in range(len(w)):\n",
667 " print('weight',i,' exact:',w_exact[i],': initial:',w_initial[i],' fitted:',w_fitted[i])"
672 "execution_count": null,
678 "def model_eval(w,title):\n",
679 " # prediction on the entire dataset from zero state\n",
680 " model_predict.set_weights(w)\n",
681 " hours=Et.shape[0]\n",
682 " print('Et.shape=',Et.shape,'hours=',hours)\n",
683 " x_input=np.reshape(Et,(1, hours, 1))\n",
684 " y_output = model_predict.predict(x_input)\n",
685 " print('x_input.shape=',x_input.shape,'y_output.shape=',y_output.shape)\n",
686 " m = np.reshape(y_output,hours) - shift\n",
687 " print('weights=',w)\n",
689 " print('scaling')\n",
690 " m = scalery.inverse_transform(m)\n",
691 " m = np.reshape(m,hours)\n",
692 " plot_m(m,title=title)\n",
698 "execution_count": null,
704 "m_fitted=model_eval(w_fitted,'RNN prediction with fitted weights')"
709 "execution_count": null,
715 "m_exact=model_eval(w_exact,'RNN prediction with exact weights')"
720 "execution_count": null,
726 "m_initial=model_eval(w_initial,'RNN prediction with initial weights')"
731 "execution_count": null,
737 "out = np.empty((hours,1))\n",
740 "for i in range(Et.shape[0]):\n",
741 " h=np.dot(Et[i,0],w[0])+np.dot(h,w[1]) + w[2]\n",
742 " out[i]=np.dot(h,w[3]) + w[4]\n",
744 " print('scaling')\n",
745 " out = scalery.inverse_transform(out)\n",
746 "out=np.reshape(out,hours)\n",
747 "print('max abs diff',np.max(np.abs(m_exact-out)))\n",
748 "plot_m(out,title='Hand computed RNN prediction with exact weights')"
752 "cell_type": "markdown",
757 "### 3.2 Acquisition and preprocessing of real data"
761 "cell_type": "markdown",
766 "Data assimilation for fuel moisture from Remote Automated Weather Stations (RAWS) was developed in Vejmelka et al. (2016). First, they use regression from all RAWS in a given area to extend the data spatially from RAWS to a grid in the whole area, then they run the extended Kalman filter at each grid node. Here, we are interested in a simplified problem: estimate future fuel moisture at a single RAWS location from weather data. "
770 "cell_type": "markdown",
775 "#### 3.2.1 Acquisition of fuel moisture observations"
779 "cell_type": "markdown",
781 "id": "0CuXyWBFc91i",
785 "We try to load the data from a saved file first. If that fails, retrieve the fuel moisture data from sensors on weather stations in the Mesowest network. Get all stations with fuel moisture data in a spatial box within one hour, then pick one station and retrieve the whole time series."
790 "execution_count": null,
800 "execution_count": null,
806 "jfile = 'raws.json'; vars='fuel_moisture'; case = 1\n",
807 "# jfile = 'raws2.json'; vars='fuel_moisture,precip_accum_one_hour'; case = 2\n",
808 "def json_w(j,f):\n",
809 " print('writing json file',f)\n",
810 " json.dump(j,open(f,'w'),indent=4)\n",
812 " #! wget --no-clobber http://math.ucdenver.edu/~jmandel/data/math4779f21/raws.json\n",
813 " j = json.load(open(jfile,'r'))\n",
814 " print('loaded from ',jfile)\n",
815 " # Take the first station in the boulding box that has data between time_start and time_s2.\n",
816 " # Then retrieve data for that station between time_start and time_end\n",
817 " time_start = j['time_start'] # start of data time series\n",
818 " # time_s2 = j['time_s2'] # end of segment to read coordinates\n",
819 " time_end = j['time_end'] # end of data time series\n",
820 " meso_ts = j['meso_ts'] # get meso observations time series\n",
821 " obs_lon = j['obs_lon'] # where we retrieved observations\n",
822 " obs_lat = j['obs_lat']\n",
824 " print(\"can't read\",jfile,', creating')\n",
825 " # set up bounds\n",
826 " time_start = \"201806010800\" # June 1 2018 08:00 in format yyyymmddHHMM\n",
827 " time_s2 = \"201806010900\" # June 1 2018 09:00 in format yyyymmddHHMM \n",
828 " time_end = \"201907200900\" # June 20 2018 09:00 in format yyyymmddHHMM \n",
829 " #time_start= \"201810230100\"\n",
830 " #time_s2= \"201810230300\"\n",
831 " #time_end = \"201806022300\"\n",
832 " !pip install MesoPy\n",
833 " from MesoPy import Meso\n",
834 " bounding_box = \"-115, 38, -110, 40\" # min longtitude, latitude\n",
835 " meso_token=\"b40cb52cbdef43ef81329b84e8fd874f\" # you should get your own if you do more of this\n",
836 " m = Meso(meso_token)# create a Meso object\n",
837 " print('reading MesoWest fuel moisture data')\n",
838 " json_w(m.variables(),'variables.json')\n",
839 " meso_obss = m.timeseries(time_start, time_s2, bbox=bounding_box, \n",
840 " showemptystations = '0', vars=vars) # ask the object for data\n",
841 " json_w(meso_obss,'meso_obss.json') \n",
842 " # pick one station and retrieve the whole time series.\n",
843 " station=meso_obss['STATION'][0]\n",
844 " json_w(station,'station.json')\n",
845 " lon,lat = (float(station['LONGITUDE']),float(station['LATITUDE']))\n",
846 " print(station['NAME'],'station',station['STID'],'at',lon,lat)\n",
847 " e = 0.01 # tolerance\n",
848 " bb = '%s, %s, %s, %s' % (lon - e, lat - e, lon + e, lat + e)\n",
849 " print('bounding box',bb)\n",
850 " meso_ts = m.timeseries(time_start, time_end, bbox=bb, showemptystations = '0', vars=vars) # ask the object for data\n",
851 " json_w(meso_ts,'meso_ts.json') \n",
852 " obs_lon, obs_lat = (lon, lat) # remember station coordinates for later\n",
853 " j={'time_start':time_start,'time_s2':time_s2,'time_end':time_end,\n",
854 " 'meso_ts':meso_ts,'obs_lon':obs_lon,'obs_lat':obs_lat}\n",
855 " json_w(j,jfile)\n",
861 "execution_count": null,
868 "# process the data retrieved for this station\n",
869 "# print(json.dumps(meso_ts['STATION'][0], indent=4))\n",
870 "from datetime import datetime, timedelta, time\n",
871 "import numpy as np\n",
872 "import matplotlib.pyplot as plt\n",
874 "station = meso_ts['STATION'][0]\n",
875 "time_str = station['OBSERVATIONS']['date_time']\n",
876 "obs_time = [datetime.strptime(t, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=pytz.UTC) for t in time_str]\n",
877 "start_time = obs_time[0].replace(minute=0) # remember obs_time and start_time for later\n",
878 "end_time = obs_time[-1]\n",
879 "obs_data = np.array(station['OBSERVATIONS'][\"fuel_moisture_set_1\"])\n",
880 "# obs_data = np.array(station['OBSERVATIONS'][\"fuel_moisture\"])\n",
881 "# display the data retrieved\n",
882 "#for o_time,o_data in zip (obs_time,obs_data):\n",
883 "# print(o_time,o_data)\n",
884 "%matplotlib inline\n",
885 "plt.figure(figsize=(16,4))\n",
886 "plt.plot(obs_data,linestyle='-',c='k',label='10-h fuel data')\n",
887 "plt.title(station['STID'] + ' 10 h fuel moisture data')\n",
888 "plt.xlabel('Time (hours)') \n",
889 "plt.ylabel('Fuel moisture content (%)')\n",
895 "cell_type": "markdown",
900 "#### 3.2.2 Acquisition of weather data"
904 "cell_type": "markdown",
909 "Our weather data are results from atmospheric models, with assimilated observations from weather stations, satellites, radars, etc. The models can be run in reanalysis mode (for the past, with data for the period modeled) or in forecast mode (for the future, with only past data assimilated - because future data are not here yet). We use the Real-Time Mesoscale Analysis ([RTMA](https://www.nco.ncep.noaa.gov/pmb/products/rtma/)) interpolated to the RAWS location. RTMA is a real-time product, posted hourly, and available only for few days in the past. We have our own collection of selected RAWS data over past few years, obtained as a side effect of running the fuel moisture modeling software [WRFXPY](https://github.com/openwfm/wrfxpy).\n",
911 "First try to read the data already extracted for this RAWS and staged for download."
916 "execution_count": null,
922 "os.chdir('data')\n",
924 "jfile = 'rtma.json'\n",
926 " ! wget --no-clobber http://math.ucdenver.edu/~jmandel/data/math4779f21/rtma.json\n",
927 " j = json.load(open(jfile,'r'))\n",
928 " print('loaded from ',jfile)\n",
929 " if j['obs_lat']!=obs_lat or j['obs_lon']!=obs_lon:\n",
930 " print('lon lat doesnot agree, need to load original RTMA files')\n",
933 " read_rtma=False\n",
935 " print(\"can't read\",jfile,', creating')\n",
942 "cell_type": "markdown",
947 "Next, functions to get the files, open as grib, and interpolate to the station coordinates"
951 "cell_type": "markdown",
956 "####<font color=red>Note: If read_rtma==True, the notebook will say it crashed when run the first time. This is because it needs to install different version of some python packages and restart runtime. Simply run it again.</fonr>"
961 "execution_count": null,
967 "# Set up environment to read RTMA gribs\n",
968 "# we will need current numpy for pygrib - needed on Colab, tensorflow is using numpy 1.19\\\n",
970 " import subprocess,os\n",
971 " def load_rtma(path,file,reload=0):\n",
972 " url='http://math.ucdenver.edu/~jmandel/rtma/' + path \n",
973 " if os.path.exists(file):\n",
975 " print(file + ' already exists, removing')\n",
976 " os.remove(file)\n",
978 " print(file + ' already exists, exiting')\n",
979 " # add checking size here\n",
982 " ret = subprocess.check_output(['wget','--no-clobber','--output-document='+ file, url,],stderr=subprocess.STDOUT).decode() # execute command from python strings\n",
983 " if os.path.exists(file):\n",
984 " print('loaded ' + url + ' as ' + file)\n",
987 " print('file transfer completed, but the file is missing? ' + url) \n",
990 " print('file transfer failed: ' + url)\n",
995 "cell_type": "markdown",
1000 "Create a function to transfer RTMA files in GRIB2 format from the stash. The function returns zero if the file transfer succeeded. If the file is not available, it returns a nonzero value. Note: if needed, maybe in future add more sophisticated checks, check the return code of wget and if the file size is correct."
1004 "cell_type": "code",
1005 "execution_count": null,
1007 "id": "PL3gxK67AlBI"
1012 " def rtma_grib(t,var):\n",
1013 " tpath = '%4i%02i%02i/%02i' % (t.year, t.month, t.day, t.hour) # remote path on server\n",
1014 " tstr = '%4i%02i%02i%02i_' % (t.year, t.month, t.day, t.hour) # time string for local path\n",
1015 " gribfile = os.path.join('data',tstr + var + '.grib')\n",
1016 " remote = tpath + '/' + var + '.grib'\n",
1017 " if load_rtma(remote,gribfile):\n",
1018 " print('cannot load remote file',remote,'as',gribfile)\n",
1022 " gf=GribFile(gribfile)\n",
1023 " v = np.array(gf[1].values())\n",
1025 " print('cannot read grib file',gribfile)\n",
1027 " print('loaded ',gribfile,' containing array shape ',v.shape)\n",
1028 " return gf[1] # grib message\n"
1032 "cell_type": "code",
1033 "execution_count": null,
1035 "id": "OY1oTYKlfd17"
1040 " times = pd.date_range(start=time_start,end=time_end,freq='1H')\n",
1041 " varnames=['temp','td','precipa']\n",
1042 " j = read_interp_rtma(varnames,times,obs_lat,obs_lon) # temperature\n",
1043 " for varname in varnames:\n",
1044 " j[varname]=j[varname].tolist() \n",
1045 " j['obs_lat']=obs_lat\n",
1046 " j['obs_lon']=obs_lon\n",
1047 " json.dump(j,open('rtma.json','w'),indent=4)\n",
1052 "cell_type": "code",
1053 "execution_count": null,
1055 "id": "ccp10kurAlBI"
1059 "from scipy.interpolate import LinearNDInterpolator, interpn\n",
1060 "from scipy.optimize import root\n",
1061 "def interp_to_lat_lon_slow(lats,lons,v,lat,lon): \n",
1062 " # on mesh with coordinates lats and lons interpolate v to given lat lon\n",
1063 " interp=LinearNDInterpolator(list(zip(lats.flatten(),lons.flatten())),v.flatten())\n",
1064 " return interp(lat,lon)\n",
1065 "def interp_to_lat_lon(lats,lons,v,lat,lon):\n",
1066 " # on mesh with coordinates lats and lons interpolate v to given lat lon\n",
1067 " points=(np.array(range(lats.shape[0]),float),np.array(range(lats.shape[1]),float)) # uniform mesh\n",
1068 " def res(ij): # interpolation of lons lats on the uniform mesh, to noninteger coordinates \n",
1069 " return np.hstack((interpn(points,lats,ij)-lat, interpn(points,lons,ij)-lon))\n",
1070 " # solve for xi,xj such that lats(xi,xj)=lat lons(xi,xj)=lon, then interpolate to (xi, xj) on uniform grid \n",
1071 " result = root(res,(0,0)) # solve res(ij) = 0\n",
1072 " if not result.success:\n",
1073 " print(result.message)\n",
1075 " return interpn(points,v,result.x) \n"
1079 "cell_type": "markdown",
1081 "id": "jvnpq6S5AlBI"
1084 "The interpolation function needs to be tested."
1088 "cell_type": "code",
1089 "execution_count": null,
1091 "id": "NVMJBYI7AlBI"
1095 "def interp_to_lat_lon_test(lats,lons):\n",
1096 " print('testing interp_to_lat_lon')\n",
1097 " vx, vy = np.meshgrid(range(lats.shape[0]),range(lats.shape[1]),indexing='ij')\n",
1099 " lat,lon = ((lats[i,j]+lats[i+1,j+1])/2,(lons[i,j]+lons[i+1,j+1])/2)\n",
1100 " vi = interp_to_lat_lon(lats,lons,vx,lat,lon)\n",
1101 " vj = interp_to_lat_lon(lats,lons,vy,lat,lon)\n",
1102 " print(vi,vj,'should be about',i+0.5,j+0.5)\n",
1105 " print('Testing against the standard slow method scipy.interpolate.LinearNDInterpolator. Please wait...')\n",
1106 " vi_slow = interp_to_lat_lon_slow(lats,lons,vx,lat,lon)\n",
1107 " print(vi_slow)\n",
1108 " vj_slow = interp_to_lat_lon_slow(lats,lons,vy,lat,lon)\n",
1109 " print(vj_slow)\n",
1111 "#gf = rtma_grib(start_time,'temp') # read the first grib file and use it to test interpolation\n",
1112 "#lats, lons = gf.latlons()\n",
1113 "#interp_to_lat_lon_test(lats,lons)\n"
1117 "cell_type": "code",
1118 "execution_count": null,
1120 "id": "vt-Mk8fIc91m"
1128 "cell_type": "markdown",
1130 "id": "LQbWB_3GAlBI"
1133 "Now we are ready for a function to read the RTMA files and interpolate to the station coordinates"
1137 "cell_type": "code",
1138 "execution_count": null,
1140 "id": "b3JJH3XPAlBI"
1145 " import pandas as pd, json\n",
1146 " def read_interp_rtma(varnames,times,lat,lon):\n",
1147 " # read RTMA from start_time to end_time and interpolate to obs_lat obs_lon\n",
1148 " ntimes = len(times)\n",
1149 " time_str = 'time_str'\n",
1150 " j={time_str:times.strftime('%Y-%m-%d %H:%M').tolist()}\n",
1151 " for varname in varnames:\n",
1152 " j[varname]=np.full(ntimes,np.nan) # initialize array of nans as list\n",
1154 " for t in times:\n",
1155 " tim=t.strftime('%Y-%m-%d %H:%M')\n",
1156 " should_be = j[time_str][n]\n",
1157 " if tim != should_be:\n",
1158 " print('n=',n,'time',tim,'expected',should_be)\n",
1159 " raise 'Invalid time' \n",
1160 " for varname in varnames:\n",
1161 " gf = rtma_grib(t,varname) # read and create grib object, download if needed\n",
1163 " lats,lons = gf.latlons() # coordinates\n",
1164 " v = gf.values()\n",
1165 " vi=interp_to_lat_lon(lats,lons,v,lat,lon) # append to array\n",
1166 " print(varname,'at',t,'interpolated to',lat,lon,' value ',vi)\n",
1167 " j[varname][n] = vi\n",
1169 " print(varname,'at',t,' could not be loaded')\n",
1175 "cell_type": "code",
1176 "execution_count": null,
1178 "id": "bMpYIZT6c91o"
1186 "cell_type": "markdown",
1188 "id": "KVXBjGA0CiXr"
1191 "#### 3.2.3 Preprocessing and visualization of the weather data"
1195 "cell_type": "code",
1196 "execution_count": null,
1198 "id": "fNA3Vbo1c91o",
1200 "source_hidden": true
1207 "td = np.array(rtma['td'])\n",
1208 "t2 = np.array(rtma['temp'])\n",
1209 "rain=np.array(rtma['precipa'])\n",
1210 "# compute relative humidity\n",
1211 "rh = 100*np.exp(17.625*243.04*(td - t2) / (243.04 + t2 - 273.15) / (243.0 + td - 273.15))\n",
1212 "Ed = 0.924*rh**0.679 + 0.000499*np.exp(0.1*rh) + 0.18*(21.1 + 273.15 - t2)*(1 - np.exp(-0.115*rh))\n",
1213 "Ew = 0.618*rh**0.753 + 0.000454*np.exp(0.1*rh) + 0.18*(21.1 + 273.15 - t2)*(1 - np.exp(-0.115*rh))"
1217 "cell_type": "code",
1218 "execution_count": null,
1220 "id": "tZIK59bJAlBJ",
1222 "source_hidden": true
1228 "%matplotlib inline\n",
1229 "plt.figure(figsize=(16,4))\n",
1230 "plt.plot(t2,linestyle='-',c='k',label='Temperature')\n",
1231 "plt.title(station['STID'] + ' Temperature')\n",
1232 "plt.xlabel('Time (hours)') \n",
1233 "plt.ylabel('Temperature (K)')\n",
1238 "cell_type": "code",
1239 "execution_count": null,
1241 "id": "LbyqcuXYAlBJ"
1245 "%matplotlib inline\n",
1246 "plt.figure(figsize=(16,4))\n",
1247 "plt.plot(td,linestyle='-',c='k',label='Dew point')\n",
1248 "plt.title(station['STID'] + ' Dew point (K)')\n",
1249 "plt.xlabel('Time (hours)') \n",
1250 "plt.ylabel('Dew point (K)')\n",
1255 "cell_type": "code",
1256 "execution_count": null,
1258 "id": "dfoOK2kSc91p"
1262 "%matplotlib inline\n",
1263 "plt.figure(figsize=(16,4))\n",
1264 "plt.plot(rh,linestyle='-',c='k',label='Dew point')\n",
1265 "plt.title(station['STID'] + ' relative humidity')\n",
1266 "plt.xlabel('Time (hours)') \n",
1267 "plt.ylabel('Relative humidity (%)')\n",
1272 "cell_type": "code",
1273 "execution_count": null,
1275 "id": "MWTJ5b2kc91p",
1277 "source_hidden": true
1283 "%matplotlib inline\n",
1284 "plt.figure(figsize=(16,4))\n",
1285 "plt.plot(Ed,linestyle='-',c='r',label='drying equilibrium')\n",
1286 "plt.plot(Ew,linestyle=':',c='b',label='wetting equilibrium')\n",
1287 "plt.title(station['STID'] + ' drying and wetting equilibria')\n",
1288 "plt.xlabel('Time (hours)') \n",
1289 "plt.ylabel('Fuel moisture contents (%)')\n",
1294 "cell_type": "markdown",
1296 "id": "jY3_eeBRc91p"
1303 "cell_type": "code",
1304 "execution_count": null,
1306 "id": "PQKSRvRSAlBJ"
1310 "%matplotlib inline\n",
1311 "plt.figure(figsize=(16,4))\n",
1312 "plt.plot(rain,linestyle='-',c='k',label='Precipitation')\n",
1313 "plt.title(station['STID'] + ' Precipitation' )\n",
1314 "plt.xlabel('Time (hours)') \n",
1315 "plt.ylabel('Precipitation (mm/hour)')\n",
1320 "cell_type": "code",
1321 "execution_count": null,
1323 "id": "Dwbt4UXfro5x"
1327 "print(rain[1900:2000])"
1331 "cell_type": "markdown",
1333 "id": "_yRu_7WvHc6P"
1336 "Precipitation from RTMA is in kg/m${}^2$. 1m water depth over 1m${}^2$ is 1m${}^3$ with mass 1000 kg thus 1 kg/m${}^2$ is the same as 1 mm of precipitation. RTMA values are accumulations over 1 h so these are values in mm/h. So 9999 mm/h = 10m/h makes no sense. Replace anything over 1m/h by nan and try again."
1340 "cell_type": "code",
1341 "execution_count": null,
1343 "id": "XPYO_Iuvc91q"
1347 "rain[rain > 1000] = np.NaN"
1351 "cell_type": "code",
1352 "execution_count": null,
1354 "id": "GYWTfbBBc91q"
1358 "%matplotlib inline\n",
1359 "plt.figure(figsize=(16,4))\n",
1360 "plt.plot(rain,linestyle='-',c='k',label='Precipitation')\n",
1361 "plt.title(station['STID'] + ' Precipitation' )\n",
1362 "plt.xlabel('Time (hours)') \n",
1363 "plt.ylabel('Precipitation (mm/hour)')\n",
1368 "cell_type": "markdown",
1370 "id": "Q_L0R2Njc91q"
1373 "Fix some missing data, then we can use the data for up to 1942 hours until a biger gap."
1377 "cell_type": "code",
1378 "execution_count": null,
1380 "id": "_tkU7UJic91q"
1384 "# fix isolated nans\n",
1385 "def fixnan(a,n):\n",
1386 " for c in range(n):\n",
1387 " for i in np.where(np.isnan(a)):\n",
1388 " a[i]=0.5*(a[i-1]+a[i+1])\n",
1389 " if not any(np.isnan(a)):\n",
1393 "rain=fixnan(rain,2)\n",
1394 "t2=fixnan(t2,2)\n",
1395 "rh=fixnan(rh,2)\n",
1396 "obs_data=fixnan(obs_data,2)\n",
1397 "Ed=fixnan(Ed,2)\n",
1398 "Ew=fixnan(Ew,2)\n",
1400 "print(np.where(np.isnan(rain)))\n",
1401 "print(np.where(np.isnan(t2)))\n",
1402 "print(np.where(np.isnan(rh)))\n",
1403 "print(np.where(np.isnan(obs_data)))"
1407 "cell_type": "markdown",
1409 "id": "XqQYnyI9DIy1"
1416 "cell_type": "markdown",
1418 "id": "2tIC_Tqnc91r"
1421 "### 4.1 Kalman filter with fuel moisture observations, followed by forecasting\n",
1422 "We run the model first with Kalman filter for 150 hours. The observations are the RAWS data\n",
1423 "After 150 hours, we run in forecast mode - the RAWS data are no longer used, and we run the model from the weather data without the Kalman filter. The weather data are taken to be RTMA interpolated to one RAWS location.\n",
1424 "In a real forecasting application, the model would be run from weather forecast rather than data."
1428 "cell_type": "code",
1429 "execution_count": null,
1431 "id": "aXnSQM7wc91r"
1435 "# run KF on an initial data seqment\n",
1436 "import numpy as np\n",
1437 "import matplotlib.pyplot as plt \n",
1439 "hours=1200 # total simulation\n",
1441 "m = np.zeros(hours) # preallocate\n",
1442 "m[0]= obs_data[0] # initial state \n",
1443 "P = np.zeros(hours)\n",
1444 "P[0] = 1e-3 # background state variance\n",
1445 "H = np.array([1.]) # all oQ = np.array([0.02]) # process noise variancebserved\n",
1446 "Q = np.array([1e-3]) # process noise variance\n",
1447 "R = np.array([1e-3]) # data variance\n",
1448 "for t in range(hours-1):\n",
1449 " # using lambda construction to pass additional arguments to the model \n",
1450 " if t < h2 and not np.isnan(obs_data[t]) and not np.isnan(Ew[t]) and not np.isnan(rain[t]): # advance model and run KF\n",
1451 " m[t+1],P[t+1] = ext_kf(m[t],P[t],lambda u: model_moisture(u,Ed[t],Ew[t],rain[t],t,partials=1),Q,\n",
1452 " d=obs_data[t],H=H,R=R)\n",
1453 " else: # just advance to next hour, no process noise\n",
1454 " m[t+1],P[t+1] = ext_kf(m[t],P[t],lambda u: model_moisture(u,Ed[t],Ew[t],rain[t],t,partials=1),Q*0.0)"
1458 "cell_type": "code",
1459 "execution_count": null,
1461 "id": "peMi-OF3c91r"
1465 "%matplotlib inline\n",
1466 "plt.figure(figsize=(16,4))\n",
1467 "plt.plot(Ed[:hours],linestyle='--',c='r',label='Drying Equilibrium')\n",
1468 "plt.plot(Ew[:hours],linestyle='--',c='b',label='Wetting Equilibrium')\n",
1469 "plt.plot(obs_data[:hours],linestyle=':',c='k',label='RAWS data')\n",
1470 "plt.plot(m[:h2],linestyle='-',c='k',label='filtered')\n",
1471 "plt.plot(range(h2,hours),m[h2:hours],linestyle='-',c='r',label='forecast')\n",
1472 "plt.title(station['STID'] + ' Kalman filtering and forecast with real data')\n",
1473 "plt.xlabel('Time (hours)') \n",
1474 "plt.ylabel('Fuel moisture content (%)')\n",
1479 "cell_type": "markdown",
1481 "id": "3TnwXYcLc91r"
1484 "Clearly, there is a problem - the forecast fuel moisture is too high. We need to assimilate also some parameters of the model, not just its output state. "
1488 "cell_type": "markdown",
1490 "id": "8SuVNg8TsW4d"
1493 "### 4.3 Kalman filter on the augmented model"
1497 "cell_type": "markdown",
1499 "id": "FYAbWNCfk2wD"
1502 "Run augmented filter and plot the result:\n"
1506 "cell_type": "code",
1507 "execution_count": null,
1509 "id": "Q3NHr3oBsDg6"
1513 "m,Ec = run_augmented_kf(obs_data,Ed,Ew,rain,h2,hours) # extract from state"
1517 "cell_type": "code",
1518 "execution_count": null,
1520 "id": "hlkby_oTlB_f"
1524 "title = station['STID'] +' Kalman filtering and forecast with augmented state, real data. Training 0:%i hmax' % h2\n",
1525 "def plot_moisture(hmin,hmax):\n",
1526 " print('training from 0 to',h2,'plot from',hmin,'to',hmax)\n",
1527 " plt.figure(figsize=(16,4))\n",
1528 " plt.plot(range(hmin,hmax),Ed[hmin:hmax],linestyle='--',c='r',label='Drying Equilibrium (%)')\n",
1529 " plt.plot(range(hmin,hmax),Ew[hmin:hmax],linestyle='--',c='b',label='Wetting Equilibrium (%)')\n",
1530 " plt.plot(range(hmin,hmax),Ec[hmin:hmax],linestyle='--',c='g',label='Equilibrium Correction (%)')\n",
1531 " plt.plot(range(hmin,hmax),m[hmin:hmax],linestyle='-',c='k',label='filtered')\n",
1532 " plt.plot(range(hmin,hmax),obs_data[hmin:hmax],linestyle='-',c='b',label='RAWS data (%)')\n",
1533 " plt.plot(range(hmin,hmax),rain[hmin:hmax],linestyle='-',c='b',label='RTMA rain (mm/h)')\n",
1534 " plt.title(title)\n",
1536 " plt.plot(m[hmin:h2],linestyle='-',c='k',label='Filtered')\n",
1537 " h1 = np.maximum(hmin,h2)\n",
1538 " plt.plot(range(h1,hmax),m[h1:hmax],linestyle='-',c='r',label='Forecast (%)')\n",
1539 " plt.xlabel('Time (hours)') \n",
1540 " plt.ylabel('Fuel moisture content (%)')\n",
1545 "cell_type": "code",
1546 "execution_count": null,
1551 "from data_funcs import to_json, from_json"
1555 "cell_type": "code",
1556 "execution_count": null,
1560 "os.chdir('data')\n",
1561 "kf_orig={'title':title,'hours':hours,'h2':h2,'Ed':Ed,'Ew':Ew,'Ec':Ec,'rain':rain,\n",
1562 " 'fm':obs_data,'m':m,'note':'RAWS and RTMA data + m from augmented KF in fmda_kf_rnn_orig'}\n",
1563 "to_json(kf_orig,'kf_orig.json')"
1567 "cell_type": "code",
1568 "execution_count": null,
1570 "id": "q-h5omKgnow2"
1574 "plot_moisture(0,hours)"
1578 "cell_type": "markdown",
1580 "id": "0w0YtHtqnza5"
1583 "A detailed view of transition from training to forecast:"
1587 "cell_type": "code",
1588 "execution_count": null,
1590 "id": "B7sXGUotc91s"
1594 "plot_moisture(0,600)\n",
1599 "cell_type": "code",
1600 "execution_count": null,
1602 "id": "xy7sIs0z_Kk6"
1606 "plot_moisture(300,500)"
1610 "cell_type": "code",
1611 "execution_count": null,
1613 "id": "y-C6IRFVxGUR"
1617 "plot_moisture(300,800)"
1621 "cell_type": "code",
1622 "execution_count": null,
1624 "id": "TvlCtT0X2ejp"
1628 "plot_moisture(800,1200)"
1632 "cell_type": "markdown",
1634 "id": "7W03QTo3c91t"
1637 "Filtering by extended Kalman filter using RAWS data until 150 hours, then forecasting mode - running the model from interpolated RTMA only. For the first 60 hours the forecast is good, the equilibium correction made the model quite close to data. But then the big spike in equilibrium moisture around 230 hours attracted the solution, and it took a while for it to get back. The spike in the RAWS measurement is there but much smaller. The model becomes inaccurate during periods when the fuel moisture equilibrium is large.\n",
1639 "Possible reasons include: 1. There was something in the data we do not know about - maybe it rained but RTMA did not tell us. Try comparing with data from the RAWS itself? 2. The model is too simple, assumes the whole depth of the wood stick is wetting and drying at the same time. Perhaps the moisture got stored in the inside layers of the measurement stick. Try a two-layer model as in van der Kamp (2017) and make the state larger? "
1643 "cell_type": "markdown",
1645 "id": "owEI4EtTo7Ek"
1648 "A detailed view of rain episode:"
1652 "cell_type": "code",
1653 "execution_count": null,
1655 "id": "C_hoDjgtpMEJ"
1659 "plot_moisture(900,1100)"
1663 "cell_type": "markdown",
1665 "id": "DRraWhwdpSkV"
1668 "It seems there is some rain that the model does not know about."
1672 "cell_type": "markdown",
1674 "id": "1STfnlT40rPX"
1677 "## RNN for real data, no rain yet"
1681 "cell_type": "markdown",
1683 "id": "3cwY43iSnQ0t"
1686 "#### Linear modeling by RELU - potential for generalization"
1690 "cell_type": "code",
1691 "execution_count": null,
1693 "id": "MotzNBvOnFvC"
1703 "# network computing z = a*x1 + b*x2 with offset c\n",
1704 "def linrelu(x,a,b,c):\n",
1705 " y = np.dot(np.array([[a, b], [-a, -b] ]), x) + np.array([c, -c])\n",
1706 " y[0]=RELU(y[0])\n",
1707 " y[1]=RELU(y[1])\n",
1708 " return(np.dot([1,-1],y))-c\n",
1713 "print(a*x[0]+b*x[1])\n",
1718 "cell_type": "markdown",
1720 "id": "-p6dcLua_udD"
1723 "### Basic RNN on real data "
1727 "cell_type": "markdown",
1729 "id": "gSmbDPZIHbTr"
1732 "Try with E average between drying and wetting"
1736 "cell_type": "code",
1737 "execution_count": null,
1739 "id": "ymhNMZkoHfCl"
1743 "E = (Ed + Ew)/2\n",
1744 "print(Ed.shape,Ew.shape,rain.shape)\n",
1745 "first_rain=np.nonzero(rain>0)[0][0]\n",
1746 "print(first_rain)\n",
1747 "hours=first_rain\n",
1749 "data=obs_data[:hours]\n",
1752 "# transform as 2D, (timesteps, features) and (timesteps, outputs)\n",
1753 "Et = np.reshape(E,[E.shape[0],1])\n",
1754 "datat = np.reshape(data,[data.shape[0],1])\n",
1756 " scalerx = MinMaxScaler()\n",
1757 " scalerx.fit(Et)\n",
1758 " Et = scalerx.transform(Et)\n",
1759 " scalery = MinMaxScaler()\n",
1760 " scalery.fit(datat)\n",
1761 " datat = scalery.transform(datat)"
1765 "cell_type": "markdown",
1767 "id": "DPcxv85XILdn"
1770 "Create the model again"
1774 "cell_type": "code",
1775 "execution_count": null,
1777 "id": "gEkbHZSqIOq1"
1782 "return_sequences=False\n",
1783 "x_train, y_train = staircase(Et,datat,timesteps=5,trainsteps=h2,\n",
1784 " return_sequences=return_sequences)\n",
1785 "print('x_train shape=',x_train.shape)\n",
1786 "samples, timesteps, features = x_train.shape\n",
1787 "print('y_train shape=',y_train.shape)\n",
1788 "# the simplest model possible\n",
1789 "activation=['linear','linear']\n",
1794 "hours=Et.shape[0]\n",
1795 "h0 = tf.convert_to_tensor(datat[:samples],dtype=tf.float32)\n",
1796 "# print('initial state=',h0)\n",
1797 "# statefull model version for traning\n",
1798 "import moisture_rnn\n",
1799 "model_fit=moisture_rnn.create_RNN_2(hidden_units=hidden_units, \n",
1800 " dense_units=dense_units, \n",
1801 " batch_shape=(samples,timesteps,features),\n",
1802 " stateful=True,\n",
1803 " return_sequences=return_sequences,\n",
1804 " # initial_state=h0,\n",
1805 " activation=activation,\n",
1806 " dense_layers=dense_layers)\n",
1807 "# same model stateless for prediction on the entire dataset - to start onlg\n",
1808 "# the real application will switch to prediction after training data end\n",
1809 "# and start from the state there\n",
1810 "print('model_fit input shape',x_train.shape,'output shape',model_fit(x_train).shape)\n",
1811 "from keras.utils.vis_utils import plot_model\n",
1812 "plot_model(model_fit, to_file='model_plot.png', \n",
1813 " show_shapes=True, show_layer_names=True)"
1817 "cell_type": "code",
1818 "execution_count": null,
1820 "id": "jtFJQu33NqfL"
1824 "model_predict=create_RNN_2(hidden_units=hidden_units, dense_units=dense_units, \n",
1825 " input_shape=(hours,features),stateful = False,\n",
1826 " return_sequences=True,\n",
1827 " activation=activation,dense_layers=dense_layers)\n",
1828 "# model_predict=create_RNN_sequences(hidden_units=1, dense_units=1, input_shape=(hours,1), \n",
1829 "# activation=['linear', 'linear'])\n",
1830 "print('model_predict input shape',Et.shape,'output shape',model_predict(Et).shape)\n",
1831 "print(model_predict.summary())\n",
1832 "from keras.utils.vis_utils import plot_model\n",
1833 "plot_model(model_predict, to_file='model_plot.png', \n",
1834 " show_shapes=True, show_layer_names=True)"
1838 "cell_type": "code",
1839 "execution_count": null,
1841 "id": "wuxh5pq0OMSa"
1847 "w_exact= [np.array([[1.-np.exp(-0.1)]]), np.array([[np.exp(-0.1)]]), np.array([0.]),np.array([[1.0]]),np.array([-1.*DeltaE])]\n",
1848 "w_initial=[np.array([[1.-np.exp(-0.1)]]), np.array([[np.exp(-0.1)]]), np.array([0.]),np.array([[1.0]]),np.array([-1.0])]\n",
1849 "w=model_fit.get_weights()\n",
1850 "for i in range(len(w)):\n",
1851 " print('weight',i,'shape',w[i].shape,'ndim',w[i].ndim,'given',w_initial[i].shape)\n",
1852 " for j in range(w[i].shape[0]):\n",
1853 " if w[i].ndim==2:\n",
1854 " for k in range(w[i].shape[1]):\n",
1855 " w[i][j][k]=w_initial[i][0][0]/w[i].shape[0]\n",
1857 " w[i][j]=w_initial[i][0]\n",
1858 "model_fit.set_weights(w)\n",
1859 "model_fit.fit(x_train, y_train, epochs=5000, verbose=0,batch_size=samples)\n",
1860 "w_fitted=model_fit.get_weights()\n",
1861 "for i in range(len(w)):\n",
1862 " print('weight',i,' exact:',w_exact[i],': initial:',w_initial[i],' fitted:',w_fitted[i])"
1866 "cell_type": "code",
1867 "execution_count": null,
1875 "cell_type": "code",
1876 "execution_count": null,
1878 "id": "uJz1EgPyRTEH"
1882 "# evaluate model\n",
1883 "model_predict.set_weights(w_fitted)\n",
1884 "x_input=np.reshape(Et,(1, hours, 1))\n",
1885 "y_output = model_predict.predict(x_input)\n",
1886 "print('x_input.shape=',x_input.shape,'y_output.shape=',y_output.shape)\n",
1888 "m = np.reshape(y_output,hours)\n",
1889 "print('weights=',w)\n",
1891 " print('scaling')\n",
1892 " m = scalery.inverse_transform(m)\n",
1893 "m = np.reshape(m,hours)\n",
1894 "hour=np.array(range(hours))\n",
1895 "title=\"First RNN forecast\"\n",
1896 "plt.figure(figsize=(16,4))\n",
1897 "plt.plot(hour,E,linestyle='--',c='r',label='E=Equilibrium data')\n",
1898 "# print(len(hour),len(m_f))\n",
1899 "plt.scatter(hour,data,c='b',label='data=10-h fuel data')\n",
1900 "if m is not None:\n",
1901 " plt.plot(hour[:h2],m[:h2],linestyle='-',c='k',label='m=filtered')\n",
1902 " plt.plot(hour[h2:hours],m[h2:hours],linestyle='-',c='r',label='m=forecast')\n",
1903 "plt.title(title) \n",
1908 "cell_type": "code",
1909 "execution_count": null,
1915 "cell_type": "code",
1916 "execution_count": null,
1918 "id": "VSwtgKPJPnH4"
1922 "# plot subinterval only\n",
1923 "def plot_int(lb=0,ub=hours,title=\"RNN forecast\"):\n",
1924 " hour=np.array(range(hours))\n",
1925 " plt.figure(figsize=(16,4))\n",
1926 " plt.plot(hour[lb:ub],E[lb:ub],linestyle='--',c='r',label='Equilibrium data')\n",
1927 " # plt.scatter(hour[lb:ub],data[lb:ub],c='b',label='data=10-h fuel data')\n",
1928 " plt.plot(hour[lb:ub],m[lb:ub],linestyle='-',c='b',label='data=10-h fuel data')\n",
1930 " ub1 = min(h2,ub)\n",
1931 " plt.plot(hour[lb:ub1],m[lb:ub1],linestyle='-',c='k',label='filtered')\n",
1933 " lb1 = max(h2,lb)\n",
1934 " plt.plot(hour[lb1:ub],m[lb1:ub],linestyle='-',c='r',label='forecast')\n",
1935 " plt.title(title) \n",
1940 "cell_type": "code",
1941 "execution_count": null,
1943 "id": "vCjk9hZtkFym"
1951 "cell_type": "code",
1952 "execution_count": null,
1954 "id": "Sd3fDOnvmmdp"
1962 "cell_type": "code",
1963 "execution_count": null,
1965 "id": "vHkc4KHdkAJp"
1973 "cell_type": "code",
1974 "execution_count": null,
1976 "id": "Km5VWhcJlyvV"
1984 "cell_type": "markdown",
1986 "id": "TBayRudFcZWP"
1989 "Next step: two features - drying and wetting equilibria"
1993 "cell_type": "code",
1994 "execution_count": null,
1996 "id": "SGbgxOm_kEc4"
2000 "print(Ed.shape,Ew.shape,rain.shape)\n",
2001 "first_rain=np.nonzero(rain>0)[0][0]\n",
2002 "print(first_rain)\n",
2003 "hours=first_rain\n",
2007 "# print(Ed.shape,Ew.shape)\n",
2008 "# (timesteps, features)\n",
2009 "Et = np.vstack((Ed, Ew)).T\n",
2011 "data=obs_data[:hours]\n",
2015 "# transform as 2D, (timesteps, features) and (timesteps, outputs)\n",
2016 "datat = np.reshape(data,[data.shape[0],1])\n",
2018 " scalerx = MinMaxScaler()\n",
2019 " scalerx.fit(Et)\n",
2020 " Et = scalerx.transform(Et)\n",
2021 " scalery = MinMaxScaler()\n",
2022 " scalery.fit(datat)\n",
2023 " datat = scalery.transform(datat)"
2027 "cell_type": "code",
2028 "execution_count": null,
2032 "from utils import hash2"
2036 "cell_type": "code",
2037 "execution_count": null,
2041 "# Set seed for reproducibility\n",
2042 "reproducibility.set_seed()"
2046 "cell_type": "code",
2047 "execution_count": null,
2049 "id": "b6aJAvBEkEBl"
2054 "return_sequences=False\n",
2055 "x_train, y_train = staircase(Et,datat,timesteps=5,trainsteps=h2,\n",
2056 " return_sequences=return_sequences)\n",
2057 "print('x_train shape=',x_train.shape)\n",
2058 "samples, timesteps, features = x_train.shape\n",
2059 "print('y_train shape=',y_train.shape)\n",
2060 "# the simplest model possible\n",
2061 "activation=['linear','linear']\n",
2065 "features=Et.shape[1]\n",
2066 "hours=Et.shape[0]\n",
2067 "h0 = tf.convert_to_tensor(datat[:samples],dtype=tf.float32)\n",
2068 "# print('initial state=',h0)\n",
2069 "# statefull model version for traning\n",
2070 "import moisture_rnn\n",
2071 "model_fit=moisture_rnn.create_RNN_2(hidden_units=hidden_units, \n",
2072 " dense_units=dense_units, \n",
2073 " batch_shape=(samples,timesteps,features),\n",
2074 " stateful=True,\n",
2075 " return_sequences=return_sequences,\n",
2076 " # initial_state=h0,\n",
2077 " activation=activation,\n",
2078 " dense_layers=dense_layers)\n",
2079 "# same model stateless for prediction on the entire dataset - to start onlg\n",
2080 "# the real application will switch to prediction after training data end\n",
2081 "# and start from the state there\n",
2082 "print('model_fit input shape',x_train.shape,'output shape',model_fit(x_train).shape)\n",
2083 "print('model_fit input shape',x_train.shape,'output shape',y_train.shape)\n",
2084 "from keras.utils.vis_utils import plot_model\n",
2085 "plot_model(model_fit, to_file='model_plot.png', \n",
2086 " show_shapes=True, show_layer_names=True)"
2090 "cell_type": "code",
2091 "execution_count": null,
2095 "## Check 1: equilibrium input data the same\n",
2097 "print(hash2(Et))\n",
2098 "print(hash2(x_train))\n",
2099 "print(hash2(y_train))"
2103 "cell_type": "code",
2104 "execution_count": null,
2108 "## Check 2: Untrained RNN initialized with same weights\n",
2110 "hash2(model_fit.get_weights())"
2114 "cell_type": "code",
2115 "execution_count": null,
2117 "id": "ClBMYe8Lqr7P"
2121 "model_predict=moisture_rnn.create_RNN_2(hidden_units=hidden_units, dense_units=dense_units, \n",
2122 " input_shape=(hours,features),stateful = False,\n",
2123 " return_sequences=True,\n",
2124 " activation=activation,dense_layers=dense_layers)\n",
2125 "# model_predict=create_RNN_sequences(hidden_units=1, dense_units=1, input_shape=(hours,1), \n",
2126 "# activation=['linear', 'linear'])\n",
2127 "# print('model_predict input shape',Et.shape,'output shape',model_predict(Et).shape)\n",
2128 "print(model_predict.summary())\n",
2129 "from keras.utils.vis_utils import plot_model\n",
2130 "plot_model(model_predict, to_file='model_plot.png', \n",
2131 " show_shapes=True, show_layer_names=True)"
2135 "cell_type": "code",
2136 "execution_count": null,
2140 "## Check 3: Second model initialization same weights\n",
2142 "hash2(model_predict.get_weights())"
2146 "cell_type": "code",
2147 "execution_count": null,
2149 "id": "4U0kTEiksNZs"
2153 "w_initial=[np.array([[1.-np.exp(-0.1)]]), np.array([[np.exp(-0.1)]]), np.array([0.]),np.array([[1.0]]),np.array([-1.0])]\n",
2154 "w=model_fit.get_weights()\n",
2155 "for i in range(len(w)):\n",
2156 " print('weight',i,'shape',w[i].shape,'ndim',w[i].ndim,'given',w_initial[i].shape)\n",
2157 " for j in range(w[i].shape[0]):\n",
2158 " if w[i].ndim==2:\n",
2159 " for k in range(w[i].shape[1]):\n",
2160 " w[i][j][k]=w_initial[i][0][0]/w[i].shape[0]\n",
2162 " w[i][j]=w_initial[i][0]\n",
2163 "model_fit.set_weights(w)"
2167 "cell_type": "code",
2168 "execution_count": null,
2172 "## Check 4: weights the same after this step \n",
2174 "print(hash2(model_fit.get_weights()))\n",
2175 "print(hash2(x_train))\n",
2176 "print(hash2(y_train))"
2180 "cell_type": "code",
2181 "execution_count": null,
2185 "# print('model_fit input shape',x_train.shape,'output shape',y_train.shape)\n",
2186 "# print('x_train',x_train)\n",
2187 "# print('y_train',y_train)"
2191 "cell_type": "code",
2192 "execution_count": null,
2196 "# reproducibility.set_seed()"
2200 "cell_type": "code",
2201 "execution_count": null,
2205 "# model_fit.get_weights()"
2209 "cell_type": "code",
2210 "execution_count": null,
2214 "model_fit.fit(x_train, y_train, epochs=5000, verbose=0,batch_size=samples)\n",
2215 "w_fitted=model_fit.get_weights()\n",
2216 "for i in range(len(w)):\n",
2217 " print('weight',i,' exact:',w_exact[i],': initial:',w_initial[i],' fitted:',w_fitted[i])"
2221 "cell_type": "code",
2222 "execution_count": null,
2226 "## Check 5: Weights NOT the same after fitting\n",
2228 "hash2(model_fit.get_weights())"
2232 "cell_type": "code",
2233 "execution_count": null,
2235 "id": "o10lIOl4sndv"
2239 "# evaluate model\n",
2240 "model_predict.set_weights(w_fitted)\n",
2241 "x_input=np.reshape(Et,(1, hours, 2))\n",
2242 "y_output = model_predict.predict(x_input)\n",
2243 "print('x_input.shape=',x_input.shape,'y_output.shape=',y_output.shape)\n",
2245 "m = np.reshape(y_output,hours)\n",
2246 "print('weights=',w)\n",
2248 " print('scaling')\n",
2249 " m = scalery.inverse_transform(m)\n",
2250 "m = np.reshape(m,hours)\n",
2251 "hour=np.array(range(hours))\n",
2252 "title=\"First RNN forecast\"\n",
2253 "plt.figure(figsize=(16,4))\n",
2254 "plt.plot(hour,Ed,linestyle='--',c='r',label='Drying equilibrium')\n",
2255 "plt.plot(hour,Ew,linestyle='--',c='b',label='Wetting equilibrium')\n",
2256 "# print(len(hour),len(m_f))\n",
2257 "plt.scatter(hour,data,c='b',label='data=10-h fuel data')\n",
2258 "if m is not None:\n",
2259 " plt.plot(hour[:h2],m[:h2],linestyle='-',c='k',label='m=filtered')\n",
2260 " plt.plot(hour[h2:hours],m[h2:hours],linestyle='-',c='r',label='m=forecast')\n",
2261 "plt.title(title) \n",
2266 "cell_type": "code",
2267 "execution_count": null,
2271 "rnn_orig={'title':'RNN fitting and prediction - original','hours':hours,'h2':h2,'Ed':Ed,'Ew':Ew,'rain':rain,\n",
2272 " 'fm':obs_data,'m':m}\n",
2273 "# 'w_exact':w_exact,'w_initial':w_initial,'w_fitted':w_fitted\n",
2274 "to_json(rnn_orig,'rnn_orig.json')"
2278 "cell_type": "code",
2279 "execution_count": null,
2283 "hash2(rnn_orig, ['Ed', 'Ew', 'fm', 'rain'])"
2287 "cell_type": "code",
2288 "execution_count": null,
2292 "import pandas as pd\n",
2293 "print(np.sum(pd.util.hash_array(rnn_orig['m'])))\n",
2294 "hash2(rnn_orig['m'])"
2298 "cell_type": "code",
2299 "execution_count": null,
2305 "print(rnn_orig['m'][0])"
2309 "cell_type": "code",
2310 "execution_count": null,
2314 "print(hash2(rnn_orig['Ed']))\n",
2315 "print(rnn_orig['Ed'][0])"
2319 "cell_type": "code",
2320 "execution_count": null,
2322 "id": "mrWioCJVuU-G"
2326 "# plot subinterval only\n",
2327 "def plot_int(lb=0,ub=hours,title=\"RNN Prediction\"):\n",
2328 " hour=np.array(range(hours))\n",
2329 " plt.figure(figsize=(16,4))\n",
2330 " plt.plot(hour[lb:ub],Ed[lb:ub],linestyle='--',c='r',label='Drying equilibrium')\n",
2331 " plt.plot(hour[lb:ub],Ew[lb:ub],linestyle='--',c='b',label='Wetting equilibrium')\n",
2332 " plt.plot(hour[lb:ub],data[lb:ub],linestyle='-',c='b',label='RAWS fuel moisture data')\n",
2334 " ub1 = min(h2,ub)\n",
2335 " plt.plot(hour[lb:ub1],m[lb:ub1],linestyle='-',c='k',label='Fuel moisture fitted')\n",
2337 " lb1 = max(h2,lb)\n",
2338 " plt.plot(hour[lb1:ub],m[lb1:ub],linestyle='-',c='r',label='Fuel moisture prediction')\n",
2339 " plt.title(title) \n",
2344 "cell_type": "code",
2345 "execution_count": null,
2347 "id": "qmGPeG61uqGI"
2351 "plot_int(0,600,title='RNN fitting and prediction') # again the whole thing"
2355 "cell_type": "code",
2356 "execution_count": null,
2358 "id": "SwnOSJlOuvAA"
2362 "plot_int(0,300,title='RNN Fitting') "
2366 "cell_type": "code",
2367 "execution_count": null,
2369 "id": "EqCZD7uCvDrS"
2377 "cell_type": "code",
2378 "execution_count": null,
2380 "id": "hYgLAXpUvSLo"
2388 "cell_type": "code",
2389 "execution_count": null,
2393 "type(model_predict)"
2397 "cell_type": "markdown",
2399 "id": "gVQxv9Blc91t"
2402 "### 4.4 A comment on the information flow in the Kalman filter and in neural networks"
2406 "cell_type": "markdown",
2408 "id": "CZmR4HPAc91t"
2413 "cell_type": "markdown",
2415 "id": "_g_OTEg6ePb9"
2422 "cell_type": "markdown",
2424 "id": "aNxw7xI3FqFt"
2427 "We have shown how to combine a model and data for improved forecasting of fuel moisture from weather forecast using the Kalman filter. Augmenting the filter state by a model parameter and joint estimation of augmented state resulted in an improvement of the forecast."
2431 "cell_type": "markdown",
2433 "id": "IWpmDwUPGElR"
2436 "## Contributions of authors "
2440 "cell_type": "markdown",
2442 "id": "jujW1VFgGOCn"
2449 "cell_type": "markdown",
2451 "id": "HWslw7HmGZmP"
2454 "## Acknowledgements"
2458 "cell_type": "markdown",
2460 "id": "xubqDAV2GjkZ"
2463 "This Math Clinic was sponsored by the team of investigators of the NASA grant no. 80NSSC19K1091 *Coupled Interactive Forecasting of Weather, Fire Behavior, and Smoke Impact for Improved Wildland Fire Decision Making* under the NASA ROSES18 Disasters program. The author would like to thank Brian Zhang from the Math Clinic class for bringing the reference van der Kamp et al. (2017) to his attention."
2467 "cell_type": "markdown",
2469 "id": "ZsNZxOv7c91t"
2476 "cell_type": "markdown",
2478 "id": "vFY-iS1Wc91t"
2481 "J. Mandel, S. Amram, J. D. Beezley, G. Kelman, A. K. Kochanski, V. Y. Kondratenko, B. H. Lynn, B. Regev, and M. Vejmelka. *Recent advances and applications of WRF-SFIRE.* Natural Hazards and Earth System Science, 14(10):2829–2845, 2014. [doi:10.5194/nhessd-2-1759-2014](https://doi.org/10.5194/nhessd-2-1759-2014)\n",
2483 "R. E. Kalman. *A new approach to linear filtering and prediction problems.* Transactions of the ASME – Journal of Basic Engineering, Series D, 82:35–45, 1960. [doi:10.1115/1.3662552](https://doi.org/10.1115/1.3662552)\n",
2485 "E. Kalnay. *Atmospheric Modeling, Data Assimilation and Predictability.* Cambridge University Press, 2003. [doi:10.1017/CBO9780511802270](https://doi.org/10.1017/CBO9780511802270)\n",
2487 "D. W. van der Kamp, R. D. Moore, and I. G. McKendry. *A model for simulating the moisture content of standardized fuel sticks of various sizes.* Agricultural and Forest Meteorology, 236:123–134, 2017. [doi:10.1016/j.agrformet.2017.01.013](https://doi.org/10.1016/j.agrformet.2017.01.013)\n",
2489 "S. F. Schmidt. *Application of state-space methods to navigation problems.* volume 3 of Advances in Control Systems, C. T. Leondes, ed., pages 293–340. Elsevier, 1966. [doi:10.1016/B978-1-4831-6716-9.50011-4](https://doi.org/10.1016/B978-1-4831-6716-9.50011-4)\n",
2491 "M. Vejmelka, A. K. Kochanski, and J. Mandel. *Data assimilation of dead fuel moisture observations from remote automatic weather stations.* International Journal of Wildland Fire, 25:558– 568, 2016. [doi:10.1071/WF14085](https://doi.org/10.1071/WF14085)\n"
2496 "accelerator": "GPU",
2498 "collapsed_sections": [],
2501 "gpuClass": "standard",
2503 "display_name": "Python 3 (ipykernel)",
2504 "language": "python",
2508 "codemirror_mode": {
2512 "file_extension": ".py",
2513 "mimetype": "text/x-python",
2515 "nbconvert_exporter": "python",
2516 "pygments_lexer": "ipython3",