opm-common
ml_model.hpp
1 /*
2  Copyright (c) 2016 Robert W. Rose
3  Copyright (c) 2018 Paul Maevskikh
4  Copyright (c) 2024 NORCE
5 
6  Permission is hereby granted, free of charge, to any person obtaining a copy
7  of this software and associated documentation files (the "Software"), to deal
8  in the Software without restriction, including without limitation the rights
9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  copies of the Software, and to permit persons to whom the Software is
11  furnished to do so, subject to the following conditions:
12 
13  The above copyright notice and this permission notice shall be included in all
14  copies or substantial portions of the Software.
15 
16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  SOFTWARE.
23 
24  Note: This file is based on kerasify/keras_model.hh
25 */
26 
27 #ifndef ML_MODEL_H_
28 #define ML_MODEL_H_
29 
30 #include <opm/common/ErrorMacros.hpp>
32 
33 #include <algorithm>
34 #include <chrono>
35 #include <cmath>
36 #include <cstddef>
37 #include <cstdio>
38 #include <fstream>
39 #include <numeric>
40 #include <string>
41 #include <vector>
42 
43 #include <fmt/format.h>
44 
45 namespace Opm
46 {
47 
48 namespace ML
49 {
50 
51  // NN layer
52  // ---------------------
56  template <class T>
57  class Tensor
58  {
59  public:
60  Tensor()
61  {
62  }
63 
64  explicit Tensor(int i)
65  {
66  resizeI<std::vector<int>>({i});
67  }
68 
69  Tensor(int i, int j)
70  {
71  resizeI<std::vector<int>>({i, j});
72  }
73 
74  Tensor(int i, int j, int k)
75  {
76  resizeI<std::vector<int>>({i, j, k});
77  }
78 
79  Tensor(int i, int j, int k, int l)
80  {
81  resizeI<std::vector<int>>({i, j, k, l});
82  }
83 
84  template <typename Sizes>
85  void resizeI(const Sizes& sizes)
86  {
87  if (sizes.size() == 1)
88  dims_ = {(int)sizes[0]};
89  if (sizes.size() == 2)
90  dims_ = {(int)sizes[0], (int)sizes[1]};
91  if (sizes.size() == 3)
92  dims_ = {(int)sizes[0], (int)sizes[1], (int)sizes[2]};
93  if (sizes.size() == 4)
94  dims_ = {(int)sizes[0], (int)sizes[1], (int)sizes[2], (int)sizes[3]};
95 
96  data_.resize(std::accumulate(begin(dims_), end(dims_), 1.0, std::multiplies<>()));
97  }
98 
99  void flatten()
100  {
101  OPM_ERROR_IF(dims_.size() == 0, "Invalid tensor");
102 
103  int elements = dims_[0];
104  for (unsigned int i = 1; i < dims_.size(); i++) {
105  elements *= dims_[i];
106  }
107  dims_ = {elements};
108  }
109 
110  T& operator()(int i)
111  {
112  OPM_ERROR_IF(dims_.size() != 1, "Invalid indexing for tensor");
113 
114  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
115  fmt::format(fmt::runtime(" Invalid i: "
116  "{}"
117  " max: "
118  "{}"),
119  i,
120  dims_[0]));
121 
122  return data_[i];
123  }
124 
125  const T& operator()(int i) const
126  {
127  OPM_ERROR_IF(dims_.size() != 1, "Invalid indexing for tensor");
128 
129  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
130  fmt::format(fmt::runtime(" Invalid i: "
131  "{}"
132  " max: "
133  "{}"),
134  i,
135  dims_[0]));
136 
137  return data_[i];
138  }
139 
140  T& operator()(int i, int j)
141  {
142  OPM_ERROR_IF(dims_.size() != 2, "Invalid indexing for tensor");
143  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
144  fmt::format(fmt::runtime(" Invalid i: "
145  "{}"
146  " max: "
147  "{}"),
148  i,
149  dims_[0]));
150  OPM_ERROR_IF(!(j < dims_[1] && j >= 0),
151  fmt::format(fmt::runtime(" Invalid j: "
152  "{}"
153  " max: "
154  "{}"),
155  j,
156  dims_[1]));
157 
158  return data_[dims_[1] * i + j];
159  }
160 
161  const T& operator()(int i, int j) const
162  {
163  OPM_ERROR_IF(dims_.size() != 2, "Invalid indexing for tensor");
164  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
165  fmt::format(fmt::runtime(" Invalid i: "
166  "{}"
167  " max: "
168  "{}"),
169  i,
170  dims_[0]));
171  OPM_ERROR_IF(!(j < dims_[1] && j >= 0),
172  fmt::format(fmt::runtime(" Invalid j: "
173  "{}"
174  " max: "
175  "{}"),
176  j,
177  dims_[1]));
178  return data_[dims_[1] * i + j];
179  }
180 
181  T& operator()(int i, int j, int k)
182  {
183  OPM_ERROR_IF(dims_.size() != 3, "Invalid indexing for tensor");
184  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
185  fmt::format(fmt::runtime(" Invalid i: "
186  "{}"
187  " max: "
188  "{}"),
189  i,
190  dims_[0]));
191  OPM_ERROR_IF(!(j < dims_[1] && j >= 0),
192  fmt::format(fmt::runtime(" Invalid j: "
193  "{}"
194  " max: "
195  "{}"),
196  j,
197  dims_[1]));
198  OPM_ERROR_IF(!(k < dims_[2] && k >= 0),
199  fmt::format(fmt::runtime(" Invalid k: "
200  "{}"
201  " max: "
202  "{}"),
203  k,
204  dims_[2]));
205 
206  return data_[dims_[2] * (dims_[1] * i + j) + k];
207  }
208 
209  const T& operator()(int i, int j, int k) const
210  {
211  OPM_ERROR_IF(dims_.size() != 3, "Invalid indexing for tensor");
212  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
213  fmt::format(fmt::runtime(" Invalid i: "
214  "{}"
215  " max: "
216  "{}"),
217  i,
218  dims_[0]));
219  OPM_ERROR_IF(!(j < dims_[1] && j >= 0),
220  fmt::format(fmt::runtime(" Invalid j: "
221  "{}"
222  " max: "
223  "{}"),
224  j,
225  dims_[1]));
226  OPM_ERROR_IF(!(k < dims_[2] && k >= 0),
227  fmt::format(fmt::runtime(" Invalid k: "
228  "{}"
229  " max: "
230  "{}"),
231  k,
232  dims_[2]));
233 
234  return data_[dims_[2] * (dims_[1] * i + j) + k];
235  }
236 
237  T& operator()(int i, int j, int k, int l)
238  {
239  OPM_ERROR_IF(dims_.size() != 4, "Invalid indexing for tensor");
240  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
241  fmt::format(fmt::runtime(" Invalid i: "
242  "{}"
243  " max: "
244  "{}"),
245  i,
246  dims_[0]));
247  OPM_ERROR_IF(!(j < dims_[1] && j >= 0),
248  fmt::format(fmt::runtime(" Invalid j: "
249  "{}"
250  " max: "
251  "{}"),
252  j,
253  dims_[1]));
254  OPM_ERROR_IF(!(k < dims_[2] && k >= 0),
255  fmt::format(fmt::runtime(" Invalid k: "
256  "{}"
257  " max: "
258  "{}"),
259  k,
260  dims_[2]));
261  OPM_ERROR_IF(!(l < dims_[3] && l >= 0),
262  fmt::format(fmt::runtime(" Invalid l: "
263  "{}"
264  " max: "
265  "{}"),
266  l,
267  dims_[3]));
268 
269  return data_[dims_[3] * (dims_[2] * (dims_[1] * i + j) + k) + l];
270  }
271 
272  const T& operator()(int i, int j, int k, int l) const
273  {
274  OPM_ERROR_IF(dims_.size() != 4, "Invalid indexing for tensor");
275  OPM_ERROR_IF(!(i < dims_[0] && i >= 0),
276  fmt::format(fmt::runtime(" Invalid i: "
277  "{}"
278  " max: "
279  "{}"),
280  i,
281  dims_[0]));
282  OPM_ERROR_IF(!(j < dims_[1] && j >= 0),
283  fmt::format(fmt::runtime(" Invalid j: "
284  "{}"
285  " max: "
286  "{}"),
287  j,
288  dims_[1]));
289  OPM_ERROR_IF(!(k < dims_[2] && k >= 0),
290  fmt::format(fmt::runtime(" Invalid k: "
291  "{}"
292  " max: "
293  "{}"),
294  k,
295  dims_[2]));
296  OPM_ERROR_IF(!(l < dims_[3] && l >= 0),
297  fmt::format(fmt::runtime(" Invalid l: "
298  "{}"
299  " max: "
300  "{}"),
301  l,
302  dims_[3]));
303 
304  return data_[dims_[3] * (dims_[2] * (dims_[1] * i + j) + k) + l];
305  }
306 
307  void fill(const T& value)
308  {
309  std::ranges::fill(data_, value);
310  }
311 
312  // Tensor addition
313  Tensor operator+(const Tensor& other)
314  {
315  OPM_ERROR_IF(dims_.size() != other.dims_.size(),
316  "Cannot add tensors with different dimensions");
317  Tensor result;
318  result.dims_ = dims_;
319  result.data_.resize(data_.size());
320 
321  std::ranges::transform(data_, other.data_, result.data_.begin(),
322  [](const T& x, const T& y) { return x + y; });
323 
324  return result;
325  }
326 
327  // Tensor multiplication
328  Tensor multiply(const Tensor& other)
329  {
330  OPM_ERROR_IF(dims_.size() != other.dims_.size(),
331  "Cannot multiply elements with different dimensions");
332 
333  Tensor result;
334  result.dims_ = dims_;
335  result.data_.resize(data_.size());
336 
337  std::ranges::transform(data_, other.data_, result.data_.begin(),
338  [](const T& x, const T& y) { return x * y; });
339 
340  return result;
341  }
342 
343  // Tensor dot for 2d tensor
344  Tensor dot(const Tensor& other)
345  {
346  OPM_ERROR_IF(dims_.size() != 2, "Invalid tensor dimensions");
347  OPM_ERROR_IF(other.dims_.size() != 2, "Invalid tensor dimensions");
348 
349  OPM_ERROR_IF(dims_[1] != other.dims_[0],
350  "Cannot multiply with different inner dimensions");
351 
352  Tensor tmp(dims_[0], other.dims_[1]);
353 
354  for (int i = 0; i < dims_[0]; i++) {
355  for (int j = 0; j < other.dims_[1]; j++) {
356  for (int k = 0; k < dims_[1]; k++) {
357  tmp(i, j) += (*this)(i, k) * other(k, j);
358  }
359  }
360  }
361 
362  return tmp;
363  }
364 
365  void swap(Tensor& other)
366  {
367  dims_.swap(other.dims_);
368  data_.swap(other.data_);
369  }
370 
371  std::vector<int> dims_;
372  std::vector<T> data_;
373  };
374 
375  // NN layer
376  // ---------------------
381  template <class Evaluation>
382  class NNLayer
383  {
384  public:
385  NNLayer()
386  {
387  }
388 
389  virtual ~NNLayer()
390  {
391  }
392 
393  // Loads the ML trained file, returns true if the file exists
394  virtual bool loadLayer(std::ifstream& file) = 0;
395  // Apply the NN layers
396  virtual bool apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out) = 0;
397  };
398 
400  enum class ActivationType {
401  kLinear = 1,
402  kRelu = 2,
403  kSoftPlus = 3,
404  kSigmoid = 4,
405  kTanh = 5,
406  kHardSigmoid = 6
407  };
408 
412  template <class Evaluation>
413  class NNLayerActivation : public NNLayer<Evaluation>
414  {
415  public:
416 
417  NNLayerActivation(ActivationType activation_type = ActivationType::kLinear)
418  : activation_type_(activation_type)
419  {
420  }
421 
422  bool loadLayer(std::ifstream& file) override;
423 
424  bool apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out) override;
425 
426  private:
427  ActivationType activation_type_;
428  };
429 
433  template <class Evaluation>
434  class NNLayerScaling : public NNLayer<Evaluation>
435  {
436  public:
437  NNLayerScaling(float data_min = 1.0f,
438  float data_max = 1.0f,
439  float feat_inf = 1.0f,
440  float feat_sup = 1.0f);
441 
442  bool loadLayer(std::ifstream& file) override;
443 
444  bool apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out) override;
445 
446  private:
447  float data_min_;
448  float data_max_;
449  float feat_inf_;
450  float feat_sup_;
451  };
452 
456  template <class Evaluation>
457  class NNLayerUnScaling : public NNLayer<Evaluation>
458  {
459  public:
460  NNLayerUnScaling(float data_min = 1.0f,
461  float data_max = 1.0f,
462  float feat_inf = 1.0f,
463  float feat_sup = 1.0f);
464 
465  bool loadLayer(std::ifstream& file) override;
466 
467  bool apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out) override;
468 
469  private:
470  float data_min_;
471  float data_max_;
472  float feat_inf_;
473  float feat_sup_;
474  };
475 
479  template <class Evaluation>
480  class NNLayerDense : public NNLayer<Evaluation>
481  {
482  public:
483  NNLayerDense(Tensor<float> weights = {}, Tensor<float> biases = {}, ActivationType activation_type = ActivationType::kLinear);
484 
485  bool loadLayer(std::ifstream& file) override;
486 
487  bool apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out) override;
488 
489  private:
490  Tensor<float> weights_;
491  Tensor<float> biases_;
492 
493  NNLayerActivation<Evaluation> activation_;
494  };
495 
499  template <class Evaluation>
500  class NNModel
501  {
502  public:
503  enum class LayerType { kScaling = 1, kUnScaling = 2, kDense = 3, kActivation = 4 };
504 
505  virtual ~NNModel() = default;
506 
507  // loads models (.model files) generated by Kerasify
508  virtual bool loadModel(const std::string& filename);
509 
510  virtual bool apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out);
511 
512  private:
513  std::vector<std::unique_ptr<NNLayer<Evaluation>>> layers_;
514  };
515 
518  class NNTimer
519  {
520  public:
522  void start();
524  float stop();
525 
526  private:
527  std::chrono::time_point<std::chrono::high_resolution_clock> start_;
528  };
529 
530 
531 
532 
533  template <typename T>
534  bool readFile(std::ifstream& file, T& data)
535  {
536  file.read(reinterpret_cast<char*>(&data), sizeof(T));
537  return !file.fail();
538  }
539 
540  template <typename T>
541  bool readFile(std::ifstream& file, T* data, std::size_t n)
542  {
543  file.read(reinterpret_cast<char*>(data), sizeof(T) * n);
544  return !file.fail();
545  }
546 
547  template <class Evaluation>
548  bool NNLayerActivation<Evaluation>::loadLayer(std::ifstream& file)
549  {
550  unsigned int activation = 0;
551  OPM_ERROR_IF(!readFile<unsigned int>(file, activation), "Failed to read activation type");
552 
553  switch (static_cast<ActivationType>(activation)) {
554  case ActivationType::kLinear:
555  activation_type_ = ActivationType::kLinear;
556  break;
557  case ActivationType::kRelu:
558  activation_type_ = ActivationType::kRelu;
559  break;
560  case ActivationType::kSoftPlus:
561  activation_type_ = ActivationType::kSoftPlus;
562  break;
563  case ActivationType::kHardSigmoid:
564  activation_type_ = ActivationType::kHardSigmoid;
565  break;
566  case ActivationType::kSigmoid:
567  activation_type_ = ActivationType::kSigmoid;
568  break;
569  case ActivationType::kTanh:
570  activation_type_ = ActivationType::kTanh;
571  break;
572  default:
573  OPM_ERROR_IF(true,
574  fmt::format(fmt::runtime("\n Unsupported activation type "
575  "{}"),
576  activation));
577  }
578 
579  return true;
580  }
581 
582  template <class Evaluation>
583  bool NNLayerActivation<Evaluation>::apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out)
584  {
585  out = in;
586 
587  switch (activation_type_) {
588  case ActivationType::kLinear:
589  break;
590  case ActivationType::kRelu:
591  for (std::size_t i = 0; i < out.data_.size(); i++) {
592  if (out.data_[i] < 0.0) {
593  out.data_[i] = 0.0;
594  }
595  }
596  break;
597  case ActivationType::kSoftPlus:
598  for (std::size_t i = 0; i < out.data_.size(); i++) {
599  out.data_[i] = log(1.0 + exp(out.data_[i]));
600  }
601  break;
602  case ActivationType::kHardSigmoid:
603  for (std::size_t i = 0; i < out.data_.size(); i++) {
604  constexpr double sigmoid_scale = 0.2;
605  const Evaluation& x = (out.data_[i] * sigmoid_scale) + 0.5;
606 
607  if (x <= 0) {
608  out.data_[i] = 0.0;
609  } else if (x >= 1) {
610  out.data_[i] = 1.0;
611  } else {
612  out.data_[i] = x;
613  }
614  }
615  break;
616  case ActivationType::kSigmoid:
617  for (std::size_t i = 0; i < out.data_.size(); i++) {
618  const Evaluation& x = out.data_[i];
619 
620  if (x >= 0) {
621  out.data_[i] = 1.0 / (1.0 + exp(-x));
622  } else {
623  const Evaluation& z = exp(x);
624  out.data_[i] = z / (1.0 + z);
625  }
626  }
627  break;
628  case ActivationType::kTanh:
629  for (std::size_t i = 0; i < out.data_.size(); i++) {
630  out.data_[i] = sinh(out.data_[i]) / cosh(out.data_[i]);
631  }
632  break;
633  default:
634  break;
635  }
636 
637  return true;
638  }
639 
640  template <class Evaluation>
641  NNLayerScaling<Evaluation>::NNLayerScaling(float data_min, float data_max, float feat_inf, float feat_sup)
642  : data_min_(data_min)
643  , data_max_(data_max)
644  , feat_inf_(feat_inf)
645  , feat_sup_(feat_sup)
646  {
647  }
648 
649  template <class Evaluation>
650  bool NNLayerScaling<Evaluation>::loadLayer(std::ifstream& file)
651  {
652  OPM_ERROR_IF(!readFile<float>(file, data_min_), "Failed to read data min");
653  OPM_ERROR_IF(!readFile<float>(file, data_max_), "Failed to read data max");
654  OPM_ERROR_IF(!readFile<float>(file, feat_inf_), "Failed to read feat inf");
655  OPM_ERROR_IF(!readFile<float>(file, feat_sup_), "Failed to read feat sup");
656  return true;
657  }
658 
659  template <class Evaluation>
660  bool NNLayerScaling<Evaluation>::apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out)
661  {
662  out.data_.resize(in.data_.size());
663  out.dims_ = in.dims_;
664  for (std::size_t i = 0; i < out.data_.size(); i++) {
665  auto tempscale = (in.data_[i] - data_min_) / (data_max_ - data_min_);
666  out.data_[i] = tempscale * (feat_sup_ - feat_inf_) + feat_inf_;
667  }
668 
669  return true;
670  }
671 
672  template <class Evaluation>
673  NNLayerUnScaling<Evaluation>::NNLayerUnScaling(
674  float data_min, float data_max, float feat_inf, float feat_sup)
675  : data_min_(data_min)
676  , data_max_(data_max)
677  , feat_inf_(feat_inf)
678  , feat_sup_(feat_sup)
679  {
680  }
681 
682  template <class Evaluation>
683  bool NNLayerUnScaling<Evaluation>::loadLayer(std::ifstream& file)
684  {
685  OPM_ERROR_IF(!readFile<float>(file, data_min_), "Failed to read data min");
686  OPM_ERROR_IF(!readFile<float>(file, data_max_), "Failed to read data max");
687  OPM_ERROR_IF(!readFile<float>(file, feat_inf_), "Failed to read feat inf");
688  OPM_ERROR_IF(!readFile<float>(file, feat_sup_), "Failed to read feat sup");
689 
690  return true;
691  }
692 
693  template <class Evaluation>
694  bool NNLayerUnScaling<Evaluation>::apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out)
695  {
696  out.data_.resize(in.data_.size());
697  out.dims_ = in.dims_;
698  for (std::size_t i = 0; i < out.data_.size(); i++) {
699  auto tempscale = (in.data_[i] - feat_inf_) / (feat_sup_ - feat_inf_);
700 
701  out.data_[i] = tempscale * (data_max_ - data_min_) + data_min_;
702  }
703 
704  return true;
705  }
706 
707  template <class Evaluation>
708  NNLayerDense<Evaluation>::NNLayerDense(Tensor<float> weights, Tensor<float> biases, ActivationType activation_type)
709  : weights_(weights)
710  , biases_(biases)
711  , activation_(activation_type)
712  {
713  }
714 
715  template <class Evaluation>
716  bool NNLayerDense<Evaluation>::loadLayer(std::ifstream& file)
717  {
718  unsigned int weights_rows = 0;
719  OPM_ERROR_IF(!readFile<unsigned int>(file, weights_rows), "Expected weight rows");
720  OPM_ERROR_IF(!(weights_rows > 0), "Invalid weights # rows");
721 
722  unsigned int weights_cols = 0;
723  OPM_ERROR_IF(!readFile<unsigned int>(file, weights_cols), "Expected weight cols");
724  OPM_ERROR_IF(!(weights_cols > 0), "Invalid weights shape");
725 
726  unsigned int biases_shape = 0;
727  OPM_ERROR_IF(!readFile<unsigned int>(file, biases_shape), "Expected biases shape");
728  OPM_ERROR_IF(!(biases_shape > 0), "Invalid biases shape");
729 
730  weights_.resizeI<std::vector<unsigned int>>({weights_rows, weights_cols});
731  OPM_ERROR_IF(!readFile<float>(file, weights_.data_.data(), weights_rows * weights_cols), "Expected weights");
732 
733  biases_.resizeI<std::vector<unsigned int>>({biases_shape});
734  OPM_ERROR_IF(!readFile<float>(file, biases_.data_.data(), biases_shape), "Expected biases");
735 
736  OPM_ERROR_IF(!activation_.loadLayer(file), "Failed to load activation");
737 
738  return true;
739  }
740 
775  template <class Evaluation>
777  {
778  Tensor<Evaluation> tmp(weights_.dims_[1]);
779  Tensor<Evaluation> temp_in(in);
780  for (int i = 0; i < weights_.dims_[0]; i++) {
781  for (int j = 0; j < weights_.dims_[1]; j++) {
782  tmp(j) += (temp_in)(i)*weights_(i, j);
783  }
784  }
785 
786  for (int i = 0; i < biases_.dims_[0]; i++) {
787  tmp(i) += biases_(i);
788  }
789 
790  OPM_ERROR_IF(!activation_.apply(tmp, out), "Failed to apply activation");
791 
792  return true;
793  }
794 
795  template <class Evaluation>
796  bool NNModel<Evaluation>::loadModel(const std::string& filename)
797  {
798  std::ifstream file(filename.c_str(), std::ios::binary);
799  OPM_ERROR_IF(!file.is_open(),
800  fmt::format(fmt::runtime("\n Unable to open file "
801  "{}"),
802  filename.c_str()));
803 
804  unsigned int num_layers = 0;
805  OPM_ERROR_IF(!readFile<unsigned int>(file, num_layers), "Expected number of layers");
806 
807  for (unsigned int i = 0; i < num_layers; i++) {
808  unsigned int layer_type = 0;
809  OPM_ERROR_IF(!readFile<unsigned int>(file, layer_type), "Expected layer type");
810 
811  std::unique_ptr<NNLayer<Evaluation>> layer = nullptr;
812 
813  switch (static_cast<LayerType>(layer_type)) {
814  case LayerType::kScaling:
815  layer = std::make_unique<NNLayerScaling<Evaluation>>();
816  break;
817  case LayerType::kUnScaling:
818  layer = std::make_unique<NNLayerUnScaling<Evaluation>>();
819  break;
820  case LayerType::kDense:
821  layer = std::make_unique<NNLayerDense<Evaluation>>();
822  break;
823  case LayerType::kActivation:
824  layer = std::make_unique<NNLayerActivation<Evaluation>>();
825  break;
826  default:
827  break;
828  }
829 
830  OPM_ERROR_IF(!layer,
831  fmt::format(fmt::runtime("\n Unknown layer type "
832  "{}"),
833  layer_type));
834  OPM_ERROR_IF(!layer->loadLayer(file),
835  fmt::format(fmt::runtime("\n Failed to load layer "
836  "{}"),
837  i));
838 
839  layers_.emplace_back(std::move(layer));
840  }
841 
842  return true;
843  }
844 
845  template <class Evaluation>
846  bool NNModel<Evaluation>::apply(const Tensor<Evaluation>& in, Tensor<Evaluation>& out)
847  {
848  Tensor<Evaluation> temp_in(in);
849 
850  for (unsigned int i = 0; i < layers_.size(); i++) {
851 
852  if (i > 0) {
853  temp_in.swap(out);
854  }
855 
856  OPM_ERROR_IF(!(layers_[i]->apply(temp_in, out)),
857  fmt::format(fmt::runtime("\n Failed to apply layer "
858  "{}"),
859  i));
860  }
861  return true;
862  }
863 
864 } // namespace ML
865 
866 } // namespace Opm
867 
868 #endif // ML_MODEL_H_
void start()
Start the timer.
Definition: ml_model.cpp:34
Definition: ml_model.hpp:480
Definition: ml_model.hpp:382
bool apply(const Tensor< Evaluation > &in, Tensor< Evaluation > &out) override
Applies the forward pass of a dense (fully connected) neural-network layer.
Definition: ml_model.hpp:776
float stop()
Stop the timer and return elapsed time in milliseconds.
Definition: ml_model.cpp:39
This class implements a small container which holds the transmissibility mulitpliers for all the face...
Definition: Exceptions.hpp:30
Definition: ml_model.hpp:413
Definition: ml_model.hpp:434
A number of commonly used algebraic functions for the localized OPM automatic differentiation (AD) fr...
Definition: ml_model.hpp:500
Definition: ml_model.hpp:518
Definition: ml_model.hpp:457
Implements mathematical tensor (Max 4d)
Definition: ml_model.hpp:57