From e717d93e5502fa0b6a11f0664e392c636c666f8a Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 19:35:14 -0700
Subject: [PATCH 001/105] refac(neuron_t): add kind parameter

---
 src/inference_engine/kind_parameters_m.f90 |  4 ++++
 src/inference_engine/neuron_m.f90          | 20 ++++++++++----------
 src/inference_engine/neuron_s.f90          |  2 +-
 3 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/src/inference_engine/kind_parameters_m.f90 b/src/inference_engine/kind_parameters_m.f90
index 17afad275..973319d4d 100644
--- a/src/inference_engine/kind_parameters_m.f90
+++ b/src/inference_engine/kind_parameters_m.f90
@@ -4,6 +4,10 @@ module kind_parameters_m
   implicit none
   private
   public :: rkind
+  public :: default_real
+  public :: double_precision
 
   integer, parameter :: rkind = kind(1.0)
+  integer, parameter :: default_real = kind(1.)
+  integer, parameter :: double_precision = kind(1D0)
 end module kind_parameters_m
diff --git a/src/inference_engine/neuron_m.f90 b/src/inference_engine/neuron_m.f90
index bdfb5e42f..45dcc652b 100644
--- a/src/inference_engine/neuron_m.f90
+++ b/src/inference_engine/neuron_m.f90
@@ -2,18 +2,18 @@
 ! Terms of use are as specified in LICENSE.txt
 module neuron_m
   use julienne_string_m, only : string_t
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real
   implicit none
 
   private
   public :: neuron_t
 
-  type neuron_t
+  type neuron_t(k)
     !! linked list of neurons
-    private
-    real(rkind), allocatable :: weights_(:)
-    real(rkind) bias_
-    type(neuron_t), allocatable :: next
+    integer, kind :: k = default_real
+    real(k),        allocatable, private :: weights_(:)
+    real(k),                     private :: bias_
+    type(neuron_t(k)), allocatable, private :: next
   contains
     procedure :: to_json
     procedure :: weights
@@ -35,8 +35,8 @@ pure recursive module function from_json(neuron_lines, start) result(neuron)
 
     pure module function from_components(weights, bias) result(neuron)
       !! construct single neuron_t object from an array of weights and a bias
-      real(rkind), intent(in) :: weights(:)
-      real(rkind), intent(in) :: bias
+      real, intent(in) :: weights(:)
+      real, intent(in) :: bias
       type(neuron_t) neuron
     end function
 
@@ -53,13 +53,13 @@ pure module function to_json(self) result(lines)
     module function weights(self) result(my_weights)
       implicit none
       class(neuron_t), intent(in) :: self
-      real(rkind), allocatable :: my_weights(:)
+      real, allocatable :: my_weights(:)
     end function
 
     module function bias(self) result(my_bias)
       implicit none
       class(neuron_t), intent(in) :: self
-      real(rkind) my_bias
+      real my_bias
     end function
 
     module function next_allocated(self) result(next_is_allocated)
diff --git a/src/inference_engine/neuron_s.f90 b/src/inference_engine/neuron_s.f90
index 2d9502278..eb195a38f 100644
--- a/src/inference_engine/neuron_s.f90
+++ b/src/inference_engine/neuron_s.f90
@@ -14,7 +14,7 @@
 
     call assert(allocated(self%weights_), "neuron_s(to_json): allocated weights_")
 
-    csv_format = separated_values(separator=",", mold=[real(rkind)::])
+    csv_format = separated_values(separator=",", mold=[real::])
     allocate(character(len=size(self%weights_)*(characters_per_value+1)-1)::weights_string)
     allocate(character(len=characters_per_value)::bias_string)
     write(weights_string, fmt = csv_format) self%weights_

From 8efec6f3789b18c05be1912810bbf8fb6981a56d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 19:42:39 -0700
Subject: [PATCH 002/105] refac(layer_t): add kind parameter

---
 src/inference_engine/layer_m.f90 | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/inference_engine/layer_m.f90 b/src/inference_engine/layer_m.f90
index 78fccbd89..788cf2f63 100644
--- a/src/inference_engine/layer_m.f90
+++ b/src/inference_engine/layer_m.f90
@@ -5,16 +5,17 @@ module layer_m
   use julienne_string_m, only : string_t
   use inference_engine_m_, only : inference_engine_t
   use tensor_map_m, only :  tensor_map_t
+  use kind_parameters_m, only : default_real
   implicit none
 
   private
   public :: layer_t
 
-  type layer_t
+  type layer_t(k)
     !! linked list of layers, each comprised of a linked list of neurons
-    private
-    type(neuron_t) neuron !! linked list of this layer's neurons 
-    type(layer_t), allocatable :: next !! next layer
+    integer, kind :: k = default_real
+    type(neuron_t(k)),             private :: neuron !! linked list of this layer's neurons 
+    type(layer_t(k)), allocatable, private :: next !! next layer
   contains
     procedure :: inference_engine
     procedure :: count_layers

From 1e25cd6d515a6db3a40b97ad86ebe7cde9d9e711 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 19:48:05 -0700
Subject: [PATCH 003/105] refac(tensor_map): add kind parameter

---
 src/inference_engine/tensor_map_m.f90 | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/inference_engine/tensor_map_m.f90 b/src/inference_engine/tensor_map_m.f90
index 5560cf168..247e4d86e 100644
--- a/src/inference_engine/tensor_map_m.f90
+++ b/src/inference_engine/tensor_map_m.f90
@@ -3,16 +3,16 @@
 module tensor_map_m
   use tensor_m, only : tensor_t
   use julienne_m, only : string_t
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real
   implicit none
   
   private
   public :: tensor_map_t
 
-  type tensor_map_t
-    private
-    character(len=:), allocatable :: layer_
-    real(rkind), allocatable, dimension(:) :: intercept_, slope_
+  type tensor_map_t(k)
+    integer, kind :: k = default_real 
+    character(len=:),      allocatable, private :: layer_
+    real(k), dimension(:), allocatable, private :: intercept_, slope_
   contains
     procedure map_to_training_range
     procedure map_from_training_range
@@ -27,7 +27,7 @@ module tensor_map_m
     pure module function from_component_ranges(layer, minima, maxima) result(tensor_map)
       implicit none
       character(len=*), intent(in) :: layer
-      real(rkind), dimension(:), intent(in) :: minima, maxima
+      real, dimension(:), intent(in) :: minima, maxima
       type(tensor_map_t) tensor_map
     end function
 

From c63d9bbba880b276df69152d41f55f46672df9b7 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 19:51:06 -0700
Subject: [PATCH 004/105] refac(tensor_m): add kind parameter

---
 src/inference_engine/tensor_m.f90 | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/inference_engine/tensor_m.f90 b/src/inference_engine/tensor_m.f90
index e79b6deab..d1faee13c 100644
--- a/src/inference_engine/tensor_m.f90
+++ b/src/inference_engine/tensor_m.f90
@@ -1,15 +1,15 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 module tensor_m
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real
   implicit none
 
   private
   public :: tensor_t
 
-  type tensor_t
-    private
-    real(rkind), allocatable :: values_(:)
+  type tensor_t(k)
+    integer, kind :: k = default_real 
+    real, allocatable, private :: values_(:)
   contains
     procedure values
     procedure num_components
@@ -19,7 +19,7 @@ module tensor_m
 
     pure module function construct_from_components(values) result(tensor)
       implicit none
-      real(rkind), intent(in) :: values(:)
+      real, intent(in) :: values(:)
       type(tensor_t) tensor
     end function
 
@@ -30,7 +30,7 @@ pure module function construct_from_components(values) result(tensor)
     pure module function values(self) result(tensor_values)
       implicit none
       class(tensor_t), intent(in) :: self
-      real(rkind), allocatable :: tensor_values(:)
+      real, allocatable :: tensor_values(:)
     end function
 
     pure module function num_components(self) result(n)

From 8bd7ae799b4ba6d92a4170dbe60e9e2f23cfa7ce Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 19:57:47 -0700
Subject: [PATCH 005/105] refac(inference_engine_t): add kind parameter

---
 src/inference_engine/inference_engine_m_.f90 | 35 ++++++++++----------
 src/inference_engine/inference_engine_s.F90  | 11 +++---
 2 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index ef7fe1ade..c4dbe2be9 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -5,7 +5,7 @@ module inference_engine_m_
   use activation_strategy_m, only : activation_strategy_t
   use julienne_file_m, only : file_t
   use julienne_string_m, only : string_t
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real
   use metadata_m, only : metadata_t
   use tensor_m, only : tensor_t
   use tensor_map_m, only : tensor_map_t
@@ -17,14 +17,14 @@ module inference_engine_m_
   public :: exchange_t
   public :: infer
 
-  type inference_engine_t
+  type inference_engine_t(k)
     !! Encapsulate the minimal information needed to perform inference
-    private
-    type(tensor_map_t) input_map_, output_map_
-    type(metadata_t) metadata_
-    real(rkind), allocatable :: weights_(:,:,:), biases_(:,:)
-    integer, allocatable :: nodes_(:)
-    class(activation_strategy_t), allocatable :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
+    integer, kind :: k = default_real 
+    type(tensor_map_t(k)), private :: input_map_, output_map_
+    type(metadata_t), private :: metadata_
+    real(k), allocatable, private :: weights_(:,:,:), biases_(:,:)
+    integer, allocatable, private :: nodes_(:)
+    class(activation_strategy_t), allocatable, private :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   contains
     procedure :: infer
     procedure :: to_json
@@ -42,18 +42,19 @@ module inference_engine_m_
     procedure :: to_exchange
   end type
 
-  type exchange_t
-    type(tensor_map_t) input_map_, output_map_
+  type exchange_t(k)
+    integer, kind :: k = default_real
+    type(tensor_map_t(k)) input_map_, output_map_
     type(metadata_t) metadata_
-    real(rkind), allocatable :: weights_(:,:,:), biases_(:,:)
+    real(k), allocatable :: weights_(:,:,:), biases_(:,:)
     integer, allocatable :: nodes_(:)
     class(activation_strategy_t), allocatable :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   end type
 
-  type difference_t
-    private
-    real(rkind), allocatable :: weights_difference_(:,:,:), biases_difference_(:,:)
-    integer, allocatable :: nodes_difference_(:)
+  type difference_t(k)
+    integer, kind :: k = default_real
+    real(k), allocatable, private :: weights_difference_(:,:,:), biases_difference_(:,:)
+    integer, allocatable, private :: nodes_difference_(:)
   contains
     procedure :: norm
   end type
@@ -64,7 +65,7 @@ impure module function construct_from_padded_arrays(metadata, weights, biases, n
       result(inference_engine)
       implicit none
       type(string_t), intent(in) :: metadata(:)
-      real(rkind), intent(in) :: weights(:,:,:), biases(:,:)
+      real, intent(in) :: weights(:,:,:), biases(:,:)
       integer, intent(in) :: nodes(0:)
       type(tensor_map_t), intent(in), optional :: input_map, output_map
       type(inference_engine_t) inference_engine
@@ -111,7 +112,7 @@ impure elemental module function to_json(self) result(json_file)
     elemental module function norm(self) result(norm_of_self)
       implicit none
       class(difference_t), intent(in) :: self
-      real(rkind)  norm_of_self
+      real  norm_of_self
     end function
 
     elemental module function subtract(self, rhs) result(difference)
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index bc95cfd7b..1b3fba5d7 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -17,7 +17,6 @@
   end interface
 
   character(len=*), parameter :: acceptable_engine_tag = "0.13.0" ! git tag capable of reading the current json file format
-  real, parameter :: zero = 0._rkind, one = 1._rkind
 
 contains
 
@@ -43,7 +42,7 @@
 
   module procedure infer
 
-    real(rkind), allocatable :: a(:,:)
+    real, allocatable :: a(:,:)
     integer, parameter :: input_layer = 0
     integer l
 
@@ -197,9 +196,9 @@ impure function activation_factory_method(activation_name) result(activation)
     type(tensor_map_t) proto_map
     type(metadata_t) proto_meta
     type(neuron_t) proto_neuron
-    proto_map = tensor_map_t("",[zero],[one])
+    proto_map = tensor_map_t("",[0.],[1.])
     proto_meta = metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t(""))
-    proto_neuron = neuron_t([zero],one)
+    proto_neuron = neuron_t([0.],1.)
 #endif
 
     call assert_consistency(self)
@@ -211,9 +210,9 @@ impure function activation_factory_method(activation_name) result(activation)
       ,first_hidden => lbound(self%nodes_,1) + 1 &
       ,last_hidden => ubound(self%nodes_,1) - 1 &
 #ifndef _CRAYFTN
-      ,proto_map => tensor_map_t("",[zero],[one]) &
+      ,proto_map => tensor_map_t("",[0.],[1.]) &
       ,proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")) &
-      ,proto_neuron => neuron_t([zero],zero) &
+      ,proto_neuron => neuron_t([0.],0.) &
 #endif
     )
       associate( &

From dff93d9ed68260bbf88a90a30b1de21d92be7da1 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 21:21:55 -0700
Subject: [PATCH 006/105] test(inference_engine_t): use eqv op for compare

This commit replaces the inference_engine_t type-bound, generic
subtraction operator(-) with an user-defined approximate
equivalence operator(==), which simplifies comparisons by
eliminating the diffence_t type.  The new operator is used in
inference_engine_test_m instead of the difference_t, operator(-),
and the norm() function.
---
 src/inference_engine/inference_engine_m_.f90 | 33 +++--------
 src/inference_engine/inference_engine_s.F90  | 60 +++++---------------
 src/inference_engine_m.f90                   |  2 +-
 test/inference_engine_test_m.F90             | 12 +---
 4 files changed, 28 insertions(+), 79 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index c4dbe2be9..f7413dd12 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -13,7 +13,6 @@ module inference_engine_m_
 
   private
   public :: inference_engine_t
-  public :: difference_t
   public :: exchange_t
   public :: infer
 
@@ -36,8 +35,8 @@ module inference_engine_m_
     procedure :: nodes_per_layer
     procedure :: assert_conformable_with
     procedure :: skip
-    procedure, private :: subtract
-    generic :: operator(-) => subtract
+    procedure, private :: approximately_equal
+    generic :: operator(==) => approximately_equal
     procedure :: activation_function_name
     procedure :: to_exchange
   end type
@@ -51,14 +50,6 @@ module inference_engine_m_
     class(activation_strategy_t), allocatable :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   end type
 
-  type difference_t(k)
-    integer, kind :: k = default_real
-    real(k), allocatable, private :: weights_difference_(:,:,:), biases_difference_(:,:)
-    integer, allocatable, private :: nodes_difference_(:)
-  contains
-    procedure :: norm
-  end type
-
   interface inference_engine_t
 
     impure module function construct_from_padded_arrays(metadata, weights, biases, nodes, input_map, output_map) &
@@ -81,6 +72,13 @@ impure elemental module function from_json(file_) result(inference_engine)
 
   interface
 
+    elemental module function approximately_equal(lhs, rhs) result(lhs_eq_rhs)
+      !! The result is true if lhs and rhs are the same to within a tolerance
+      implicit none
+      class(inference_engine_t), intent(in) :: lhs, rhs
+      logical lhs_eq_rhs
+    end function
+
     elemental module function map_to_input_range(self, tensor) result(normalized_tensor)
       !! The result contains the input tensor values normalized to fall on the range used during training
       implicit none
@@ -109,19 +107,6 @@ impure elemental module function to_json(self) result(json_file)
       type(file_t) json_file
     end function
 
-    elemental module function norm(self) result(norm_of_self)
-      implicit none
-      class(difference_t), intent(in) :: self
-      real  norm_of_self
-    end function
-
-    elemental module function subtract(self, rhs) result(difference)
-      implicit none
-      class(inference_engine_t), intent(in) :: self
-      type(inference_engine_t), intent(in) :: rhs
-      type(difference_t)  difference
-    end function
-
     elemental module subroutine assert_conformable_with(self, inference_engine)
       implicit none
       class(inference_engine_t), intent(in) :: self
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 1b3fba5d7..9ffa67580 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -13,7 +13,6 @@
 
   interface assert_consistency
     procedure inference_engine_consistency
-    procedure difference_consistency
   end interface
 
   character(len=*), parameter :: acceptable_engine_tag = "0.13.0" ! git tag capable of reading the current json file format
@@ -114,24 +113,6 @@ pure subroutine inference_engine_consistency(self)
 
   end subroutine
 
-  pure subroutine difference_consistency(self)
-
-    type(difference_t), intent(in) :: self
-
-    integer, parameter :: input_layer=0
-
-    associate( &
-      all_allocated=>[allocated(self%weights_difference_),allocated(self%biases_difference_),allocated(self%nodes_difference_)] &
-    )   
-      call assert(all(all_allocated),"inference_engine_s(difference_consistency): fully_allocated",intrinsic_array_t(all_allocated))
-    end associate
-
-    call assert(all(size(self%biases_difference_,1)==[size(self%weights_difference_,1), size(self%weights_difference_,2)]), &
-      "inference_engine_s(difference_consistency): conformable arrays" &
-    )
-
-  end subroutine
-
   impure function activation_factory_method(activation_name) result(activation)
     character(len=*), intent(in) :: activation_name
     class(activation_strategy_t), allocatable :: activation
@@ -424,32 +405,26 @@ impure function activation_factory_method(activation_name) result(activation)
     
   end procedure
 
-  module procedure subtract
+  module procedure approximately_equal
 
-    call assert_consistency(self)
+    logical nodes_eq
+
+    nodes_eq = all(lhs%nodes_ == rhs%nodes_)
+
+    call assert_consistency(lhs)
     call assert_consistency(rhs)
-    call self%assert_conformable_with(rhs)
+    call lhs%assert_conformable_with(rhs)
 
     block
       integer l
+      logical layer_eq(ubound(lhs%nodes_,1))
+      real, parameter :: tolerance = 1.E-06
 
-      allocate(difference%weights_difference_, mold = self%weights_)
-      allocate(difference%biases_difference_, mold = self%biases_)
-      allocate(difference%nodes_difference_, mold = self%nodes_)
-
-      difference%weights_difference_ = 0.
-      difference%biases_difference_ = 0.
-      difference%nodes_difference_ = 0.
-
-      l = 0
-      difference%nodes_difference_(l)  = self%nodes_(l) - rhs%nodes_(l)
-     
-      associate(n => self%nodes_)
+      associate(n => lhs%nodes_)
 #ifndef __INTEL_COMPILER
         do concurrent(l = 1:ubound(n,1))
-          difference%weights_difference_(1:n(l),1:n(l-1),l) = self%weights_(1:n(l),1:n(l-1),l) - rhs%weights_(1:n(l),1:n(l-1),l)
-          difference%biases_difference_(1:n(l),l) = self%biases_(1:n(l),l) - rhs%biases_(1:n(l),l)
-          difference%nodes_difference_(l) = self%nodes_(l) - rhs%nodes_(l)
+            layer_eq(l) = all(abs(lhs%weights_(1:n(l),1:n(l-1),l) - rhs%weights_(1:n(l),1:n(l-1),l)) < tolerance) .and. &
+                          all(abs(lhs%biases_(1:n(l),l)           - rhs%biases_(1:n(l),l)) < tolerance)
         end do
 #else
         block
@@ -457,9 +432,8 @@ impure function activation_factory_method(activation_name) result(activation)
           do l = 1, ubound(n,1)
             do j = 1, n(l)
               do k = 1, n(l-1)
-                difference%weights_difference_(j,k,l) = self%weights_(j,k,l) - rhs%weights_(j,k,l)
-                difference%biases_difference_(j,l) = self%biases_(j,l) - rhs%biases_(j,l)
-                difference%nodes_difference_(l) = self%nodes_(l) - rhs%nodes_(l)
+                layer_eq(l) = all(abs(lhs%weights_(j,k,l) - rhs%weights_(j,k,l)) < tolerance) .and. &
+                              all(abs(lhs%biases_(j,l)    - rhs%biases_(j,l)) < tolerance)
               end do
             end do
           end do
@@ -467,13 +441,9 @@ impure function activation_factory_method(activation_name) result(activation)
 #endif
       end associate
 
+      lhs_eq_rhs = nodes_eq .and. all(layer_eq)
     end block
 
-    call assert_consistency(difference)
-  end procedure
-
-  module procedure norm 
-    norm_of_self = maxval([abs(self%weights_difference_), abs(self%biases_difference_), real(abs(self%nodes_difference_))])
   end procedure
 
   module procedure num_outputs
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 50f3cff31..efd0562bc 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -6,7 +6,7 @@ module inference_engine_m
  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
- use inference_engine_m_, only : inference_engine_t, difference_t, infer
+ use inference_engine_m_, only : inference_engine_t, infer
  use kind_parameters_m, only : rkind
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t
diff --git a/test/inference_engine_test_m.F90 b/test/inference_engine_test_m.F90
index 435bd9ced..a2ecb10b1 100644
--- a/test/inference_engine_test_m.F90
+++ b/test/inference_engine_test_m.F90
@@ -12,7 +12,7 @@ module inference_engine_test_m
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : inference_engine_t, tensor_t, difference_t
+  use inference_engine_m, only : inference_engine_t, tensor_t
 
   implicit none
 
@@ -163,25 +163,19 @@ function multi_hidden_layer_net_to_from_json() result(test_passes)
     logical test_passes
     type(inference_engine_t) inference_engine, from_json
     type(file_t) json_file
-    type(difference_t) difference
-    real, parameter :: tolerance = 1.0E-06
 
     inference_engine = distinct_parameters()
     json_file = inference_engine%to_json()
     from_json = inference_engine_t(json_file)
-    difference = inference_engine  - from_json
-    test_passes = difference%norm() < tolerance
+    test_passes = inference_engine == from_json 
   end function
 
   function varying_width_net_to_from_json() result(test_passes)
     logical test_passes
-    real, parameter :: tolerance = 1.0E-06
-    type(difference_t) difference
 
     associate(inference_engine => varying_width())
       associate(from_json => inference_engine_t( inference_engine%to_json() ))
-        difference = inference_engine  - from_json
-        test_passes = difference%norm() < tolerance
+        test_passes = inference_engine == from_json 
       end associate
     end associate
   end function

From 1c1a93eff4927efdd8ba5a27601a792fb57092a5 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 27 Aug 2024 22:35:49 -0700
Subject: [PATCH 007/105] refac: define generic activation/derivative

This commit sets up for adding double-precision support by
making the activation and activation-derivative bindings generic.
---
 src/inference_engine/activation_strategy_m.f90  |  9 ++++-----
 .../differentiable_activation_strategy_m.f90    |  3 ++-
 src/inference_engine/gelu_m.f90                 | 17 ++++++++---------
 src/inference_engine/gelu_s.f90                 | 15 +++++++--------
 src/inference_engine/relu_m.f90                 | 17 ++++++++---------
 src/inference_engine/relu_s.f90                 | 11 ++++-------
 src/inference_engine/sigmoid_m.f90              | 17 ++++++++---------
 src/inference_engine/sigmoid_s.f90              |  4 ++--
 src/inference_engine/step_m.f90                 |  9 ++++-----
 src/inference_engine/step_s.f90                 |  4 ++--
 src/inference_engine/swish_m.f90                | 17 ++++++++---------
 src/inference_engine/swish_s.f90                |  4 ++--
 12 files changed, 59 insertions(+), 68 deletions(-)

diff --git a/src/inference_engine/activation_strategy_m.f90 b/src/inference_engine/activation_strategy_m.f90
index ff2bffa4d..80b754882 100644
--- a/src/inference_engine/activation_strategy_m.f90
+++ b/src/inference_engine/activation_strategy_m.f90
@@ -3,7 +3,6 @@
 module activation_strategy_m
 
   ! External dependencies
-  use kind_parameters_m, only : rkind
   use julienne_string_m, only : string_t
   implicit none
 
@@ -14,17 +13,17 @@ module activation_strategy_m
 
   type, abstract :: activation_strategy_t
   contains
-     procedure(activation_i), nopass, deferred :: activation
+     procedure(activation_i), nopass, deferred :: default_real_activation
+     generic :: activation => default_real_activation
      procedure(function_name_i), deferred :: function_name
   end type
 
   abstract interface
 
     elemental function activation_i(x) result(y)
-      import rkind
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
     elemental function function_name_i(self) result(string)
diff --git a/src/inference_engine/differentiable_activation_strategy_m.f90 b/src/inference_engine/differentiable_activation_strategy_m.f90
index 3b373bf39..33b62fc23 100644
--- a/src/inference_engine/differentiable_activation_strategy_m.f90
+++ b/src/inference_engine/differentiable_activation_strategy_m.f90
@@ -9,7 +9,8 @@ module differentiable_activation_strategy_m
 
   type, extends(activation_strategy_t), abstract :: differentiable_activation_strategy_t
   contains
-     procedure(activation_i), nopass, deferred :: activation_derivative
+     procedure(activation_i), nopass, deferred :: default_real_activation_derivative
+     generic :: activation_derivative => default_real_activation_derivative
   end type
 
 end module differentiable_activation_strategy_m
diff --git a/src/inference_engine/gelu_m.f90 b/src/inference_engine/gelu_m.f90
index 8bd8ab06b..f32078ca8 100644
--- a/src/inference_engine/gelu_m.f90
+++ b/src/inference_engine/gelu_m.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 module gelu_m
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use kind_parameters_m, only : rkind
   use julienne_string_m, only : string_t
   implicit none
 
@@ -11,23 +10,23 @@ module gelu_m
 
   type, extends(differentiable_activation_strategy_t) :: gelu_t
   contains
-     procedure, nopass :: activation
-     procedure, nopass :: activation_derivative
+     procedure, nopass :: default_real_activation
+     procedure, nopass :: default_real_activation_derivative
      procedure :: function_name
   end type
 
   interface
 
-    elemental module function activation(x) result(y)
+    elemental module function default_real_activation(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
-    elemental module function activation_derivative(x) result(y)
+    elemental module function default_real_activation_derivative(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
     elemental module function function_name(self) result(string)
diff --git a/src/inference_engine/gelu_s.f90 b/src/inference_engine/gelu_s.f90
index 416fd3699..586955e08 100644
--- a/src/inference_engine/gelu_s.f90
+++ b/src/inference_engine/gelu_s.f90
@@ -1,21 +1,20 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 submodule(gelu_m) gelu_s
-  use kind_parameters_m, only : rkind
   implicit none
 
-  real(rkind), parameter :: pi = 3.141592653589793_rkind
-  real(rkind), parameter :: half = 0.5_rkind, one = 1._rkind, two=2._rkind
-  real(rkind), parameter :: sqrt_2_pi = sqrt(two*pi), sqrt_2 = sqrt(two)
+  real, parameter :: pi = 3.141592653589793
+  real, parameter :: half = 0.5, one = 1., two=2.
+  real, parameter :: sqrt_2_pi = sqrt(two*pi), sqrt_2 = sqrt(two)
 
 contains
 
-    module procedure activation
-      y = half*x*(one  + erf(x/sqrt_2))
+    module procedure default_real_activation
+      y = half*x*(1. + erf(x/sqrt_2))
     end procedure
 
-    module procedure activation_derivative
-      y = half*(one + erf(x/sqrt_2)) + x*exp(-x**2/two)/sqrt_2_pi
+    module procedure default_real_activation_derivative
+      y = half*(1. + erf(x/sqrt_2)) + x*exp(-x**2/two)/sqrt_2_pi
     end procedure
 
     module procedure function_name
diff --git a/src/inference_engine/relu_m.f90 b/src/inference_engine/relu_m.f90
index a303c1280..c0f938785 100644
--- a/src/inference_engine/relu_m.f90
+++ b/src/inference_engine/relu_m.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 module relu_m
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use kind_parameters_m, only : rkind
   use julienne_string_m, only : string_t
   implicit none
 
@@ -11,23 +10,23 @@ module relu_m
 
   type, extends(differentiable_activation_strategy_t) :: relu_t
   contains
-     procedure, nopass :: activation
-     procedure, nopass :: activation_derivative
+     procedure, nopass :: default_real_activation
+     procedure, nopass :: default_real_activation_derivative
      procedure :: function_name
   end type
 
   interface
 
-    elemental module function activation(x) result(y)
+    elemental module function default_real_activation(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
-    elemental module function activation_derivative(x) result(y)
+    elemental module function default_real_activation_derivative(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
     elemental module function function_name(self) result(string)
diff --git a/src/inference_engine/relu_s.f90 b/src/inference_engine/relu_s.f90
index 775b304fe..ec35cc8ab 100644
--- a/src/inference_engine/relu_s.f90
+++ b/src/inference_engine/relu_s.f90
@@ -1,19 +1,16 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 submodule(relu_m) relu_s
-  use kind_parameters_m, only : rkind
   implicit none
 
-  real(rkind), parameter :: zero = 0._rkind, one = 1._rkind
-
 contains
 
-    module procedure activation
-      y = max(zero, x)
+    module procedure default_real_activation
+      y = max(0., x)
     end procedure
 
-    module procedure activation_derivative
-      y = merge(one, zero, x>zero)
+    module procedure default_real_activation_derivative
+      y = merge(1., 0., x>0.)
     end procedure
 
     module procedure function_name
diff --git a/src/inference_engine/sigmoid_m.f90 b/src/inference_engine/sigmoid_m.f90
index 67f3a2fcb..bd0626adc 100644
--- a/src/inference_engine/sigmoid_m.f90
+++ b/src/inference_engine/sigmoid_m.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 module sigmoid_m
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use kind_parameters_m, only : rkind
   use julienne_string_m, only : string_t
   implicit none
 
@@ -11,23 +10,23 @@ module sigmoid_m
 
   type, extends(differentiable_activation_strategy_t) :: sigmoid_t
   contains
-     procedure, nopass :: activation
-     procedure, nopass :: activation_derivative
+     procedure, nopass :: default_real_activation
+     procedure, nopass :: default_real_activation_derivative
      procedure :: function_name
   end type
 
   interface
 
-    elemental module function activation(x) result(y)
+    elemental module function default_real_activation(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
-    elemental module function activation_derivative(x) result(y)
+    elemental module function default_real_activation_derivative(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
     elemental module function function_name(self) result(string)
diff --git a/src/inference_engine/sigmoid_s.f90 b/src/inference_engine/sigmoid_s.f90
index 657e51609..fe7988efc 100644
--- a/src/inference_engine/sigmoid_s.f90
+++ b/src/inference_engine/sigmoid_s.f90
@@ -5,11 +5,11 @@
 
 contains
 
-    module procedure activation
+    module procedure default_real_activation
       y =  1./(1.+exp(-x))
     end procedure
 
-    module procedure activation_derivative
+    module procedure default_real_activation_derivative
       y =  exp(-x)/(1.+exp(-x))**2
     end procedure
 
diff --git a/src/inference_engine/step_m.f90 b/src/inference_engine/step_m.f90
index 50f180f51..c5a1c9a96 100644
--- a/src/inference_engine/step_m.f90
+++ b/src/inference_engine/step_m.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 module step_m
   use activation_strategy_m, only : activation_strategy_t
-  use kind_parameters_m, only : rkind
   use julienne_string_m, only : string_t
   implicit none
 
@@ -11,16 +10,16 @@ module step_m
 
   type, extends(activation_strategy_t) :: step_t
   contains
-     procedure, nopass :: activation
+     procedure, nopass :: default_real_activation
      procedure :: function_name
   end type
 
   interface
 
-    elemental module function activation(x) result(y)
+    elemental module function default_real_activation(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
     elemental module function function_name(self) result(string)
diff --git a/src/inference_engine/step_s.f90 b/src/inference_engine/step_s.f90
index 102824908..643530225 100644
--- a/src/inference_engine/step_s.f90
+++ b/src/inference_engine/step_s.f90
@@ -6,8 +6,8 @@
 
 contains
 
-    module procedure activation
-      y = merge(1._rkind, 0._rkind, x>0._rkind)
+    module procedure default_real_activation
+      y = merge(1., 0., x>0.)
     end procedure
 
     module procedure function_name
diff --git a/src/inference_engine/swish_m.f90 b/src/inference_engine/swish_m.f90
index 993bcbf1d..31120fde9 100644
--- a/src/inference_engine/swish_m.f90
+++ b/src/inference_engine/swish_m.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 module swish_m
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use kind_parameters_m, only : rkind
   use julienne_string_m, only : string_t
   implicit none
 
@@ -11,23 +10,23 @@ module swish_m
 
   type, extends(differentiable_activation_strategy_t) :: swish_t
   contains
-     procedure, nopass :: activation
-     procedure, nopass :: activation_derivative
+     procedure, nopass :: default_real_activation
+     procedure, nopass :: default_real_activation_derivative
      procedure :: function_name
   end type
 
   interface
 
-    elemental module function activation(x) result(y)
+    elemental module function default_real_activation(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
-    elemental module function activation_derivative(x) result(y)
+    elemental module function default_real_activation_derivative(x) result(y)
       implicit none
-      real(rkind), intent(in) :: x
-      real(rkind) y
+      real, intent(in) :: x
+      real y
     end function
 
     elemental module function function_name(self) result(string)
diff --git a/src/inference_engine/swish_s.f90 b/src/inference_engine/swish_s.f90
index 410b3b8ca..0d968798b 100644
--- a/src/inference_engine/swish_s.f90
+++ b/src/inference_engine/swish_s.f90
@@ -6,12 +6,12 @@
 
 contains
 
-    module procedure activation
+    module procedure default_real_activation
       type(sigmoid_t) sigmoid
       y =  x*sigmoid%activation(x)
     end procedure
 
-    module procedure activation_derivative
+    module procedure default_real_activation_derivative
       type(sigmoid_t) sigmoid
       y =  sigmoid%activation(x) + x * sigmoid%activation_derivative(x)
     end procedure

From 216e59b8ba0e1f9baf4c8cb0b214cc5e2bbab62e Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 10:51:24 -0700
Subject: [PATCH 008/105] chore(tensor_map_test): match file name to module

---
 test/{tensor_range_test_m.F90 => tensor_map_test_m.F90} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename test/{tensor_range_test_m.F90 => tensor_map_test_m.F90} (100%)

diff --git a/test/tensor_range_test_m.F90 b/test/tensor_map_test_m.F90
similarity index 100%
rename from test/tensor_range_test_m.F90
rename to test/tensor_map_test_m.F90

From 72b6348921fc648812798278dcff732ce6580090 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 21:03:19 -0700
Subject: [PATCH 009/105] feat(tensor_t):dble-prec constructor/accessor/test

---
 src/inference_engine/tensor_m.f90 | 32 +++++++++++---
 src/inference_engine/tensor_s.f90 | 30 +++++++++----
 test/main.F90                     |  3 ++
 test/tensor_test_m.f90            | 72 +++++++++++++++++++++++++++++++
 4 files changed, 122 insertions(+), 15 deletions(-)
 create mode 100644 test/tensor_test_m.f90

diff --git a/src/inference_engine/tensor_m.f90 b/src/inference_engine/tensor_m.f90
index d1faee13c..bea936ad3 100644
--- a/src/inference_engine/tensor_m.f90
+++ b/src/inference_engine/tensor_m.f90
@@ -1,7 +1,7 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 module tensor_m
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   implicit none
 
   private
@@ -11,34 +11,54 @@ module tensor_m
     integer, kind :: k = default_real 
     real, allocatable, private :: values_(:)
   contains
-    procedure values
-    procedure num_components
+    generic   :: values => default_real_values, double_precision_values
+    procedure, private ::  default_real_values, double_precision_values
+    generic :: num_components => default_real_num_components, double_precision_num_components
+    procedure, private ::        default_real_num_components, double_precision_num_components
   end type
 
   interface tensor_t
 
-    pure module function construct_from_components(values) result(tensor)
+    pure module function construct_default_real(values) result(tensor)
       implicit none
       real, intent(in) :: values(:)
       type(tensor_t) tensor
     end function
 
+    pure module function construct_double_precision(values) result(tensor)
+      implicit none
+      double precision, intent(in) :: values(:)
+      type(tensor_t(double_precision)) tensor
+    end function
+
   end interface
 
   interface
 
-    pure module function values(self) result(tensor_values)
+    pure module function default_real_values(self) result(tensor_values)
       implicit none
       class(tensor_t), intent(in) :: self
       real, allocatable :: tensor_values(:)
     end function
 
-    pure module function num_components(self) result(n)
+    pure module function double_precision_values(self) result(tensor_values)
+      implicit none
+      class(tensor_t(double_precision)), intent(in) :: self
+      double precision, allocatable :: tensor_values(:)
+    end function
+
+    pure module function default_real_num_components(self) result(n)
       implicit none
       class(tensor_t), intent(in) :: self
       integer n
     end function
 
+    pure module function double_precision_num_components(self) result(n)
+      implicit none
+      class(tensor_t(double_precision)), intent(in) :: self
+      integer n
+    end function
+
   end interface
 
 end module tensor_m
diff --git a/src/inference_engine/tensor_s.f90 b/src/inference_engine/tensor_s.f90
index 9760fe767..c0cf3fbb3 100644
--- a/src/inference_engine/tensor_s.f90
+++ b/src/inference_engine/tensor_s.f90
@@ -5,16 +5,28 @@
 
 contains
 
-    module procedure construct_from_components
-      tensor%values_ = values
-    end procedure
+  module procedure construct_default_real
+    tensor%values_ = values
+  end procedure
 
-    module procedure values
-      tensor_values = self%values_
-    end procedure
+  module procedure construct_double_precision
+    tensor%values_ = values
+  end procedure
 
-    module procedure num_components
-      n = size(self%values_)
-    end procedure
+  module procedure default_real_values
+    tensor_values = self%values_
+  end procedure
+
+  module procedure double_precision_values
+    tensor_values = self%values_
+  end procedure
+
+  module procedure default_real_num_components
+    n = size(self%values_)
+  end procedure
+
+  module procedure double_precision_num_components
+    n = size(self%values_)
+  end procedure
 
 end submodule tensor_s
diff --git a/test/main.F90 b/test/main.F90
index ba9fe4f40..1b7d8d4ec 100644
--- a/test/main.F90
+++ b/test/main.F90
@@ -9,6 +9,7 @@ program main
   use network_configuration_test_m, only : network_configuration_test_t
   use training_configuration_test_m, only : training_configuration_test_t
   use tensor_map_test_m, only : tensor_map_test_t
+  use tensor_test_m, only : tensor_test_t
   use julienne_m, only : command_line_t
   implicit none
 
@@ -20,6 +21,7 @@ program main
   type(network_configuration_test_t) network_configuration_test
   type(training_configuration_test_t) training_configuration_test
   type(tensor_map_test_t) tensor_map_test
+  type(tensor_test_t) tensor_test
   real t_start, t_finish
 
   integer :: passes=0, tests=0
@@ -44,6 +46,7 @@ program main
   call metadata_test%report(passes, tests)
   call training_configuration_test%report(passes, tests)
   call tensor_map_test%report(passes, tests)
+  call tensor_test%report(passes, tests)
   call asymmetric_engine_test%report(passes, tests)
   call inference_engine_test%report(passes, tests)
   call trainable_engine_test%report(passes, tests)
diff --git a/test/tensor_test_m.f90 b/test/tensor_test_m.f90
new file mode 100644
index 000000000..f7e321504
--- /dev/null
+++ b/test/tensor_test_m.f90
@@ -0,0 +1,72 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
+module tensor_test_m
+  !! Define inference tests and procedures required for reporting results
+
+  ! External dependencies
+  use kind_parameters_m, only : double_precision 
+  use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t, file_t
+#ifdef __GFORTRAN__
+  use julienne_m, only : test_function_i
+#endif
+
+  ! Internal dependencies
+  use inference_engine_m, only : tensor_t
+
+  implicit none
+
+  private
+  public :: tensor_test_t
+
+  type, extends(test_t) :: tensor_test_t
+  contains
+    procedure, nopass :: subject
+    procedure, nopass :: results
+  end type
+
+contains
+
+  pure function subject() result(specimen)
+    character(len=:), allocatable :: specimen
+    specimen = "An tensor_t object" 
+  end function
+
+  function results() result(test_results)
+    type(test_result_t), allocatable :: test_results(:)
+    type(test_description_t), allocatable :: test_descriptions(:)
+
+#ifndef __GFORTRAN__
+    test_descriptions = [ &
+      test_description_t("double-precision construction and value extraction", double_precision_construction) &
+    ]
+#else
+    procedure(test_function_i), pointer :: double_precision_construction_ptr
+      
+    double_precision_construction_ptr => double_precision_construction
+
+    test_descriptions = [ &
+      test_description_t("double-precision construction and value extraction", double_precision_construction_ptr) &
+    ]
+#endif
+    associate( &
+      substring_in_subject => index(subject(), test_description_substring) /= 0, &
+      substring_in_description => test_descriptions%contains_text(string_t(test_description_substring)) &
+    )
+      test_descriptions = pack(test_descriptions, substring_in_subject .or. substring_in_description)
+    end associate
+    test_results = test_descriptions%run()
+  end function
+
+  function double_precision_construction() result(test_passes)
+    logical test_passes
+    type(tensor_t(double_precision)) tensor
+    double precision, parameter :: tolerance = 1.0D-12
+    double precision, parameter :: values(*) = [1.000000000001D-12]
+
+    tensor = tensor_t(values) ! this will fail to compile if no double_precision constructor exists and the 
+    associate(tensor_values => tensor%values())
+      test_passes = kind(tensor_values) == double_precision .and. all(abs(tensor_values - values) < tolerance)
+    end associate
+  end function
+
+end module tensor_test_m

From dd0cb91995afaab9c8ab019d448b8b1ea57a27b6 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 21:19:07 -0700
Subject: [PATCH 010/105] feat(step): add double-precision activation

---
 src/inference_engine/step_m.f90 | 8 +++++++-
 src/inference_engine/step_s.f90 | 4 ++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/inference_engine/step_m.f90 b/src/inference_engine/step_m.f90
index c5a1c9a96..90abab9cb 100644
--- a/src/inference_engine/step_m.f90
+++ b/src/inference_engine/step_m.f90
@@ -10,7 +10,7 @@ module step_m
 
   type, extends(activation_strategy_t) :: step_t
   contains
-     procedure, nopass :: default_real_activation
+     procedure, nopass :: default_real_activation, double_precision_activation
      procedure :: function_name
   end type
 
@@ -22,6 +22,12 @@ elemental module function default_real_activation(x) result(y)
       real y
     end function
 
+    elemental module function double_precision_activation(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function function_name(self) result(string)
       implicit none
       class(step_t), intent(in) :: self
diff --git a/src/inference_engine/step_s.f90 b/src/inference_engine/step_s.f90
index 643530225..380dbf617 100644
--- a/src/inference_engine/step_s.f90
+++ b/src/inference_engine/step_s.f90
@@ -10,6 +10,10 @@
       y = merge(1., 0., x>0.)
     end procedure
 
+    module procedure double_precision_activation
+      y = merge(1.D0, 0.D0, x>0.D0)
+    end procedure
+
     module procedure function_name
       string = string_t("step")
     end procedure

From 6ceabf0d0ecad4563113dd54da7e7eecdc794a7b Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 21:58:13 -0700
Subject: [PATCH 011/105] feat(sigmoid):double-precision activation/derivat

---
 src/inference_engine/sigmoid_m.f90 | 16 ++++++++++++++--
 src/inference_engine/sigmoid_s.f90 |  8 ++++++++
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/sigmoid_m.f90 b/src/inference_engine/sigmoid_m.f90
index bd0626adc..64160bc73 100644
--- a/src/inference_engine/sigmoid_m.f90
+++ b/src/inference_engine/sigmoid_m.f90
@@ -10,8 +10,8 @@ module sigmoid_m
 
   type, extends(differentiable_activation_strategy_t) :: sigmoid_t
   contains
-     procedure, nopass :: default_real_activation
-     procedure, nopass :: default_real_activation_derivative
+     procedure, nopass :: default_real_activation,            double_precision_activation
+     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
      procedure :: function_name
   end type
 
@@ -23,12 +23,24 @@ elemental module function default_real_activation(x) result(y)
       real y
     end function
 
+    elemental module function double_precision_activation(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function default_real_activation_derivative(x) result(y)
       implicit none
       real, intent(in) :: x
       real y
     end function
 
+    elemental module function double_precision_activation_derivative(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function function_name(self) result(string)
       implicit none
       class(sigmoid_t), intent(in) :: self
diff --git a/src/inference_engine/sigmoid_s.f90 b/src/inference_engine/sigmoid_s.f90
index fe7988efc..ebbb4efbe 100644
--- a/src/inference_engine/sigmoid_s.f90
+++ b/src/inference_engine/sigmoid_s.f90
@@ -9,10 +9,18 @@
       y =  1./(1.+exp(-x))
     end procedure
 
+    module procedure double_precision_activation
+      y =  1./(1.+exp(-x))
+    end procedure
+
     module procedure default_real_activation_derivative
       y =  exp(-x)/(1.+exp(-x))**2
     end procedure
 
+    module procedure double_precision_activation_derivative
+      y =  exp(-x)/(1.+exp(-x))**2
+    end procedure
+
     module procedure function_name
       string = string_t("sigmoid")
     end procedure

From 0d69cce2aab65ddc297544f805c1e42768d6de96 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 22:05:27 -0700
Subject: [PATCH 012/105] feat(relu): add double-precision activation/deriv

---
 src/inference_engine/relu_m.f90 | 16 ++++++++++++++--
 src/inference_engine/relu_s.f90 |  8 ++++++++
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/relu_m.f90 b/src/inference_engine/relu_m.f90
index c0f938785..d537dee01 100644
--- a/src/inference_engine/relu_m.f90
+++ b/src/inference_engine/relu_m.f90
@@ -10,8 +10,8 @@ module relu_m
 
   type, extends(differentiable_activation_strategy_t) :: relu_t
   contains
-     procedure, nopass :: default_real_activation
-     procedure, nopass :: default_real_activation_derivative
+     procedure, nopass :: default_real_activation,            double_precision_activation
+     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
      procedure :: function_name
   end type
 
@@ -23,12 +23,24 @@ elemental module function default_real_activation(x) result(y)
       real y
     end function
 
+    elemental module function double_precision_activation(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function default_real_activation_derivative(x) result(y)
       implicit none
       real, intent(in) :: x
       real y
     end function
 
+    elemental module function double_precision_activation_derivative(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function function_name(self) result(string)
       implicit none
       class(relu_t), intent(in) :: self
diff --git a/src/inference_engine/relu_s.f90 b/src/inference_engine/relu_s.f90
index ec35cc8ab..29c3980e5 100644
--- a/src/inference_engine/relu_s.f90
+++ b/src/inference_engine/relu_s.f90
@@ -9,10 +9,18 @@
       y = max(0., x)
     end procedure
 
+    module procedure double_precision_activation
+      y = max(0.D0, x)
+    end procedure
+
     module procedure default_real_activation_derivative
       y = merge(1., 0., x>0.)
     end procedure
 
+    module procedure double_precision_activation_derivative
+      y = merge(1.D0, 0.D0, x>0.D0)
+    end procedure
+
     module procedure function_name
       string = string_t("relu")
     end procedure

From 59547e7f3d79177ddc804a604714e7ba1a132f19 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 22:41:12 -0700
Subject: [PATCH 013/105] feat(gelu): add double-prec activation/derivatie

---
 src/inference_engine/gelu_m.f90 | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/gelu_m.f90 b/src/inference_engine/gelu_m.f90
index f32078ca8..51a872c46 100644
--- a/src/inference_engine/gelu_m.f90
+++ b/src/inference_engine/gelu_m.f90
@@ -10,8 +10,8 @@ module gelu_m
 
   type, extends(differentiable_activation_strategy_t) :: gelu_t
   contains
-     procedure, nopass :: default_real_activation
-     procedure, nopass :: default_real_activation_derivative
+     procedure, nopass :: default_real_activation,            double_precision_activation
+     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
      procedure :: function_name
   end type
 
@@ -23,12 +23,24 @@ elemental module function default_real_activation(x) result(y)
       real y
     end function
 
+    elemental module function double_precision_activation(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function default_real_activation_derivative(x) result(y)
       implicit none
       real, intent(in) :: x
       real y
     end function
 
+    elemental module function double_precision_activation_derivative(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function function_name(self) result(string)
       implicit none
       class(gelu_t), intent(in) :: self

From fc54a18de2ac829987f2089957d2878a2f9d2b71 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 23:17:26 -0700
Subject: [PATCH 014/105] feat: finish double-prec activation/derivs

---
 src/inference_engine/activation_strategy_m.f90 | 16 ++++++++++++----
 .../differentiable_activation_strategy_m.f90   |  7 ++++---
 src/inference_engine/gelu_s.f90                | 16 ++++++++++++++--
 src/inference_engine/swish_m.f90               | 18 +++++++++++++++---
 src/inference_engine/swish_s.f90               | 10 ++++++++++
 5 files changed, 55 insertions(+), 12 deletions(-)

diff --git a/src/inference_engine/activation_strategy_m.f90 b/src/inference_engine/activation_strategy_m.f90
index 80b754882..8bca0a026 100644
--- a/src/inference_engine/activation_strategy_m.f90
+++ b/src/inference_engine/activation_strategy_m.f90
@@ -8,24 +8,32 @@ module activation_strategy_m
 
   private
   public :: activation_strategy_t
-  public :: activation_i
+  public :: default_real_activation_i
+  public :: double_precision_activation_i
   public :: function_name_i
 
   type, abstract :: activation_strategy_t
   contains
-     procedure(activation_i), nopass, deferred :: default_real_activation
-     generic :: activation => default_real_activation
+     procedure(default_real_activation_i), nopass, deferred :: default_real_activation
+     procedure(double_precision_activation_i), nopass, deferred :: double_precision_activation
+     generic :: activation => default_real_activation, double_precision_activation
      procedure(function_name_i), deferred :: function_name
   end type
 
   abstract interface
 
-    elemental function activation_i(x) result(y)
+    elemental function default_real_activation_i(x) result(y)
       implicit none
       real, intent(in) :: x
       real y
     end function
 
+    elemental function double_precision_activation_i(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental function function_name_i(self) result(string)
       import string_t, activation_strategy_t
       implicit none
diff --git a/src/inference_engine/differentiable_activation_strategy_m.f90 b/src/inference_engine/differentiable_activation_strategy_m.f90
index 33b62fc23..b1e226472 100644
--- a/src/inference_engine/differentiable_activation_strategy_m.f90
+++ b/src/inference_engine/differentiable_activation_strategy_m.f90
@@ -1,7 +1,7 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 module differentiable_activation_strategy_m
-  use activation_strategy_m, only : activation_strategy_t, activation_i
+  use activation_strategy_m, only : activation_strategy_t, default_real_activation_i, double_precision_activation_i
   implicit none
 
   private
@@ -9,8 +9,9 @@ module differentiable_activation_strategy_m
 
   type, extends(activation_strategy_t), abstract :: differentiable_activation_strategy_t
   contains
-     procedure(activation_i), nopass, deferred :: default_real_activation_derivative
-     generic :: activation_derivative => default_real_activation_derivative
+     procedure(default_real_activation_i), nopass, deferred :: default_real_activation_derivative
+     procedure(double_precision_activation_i), nopass, deferred :: double_precision_activation_derivative
+     generic :: activation_derivative => default_real_activation_derivative, double_precision_activation_derivative
   end type
 
 end module differentiable_activation_strategy_m
diff --git a/src/inference_engine/gelu_s.f90 b/src/inference_engine/gelu_s.f90
index 586955e08..c684a1721 100644
--- a/src/inference_engine/gelu_s.f90
+++ b/src/inference_engine/gelu_s.f90
@@ -4,8 +4,12 @@
   implicit none
 
   real, parameter :: pi = 3.141592653589793
-  real, parameter :: half = 0.5, one = 1., two=2.
-  real, parameter :: sqrt_2_pi = sqrt(two*pi), sqrt_2 = sqrt(two)
+  real, parameter :: half = 0.5, two = 2.D0
+  real, parameter :: sqrt_2_pi = sqrt(2*pi), sqrt_2 = sqrt(2.)
+
+  real, parameter :: pi_dp = 3.141592653589793D0
+  real, parameter :: half_dp = 0.5D0, two_dp = 2.D0
+  real, parameter :: sqrt_2_pi_dp = sqrt(two_dp*pi_dp), sqrt_2_dp = sqrt(2.D0)
 
 contains
 
@@ -13,10 +17,18 @@
       y = half*x*(1. + erf(x/sqrt_2))
     end procedure
 
+    module procedure double_precision_activation
+      y = half_dp*x*(1.D0 + erf(x/sqrt_2_dp))
+    end procedure
+
     module procedure default_real_activation_derivative
       y = half*(1. + erf(x/sqrt_2)) + x*exp(-x**2/two)/sqrt_2_pi
     end procedure
 
+    module procedure double_precision_activation_derivative
+      y = half_dp*(1.D0 + erf(x/sqrt_2_dp)) + x*exp(-x**2/2.D0)/sqrt_2_pi_dp
+    end procedure
+
     module procedure function_name
       string = string_t("gelu")
     end procedure
diff --git a/src/inference_engine/swish_m.f90 b/src/inference_engine/swish_m.f90
index 31120fde9..476764004 100644
--- a/src/inference_engine/swish_m.f90
+++ b/src/inference_engine/swish_m.f90
@@ -10,8 +10,8 @@ module swish_m
 
   type, extends(differentiable_activation_strategy_t) :: swish_t
   contains
-     procedure, nopass :: default_real_activation
-     procedure, nopass :: default_real_activation_derivative
+     procedure, nopass :: default_real_activation           , double_precision_activation
+     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
      procedure :: function_name
   end type
 
@@ -23,12 +23,24 @@ elemental module function default_real_activation(x) result(y)
       real y
     end function
 
+    elemental module function double_precision_activation(x) result(y)
+      implicit none
+      double precision, intent(in) :: x
+      double precision y
+    end function
+
     elemental module function default_real_activation_derivative(x) result(y)
       implicit none
-      real, intent(in) :: x
+      real , intent(in) :: x
       real y
     end function
 
+    elemental module function double_precision_activation_derivative(x) result(y)
+      implicit none
+      double precision , intent(in) :: x
+      double precision y
+    end function
+
     elemental module function function_name(self) result(string)
       implicit none
       class(swish_t), intent(in) :: self
diff --git a/src/inference_engine/swish_s.f90 b/src/inference_engine/swish_s.f90
index 0d968798b..d85f020cf 100644
--- a/src/inference_engine/swish_s.f90
+++ b/src/inference_engine/swish_s.f90
@@ -11,11 +11,21 @@
       y =  x*sigmoid%activation(x)
     end procedure
 
+    module procedure double_precision_activation
+      type(sigmoid_t) sigmoid
+      y =  x*sigmoid%activation(x)
+    end procedure
+
     module procedure default_real_activation_derivative
       type(sigmoid_t) sigmoid
       y =  sigmoid%activation(x) + x * sigmoid%activation_derivative(x)
     end procedure
 
+    module procedure double_precision_activation_derivative
+      type(sigmoid_t) sigmoid
+      y =  sigmoid%activation(x) + x * sigmoid%activation_derivative(x)
+    end procedure
+
     module procedure function_name
       string = string_t("swish")
     end procedure

From 84d21f710641c655fd39aadd875a15563db47b3d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 23:35:55 -0700
Subject: [PATCH 015/105] refac(tensor_map):mk type-bound proc names generic

---
 src/inference_engine/tensor_map_m.f90 | 27 ++++++++++++++++++---------
 src/inference_engine/tensor_map_s.f90 | 21 ++++++++++++++-------
 2 files changed, 32 insertions(+), 16 deletions(-)

diff --git a/src/inference_engine/tensor_map_m.f90 b/src/inference_engine/tensor_map_m.f90
index 247e4d86e..49660c613 100644
--- a/src/inference_engine/tensor_map_m.f90
+++ b/src/inference_engine/tensor_map_m.f90
@@ -3,7 +3,7 @@
 module tensor_map_m
   use tensor_m, only : tensor_t
   use julienne_m, only : string_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   implicit none
   
   private
@@ -14,23 +14,32 @@ module tensor_map_m
     character(len=:),      allocatable, private :: layer_
     real(k), dimension(:), allocatable, private :: intercept_, slope_
   contains
-    procedure map_to_training_range
-    procedure map_from_training_range
+    generic :: map_to_training_range => default_real_map_to_training_range
+    procedure, private :: default_real_map_to_training_range
+    generic :: map_from_training_range => default_real_map_from_training_range
+    procedure, private :: default_real_map_from_training_range
     procedure to_json
-    generic :: operator(==) => equals
-    procedure, private :: equals
+    generic :: operator(==) => default_real_equals
+    procedure, private :: default_real_equals
   end type
 
 
   interface tensor_map_t
 
-    pure module function from_component_ranges(layer, minima, maxima) result(tensor_map)
+    pure module function construct_default_real(layer, minima, maxima) result(tensor_map)
       implicit none
       character(len=*), intent(in) :: layer
       real, dimension(:), intent(in) :: minima, maxima
       type(tensor_map_t) tensor_map
     end function
 
+    pure module function construct_double_precision(layer, minima, maxima) result(tensor_map)
+      implicit none
+      character(len=*), intent(in) :: layer
+      double precision, dimension(:), intent(in) :: minima, maxima
+      type(tensor_map_t(double_precision)) tensor_map
+    end function
+
     module function from_json(lines) result(tensor_map)
       implicit none
       type(string_t), intent(in) :: lines(:)
@@ -41,14 +50,14 @@ module function from_json(lines) result(tensor_map)
 
   interface
 
-    elemental module function map_to_training_range(self, tensor) result(normalized_tensor)
+    elemental module function default_real_map_to_training_range(self, tensor) result(normalized_tensor)
       implicit none
       class(tensor_map_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
       type(tensor_t) normalized_tensor
     end function
 
-    elemental module function map_from_training_range(self, tensor) result(unnormalized_tensor)
+    elemental module function default_real_map_from_training_range(self, tensor) result(unnormalized_tensor)
       implicit none
       class(tensor_map_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
@@ -61,7 +70,7 @@ pure module function to_json(self) result(lines)
       type(string_t), allocatable :: lines(:)
     end function
 
-    elemental module function equals(lhs, rhs) result(lhs_equals_rhs)
+    elemental module function default_real_equals(lhs, rhs) result(lhs_equals_rhs)
       implicit none
       class(tensor_map_t), intent(in) :: lhs, rhs
       logical lhs_equals_rhs
diff --git a/src/inference_engine/tensor_map_s.f90 b/src/inference_engine/tensor_map_s.f90
index dd52ab364..e02fc6675 100644
--- a/src/inference_engine/tensor_map_s.f90
+++ b/src/inference_engine/tensor_map_s.f90
@@ -3,13 +3,20 @@
 submodule(tensor_map_m) tensor_map_s
   use assert_m, only : assert
   use julienne_m, only : separated_values
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real
   implicit none
   
 contains
 
-  module procedure from_component_ranges
-    call assert(size(minima)==size(maxima),"tensor_map_s(from_components): size(minima)==size(maxima)")
+  module procedure construct_default_real
+    call assert(size(minima)==size(maxima),"tensor_map_s(construct_default_real): size(minima)==size(maxima)")
+    tensor_map%layer_ = layer
+    tensor_map%intercept_ = minima
+    tensor_map%slope_ = maxima - minima
+  end procedure
+
+  module procedure construct_double_precision
+    call assert(size(minima)==size(maxima),"tensor_map_s(construct_double_precision): size(minima)==size(maxima)")
     tensor_map%layer_ = layer
     tensor_map%intercept_ = minima
     tensor_map%slope_ = maxima - minima
@@ -34,7 +41,7 @@
     call assert(tensor_map_key_found, "tensor_map_s(from_json): 'tensor_map' key found")
   end procedure
 
-  module procedure equals
+  module procedure default_real_equals
     real, parameter :: tolerance = 1.E-08
 
     call assert(allocated(lhs%layer_) .and. allocated(rhs%layer_), "tensor_map_s(equals): allocated layer_ components")
@@ -57,7 +64,7 @@
     call assert(allocated(self%layer_), "tensor_map_s(to_json): allocated layer_")
     call assert(allocated(self%intercept_) .and. allocated(self%slope_), "tensor_map_s(to_json): allocated intercept_/slope_")
 
-    csv_format = separated_values(separator=",", mold=[real(rkind)::])
+    csv_format = separated_values(separator=",", mold=[real(default_real)::])
     allocate(character(len=size(self%intercept_)*(characters_per_value+1)-1)::intercept_string)
     allocate(character(len=size(self%slope_)*(characters_per_value+1)-1)::slope_string)
     write(intercept_string, fmt = csv_format) self%intercept_
@@ -75,7 +82,7 @@
     end block
   end procedure
 
-  module procedure map_to_training_range
+  module procedure default_real_map_to_training_range
     associate(tensor_values => tensor%values())
       associate(normalized_values => (tensor_values - self%intercept_)/self%slope_)
         normalized_tensor = tensor_t(normalized_values)
@@ -83,7 +90,7 @@
     end associate
   end procedure
 
-  module procedure map_from_training_range
+  module procedure default_real_map_from_training_range
     associate(tensor_values => tensor%values())
       associate(unnormalized_values => self%intercept_ + tensor_values*self%slope_)
         unnormalized_tensor = tensor_t(unnormalized_values)

From d878713ede301c1c5d3473c9abff117252ec759b Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 28 Aug 2024 23:56:26 -0700
Subject: [PATCH 016/105] feat(tensor_map): add double-precision procedures

This commit adds double-precision versions of all tensor_map_t
type-bound and constructor procedures except the
default_real_from_json constructor function.
---
 src/inference_engine/tensor_map_m.f90 | 43 ++++++++++---
 src/inference_engine/tensor_map_s.f90 | 89 ++++++++++++++++++++++++---
 2 files changed, 116 insertions(+), 16 deletions(-)

diff --git a/src/inference_engine/tensor_map_m.f90 b/src/inference_engine/tensor_map_m.f90
index 49660c613..792327f00 100644
--- a/src/inference_engine/tensor_map_m.f90
+++ b/src/inference_engine/tensor_map_m.f90
@@ -14,13 +14,14 @@ module tensor_map_m
     character(len=:),      allocatable, private :: layer_
     real(k), dimension(:), allocatable, private :: intercept_, slope_
   contains
-    generic :: map_to_training_range => default_real_map_to_training_range
-    procedure, private :: default_real_map_to_training_range
-    generic :: map_from_training_range => default_real_map_from_training_range
-    procedure, private :: default_real_map_from_training_range
-    procedure to_json
-    generic :: operator(==) => default_real_equals
-    procedure, private :: default_real_equals
+    generic :: map_to_training_range => default_real_map_to_training_range, double_precision_map_to_training_range
+    procedure, private ::               default_real_map_to_training_range, double_precision_map_to_training_range
+    generic :: map_from_training_range => default_real_map_from_training_range, double_precision_map_from_training_range
+    procedure, private ::                 default_real_map_from_training_range, double_precision_map_from_training_range
+    generic :: to_json => default_real_to_json, double_precision_to_json
+    procedure, private :: default_real_to_json, double_precision_to_json
+    generic :: operator(==) => default_real_equals, double_precision_equals
+    procedure, private ::      default_real_equals, double_precision_equals
   end type
 
 
@@ -57,6 +58,13 @@ elemental module function default_real_map_to_training_range(self, tensor) resul
       type(tensor_t) normalized_tensor
     end function
 
+    elemental module function double_precision_map_to_training_range(self, tensor) result(normalized_tensor)
+      implicit none
+      class(tensor_map_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: tensor
+      type(tensor_t(double_precision)) normalized_tensor
+    end function
+
     elemental module function default_real_map_from_training_range(self, tensor) result(unnormalized_tensor)
       implicit none
       class(tensor_map_t), intent(in) :: self
@@ -64,18 +72,37 @@ elemental module function default_real_map_from_training_range(self, tensor) res
       type(tensor_t) unnormalized_tensor
     end function
 
-    pure module function to_json(self) result(lines)
+    elemental module function double_precision_map_from_training_range(self, tensor) result(unnormalized_tensor)
+      implicit none
+      class(tensor_map_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: tensor
+      type(tensor_t(double_precision)) unnormalized_tensor
+    end function
+
+    pure module function default_real_to_json(self) result(lines)
       implicit none
       class(tensor_map_t), intent(in) :: self
       type(string_t), allocatable :: lines(:)
     end function
 
+    pure module function double_precision_to_json(self) result(lines)
+      implicit none
+      class(tensor_map_t(double_precision)), intent(in) :: self
+      type(string_t), allocatable :: lines(:)
+    end function
+
     elemental module function default_real_equals(lhs, rhs) result(lhs_equals_rhs)
       implicit none
       class(tensor_map_t), intent(in) :: lhs, rhs
       logical lhs_equals_rhs
     end function
 
+    elemental module function double_precision_equals(lhs, rhs) result(lhs_equals_rhs)
+      implicit none
+      class(tensor_map_t(double_precision)), intent(in) :: lhs, rhs
+      logical lhs_equals_rhs
+    end function
+
   end interface
 
 end module tensor_map_m
diff --git a/src/inference_engine/tensor_map_s.f90 b/src/inference_engine/tensor_map_s.f90
index e02fc6675..92ea17daa 100644
--- a/src/inference_engine/tensor_map_s.f90
+++ b/src/inference_engine/tensor_map_s.f90
@@ -44,11 +44,16 @@
   module procedure default_real_equals
     real, parameter :: tolerance = 1.E-08
 
-    call assert(allocated(lhs%layer_) .and. allocated(rhs%layer_), "tensor_map_s(equals): allocated layer_ components")
-    call assert(allocated(lhs%intercept_) .and. allocated(rhs%intercept_), "tensor_map_s(equals): allocated intercept_ components)")
-    call assert(allocated(lhs%slope_) .and.  allocated(rhs%slope_), "tensor_map_s(equals): allocated slope_ components)")
-    call assert(size(lhs%intercept_) == size(rhs%intercept_), "tensor_map_s(equals): size(lhs%intercept_) == size(rhs%intercept_)")
-    call assert(size(lhs%slope_) == size(rhs%slope_), "tensor_map_s(equals): size(lhs%slope_) == size(rhs%slope_)")
+    call assert(allocated(lhs%layer_) .and. allocated(rhs%layer_), &
+      "tensor_map_s(default_real_equals): allocated layer_ components")
+    call assert(allocated(lhs%intercept_) .and. allocated(rhs%intercept_), &
+      "tensor_map_s(default_real_equals): allocated intercept_ components)")
+    call assert(allocated(lhs%slope_) .and.  allocated(rhs%slope_), &
+      "tensor_map_s(default_real_equals): allocated slope_ components)")
+    call assert(size(lhs%intercept_) == size(rhs%intercept_), &
+       "tensor_map_s(default_real_equals): size(lhs%intercept_) == size(rhs%intercept_)")
+    call assert(size(lhs%slope_) == size(rhs%slope_), &
+       "tensor_map_s(default_real_equals): size(lhs%slope_) == size(rhs%slope_)")
 
     lhs_equals_rhs = &
       lhs%layer_ == rhs%layer_ .and. &
@@ -56,19 +61,71 @@
       all(abs(lhs%slope_ - rhs%slope_) <= tolerance)
   end procedure 
 
-  module procedure to_json
+  module procedure double_precision_equals
+    double precision, parameter :: tolerance = 1.D-015
+
+    call assert(allocated(lhs%layer_) .and. allocated(rhs%layer_), &
+      "tensor_map_s(double_precision_equals): allocated layer_ components")
+    call assert(allocated(lhs%intercept_) .and. allocated(rhs%intercept_), &
+      "tensor_map_s(double_precision_equals): allocated intercept_ components)")
+    call assert(allocated(lhs%slope_) .and.  allocated(rhs%slope_), &
+      "tensor_map_s(double_precision_equals): allocated slope_ components)")
+    call assert(size(lhs%intercept_) == size(rhs%intercept_), &
+      "tensor_map_s(double_precision_equals): size(lhs%intercept_) == size(rhs%intercept_)")
+    call assert(size(lhs%slope_) == size(rhs%slope_), &
+      "tensor_map_s(double_precision_equals): size(lhs%slope_) == size(rhs%slope_)")
+
+    lhs_equals_rhs = &
+      lhs%layer_ == rhs%layer_ .and. &
+      all(abs(lhs%intercept_ - rhs%intercept_) <= tolerance).and. &
+      all(abs(lhs%slope_ - rhs%slope_) <= tolerance)
+  end procedure 
+
+  module procedure default_real_to_json
     integer, parameter :: characters_per_value=17
     character(len=*), parameter :: indent = repeat(" ",ncopies=4)
     character(len=:), allocatable :: csv_format, intercept_string, slope_string
 
-    call assert(allocated(self%layer_), "tensor_map_s(to_json): allocated layer_")
-    call assert(allocated(self%intercept_) .and. allocated(self%slope_), "tensor_map_s(to_json): allocated intercept_/slope_")
+    call assert(allocated(self%layer_), &
+      "tensor_map_s(default_real_to_json): allocated layer_")
+    call assert(allocated(self%intercept_) .and. allocated(self%slope_), &
+      "tensor_map_s(default_real_to_json): allocated intercept_/slope_")
 
     csv_format = separated_values(separator=",", mold=[real(default_real)::])
     allocate(character(len=size(self%intercept_)*(characters_per_value+1)-1)::intercept_string)
     allocate(character(len=size(self%slope_)*(characters_per_value+1)-1)::slope_string)
     write(intercept_string, fmt = csv_format) self%intercept_
     write(slope_string, fmt = csv_format) self%slope_
+
+    block 
+      character(len=:), allocatable :: layer
+      layer = trim(adjustl(self%layer_))
+      lines = [ &
+        string_t(indent // '"'//layer//'_map": {'), &
+        string_t(indent // '  "layer": "' // layer // '",'), &
+        string_t(indent // '  "intercept": [' // trim(adjustl(intercept_string)) // '],'), & 
+        string_t(indent // '  "slope": [' // trim(adjustl(slope_string)) // ']'), &
+        string_t(indent // '}') &
+      ]
+    end block
+  end procedure
+
+  module procedure double_precision_to_json
+    integer, parameter :: characters_per_value=34
+    character(len=*), parameter :: indent = repeat(" ",ncopies=4)
+    character(len=:), allocatable :: csv_format, intercept_string, slope_string
+
+    call assert(allocated(self%layer_), &
+      "tensor_map_s(default_real_to_json): allocated layer_")
+    call assert(allocated(self%intercept_) .and. allocated(self%slope_), &
+      "tensor_map_s(default_real_to_json): allocated intercept_/slope_")
+
+    csv_format = separated_values(separator=",", mold=[double precision::])
+    allocate(character(len=size(self%intercept_)*(characters_per_value+1)-1)::intercept_string)
+    allocate(character(len=size(self%slope_)*(characters_per_value+1)-1)::slope_string)
+    write(intercept_string, fmt = csv_format) self%intercept_
+    write(slope_string, fmt = csv_format) self%slope_
+
     block 
       character(len=:), allocatable :: layer
       layer = trim(adjustl(self%layer_))
@@ -90,6 +147,14 @@
     end associate
   end procedure
 
+  module procedure double_precision_map_to_training_range
+    associate(tensor_values => tensor%values())
+      associate(normalized_values => (tensor_values - self%intercept_)/self%slope_)
+        normalized_tensor = tensor_t(normalized_values)
+      end associate
+    end associate
+  end procedure
+
   module procedure default_real_map_from_training_range
     associate(tensor_values => tensor%values())
       associate(unnormalized_values => self%intercept_ + tensor_values*self%slope_)
@@ -98,4 +163,12 @@
     end associate
   end procedure
 
+  module procedure double_precision_map_from_training_range
+    associate(tensor_values => tensor%values())
+      associate(unnormalized_values => self%intercept_ + tensor_values*self%slope_)
+        unnormalized_tensor = tensor_t(unnormalized_values)
+      end associate
+    end associate
+  end procedure
+
 end submodule tensor_map_s

From d011f0dd132a8a37324864cfac5e73dde5116630 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 29 Aug 2024 00:13:25 -0700
Subject: [PATCH 017/105] refac(neuron): mk procedure names generic bindings

---
 src/inference_engine/neuron_m.f90 | 30 ++++++++++++++++++------------
 src/inference_engine/neuron_s.f90 | 12 ++++++------
 2 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/src/inference_engine/neuron_m.f90 b/src/inference_engine/neuron_m.f90
index 45dcc652b..4042bb6f6 100644
--- a/src/inference_engine/neuron_m.f90
+++ b/src/inference_engine/neuron_m.f90
@@ -15,12 +15,18 @@ module neuron_m
     real(k),                     private :: bias_
     type(neuron_t(k)), allocatable, private :: next
   contains
-    procedure :: to_json
-    procedure :: weights
-    procedure :: bias
-    procedure :: next_allocated
-    procedure :: next_pointer
-    procedure :: num_inputs
+    generic :: to_json => default_real_to_json
+    procedure, private :: default_real_to_json
+    generic :: weights => default_real_weights
+    procedure, private :: default_real_weights
+    generic :: bias =>    default_real_bias
+    procedure, private :: default_real_bias
+    generic :: next_allocated => default_real_next_allocated
+    procedure, private ::        default_real_next_allocated
+    generic :: next_pointer => default_real_next_pointer
+    procedure, private ::      default_real_next_pointer
+    generic :: num_inputs => default_real_num_inputs
+    procedure, private ::    default_real_num_inputs
   end type
 
   interface neuron_t
@@ -44,37 +50,37 @@ pure module function from_components(weights, bias) result(neuron)
 
   interface
 
-    pure module function to_json(self) result(lines)
+    pure module function default_real_to_json(self) result(lines)
       implicit none
       class(neuron_t), intent(in) :: self
       type(string_t), allocatable :: lines(:)
     end function
 
-    module function weights(self) result(my_weights)
+    module function default_real_weights(self) result(my_weights)
       implicit none
       class(neuron_t), intent(in) :: self
       real, allocatable :: my_weights(:)
     end function
 
-    module function bias(self) result(my_bias)
+    module function default_real_bias(self) result(my_bias)
       implicit none
       class(neuron_t), intent(in) :: self
       real my_bias
     end function
 
-    module function next_allocated(self) result(next_is_allocated)
+    module function default_real_next_allocated(self) result(next_is_allocated)
       implicit none
       class(neuron_t), intent(in) :: self
       logical next_is_allocated
     end function
 
-    module function next_pointer(self) result(next_ptr)
+    module function default_real_next_pointer(self) result(next_ptr)
       implicit none
       class(neuron_t), intent(in), target :: self
       type(neuron_t), pointer :: next_ptr
     end function
 
-    pure module function num_inputs(self) result(size_weights)
+    pure module function default_real_num_inputs(self) result(size_weights)
       implicit none
       class(neuron_t), intent(in) :: self
       integer size_weights
diff --git a/src/inference_engine/neuron_s.f90 b/src/inference_engine/neuron_s.f90
index eb195a38f..2fd1f3f0c 100644
--- a/src/inference_engine/neuron_s.f90
+++ b/src/inference_engine/neuron_s.f90
@@ -7,7 +7,7 @@
 
 contains
 
-  module procedure to_json
+  module procedure default_real_to_json
     integer, parameter :: characters_per_value=17
     character(len=*), parameter :: indent = repeat(" ",ncopies=12)
     character(len=:), allocatable :: csv_format, weights_string, bias_string
@@ -67,19 +67,19 @@
     neuron%bias_ = bias
   end procedure
 
-  module procedure weights
+  module procedure default_real_weights
     my_weights = self%weights_
   end procedure
 
-  module procedure bias
+  module procedure default_real_bias
     my_bias = self%bias_
   end procedure
 
-  module procedure next_allocated
+  module procedure default_real_next_allocated
     next_is_allocated = allocated(self%next)
   end procedure
 
-  module procedure next_pointer
+  module procedure default_real_next_pointer
     if (allocated(self%next)) then
       next_ptr => self%next
     else
@@ -87,7 +87,7 @@
     end if
   end procedure
 
-  module procedure num_inputs
+  module procedure default_real_num_inputs
     size_weights = size(self%weights_)
   end procedure
 

From c496147cfc9524a5ce54ddc88edf65cb8ac38d98 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 29 Aug 2024 00:20:44 -0700
Subject: [PATCH 018/105] refac(layer):make procedure names generic bindings

---
 src/inference_engine/layer_m.f90 | 37 +++++++++++++++++++-------------
 src/inference_engine/layer_s.f90 | 20 ++++++++---------
 2 files changed, 32 insertions(+), 25 deletions(-)

diff --git a/src/inference_engine/layer_m.f90 b/src/inference_engine/layer_m.f90
index 788cf2f63..7842fd834 100644
--- a/src/inference_engine/layer_m.f90
+++ b/src/inference_engine/layer_m.f90
@@ -17,18 +17,25 @@ module layer_m
     type(neuron_t(k)),             private :: neuron !! linked list of this layer's neurons 
     type(layer_t(k)), allocatable, private :: next !! next layer
   contains
-    procedure :: inference_engine
-    procedure :: count_layers
-    procedure :: count_neurons
-    procedure :: count_inputs
-    procedure :: neurons_per_layer
-    procedure :: next_allocated
-    procedure :: next_pointer
+    generic :: inference_engine => default_real_inference_engine 
+    procedure, private ::          default_real_inference_engine
+    generic :: count_layers => default_real_count_layers 
+    procedure, private ::      default_real_count_layers
+    generic :: count_neurons => default_real_count_neurons 
+    procedure, private ::       default_real_count_neurons
+    generic :: count_inputs => default_real_count_inputs 
+    procedure, private ::      default_real_count_inputs
+    generic :: neurons_per_layer => default_real_neurons_per_layer 
+    procedure, private ::           default_real_neurons_per_layer
+    generic :: next_allocated => default_real_next_allocated 
+    procedure, private ::        default_real_next_allocated
+    generic :: next_pointer => default_real_next_pointer 
+    procedure, private ::      default_real_next_pointer
   end type
 
   interface layer_t
 
-    recursive module function construct_layer(layer_lines, start) result(layer)
+    recursive module function default_real_construct_layer(layer_lines, start) result(layer)
       !! construct a linked list of layer_t objects from an array of JSON-formatted text lines
       implicit none
       type(string_t), intent(in) :: layer_lines(:)
@@ -40,7 +47,7 @@ recursive module function construct_layer(layer_lines, start) result(layer)
 
   interface
 
-    module function inference_engine(hidden_layers, metadata, output_layer, input_map, output_map) result(inference_engine_)
+    module function default_real_inference_engine(hidden_layers, metadata, output_layer, input_map, output_map) result(inference_engine_)
       implicit none
       class(layer_t), intent(in), target :: hidden_layers
       type(layer_t), intent(in), target :: output_layer
@@ -49,37 +56,37 @@ module function inference_engine(hidden_layers, metadata, output_layer, input_ma
       type(inference_engine_t) inference_engine_
     end function
 
-    module function count_layers(layer) result(num_layers)
+    module function default_real_count_layers(layer) result(num_layers)
       implicit none
       class(layer_t), intent(in), target :: layer
       integer num_layers
     end function
 
-    module function count_neurons(layer) result(neurons_per_layer_result)
+    module function default_real_count_neurons(layer) result(neurons_per_layer_result)
       implicit none
       class(layer_t), intent(in), target :: layer
       integer, allocatable :: neurons_per_layer_result(:)
     end function
 
-    module function count_inputs(layer) result(num_inputs)
+    module function default_real_count_inputs(layer) result(num_inputs)
       implicit none
       class(layer_t), intent(in) :: layer
       integer num_inputs
     end function
 
-    module function neurons_per_layer(self) result(num_neurons)
+    module function default_real_neurons_per_layer(self) result(num_neurons)
       implicit none
       class(layer_t), intent(in), target :: self
       integer num_neurons
     end function
 
-    module function next_allocated(self) result(next_is_allocated)
+    module function default_real_next_allocated(self) result(next_is_allocated)
       implicit none
       class(layer_t), intent(in) :: self
       logical next_is_allocated
     end function
 
-    module function next_pointer(self) result(next_ptr)
+    module function default_real_next_pointer(self) result(next_ptr)
       implicit none
       class(layer_t), intent(in), target :: self
       type(layer_t), pointer :: next_ptr
diff --git a/src/inference_engine/layer_s.f90 b/src/inference_engine/layer_s.f90
index 1dc847d74..d08b1b689 100644
--- a/src/inference_engine/layer_s.f90
+++ b/src/inference_engine/layer_s.f90
@@ -7,7 +7,7 @@
 
 contains
 
-  module procedure construct_layer
+  module procedure default_real_construct_layer
 
     type(neuron_t), pointer ::  neuron 
     integer num_inputs, neurons_in_layer
@@ -34,11 +34,11 @@
     line = trim(adjustl(layer_lines(start+4*neurons_in_layer+1)%string()))
     call assert(line(1:1)==']', "read_layer_list: hidden layer end")
 
-    if (line(len(line):len(line)) == ",") layer%next = construct_layer(layer_lines, start+4*neurons_in_layer+2)
+    if (line(len(line):len(line)) == ",") layer%next = default_real_construct_layer(layer_lines, start+4*neurons_in_layer+2)
 
   end procedure
 
-  module procedure inference_engine
+  module procedure default_real_inference_engine
 
     associate( &
       num_inputs => hidden_layers%count_inputs(), &
@@ -47,7 +47,7 @@
       num_hidden_layers =>  hidden_layers%count_layers(), &
       num_output_layers => output_layer%count_layers() &
     )   
-      call assert(num_output_layers==1, "inference_engine_s(construct_from_json): 1 output layer", num_output_layers)
+      call assert(num_output_layers==1, "inference_engine_s(default_real_construct_from_json): 1 output layer", num_output_layers)
 
       associate(nodes => [num_inputs, neurons_per_hidden_layer, num_outputs])
         associate(n_max => maxval(nodes))
@@ -110,7 +110,7 @@
     
   end procedure
 
-  module procedure count_layers
+  module procedure default_real_count_layers
 
     type(layer_t), pointer :: layer_ptr
 
@@ -124,7 +124,7 @@
 
   end procedure
 
-  module procedure count_neurons
+  module procedure default_real_count_neurons
 
     type(layer_t), pointer :: layer_ptr
     type(neuron_t), pointer :: neuron_ptr
@@ -149,11 +149,11 @@
  
   end procedure
 
-  module procedure count_inputs
+  module procedure default_real_count_inputs
     num_inputs = layer%neuron%num_inputs() ! assume fully connected input layer
   end procedure
 
-  module procedure neurons_per_layer
+  module procedure default_real_neurons_per_layer
 
     type(neuron_t), pointer ::  neuron 
 
@@ -167,11 +167,11 @@
 
   end procedure
 
-  module procedure next_allocated
+  module procedure default_real_next_allocated
     next_is_allocated = allocated(self%next)
   end procedure
 
-  module procedure next_pointer
+  module procedure default_real_next_pointer
     if (allocated(self%next)) then
       next_ptr => self%next
     else

From 85fba964ededb281f3c81776e8b6193e7ca54569 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 29 Aug 2024 00:58:17 -0700
Subject: [PATCH 019/105] feat(neuron_t): add double-precision procedures

---
 src/inference_engine/neuron_m.f90 | 71 +++++++++++++++++++++++++------
 src/inference_engine/neuron_s.f90 | 51 +++++++++++++++++++++-
 2 files changed, 107 insertions(+), 15 deletions(-)

diff --git a/src/inference_engine/neuron_m.f90 b/src/inference_engine/neuron_m.f90
index 4042bb6f6..b078a7b29 100644
--- a/src/inference_engine/neuron_m.f90
+++ b/src/inference_engine/neuron_m.f90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module neuron_m
   use julienne_string_m, only : string_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   implicit none
 
   private
@@ -15,18 +15,18 @@ module neuron_m
     real(k),                     private :: bias_
     type(neuron_t(k)), allocatable, private :: next
   contains
-    generic :: to_json => default_real_to_json
-    procedure, private :: default_real_to_json
-    generic :: weights => default_real_weights
-    procedure, private :: default_real_weights
-    generic :: bias =>    default_real_bias
-    procedure, private :: default_real_bias
-    generic :: next_allocated => default_real_next_allocated
-    procedure, private ::        default_real_next_allocated
-    generic :: next_pointer => default_real_next_pointer
-    procedure, private ::      default_real_next_pointer
-    generic :: num_inputs => default_real_num_inputs
-    procedure, private ::    default_real_num_inputs
+    generic :: to_json => default_real_to_json, double_precision_to_json
+    procedure, private :: default_real_to_json, double_precision_to_json
+    generic :: weights => default_real_weights, double_precision_weights
+    procedure, private :: default_real_weights, double_precision_weights
+    generic :: bias =>    default_real_bias, double_precision_bias
+    procedure, private :: default_real_bias, double_precision_bias
+    generic :: next_allocated => default_real_next_allocated, double_precision_next_allocated
+    procedure, private ::        default_real_next_allocated, double_precision_next_allocated
+    generic :: next_pointer => default_real_next_pointer, double_precision_next_pointer
+    procedure, private ::      default_real_next_pointer, double_precision_next_pointer
+    generic :: num_inputs => default_real_num_inputs, double_precision_num_inputs
+    procedure, private ::    default_real_num_inputs, double_precision_num_inputs
   end type
 
   interface neuron_t
@@ -39,13 +39,20 @@ pure recursive module function from_json(neuron_lines, start) result(neuron)
       type(neuron_t) neuron
     end function
 
-    pure module function from_components(weights, bias) result(neuron)
+    pure module function default_real_from_components(weights, bias) result(neuron)
       !! construct single neuron_t object from an array of weights and a bias
       real, intent(in) :: weights(:)
       real, intent(in) :: bias
       type(neuron_t) neuron
     end function
 
+    pure module function double_precision_from_components(weights, bias) result(neuron)
+      !! construct single neuron_t object from an array of weights and a bias
+      double precision, intent(in) :: weights(:)
+      double precision, intent(in) :: bias
+      type(neuron_t(double_precision)) neuron
+    end function
+
   end interface
 
   interface
@@ -56,36 +63,72 @@ pure module function default_real_to_json(self) result(lines)
       type(string_t), allocatable :: lines(:)
     end function
 
+    pure module function double_precision_to_json(self) result(lines)
+      implicit none
+      class(neuron_t(double_precision)), intent(in) :: self
+      type(string_t), allocatable :: lines(:)
+    end function
+
     module function default_real_weights(self) result(my_weights)
       implicit none
       class(neuron_t), intent(in) :: self
       real, allocatable :: my_weights(:)
     end function
 
+    module function double_precision_weights(self) result(my_weights)
+      implicit none
+      class(neuron_t(double_precision)), intent(in) :: self
+      double precision, allocatable :: my_weights(:)
+    end function
+
     module function default_real_bias(self) result(my_bias)
       implicit none
       class(neuron_t), intent(in) :: self
       real my_bias
     end function
 
+    module function double_precision_bias(self) result(my_bias)
+      implicit none
+      class(neuron_t(double_precision)), intent(in) :: self
+      double precision my_bias
+    end function
+
     module function default_real_next_allocated(self) result(next_is_allocated)
       implicit none
       class(neuron_t), intent(in) :: self
       logical next_is_allocated
     end function
 
+    module function double_precision_next_allocated(self) result(next_is_allocated)
+      implicit none
+      class(neuron_t(double_precision)), intent(in) :: self
+      logical next_is_allocated
+    end function
+
     module function default_real_next_pointer(self) result(next_ptr)
       implicit none
       class(neuron_t), intent(in), target :: self
       type(neuron_t), pointer :: next_ptr
     end function
 
+    module function double_precision_next_pointer(self) result(next_ptr)
+      implicit none
+      class(neuron_t(double_precision)), intent(in), target :: self
+      type(neuron_t(double_precision)), pointer :: next_ptr
+    end function
+
     pure module function default_real_num_inputs(self) result(size_weights)
       implicit none
       class(neuron_t), intent(in) :: self
       integer size_weights
     end function
 
+    pure module function double_precision_num_inputs(self) result(size_weights)
+      implicit none
+      class(neuron_t(double_precision)), intent(in) :: self
+      integer size_weights
+    end function
+
   end interface
 
 end module
diff --git a/src/inference_engine/neuron_s.f90 b/src/inference_engine/neuron_s.f90
index 2fd1f3f0c..b985cf8bd 100644
--- a/src/inference_engine/neuron_s.f90
+++ b/src/inference_engine/neuron_s.f90
@@ -27,6 +27,26 @@
     ]
   end procedure
 
+  module procedure double_precision_to_json
+    integer, parameter :: characters_per_value=34
+    character(len=*), parameter :: indent = repeat(" ",ncopies=12)
+    character(len=:), allocatable :: csv_format, weights_string, bias_string
+
+    call assert(allocated(self%weights_), "neuron_s(to_json): allocated weights_")
+
+    csv_format = separated_values(separator=",", mold=[double precision::])
+    allocate(character(len=size(self%weights_)*(characters_per_value+1)-1)::weights_string)
+    allocate(character(len=characters_per_value)::bias_string)
+    write(weights_string, fmt = csv_format) self%weights_
+    write(bias_string,*) self%bias_
+    lines = [ &
+      string_t(indent // '{'), &
+      string_t(indent // '  "weights": [' // trim(adjustl(weights_string)) // '],'), &
+      string_t(indent // '  "bias": ' // trim(adjustl(bias_string))), &
+      string_t(indent // '}') &
+    ]
+  end procedure
+
   module procedure from_json
 
     character(len=:), allocatable :: line
@@ -62,7 +82,12 @@
 
   end procedure
 
-  module procedure from_components
+  module procedure default_real_from_components
+    neuron%weights_ = weights
+    neuron%bias_ = bias
+  end procedure
+
+  module procedure double_precision_from_components
     neuron%weights_ = weights
     neuron%bias_ = bias
   end procedure
@@ -71,14 +96,26 @@
     my_weights = self%weights_
   end procedure
 
+  module procedure double_precision_weights
+    my_weights = self%weights_
+  end procedure
+
   module procedure default_real_bias
     my_bias = self%bias_
   end procedure
 
+  module procedure double_precision_bias
+    my_bias = self%bias_
+  end procedure
+
   module procedure default_real_next_allocated
     next_is_allocated = allocated(self%next)
   end procedure
 
+  module procedure double_precision_next_allocated
+    next_is_allocated = allocated(self%next)
+  end procedure
+
   module procedure default_real_next_pointer
     if (allocated(self%next)) then
       next_ptr => self%next
@@ -87,8 +124,20 @@
     end if
   end procedure
 
+  module procedure double_precision_next_pointer
+    if (allocated(self%next)) then
+      next_ptr => self%next
+    else
+      next_ptr => null()
+    end if
+  end procedure
+
   module procedure default_real_num_inputs
     size_weights = size(self%weights_)
   end procedure
 
+  module procedure double_precision_num_inputs
+    size_weights = size(self%weights_)
+  end procedure
+
 end submodule neuron_s

From 71bbff872a359049e7a5757f5cc8dbe9c043c209 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 29 Aug 2024 09:45:03 -0700
Subject: [PATCH 020/105] feat(layer_t): add double-precision procedures

---
 src/inference_engine/layer_m.f90 | 62 +++++++++++++++++++------
 src/inference_engine/layer_s.f90 | 80 +++++++++++++++++++++++++++++---
 2 files changed, 123 insertions(+), 19 deletions(-)

diff --git a/src/inference_engine/layer_m.f90 b/src/inference_engine/layer_m.f90
index 7842fd834..f831eaadb 100644
--- a/src/inference_engine/layer_m.f90
+++ b/src/inference_engine/layer_m.f90
@@ -5,7 +5,7 @@ module layer_m
   use julienne_string_m, only : string_t
   use inference_engine_m_, only : inference_engine_t
   use tensor_map_m, only :  tensor_map_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   implicit none
 
   private
@@ -19,18 +19,18 @@ module layer_m
   contains
     generic :: inference_engine => default_real_inference_engine 
     procedure, private ::          default_real_inference_engine
-    generic :: count_layers => default_real_count_layers 
-    procedure, private ::      default_real_count_layers
-    generic :: count_neurons => default_real_count_neurons 
-    procedure, private ::       default_real_count_neurons
-    generic :: count_inputs => default_real_count_inputs 
-    procedure, private ::      default_real_count_inputs
-    generic :: neurons_per_layer => default_real_neurons_per_layer 
-    procedure, private ::           default_real_neurons_per_layer
-    generic :: next_allocated => default_real_next_allocated 
-    procedure, private ::        default_real_next_allocated
-    generic :: next_pointer => default_real_next_pointer 
-    procedure, private ::      default_real_next_pointer
+    generic :: count_layers => default_real_count_layers, double_precision_count_layers
+    procedure, private ::      default_real_count_layers, double_precision_count_layers
+    generic :: count_neurons => default_real_count_neurons, double_precision_count_neurons
+    procedure, private ::       default_real_count_neurons, double_precision_count_neurons
+    generic :: count_inputs => default_real_count_inputs, double_precision_count_inputs
+    procedure, private ::      default_real_count_inputs, double_precision_count_inputs
+    generic :: neurons_per_layer => default_real_neurons_per_layer, double_precision_neurons_per_layer
+    procedure, private ::           default_real_neurons_per_layer, double_precision_neurons_per_layer
+    generic :: next_allocated => default_real_next_allocated, double_precision_next_allocated
+    procedure, private ::        default_real_next_allocated, double_precision_next_allocated
+    generic :: next_pointer => default_real_next_pointer, double_precision_next_pointer 
+    procedure, private ::      default_real_next_pointer, double_precision_next_pointer
   end type
 
   interface layer_t
@@ -62,36 +62,72 @@ module function default_real_count_layers(layer) result(num_layers)
       integer num_layers
     end function
 
+    module function double_precision_count_layers(layer) result(num_layers)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: layer
+      integer num_layers
+    end function
+
     module function default_real_count_neurons(layer) result(neurons_per_layer_result)
       implicit none
       class(layer_t), intent(in), target :: layer
       integer, allocatable :: neurons_per_layer_result(:)
     end function
 
+    module function double_precision_count_neurons(layer) result(neurons_per_layer_result)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: layer
+      integer, allocatable :: neurons_per_layer_result(:)
+    end function
+
     module function default_real_count_inputs(layer) result(num_inputs)
       implicit none
       class(layer_t), intent(in) :: layer
       integer num_inputs
     end function
 
+    module function double_precision_count_inputs(layer) result(num_inputs)
+      implicit none
+      class(layer_t(double_precision)), intent(in) :: layer
+      integer num_inputs
+    end function
+
     module function default_real_neurons_per_layer(self) result(num_neurons)
       implicit none
       class(layer_t), intent(in), target :: self
       integer num_neurons
     end function
 
+    module function double_precision_neurons_per_layer(self) result(num_neurons)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: self
+      integer num_neurons
+    end function
+
     module function default_real_next_allocated(self) result(next_is_allocated)
       implicit none
       class(layer_t), intent(in) :: self
       logical next_is_allocated
     end function
 
+    module function double_precision_next_allocated(self) result(next_is_allocated)
+      implicit none
+      class(layer_t(double_precision)), intent(in) :: self
+      logical next_is_allocated
+    end function
+
     module function default_real_next_pointer(self) result(next_ptr)
       implicit none
       class(layer_t), intent(in), target :: self
       type(layer_t), pointer :: next_ptr
     end function
 
+    module function double_precision_next_pointer(self) result(next_ptr)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: self
+      type(layer_t(double_precision)), pointer :: next_ptr
+    end function
+
   end interface
 
 end module
diff --git a/src/inference_engine/layer_s.f90 b/src/inference_engine/layer_s.f90
index d08b1b689..ac034d7aa 100644
--- a/src/inference_engine/layer_s.f90
+++ b/src/inference_engine/layer_s.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 submodule(layer_m) layer_s
   use assert_m, only : assert
-  use kind_parameters_m, only : rkind
   implicit none
 
 contains
@@ -17,7 +16,7 @@
     line = adjustl(layer_lines(start)%string())
     hidden_layers = line == '['
     output_layer = line == '"output_layer": ['
-    call assert(hidden_layers .or. output_layer, "layer_t construct_layer: layer start", line)
+    call assert(hidden_layers .or. output_layer, "layer_s(default_real_construct_layer): layer start", line)
 
     layer%neuron = neuron_t(layer_lines, start+1)
     num_inputs = size(layer%neuron%weights())
@@ -27,12 +26,12 @@
     do 
       if (.not. neuron%next_allocated()) exit
       neuron => neuron%next_pointer()
-      call assert(size(neuron%weights()) == num_inputs, "layer_t construct_layer: constant number of inputs")
+      call assert(size(neuron%weights()) == num_inputs, "layer_s(default_real_construct_layer): constant number of inputs")
       neurons_in_layer = neurons_in_layer + 1
     end do
 
     line = trim(adjustl(layer_lines(start+4*neurons_in_layer+1)%string()))
-    call assert(line(1:1)==']', "read_layer_list: hidden layer end")
+    call assert(line(1:1)==']', "layer_s(default_real_construct_layer): hidden layer end")
 
     if (line(len(line):len(line)) == ",") layer%next = default_real_construct_layer(layer_lines, start+4*neurons_in_layer+2)
 
@@ -47,12 +46,12 @@
       num_hidden_layers =>  hidden_layers%count_layers(), &
       num_output_layers => output_layer%count_layers() &
     )   
-      call assert(num_output_layers==1, "inference_engine_s(default_real_construct_from_json): 1 output layer", num_output_layers)
+      call assert(num_output_layers==1, "inference_engine_s(default_real_inference_engine): 1 output layer", num_output_layers)
 
       associate(nodes => [num_inputs, neurons_per_hidden_layer, num_outputs])
         associate(n_max => maxval(nodes))
           block
-            real(rkind), allocatable :: weights(:,:,:), biases(:,:)
+            real, allocatable :: weights(:,:,:), biases(:,:)
             type(layer_t), pointer :: layer_ptr
             type(neuron_t), pointer :: neuron_ptr
             integer j, l
@@ -124,6 +123,20 @@
 
   end procedure
 
+  module procedure double_precision_count_layers
+
+    type(layer_t(double_precision)), pointer :: layer_ptr
+
+    layer_ptr => layer
+    num_layers = 1 
+    do  
+      if (.not. allocated(layer_ptr%next)) exit
+      layer_ptr => layer_ptr%next
+      num_layers = num_layers + 1 
+    end do
+
+  end procedure
+
   module procedure default_real_count_neurons
 
     type(layer_t), pointer :: layer_ptr
@@ -149,10 +162,39 @@
  
   end procedure
 
+  module procedure double_precision_count_neurons
+
+    type(layer_t(double_precision)), pointer :: layer_ptr
+    type(neuron_t(double_precision)), pointer :: neuron_ptr
+    integer num_neurons
+
+    layer_ptr => layer
+
+    allocate(neurons_per_layer_result(0))
+
+    do  
+      num_neurons = 1 
+      neuron_ptr => layer_ptr%neuron
+      do  
+        if (.not. neuron_ptr%next_allocated()) exit
+        neuron_ptr => neuron_ptr%next_pointer()
+        num_neurons = num_neurons + 1 
+      end do
+      neurons_per_layer_result = [neurons_per_layer_result, num_neurons]
+      if (.not. allocated(layer_ptr%next)) exit
+      layer_ptr => layer_ptr%next
+    end do
+ 
+  end procedure
+
   module procedure default_real_count_inputs
     num_inputs = layer%neuron%num_inputs() ! assume fully connected input layer
   end procedure
 
+  module procedure double_precision_count_inputs
+    num_inputs = layer%neuron%num_inputs() ! assume fully connected input layer
+  end procedure
+
   module procedure default_real_neurons_per_layer
 
     type(neuron_t), pointer ::  neuron 
@@ -167,10 +209,28 @@
 
   end procedure
 
+  module procedure double_precision_neurons_per_layer
+
+    type(neuron_t(double_precision)), pointer ::  neuron 
+
+    neuron => self%neuron
+    num_neurons = 1
+    do 
+      if (.not. neuron%next_allocated()) exit
+      neuron => neuron%next_pointer()
+      num_neurons = num_neurons + 1
+    end do
+
+  end procedure
+
   module procedure default_real_next_allocated
     next_is_allocated = allocated(self%next)
   end procedure
 
+  module procedure double_precision_next_allocated
+    next_is_allocated = allocated(self%next)
+  end procedure
+
   module procedure default_real_next_pointer
     if (allocated(self%next)) then
       next_ptr => self%next
@@ -179,4 +239,12 @@
     end if
   end procedure
 
+  module procedure double_precision_next_pointer
+    if (allocated(self%next)) then
+      next_ptr => self%next
+    else
+      next_ptr => null()
+    end if
+  end procedure
+
 end submodule layer_s

From 9c99739fdc320e86b14eabd9edeaf5e6d6db534c Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 29 Aug 2024 09:45:03 -0700
Subject: [PATCH 021/105] feat(layer_t): add double-precision procedures

---
 .../double_precision_string_m.f90             |   8 ++
 src/inference_engine/layer_m.f90              |  71 +++++++++--
 src/inference_engine/layer_s.f90              | 113 ++++++++++++++++--
 src/inference_engine/neuron_m.f90             |   9 ++
 src/inference_engine/neuron_s.f90             |  36 ++++++
 5 files changed, 217 insertions(+), 20 deletions(-)
 create mode 100644 src/inference_engine/double_precision_string_m.f90

diff --git a/src/inference_engine/double_precision_string_m.f90 b/src/inference_engine/double_precision_string_m.f90
new file mode 100644
index 000000000..54edde09d
--- /dev/null
+++ b/src/inference_engine/double_precision_string_m.f90
@@ -0,0 +1,8 @@
+module double_precision_string_m
+  use julienne_m, only : string_t
+  implicit none
+
+  type, extends(string_t) :: double_precision_string_t
+  end type
+
+end module double_precision_string_m
diff --git a/src/inference_engine/layer_m.f90 b/src/inference_engine/layer_m.f90
index 7842fd834..47065eef1 100644
--- a/src/inference_engine/layer_m.f90
+++ b/src/inference_engine/layer_m.f90
@@ -5,7 +5,8 @@ module layer_m
   use julienne_string_m, only : string_t
   use inference_engine_m_, only : inference_engine_t
   use tensor_map_m, only :  tensor_map_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
 
   private
@@ -19,18 +20,18 @@ module layer_m
   contains
     generic :: inference_engine => default_real_inference_engine 
     procedure, private ::          default_real_inference_engine
-    generic :: count_layers => default_real_count_layers 
-    procedure, private ::      default_real_count_layers
-    generic :: count_neurons => default_real_count_neurons 
-    procedure, private ::       default_real_count_neurons
-    generic :: count_inputs => default_real_count_inputs 
-    procedure, private ::      default_real_count_inputs
-    generic :: neurons_per_layer => default_real_neurons_per_layer 
-    procedure, private ::           default_real_neurons_per_layer
-    generic :: next_allocated => default_real_next_allocated 
-    procedure, private ::        default_real_next_allocated
-    generic :: next_pointer => default_real_next_pointer 
-    procedure, private ::      default_real_next_pointer
+    generic :: count_layers => default_real_count_layers, double_precision_count_layers
+    procedure, private ::      default_real_count_layers, double_precision_count_layers
+    generic :: count_neurons => default_real_count_neurons, double_precision_count_neurons
+    procedure, private ::       default_real_count_neurons, double_precision_count_neurons
+    generic :: count_inputs => default_real_count_inputs, double_precision_count_inputs
+    procedure, private ::      default_real_count_inputs, double_precision_count_inputs
+    generic :: neurons_per_layer => default_real_neurons_per_layer, double_precision_neurons_per_layer
+    procedure, private ::           default_real_neurons_per_layer, double_precision_neurons_per_layer
+    generic :: next_allocated => default_real_next_allocated, double_precision_next_allocated
+    procedure, private ::        default_real_next_allocated, double_precision_next_allocated
+    generic :: next_pointer => default_real_next_pointer, double_precision_next_pointer 
+    procedure, private ::      default_real_next_pointer, double_precision_next_pointer
   end type
 
   interface layer_t
@@ -43,6 +44,14 @@ recursive module function default_real_construct_layer(layer_lines, start) resul
       type(layer_t), target :: layer
     end function
 
+    recursive module function double_precision_construct_layer(layer_lines, start) result(layer)
+      !! construct a linked list of layer_t objects from an array of JSON-formatted text lines
+      implicit none
+      type(double_precision_string_t), intent(in) :: layer_lines(:)
+      integer, intent(in) :: start
+      type(layer_t(double_precision)), target :: layer
+    end function
+
   end interface
 
   interface
@@ -62,36 +71,72 @@ module function default_real_count_layers(layer) result(num_layers)
       integer num_layers
     end function
 
+    module function double_precision_count_layers(layer) result(num_layers)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: layer
+      integer num_layers
+    end function
+
     module function default_real_count_neurons(layer) result(neurons_per_layer_result)
       implicit none
       class(layer_t), intent(in), target :: layer
       integer, allocatable :: neurons_per_layer_result(:)
     end function
 
+    module function double_precision_count_neurons(layer) result(neurons_per_layer_result)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: layer
+      integer, allocatable :: neurons_per_layer_result(:)
+    end function
+
     module function default_real_count_inputs(layer) result(num_inputs)
       implicit none
       class(layer_t), intent(in) :: layer
       integer num_inputs
     end function
 
+    module function double_precision_count_inputs(layer) result(num_inputs)
+      implicit none
+      class(layer_t(double_precision)), intent(in) :: layer
+      integer num_inputs
+    end function
+
     module function default_real_neurons_per_layer(self) result(num_neurons)
       implicit none
       class(layer_t), intent(in), target :: self
       integer num_neurons
     end function
 
+    module function double_precision_neurons_per_layer(self) result(num_neurons)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: self
+      integer num_neurons
+    end function
+
     module function default_real_next_allocated(self) result(next_is_allocated)
       implicit none
       class(layer_t), intent(in) :: self
       logical next_is_allocated
     end function
 
+    module function double_precision_next_allocated(self) result(next_is_allocated)
+      implicit none
+      class(layer_t(double_precision)), intent(in) :: self
+      logical next_is_allocated
+    end function
+
     module function default_real_next_pointer(self) result(next_ptr)
       implicit none
       class(layer_t), intent(in), target :: self
       type(layer_t), pointer :: next_ptr
     end function
 
+    module function double_precision_next_pointer(self) result(next_ptr)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: self
+      type(layer_t(double_precision)), pointer :: next_ptr
+    end function
+
   end interface
 
 end module
diff --git a/src/inference_engine/layer_s.f90 b/src/inference_engine/layer_s.f90
index d08b1b689..1fdb858f9 100644
--- a/src/inference_engine/layer_s.f90
+++ b/src/inference_engine/layer_s.f90
@@ -2,7 +2,6 @@
 ! Terms of use are as specified in LICENSE.txt
 submodule(layer_m) layer_s
   use assert_m, only : assert
-  use kind_parameters_m, only : rkind
   implicit none
 
 contains
@@ -17,7 +16,7 @@
     line = adjustl(layer_lines(start)%string())
     hidden_layers = line == '['
     output_layer = line == '"output_layer": ['
-    call assert(hidden_layers .or. output_layer, "layer_t construct_layer: layer start", line)
+    call assert(hidden_layers .or. output_layer, "layer_s(default_real_construct_layer): layer start", line)
 
     layer%neuron = neuron_t(layer_lines, start+1)
     num_inputs = size(layer%neuron%weights())
@@ -27,14 +26,45 @@
     do 
       if (.not. neuron%next_allocated()) exit
       neuron => neuron%next_pointer()
-      call assert(size(neuron%weights()) == num_inputs, "layer_t construct_layer: constant number of inputs")
+      call assert(size(neuron%weights()) == num_inputs, "layer_s(default_real_construct_layer): constant number of inputs")
       neurons_in_layer = neurons_in_layer + 1
     end do
 
     line = trim(adjustl(layer_lines(start+4*neurons_in_layer+1)%string()))
-    call assert(line(1:1)==']', "read_layer_list: hidden layer end")
+    call assert(line(1:1)==']', "layer_s(default_real_construct_layer): hidden layer end")
 
-    if (line(len(line):len(line)) == ",") layer%next = default_real_construct_layer(layer_lines, start+4*neurons_in_layer+2)
+    if (line(len(line):len(line)) == ",") layer%next = layer_t(layer_lines, start+4*neurons_in_layer+2)
+
+  end procedure
+
+  module procedure double_precision_construct_layer
+
+    type(neuron_t(double_precision)), pointer ::  neuron 
+    integer num_inputs, neurons_in_layer
+    character(len=:), allocatable :: line
+    logical hidden_layers, output_layer
+
+    line = adjustl(layer_lines(start)%string())
+    hidden_layers = line == '['
+    output_layer = line == '"output_layer": ['
+    call assert(hidden_layers .or. output_layer, "layer_s(double_precision_construct_layer): layer start", line)
+
+    layer%neuron = neuron_t(layer_lines, start+1)
+    num_inputs = size(layer%neuron%weights())
+
+    neuron => layer%neuron
+    neurons_in_layer = 1
+    do 
+      if (.not. neuron%next_allocated()) exit
+      neuron => neuron%next_pointer()
+      call assert(size(neuron%weights()) == num_inputs, "layer_s(double_precision_construct_layer): constant number of inputs")
+      neurons_in_layer = neurons_in_layer + 1
+    end do
+
+    line = trim(adjustl(layer_lines(start+4*neurons_in_layer+1)%string()))
+    call assert(line(1:1)==']', "layer_s(double_precision_construct_layer): hidden layer end")
+
+    if (line(len(line):len(line)) == ",") layer%next = layer_t(layer_lines, start+4*neurons_in_layer+2)
 
   end procedure
 
@@ -47,12 +77,12 @@
       num_hidden_layers =>  hidden_layers%count_layers(), &
       num_output_layers => output_layer%count_layers() &
     )   
-      call assert(num_output_layers==1, "inference_engine_s(default_real_construct_from_json): 1 output layer", num_output_layers)
+      call assert(num_output_layers==1, "inference_engine_s(default_real_inference_engine): 1 output layer", num_output_layers)
 
       associate(nodes => [num_inputs, neurons_per_hidden_layer, num_outputs])
         associate(n_max => maxval(nodes))
           block
-            real(rkind), allocatable :: weights(:,:,:), biases(:,:)
+            real, allocatable :: weights(:,:,:), biases(:,:)
             type(layer_t), pointer :: layer_ptr
             type(neuron_t), pointer :: neuron_ptr
             integer j, l
@@ -124,6 +154,20 @@
 
   end procedure
 
+  module procedure double_precision_count_layers
+
+    type(layer_t(double_precision)), pointer :: layer_ptr
+
+    layer_ptr => layer
+    num_layers = 1 
+    do  
+      if (.not. allocated(layer_ptr%next)) exit
+      layer_ptr => layer_ptr%next
+      num_layers = num_layers + 1 
+    end do
+
+  end procedure
+
   module procedure default_real_count_neurons
 
     type(layer_t), pointer :: layer_ptr
@@ -149,10 +193,39 @@
  
   end procedure
 
+  module procedure double_precision_count_neurons
+
+    type(layer_t(double_precision)), pointer :: layer_ptr
+    type(neuron_t(double_precision)), pointer :: neuron_ptr
+    integer num_neurons
+
+    layer_ptr => layer
+
+    allocate(neurons_per_layer_result(0))
+
+    do  
+      num_neurons = 1 
+      neuron_ptr => layer_ptr%neuron
+      do  
+        if (.not. neuron_ptr%next_allocated()) exit
+        neuron_ptr => neuron_ptr%next_pointer()
+        num_neurons = num_neurons + 1 
+      end do
+      neurons_per_layer_result = [neurons_per_layer_result, num_neurons]
+      if (.not. allocated(layer_ptr%next)) exit
+      layer_ptr => layer_ptr%next
+    end do
+ 
+  end procedure
+
   module procedure default_real_count_inputs
     num_inputs = layer%neuron%num_inputs() ! assume fully connected input layer
   end procedure
 
+  module procedure double_precision_count_inputs
+    num_inputs = layer%neuron%num_inputs() ! assume fully connected input layer
+  end procedure
+
   module procedure default_real_neurons_per_layer
 
     type(neuron_t), pointer ::  neuron 
@@ -167,10 +240,28 @@
 
   end procedure
 
+  module procedure double_precision_neurons_per_layer
+
+    type(neuron_t(double_precision)), pointer ::  neuron 
+
+    neuron => self%neuron
+    num_neurons = 1
+    do 
+      if (.not. neuron%next_allocated()) exit
+      neuron => neuron%next_pointer()
+      num_neurons = num_neurons + 1
+    end do
+
+  end procedure
+
   module procedure default_real_next_allocated
     next_is_allocated = allocated(self%next)
   end procedure
 
+  module procedure double_precision_next_allocated
+    next_is_allocated = allocated(self%next)
+  end procedure
+
   module procedure default_real_next_pointer
     if (allocated(self%next)) then
       next_ptr => self%next
@@ -179,4 +270,12 @@
     end if
   end procedure
 
+  module procedure double_precision_next_pointer
+    if (allocated(self%next)) then
+      next_ptr => self%next
+    else
+      next_ptr => null()
+    end if
+  end procedure
+
 end submodule layer_s
diff --git a/src/inference_engine/neuron_m.f90 b/src/inference_engine/neuron_m.f90
index b078a7b29..45852606a 100644
--- a/src/inference_engine/neuron_m.f90
+++ b/src/inference_engine/neuron_m.f90
@@ -3,6 +3,7 @@
 module neuron_m
   use julienne_string_m, only : string_t
   use kind_parameters_m, only : default_real, double_precision
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
 
   private
@@ -39,6 +40,14 @@ pure recursive module function from_json(neuron_lines, start) result(neuron)
       type(neuron_t) neuron
     end function
 
+    pure recursive module function double_precision_from_json(neuron_lines, start) result(neuron)
+      !! construct linked list of neuron_t objects from an array of JSON-formatted text lines
+      implicit none
+      type(double_precision_string_t), intent(in) :: neuron_lines(:)
+      integer, intent(in) :: start
+      type(neuron_t(double_precision)) neuron
+    end function
+
     pure module function default_real_from_components(weights, bias) result(neuron)
       !! construct single neuron_t object from an array of weights and a bias
       real, intent(in) :: weights(:)
diff --git a/src/inference_engine/neuron_s.f90 b/src/inference_engine/neuron_s.f90
index b985cf8bd..1226f1bf3 100644
--- a/src/inference_engine/neuron_s.f90
+++ b/src/inference_engine/neuron_s.f90
@@ -82,6 +82,42 @@
 
   end procedure
 
+  module procedure double_precision_from_json
+
+    character(len=:), allocatable :: line
+    integer i
+
+    call assert(adjustl(neuron_lines(start)%string())=='{', &
+      "neuron_s(double_precison_from_json): neuron object start",neuron_lines(start)%string())
+
+    line = neuron_lines(start+1)%string()
+    associate(colon => index(line, ":"))
+      call assert(adjustl(line(:colon-1))=='"weights"', "neuron_s(double_precision_from_json): neuron weights", line)
+      associate(opening_bracket => colon + index(line(colon+1:), "["))
+        associate(closing_bracket => opening_bracket + index(line(opening_bracket+1:), "]"))
+          associate(commas => count("," == [(line(i:i), i=opening_bracket+1,closing_bracket-1)]))
+            associate(num_inputs => commas + 1)
+              allocate(neuron%weights_(num_inputs))
+              read(line(opening_bracket+1:closing_bracket-1), fmt=*) neuron%weights_
+            end associate
+          end associate
+        end associate
+      end associate
+    end associate
+
+    line = neuron_lines(start+2)%string()
+    associate(colon => index(line, ":"))
+      call assert(adjustl(line(:colon-1))=='"bias"', "neuron_s(double_precision_from_json): neuron bias", line)
+      read(line(colon+1:), fmt=*) neuron%bias_
+    end associate
+
+    line = adjustl(neuron_lines(start+3)%string())
+    call assert(line(1:1)=='}', "neuron_s(double_precision_from_json): neuron object end", line)
+    line = adjustr(neuron_lines(start+3)%string())
+    if (line(len(line):len(line)) == ",") neuron%next = double_precision_from_json(neuron_lines, start+4)
+
+  end procedure
+
   module procedure default_real_from_components
     neuron%weights_ = weights
     neuron%bias_ = bias

From d919e443b9fd51a55d192ae5fc24c583a49adba7 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 12:48:09 -0700
Subject: [PATCH 022/105] feat(trainable_engine_t): add real kind parameter

---
 src/inference_engine/trainable_engine_m.F90 | 41 +++++++++++----------
 src/inference_engine/trainable_engine_s.F90 | 25 ++++++-------
 2 files changed, 34 insertions(+), 32 deletions(-)

diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
index 74ffb04b1..cb6f8fe2f 100644
--- a/src/inference_engine/trainable_engine_m.F90
+++ b/src/inference_engine/trainable_engine_m.F90
@@ -5,7 +5,7 @@ module trainable_engine_m
   use julienne_string_m, only : string_t
   use inference_engine_m_, only : inference_engine_t
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real
   use metadata_m, only : metadata_t
   use tensor_m, only :  tensor_t
   use tensor_map_m, only :  tensor_map_t
@@ -17,22 +17,25 @@ module trainable_engine_m
   private
   public :: trainable_engine_t
 
-  type trainable_engine_t
+  type trainable_engine_t(k)
     !! Encapsulate the information needed to perform training
-    private
-    type(tensor_map_t) input_map_, output_map_
-    type(metadata_t) metadata_
-    real(rkind), allocatable :: w(:,:,:) ! weights
-    real(rkind), allocatable :: b(:,:) ! biases
-    integer, allocatable :: n(:) ! nodes per layer
+    integer, kind :: k = default_real
+    type(tensor_map_t(k)), private :: input_map_, output_map_ !! mappings to/from data ranges used during training
     class(differentiable_activation_strategy_t), allocatable :: differentiable_activation_strategy_ 
-    real(rkind), allocatable, dimension(:,:) :: a
-    real(rkind), allocatable, dimension(:,:,:) :: dcdw, vdw, sdw, vdwc, sdwc
-    real(rkind), allocatable, dimension(:,:) :: z, delta, dcdb, vdb, sdb, vdbc, sdbc
+    type(metadata_t) metadata_       !! metadata_ encapsulates strings for which default-kind suffices
+      !! stateless and thus not paremeterized; generic resolution supports different kinds
+    real(k), allocatable :: w(:,:,:) !! weights
+    real(k), allocatable :: b(:,:)   !! biases
+    integer, allocatable :: n(:)     !! nodes per layer
+    real(k), allocatable, dimension(:,:) :: a
+    real(k), allocatable, dimension(:,:,:) :: dcdw, vdw, sdw, vdwc, sdwc
+    real(k), allocatable, dimension(:,:) :: z, delta, dcdb, vdb, sdb, vdbc, sdbc
   contains
+    generic :: train =>   default_precision_train
+    procedure, private :: default_precision_train
+    generic :: infer =>   default_precision_infer
+    procedure, private :: default_precision_infer
     procedure :: assert_consistent
-    procedure :: train
-    procedure :: infer
     procedure :: num_layers
     procedure :: num_inputs
     procedure :: num_outputs
@@ -59,7 +62,7 @@ impure module function construct_from_padded_arrays( &
       result(trainable_engine)
       implicit none
       integer, intent(in) :: nodes(input_layer:)
-      real(rkind), intent(in)  :: weights(:,:,:), biases(:,:)
+      real, intent(in)  :: weights(:,:,:), biases(:,:)
       class(differentiable_activation_strategy_t), intent(in) :: differentiable_activation_strategy
       type(string_t), intent(in) :: metadata(:)
       type(tensor_map_t), intent(in), optional :: input_map, output_map
@@ -77,7 +80,7 @@ module function perturbed_identity_network(training_configuration, perturbation_
       implicit none
       type(training_configuration_t), intent(in) :: training_configuration
       type(string_t), intent(in) :: metadata(:)
-      real(rkind), intent(in) :: perturbation_magnitude
+      real, intent(in) :: perturbation_magnitude
       type(tensor_map_t) input_map, output_map
       type(trainable_engine_t) trainable_engine
     end function
@@ -91,16 +94,16 @@ pure module subroutine assert_consistent(self)
       class(trainable_engine_t), intent(in) :: self
     end subroutine
 
-    pure module subroutine train(self, mini_batches_arr, cost, adam, learning_rate)
+    pure module subroutine default_precision_train(self, mini_batches_arr, cost, adam, learning_rate)
       implicit none
       class(trainable_engine_t), intent(inout) :: self
       type(mini_batch_t), intent(in) :: mini_batches_arr(:)
-      real(rkind), intent(out), allocatable, optional :: cost(:)
+      real, intent(out), allocatable, optional :: cost(:)
       logical, intent(in) :: adam
-      real(rkind), intent(in) :: learning_rate
+      real, intent(in) :: learning_rate
     end subroutine
 
-    elemental module function infer(self, inputs) result(outputs)
+    elemental module function default_precision_infer(self, inputs) result(outputs)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: inputs
diff --git a/src/inference_engine/trainable_engine_s.F90 b/src/inference_engine/trainable_engine_s.F90
index 45affdca8..09c71f8dd 100644
--- a/src/inference_engine/trainable_engine_s.F90
+++ b/src/inference_engine/trainable_engine_s.F90
@@ -10,7 +10,6 @@
   implicit none
 
   integer, parameter :: input_layer = 0
-  real(rkind), parameter :: two = 2._rkind
 
 contains
 
@@ -71,9 +70,9 @@
 
   end procedure
 
-  module procedure infer
+  module procedure default_precision_infer
 
-    real(rkind), allocatable :: a(:,:)
+    real, allocatable :: a(:,:)
     integer l
 
     call self%assert_consistent
@@ -111,7 +110,7 @@
 
   end procedure
 
-  module procedure train
+  module procedure default_precision_train
     integer l, batch, mini_batch_size, pair
     type(tensor_t), allocatable :: inputs(:), expected_outputs(:)
 
@@ -166,7 +165,7 @@
             end block
 #endif  
             block
-              real(rkind), allocatable :: pair_cost(:)
+              real, allocatable :: pair_cost(:)
               if (present(cost)) allocate(pair_cost(mini_batch_size))
 
               iterate_through_batch: &
@@ -209,15 +208,15 @@
     
               end do iterate_through_batch
 
-              if (present(cost)) cost(batch) = sum(pair_cost)/(two*mini_batch_size)
+              if (present(cost)) cost(batch) = sum(pair_cost)/(2*mini_batch_size)
             end block
           
             if (adam) then
               block
                 ! Adam parameters  
-                real, parameter :: beta(*) = [.9_rkind, .999_rkind]
-                real, parameter :: obeta(*) = [1._rkind - beta(1), 1._rkind - beta(2)]
-                real, parameter :: epsilon = real(1.D-08,rkind)
+                real, parameter :: beta(*) = [.9, .999]
+                real, parameter :: obeta(*) = [1.- beta(1), 1.- beta(2)]
+                real, parameter :: epsilon = 1.E-08
 
                 associate(alpha => learning_rate)
                   adam_adjust_weights_and_biases: &
@@ -225,16 +224,16 @@
                     dcdw(1:n(l),1:n(l-1),l) = dcdw(1:n(l),1:n(l-1),l)/(mini_batch_size)
                     vdw(1:n(l),1:n(l-1),l)  = beta(1)*vdw(1:n(l),1:n(l-1),l) + obeta(1)*dcdw(1:n(l),1:n(l-1),l)
                     sdw (1:n(l),1:n(l-1),l) = beta(2)*sdw(1:n(l),1:n(l-1),l) + obeta(2)*(dcdw(1:n(l),1:n(l-1),l)**2)
-                    vdwc(1:n(l),1:n(l-1),l) = vdw(1:n(l),1:n(l-1),l)/(1._rkind - beta(1)**num_mini_batches)
-                    sdwc(1:n(l),1:n(l-1),l) = sdw(1:n(l),1:n(l-1),l)/(1._rkind - beta(2)**num_mini_batches)
+                    vdwc(1:n(l),1:n(l-1),l) = vdw(1:n(l),1:n(l-1),l)/(1.- beta(1)**num_mini_batches)
+                    sdwc(1:n(l),1:n(l-1),l) = sdw(1:n(l),1:n(l-1),l)/(1.- beta(2)**num_mini_batches)
                     w(1:n(l),1:n(l-1),l) = w(1:n(l),1:n(l-1),l) &
                       - alpha*vdwc(1:n(l),1:n(l-1),l)/(sqrt(sdwc(1:n(l),1:n(l-1),l))+epsilon) ! Adjust weights
 
                     dcdb(1:n(l),l) = dcdb(1:n(l),l)/mini_batch_size
                     vdb(1:n(l),l) = beta(1)*vdb(1:n(l),l) + obeta(1)*dcdb(1:n(l),l)
                     sdb(1:n(l),l) = beta(2)*sdb(1:n(l),l) + obeta(2)*(dcdb(1:n(l),l)**2)
-                    vdbc(1:n(l),l) = vdb(1:n(l),l)/(1._rkind - beta(1)**num_mini_batches)
-                    sdbc(1:n(l),l) = sdb(1:n(l),l)/(1._rkind - beta(2)**num_mini_batches)
+                    vdbc(1:n(l),l) = vdb(1:n(l),l)/(1. - beta(1)**num_mini_batches)
+                    sdbc(1:n(l),l) = sdb(1:n(l),l)/(1. - beta(2)**num_mini_batches)
                     b(1:n(l),l) = b(1:n(l),l) - alpha*vdbc(1:n(l),l)/(sqrt(sdbc(1:n(l),l))+epsilon) ! Adjust weights
                   end do adam_adjust_weights_and_biases
                 end associate

From 070b4c31a36a74ef401260a77f95f5c0061e0069 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 12:53:55 -0700
Subject: [PATCH 023/105] feat(trainable_engine_t): add generic "predict"

This commit adds a generic "predict" binding to follow PyTorch
nomenclature.  For now, the equivalent "infer" generic binding
remains to support existing code.
---
 src/inference_engine/trainable_engine_m.F90 | 13 +++++++------
 src/inference_engine/trainable_engine_s.F90 |  4 ++--
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
index cb6f8fe2f..341ebe9d7 100644
--- a/src/inference_engine/trainable_engine_m.F90
+++ b/src/inference_engine/trainable_engine_m.F90
@@ -31,10 +31,11 @@ module trainable_engine_m
     real(k), allocatable, dimension(:,:,:) :: dcdw, vdw, sdw, vdwc, sdwc
     real(k), allocatable, dimension(:,:) :: z, delta, dcdb, vdb, sdb, vdbc, sdbc
   contains
-    generic :: train =>   default_precision_train
-    procedure, private :: default_precision_train
-    generic :: infer =>   default_precision_infer
-    procedure, private :: default_precision_infer
+    generic :: train =>   default_real_train
+    procedure, private :: default_real_train
+    generic :: predict => default_real_predict
+    generic :: infer =>   default_real_predict
+    procedure, private :: default_real_predict
     procedure :: assert_consistent
     procedure :: num_layers
     procedure :: num_inputs
@@ -94,7 +95,7 @@ pure module subroutine assert_consistent(self)
       class(trainable_engine_t), intent(in) :: self
     end subroutine
 
-    pure module subroutine default_precision_train(self, mini_batches_arr, cost, adam, learning_rate)
+    pure module subroutine default_real_train(self, mini_batches_arr, cost, adam, learning_rate)
       implicit none
       class(trainable_engine_t), intent(inout) :: self
       type(mini_batch_t), intent(in) :: mini_batches_arr(:)
@@ -103,7 +104,7 @@ pure module subroutine default_precision_train(self, mini_batches_arr, cost, ada
       real, intent(in) :: learning_rate
     end subroutine
 
-    elemental module function default_precision_infer(self, inputs) result(outputs)
+    elemental module function default_real_predict(self, inputs) result(outputs)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: inputs
diff --git a/src/inference_engine/trainable_engine_s.F90 b/src/inference_engine/trainable_engine_s.F90
index 09c71f8dd..702b7a821 100644
--- a/src/inference_engine/trainable_engine_s.F90
+++ b/src/inference_engine/trainable_engine_s.F90
@@ -70,7 +70,7 @@
 
   end procedure
 
-  module procedure default_precision_infer
+  module procedure default_real_predict
 
     real, allocatable :: a(:,:)
     integer l
@@ -110,7 +110,7 @@
 
   end procedure
 
-  module procedure default_precision_train
+  module procedure default_real_train
     integer l, batch, mini_batch_size, pair
     type(tensor_t), allocatable :: inputs(:), expected_outputs(:)
 

From de372925fae8b20488356ab2796162d36bfb4ce5 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 13:45:11 -0700
Subject: [PATCH 024/105] refac(trainable_engine): make bindings generic

---
 src/inference_engine/trainable_engine_m.F90 | 50 ++++++++++++---------
 src/inference_engine/trainable_engine_s.F90 | 20 ++++-----
 2 files changed, 40 insertions(+), 30 deletions(-)

diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
index 341ebe9d7..13ae2baf8 100644
--- a/src/inference_engine/trainable_engine_m.F90
+++ b/src/inference_engine/trainable_engine_m.F90
@@ -36,16 +36,26 @@ module trainable_engine_m
     generic :: predict => default_real_predict
     generic :: infer =>   default_real_predict
     procedure, private :: default_real_predict
-    procedure :: assert_consistent
-    procedure :: num_layers
-    procedure :: num_inputs
-    procedure :: num_outputs
-    procedure :: to_inference_engine
-    procedure :: map_to_input_training_range
-    procedure :: map_from_input_training_range
-    procedure :: map_to_output_training_range
-    procedure :: map_from_output_training_range
-    procedure :: map_to_training_ranges
+    generic   :: assert_consistent => default_real_assert_consistent
+    procedure, private ::             default_real_assert_consistent
+    generic :: num_layers => default_real_num_layers
+    procedure, private ::    default_real_num_layers
+    generic :: num_inputs => default_real_num_inputs
+    procedure, private ::    default_real_num_inputs
+    generic :: num_outputs => default_real_num_outputs
+    procedure, private ::     default_real_num_outputs
+    generic :: to_inference_engine => default_real_to_inference_engine
+    procedure, private ::             default_real_to_inference_engine
+    generic :: map_to_input_training_range => default_real_map_to_input_training_range
+    procedure, private ::                     default_real_map_to_input_training_range
+    generic :: map_from_input_training_range => default_real_map_from_input_training_range
+    procedure, private ::                       default_real_map_from_input_training_range
+    generic :: map_to_output_training_range => default_real_map_to_output_training_range
+    procedure, private ::                      default_real_map_to_output_training_range
+    generic :: map_from_output_training_range => default_real_map_from_output_training_range
+    procedure, private ::                        default_real_map_from_output_training_range
+    generic :: map_to_training_ranges => default_real_map_to_training_ranges
+    procedure, private ::                default_real_map_to_training_ranges
   end type
 
   integer, parameter :: input_layer = 0
@@ -90,7 +100,7 @@ module function perturbed_identity_network(training_configuration, perturbation_
 
   interface
 
-    pure module subroutine assert_consistent(self)
+    pure module subroutine default_real_assert_consistent(self)
       implicit none
       class(trainable_engine_t), intent(in) :: self
     end subroutine
@@ -111,59 +121,59 @@ elemental module function default_real_predict(self, inputs) result(outputs)
       type(tensor_t) outputs
     end function
     
-    elemental module function num_inputs(self) result(n_in)
+    elemental module function default_real_num_inputs(self) result(n_in)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       integer n_in
     end function
 
-    elemental module function num_outputs(self) result(n_out)
+    elemental module function default_real_num_outputs(self) result(n_out)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       integer n_out
     end function
 
-    elemental module function num_layers(self) result(n_layers)
+    elemental module function default_real_num_layers(self) result(n_layers)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       integer n_layers
     end function
 
-    module function to_inference_engine(self) result(inference_engine)
+    module function default_real_to_inference_engine(self) result(inference_engine)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(inference_engine_t) :: inference_engine
     end function
 
-    elemental module function map_to_input_training_range(self, tensor) result(normalized_tensor)
+    elemental module function default_real_map_to_input_training_range(self, tensor) result(normalized_tensor)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
       type(tensor_t) normalized_tensor
     end function
 
-    elemental module function map_from_input_training_range(self, tensor) result(unnormalized_tensor)
+    elemental module function default_real_map_from_input_training_range(self, tensor) result(unnormalized_tensor)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
       type(tensor_t) unnormalized_tensor
     end function
 
-    elemental module function map_to_output_training_range(self, tensor) result(normalized_tensor)
+    elemental module function default_real_map_to_output_training_range(self, tensor) result(normalized_tensor)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
       type(tensor_t) normalized_tensor
     end function
 
-    elemental module function map_from_output_training_range(self, tensor) result(unnormalized_tensor)
+    elemental module function default_real_map_from_output_training_range(self, tensor) result(unnormalized_tensor)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
       type(tensor_t) unnormalized_tensor
     end function
 
-    elemental module function map_to_training_ranges(self, input_output_pair) result(normalized_input_output_pair)
+    elemental module function default_real_map_to_training_ranges(self, input_output_pair) result(normalized_input_output_pair)
       implicit none
       class(trainable_engine_t), intent(in) :: self
       type(input_output_pair_t), intent(in) :: input_output_pair
diff --git a/src/inference_engine/trainable_engine_s.F90 b/src/inference_engine/trainable_engine_s.F90
index 702b7a821..6a4fef537 100644
--- a/src/inference_engine/trainable_engine_s.F90
+++ b/src/inference_engine/trainable_engine_s.F90
@@ -13,15 +13,15 @@
 
 contains
 
-  module procedure num_inputs
+  module procedure default_real_num_inputs
     n_in = self%n(input_layer)
   end procedure
 
-  module procedure num_layers
+  module procedure default_real_num_layers
     n_layers = size(self%n,1)
   end procedure
 
-  module procedure num_outputs
+  module procedure default_real_num_outputs
     n_out = self%n(ubound(self%n,1))
   end procedure
 
@@ -53,7 +53,7 @@
 
   end procedure
 
-  module procedure assert_consistent
+  module procedure default_real_assert_consistent
 
     associate( &
       fully_allocated=>[allocated(self%w),allocated(self%b),allocated(self%n),allocated(self%differentiable_activation_strategy_)] &
@@ -290,7 +290,7 @@
     call trainable_engine%assert_consistent
   end procedure
 
-  module procedure to_inference_engine
+  module procedure default_real_to_inference_engine
     inference_engine = inference_engine_t(self%metadata_%strings(), self%w, self%b, self%n, self%input_map_, self%output_map_)
   end procedure
 
@@ -332,7 +332,7 @@ pure function e(j,n) result(unit_vector)
 
   end procedure
 
-  module procedure map_to_training_ranges
+  module procedure default_real_map_to_training_ranges
     associate( &
       inputs => input_output_pair%inputs(), &
       expected_outputs => input_output_pair%expected_outputs() &
@@ -346,19 +346,19 @@ pure function e(j,n) result(unit_vector)
     end associate
   end procedure
 
-  module procedure map_to_input_training_range
+  module procedure default_real_map_to_input_training_range
     normalized_tensor = self%input_map_%map_to_training_range(tensor)
   end procedure
 
-  module procedure map_from_input_training_range
+  module procedure default_real_map_from_input_training_range
     unnormalized_tensor = self%input_map_%map_from_training_range(tensor)
   end procedure
   
-  module procedure map_to_output_training_range
+  module procedure default_real_map_to_output_training_range
     normalized_tensor = self%output_map_%map_to_training_range(tensor)
   end procedure
 
-  module procedure map_from_output_training_range
+  module procedure default_real_map_from_output_training_range
     unnormalized_tensor = self%output_map_%map_from_training_range(tensor)
   end procedure
   

From a5636ddeaeb77cc06e0beff97087a12e95b7101e Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 13:55:55 -0700
Subject: [PATCH 025/105] feat(input_output_pair_t): add kind parameter

also make bindings generic
---
 src/inference_engine/input_output_pair_m.f90 | 30 +++++++++++++-------
 src/inference_engine/input_output_pair_s.f90 | 10 +++----
 2 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/src/inference_engine/input_output_pair_m.f90 b/src/inference_engine/input_output_pair_m.f90
index b05c8776f..c945906c9 100644
--- a/src/inference_engine/input_output_pair_m.f90
+++ b/src/inference_engine/input_output_pair_m.f90
@@ -2,6 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module input_output_pair_m
   use tensor_m, only : tensor_t
+  use kind_parameters_m, only : default_real
   implicit none
 
   private
@@ -9,17 +10,19 @@ module input_output_pair_m
   public :: shuffle
   public :: write_to_stdout
 
-  type input_output_pair_t
-    private
-    type(tensor_t) inputs_, expected_outputs_
+  type input_output_pair_t(k)
+    integer, kind :: k = default_real
+    type(tensor_t(k)), private :: inputs_, expected_outputs_
   contains
-    procedure :: inputs
-    procedure :: expected_outputs
+    generic :: inputs =>  default_real_inputs
+    procedure, private :: default_real_inputs
+    generic :: expected_outputs => default_real_expected_outputs
+    procedure, private ::          default_real_expected_outputs
   end type
 
   interface input_output_pair_t
 
-    elemental module function construct(inputs, expected_outputs) result(input_output_pair)
+    elemental module function default_real_construct(inputs, expected_outputs) result(input_output_pair)
       implicit none
       type(tensor_t), intent(in) :: inputs, expected_outputs
       type(input_output_pair_t) input_output_pair
@@ -29,24 +32,31 @@ elemental module function construct(inputs, expected_outputs) result(input_outpu
 
   interface
 
-    elemental module function inputs(self) result(my_inputs)
+    elemental module function default_real_inputs(self) result(my_inputs)
       implicit none
       class(input_output_pair_t), intent(in) :: self
       type(tensor_t) :: my_inputs
     end function
 
-    elemental module function expected_outputs(self) result(my_expected_outputs)
+    elemental module function default_real_expected_outputs(self) result(my_expected_outputs)
       implicit none
       class(input_output_pair_t), intent(in) :: self
       type(tensor_t) :: my_expected_outputs
     end function
 
-    module subroutine shuffle(pairs)
+  end interface
+
+  interface shuffle
+    module subroutine default_real_shuffle(pairs)
       implicit none
       type(input_output_pair_t), intent(inout) :: pairs(:)
     end subroutine
 
-    module subroutine write_to_stdout(input_output_pairs)
+  end interface
+
+  interface write_to_stdout
+
+    module subroutine default_real_write_to_stdout(input_output_pairs)
       implicit none
       type(input_output_pair_t), intent(in) :: input_output_pairs(:)
     end subroutine
diff --git a/src/inference_engine/input_output_pair_s.f90 b/src/inference_engine/input_output_pair_s.f90
index 359bd1772..14c910db9 100644
--- a/src/inference_engine/input_output_pair_s.f90
+++ b/src/inference_engine/input_output_pair_s.f90
@@ -5,20 +5,20 @@
 
 contains
 
-  module procedure construct
+  module procedure default_real_construct
     input_output_pair%inputs_ = inputs
     input_output_pair%expected_outputs_ = expected_outputs
   end procedure
 
-  module procedure inputs
+  module procedure default_real_inputs
     my_inputs = self%inputs_
   end procedure
 
-  module procedure expected_outputs
+  module procedure default_real_expected_outputs
     my_expected_outputs = self%expected_outputs_
   end procedure
 
-  module procedure shuffle
+  module procedure default_real_shuffle
     type(input_output_pair_t) temp
     real harvest(2:size(pairs))
     integer i, j
@@ -35,7 +35,7 @@
 
   end procedure
 
-  module procedure write_to_stdout
+  module procedure default_real_write_to_stdout
     integer i
     do i = 1, size(input_output_pairs)
       print *, input_output_pairs(i)%inputs_%values(), " | ", input_output_pairs(i)%expected_outputs_%values()

From d82b49866aac12547d2493e3ec04a9dd7cfbf8b4 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 19:33:05 -0700
Subject: [PATCH 026/105] feat(input_output_pair): support double precision

---
 src/inference_engine/input_output_pair_m.f90 | 39 +++++++++++++++++---
 src/inference_engine/input_output_pair_s.f90 | 37 +++++++++++++++++++
 2 files changed, 71 insertions(+), 5 deletions(-)

diff --git a/src/inference_engine/input_output_pair_m.f90 b/src/inference_engine/input_output_pair_m.f90
index c945906c9..fcbfab493 100644
--- a/src/inference_engine/input_output_pair_m.f90
+++ b/src/inference_engine/input_output_pair_m.f90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module input_output_pair_m
   use tensor_m, only : tensor_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   implicit none
 
   private
@@ -14,10 +14,10 @@ module input_output_pair_m
     integer, kind :: k = default_real
     type(tensor_t(k)), private :: inputs_, expected_outputs_
   contains
-    generic :: inputs =>  default_real_inputs
-    procedure, private :: default_real_inputs
-    generic :: expected_outputs => default_real_expected_outputs
-    procedure, private ::          default_real_expected_outputs
+    generic :: inputs =>  default_real_inputs, double_precision_inputs
+    procedure, private :: default_real_inputs, double_precision_inputs
+    generic :: expected_outputs => default_real_expected_outputs, double_precision_expected_outputs
+    procedure, private ::          default_real_expected_outputs, double_precision_expected_outputs
   end type
 
   interface input_output_pair_t
@@ -28,6 +28,12 @@ elemental module function default_real_construct(inputs, expected_outputs) resul
       type(input_output_pair_t) input_output_pair
     end function
 
+    elemental module function double_precision_construct(inputs, expected_outputs) result(input_output_pair)
+      implicit none
+      type(tensor_t(double_precision)), intent(in) :: inputs, expected_outputs
+      type(input_output_pair_t(double_precision)) input_output_pair
+    end function
+
   end interface
 
   interface
@@ -38,20 +44,38 @@ elemental module function default_real_inputs(self) result(my_inputs)
       type(tensor_t) :: my_inputs
     end function
 
+    elemental module function double_precision_inputs(self) result(my_inputs)
+      implicit none
+      class(input_output_pair_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)) :: my_inputs
+    end function
+
     elemental module function default_real_expected_outputs(self) result(my_expected_outputs)
       implicit none
       class(input_output_pair_t), intent(in) :: self
       type(tensor_t) :: my_expected_outputs
     end function
 
+    elemental module function double_precision_expected_outputs(self) result(my_expected_outputs)
+      implicit none
+      class(input_output_pair_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)) :: my_expected_outputs
+    end function
+
   end interface
 
   interface shuffle
+
     module subroutine default_real_shuffle(pairs)
       implicit none
       type(input_output_pair_t), intent(inout) :: pairs(:)
     end subroutine
 
+    module subroutine double_precision_shuffle(pairs)
+      implicit none
+      type(input_output_pair_t(double_precision)), intent(inout) :: pairs(:)
+    end subroutine
+
   end interface
 
   interface write_to_stdout
@@ -61,6 +85,11 @@ module subroutine default_real_write_to_stdout(input_output_pairs)
       type(input_output_pair_t), intent(in) :: input_output_pairs(:)
     end subroutine
 
+    module subroutine double_precision_write_to_stdout(input_output_pairs)
+      implicit none
+      type(input_output_pair_t(double_precision)), intent(in) :: input_output_pairs(:)
+    end subroutine
+
   end interface
 
 end module input_output_pair_m
diff --git a/src/inference_engine/input_output_pair_s.f90 b/src/inference_engine/input_output_pair_s.f90
index 14c910db9..92a960da8 100644
--- a/src/inference_engine/input_output_pair_s.f90
+++ b/src/inference_engine/input_output_pair_s.f90
@@ -10,14 +10,27 @@
     input_output_pair%expected_outputs_ = expected_outputs
   end procedure
 
+  module procedure double_precision_construct
+    input_output_pair%inputs_ = inputs
+    input_output_pair%expected_outputs_ = expected_outputs
+  end procedure
+
   module procedure default_real_inputs
     my_inputs = self%inputs_
   end procedure
 
+  module procedure double_precision_inputs
+    my_inputs = self%inputs_
+  end procedure
+
   module procedure default_real_expected_outputs
     my_expected_outputs = self%expected_outputs_
   end procedure
 
+  module procedure double_precision_expected_outputs
+    my_expected_outputs = self%expected_outputs_
+  end procedure
+
   module procedure default_real_shuffle
     type(input_output_pair_t) temp
     real harvest(2:size(pairs))
@@ -35,6 +48,23 @@
 
   end procedure
 
+  module procedure double_precision_shuffle
+    type(input_output_pair_t(double_precision)) temp
+    double precision harvest(2:size(pairs))
+    integer i, j
+
+    call random_number(harvest)
+
+    durstenfeld_shuffle: &
+    do i = size(pairs), 2, -1
+      j = 1 + int(harvest(i)*i)
+      temp     = pairs(i) 
+      pairs(i) = pairs(j)
+      pairs(j) = temp
+    end do durstenfeld_shuffle
+
+  end procedure
+
   module procedure default_real_write_to_stdout
     integer i
     do i = 1, size(input_output_pairs)
@@ -42,4 +72,11 @@
     end do
   end procedure
 
+  module procedure double_precision_write_to_stdout
+    integer i
+    do i = 1, size(input_output_pairs)
+      print *, input_output_pairs(i)%inputs_%values(), " | ", input_output_pairs(i)%expected_outputs_%values()
+    end do
+  end procedure
+
 end submodule input_output_pair_s

From 0e284a6a00a92af310e925d1020ab9f5e2e23d02 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 19:36:53 -0700
Subject: [PATCH 027/105] feat(mini_match): kind parameter +generic bindings

---
 src/inference_engine/mini_batch_m.f90 | 14 ++++++++------
 src/inference_engine/mini_batch_s.f90 |  4 ++--
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/inference_engine/mini_batch_m.f90 b/src/inference_engine/mini_batch_m.f90
index 43f5f63fa..196c002db 100644
--- a/src/inference_engine/mini_batch_m.f90
+++ b/src/inference_engine/mini_batch_m.f90
@@ -2,21 +2,23 @@
 ! Terms of use are as specified in LICENSE.txt
 module mini_batch_m
   use input_output_pair_m, only : input_output_pair_t
+  use kind_parameters_m, only : default_real
   implicit none
 
   private
   public :: mini_batch_t
 
-  type mini_batch_t
-    private
-    type(input_output_pair_t), allocatable :: input_output_pairs_(:)
+  type mini_batch_t(k)
+    integer, kind :: k = default_real
+    type(input_output_pair_t(k)), private, allocatable :: input_output_pairs_(:)
   contains
-    procedure :: input_output_pairs
+    generic :: input_output_pairs => default_real_input_output_pairs
+    procedure :: default_real_input_output_pairs
   end type
 
   interface mini_batch_t
 
-    pure module function construct(input_output_pairs) result(mini_batch)
+    pure module function default_real_construct(input_output_pairs) result(mini_batch)
       implicit none
       type(input_output_pair_t), intent(in) :: input_output_pairs(:)
       type(mini_batch_t) mini_batch
@@ -26,7 +28,7 @@ pure module function construct(input_output_pairs) result(mini_batch)
 
   interface
 
-    pure module function input_output_pairs(self) result(my_input_output_pairs)
+    pure module function default_real_input_output_pairs(self) result(my_input_output_pairs)
       implicit none
       class(mini_batch_t), intent(in) :: self
       type(input_output_pair_t), allocatable :: my_input_output_pairs(:)
diff --git a/src/inference_engine/mini_batch_s.f90 b/src/inference_engine/mini_batch_s.f90
index 57a4cc27c..08db6db48 100644
--- a/src/inference_engine/mini_batch_s.f90
+++ b/src/inference_engine/mini_batch_s.f90
@@ -5,11 +5,11 @@
 
 contains
 
-    module procedure construct
+    module procedure default_real_construct
       mini_batch%input_output_pairs_ = input_output_pairs
     end procedure
 
-    module procedure input_output_pairs
+    module procedure default_real_input_output_pairs
       my_input_output_pairs = self%input_output_pairs_
     end procedure
 

From 0ff2681b3a5f482087b8a7b930f87f09ef8a4c76 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 19:40:43 -0700
Subject: [PATCH 028/105] feat(mini_match): support double precision

---
 src/inference_engine/mini_batch_m.f90 | 18 +++++++++++++++---
 src/inference_engine/mini_batch_s.f90 |  8 ++++++++
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/src/inference_engine/mini_batch_m.f90 b/src/inference_engine/mini_batch_m.f90
index 196c002db..10fb0b567 100644
--- a/src/inference_engine/mini_batch_m.f90
+++ b/src/inference_engine/mini_batch_m.f90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module mini_batch_m
   use input_output_pair_m, only : input_output_pair_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   implicit none
 
   private
@@ -12,8 +12,8 @@ module mini_batch_m
     integer, kind :: k = default_real
     type(input_output_pair_t(k)), private, allocatable :: input_output_pairs_(:)
   contains
-    generic :: input_output_pairs => default_real_input_output_pairs
-    procedure :: default_real_input_output_pairs
+    generic :: input_output_pairs => default_real_input_output_pairs, double_precision_input_output_pairs
+    procedure ::                     default_real_input_output_pairs, double_precision_input_output_pairs
   end type
 
   interface mini_batch_t
@@ -24,6 +24,12 @@ pure module function default_real_construct(input_output_pairs) result(mini_batc
       type(mini_batch_t) mini_batch
     end function
 
+    pure module function double_precision_construct(input_output_pairs) result(mini_batch)
+      implicit none
+      type(input_output_pair_t(double_precision)), intent(in) :: input_output_pairs(:)
+      type(mini_batch_t(double_precision)) mini_batch
+    end function
+
   end interface
 
   interface
@@ -34,6 +40,12 @@ pure module function default_real_input_output_pairs(self) result(my_input_outpu
       type(input_output_pair_t), allocatable :: my_input_output_pairs(:)
     end function
 
+    pure module function double_precision_input_output_pairs(self) result(my_input_output_pairs)
+      implicit none
+      class(mini_batch_t(double_precision)), intent(in) :: self
+      type(input_output_pair_t(double_precision)), allocatable :: my_input_output_pairs(:)
+    end function
+
   end interface
 
 end module mini_batch_m
diff --git a/src/inference_engine/mini_batch_s.f90 b/src/inference_engine/mini_batch_s.f90
index 08db6db48..e4f126505 100644
--- a/src/inference_engine/mini_batch_s.f90
+++ b/src/inference_engine/mini_batch_s.f90
@@ -9,8 +9,16 @@
       mini_batch%input_output_pairs_ = input_output_pairs
     end procedure
 
+    module procedure double_precision_construct
+      mini_batch%input_output_pairs_ = input_output_pairs
+    end procedure
+
     module procedure default_real_input_output_pairs
       my_input_output_pairs = self%input_output_pairs_
     end procedure
 
+    module procedure double_precision_input_output_pairs
+      my_input_output_pairs = self%input_output_pairs_
+    end procedure
+
 end submodule mini_batch_s

From 7ea4e3bbf3cc2c2687a4a7ecd04fbe2e1a260054 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 31 Aug 2024 23:53:30 -0700
Subject: [PATCH 029/105] feat(hyperparameters): support double precision

also update julienne dependency to 1.2.0 for double-precsion value
extraction from JSON files.
---
 fpm.toml                                   |  2 +-
 src/inference_engine/hyperparameters_m.f90 | 96 ++++++++++++++++------
 src/inference_engine/hyperparameters_s.f90 | 87 ++++++++++++++++++--
 3 files changed, 151 insertions(+), 34 deletions(-)

diff --git a/fpm.toml b/fpm.toml
index e35768e80..033569f9d 100644
--- a/fpm.toml
+++ b/fpm.toml
@@ -8,4 +8,4 @@ maintainer = "rouson@lbl.gov"
 assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.7.0"}
 
 [dev-dependencies]
-julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.1.1"}
+julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.0"}
diff --git a/src/inference_engine/hyperparameters_m.f90 b/src/inference_engine/hyperparameters_m.f90
index 9e55dc376..e6d41a529 100644
--- a/src/inference_engine/hyperparameters_m.f90
+++ b/src/inference_engine/hyperparameters_m.f90
@@ -1,34 +1,44 @@
 module hyperparameters_m
   use julienne_string_m, only : string_t
-  use kind_parameters_m, only : rkind
+  use kind_parameters_m, only : default_real, double_precision
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
-
   private
   public :: hyperparameters_t
 
-  type hyperparameters_t
-    private
-    integer :: mini_batches_ = 10
-    real :: learning_rate_ = 1.5
+  type hyperparameters_t(k)
+    integer, kind :: k = default_real
+    integer, private:: mini_batches_ = 10
+    real(k), private :: learning_rate_ = 1.5_k
     character(len=:), allocatable :: optimizer_
   contains
-    procedure :: to_json
-    procedure :: equals
-    generic :: operator(==) => equals
-    procedure :: mini_batches
-    procedure :: optimizer_name
-    procedure :: learning_rate
-    end type
+    generic :: to_json => default_real_to_json, double_precision_to_json
+    procedure, private :: default_real_to_json, double_precision_to_json
+    generic :: operator(==) => default_real_equals, double_precision_equals
+    procedure, private ::      default_real_equals, double_precision_equals
+    generic :: mini_batches => default_real_mini_batches, double_precision_mini_batches
+    procedure, private ::      default_real_mini_batches, double_precision_mini_batches
+    generic :: optimizer_name => default_real_optimizer_name, double_precision_optimizer_name
+    procedure, private ::        default_real_optimizer_name, double_precision_optimizer_name
+    generic :: learning_rate => default_real_learning_rate, double_precision_learning_rate
+    procedure, private ::       default_real_learning_rate, double_precision_learning_rate
+  end type
 
   interface hyperparameters_t
 
-    pure module function from_json(lines) result(hyperparameters)
+    pure module function default_real_from_json(lines) result(hyperparameters)
       implicit none
       type(string_t), intent(in) :: lines(:)
       type(hyperparameters_t) hyperparameters
     end function
 
-    pure module function from_components(mini_batches, learning_rate, optimizer) result(hyperparameters)
+    pure module function double_precision_from_json(lines) result(hyperparameters)
+      implicit none
+      type(double_precision_string_t), intent(in) :: lines(:)
+      type(hyperparameters_t(double_precision)) hyperparameters
+    end function
+
+    pure module function default_real_from_components(mini_batches, learning_rate, optimizer) result(hyperparameters)
       implicit none
       integer, intent(in) :: mini_batches
       real, intent(in) :: learning_rate
@@ -36,40 +46,78 @@ pure module function from_components(mini_batches, learning_rate, optimizer) res
       type(hyperparameters_t) hyperparameters
     end function
 
+    pure module function double_precision_from_components(mini_batches, learning_rate, optimizer) result(hyperparameters)
+      implicit none
+      integer, intent(in) :: mini_batches
+      double precision, intent(in) :: learning_rate
+      character(len=*), intent(in) :: optimizer
+      type(hyperparameters_t(double_precision)) hyperparameters
+    end function
+
   end interface
 
   interface
 
-    pure module function to_json(self) result(lines)
+    pure module function default_real_to_json(self) result(lines)
       implicit none
       class(hyperparameters_t), intent(in) :: self
       type(string_t), allocatable :: lines(:)
     end function
 
-    elemental module function equals(lhs, rhs) result(lhs_equals_rhs)
+    pure module function double_precision_to_json(self) result(lines)
+      implicit none
+      class(hyperparameters_t(double_precision)), intent(in) :: self
+      type(string_t), allocatable :: lines(:)
+    end function
+
+    elemental module function default_real_equals(lhs, rhs) result(lhs_equals_rhs)
       implicit none
       class(hyperparameters_t), intent(in) :: lhs, rhs
       logical lhs_equals_rhs
     end function
 
-    elemental module function mini_batches(self) result(num_mini_batches)
+    elemental module function double_precision_equals(lhs, rhs) result(lhs_equals_rhs)
+      implicit none
+      class(hyperparameters_t(double_precision)), intent(in) :: lhs, rhs
+      logical lhs_equals_rhs
+    end function
+
+    elemental module function default_real_mini_batches(self) result(num_mini_batches)
       implicit none
       class(hyperparameters_t), intent(in) :: self
       integer num_mini_batches
     end function
 
-    elemental module function optimizer_name(self) result(identifier)
+    elemental module function double_precision_mini_batches(self) result(num_mini_batches)
+      implicit none
+      class(hyperparameters_t(double_precision)), intent(in) :: self
+      integer num_mini_batches
+    end function
+
+     elemental module function default_real_learning_rate(self) result(rate)
+       implicit none
+       class(hyperparameters_t), intent(in) :: self
+       real rate
+     end function
+
+     elemental module function double_precision_learning_rate(self) result(rate)
+       implicit none
+       class(hyperparameters_t(double_precision)), intent(in) :: self
+       double precision rate
+     end function
+
+    elemental module function default_real_optimizer_name(self) result(identifier)
       implicit none
       class(hyperparameters_t), intent(in) :: self
       type(string_t) identifier
      end function
 
-
-     elemental module function learning_rate(self) result(rate)
-       implicit none
-       class(hyperparameters_t), intent(in) :: self
-       real(rkind) rate
+    elemental module function double_precision_optimizer_name(self) result(identifier)
+      implicit none
+      class(hyperparameters_t(double_precision)), intent(in) :: self
+      type(string_t) identifier
      end function
+
   end interface
 
 end module
diff --git a/src/inference_engine/hyperparameters_s.f90 b/src/inference_engine/hyperparameters_s.f90
index f6b442008..229d1d637 100644
--- a/src/inference_engine/hyperparameters_s.f90
+++ b/src/inference_engine/hyperparameters_s.f90
@@ -8,17 +8,38 @@
 
 contains
 
-  module procedure from_components
+  module procedure default_real_from_components
     hyperparameters%mini_batches_ = mini_batches
     hyperparameters%learning_rate_ = learning_rate
     hyperparameters%optimizer_ = optimizer
   end procedure 
 
-  module procedure equals
+  module procedure double_precision_from_components
+    hyperparameters%mini_batches_ = mini_batches
+    hyperparameters%learning_rate_ = learning_rate
+    hyperparameters%optimizer_ = optimizer
+  end procedure
+
+  module procedure default_real_equals
 
     real, parameter :: tolerance = 1.E-08
 
-    call assert(allocated(lhs%optimizer_) .and. allocated(rhs%optimizer_), "hyperparameters_s(equals): allocated optimizers")
+    call assert(allocated(lhs%optimizer_) .and. allocated(rhs%optimizer_), &
+      "hyperparameters_s(default_real_equals): allocated optimizers")
+
+    lhs_equals_rhs = &
+      lhs%mini_batches_ == rhs%mini_batches_ .and. &
+      lhs%optimizer_ == rhs%optimizer_ .and. &
+      abs(lhs%learning_rate_ - rhs%learning_rate_) <= tolerance
+
+  end procedure
+
+  module procedure double_precision_equals
+
+    double precision, parameter :: tolerance = 1.D-15
+
+    call assert(allocated(lhs%optimizer_) .and. allocated(rhs%optimizer_), &
+      "hyperparameters_s(double_precisionequals): allocated optimizers")
 
     lhs_equals_rhs = &
       lhs%mini_batches_ == rhs%mini_batches_ .and. &
@@ -27,7 +48,7 @@
      
   end procedure 
 
-  module procedure from_json
+  module procedure default_real_from_json
     integer l
     logical hyperparameters_key_found 
 
@@ -43,10 +64,29 @@
       end if
     end do
 
-    call assert(hyperparameters_key_found, "hyperparameters_s(from_json): hyperparameters_found")
+    call assert(hyperparameters_key_found, "hyperparameters_s(default_real_from_json): hyperparameters_found")
   end procedure
 
-  module procedure to_json
+  module procedure double_precision_from_json
+    integer l
+    logical hyperparameters_key_found 
+
+    hyperparameters_key_found = .false.
+
+    do l=1,size(lines)
+      if (lines(l)%get_json_key() == "hyperparameters") then
+        hyperparameters_key_found = .true.
+        hyperparameters%mini_batches_  = lines(l+1)%get_json_value(string_t(mini_batches_key), mold=0)
+        hyperparameters%learning_rate_ = lines(l+2)%get_json_value(string_t(learning_rate_key), mold=0.D0)
+        hyperparameters%optimizer_ = lines(l+3)%get_json_value(string_t(optimizer_key), mold=string_t(""))
+        return
+      end if
+    end do
+
+    call assert(hyperparameters_key_found, "hyperparameters_s(double_precision_from_json): hyperparameters_found")
+  end procedure
+
+  module procedure default_real_to_json
     character(len=*), parameter :: indent = repeat(" ",ncopies=4)
     integer, parameter :: max_width= 18
     character(len=max_width) mini_batches_string, learning_rate_string
@@ -63,15 +103,44 @@
     ]
   end procedure
 
-  module procedure mini_batches
+  module procedure double_precision_to_json
+    character(len=*), parameter :: indent = repeat(" ",ncopies=4)
+    integer, parameter :: max_width= 36
+    character(len=max_width) mini_batches_string, learning_rate_string
+
+    write(mini_batches_string,*) self%mini_batches_
+    write(learning_rate_string,*) self%learning_rate_
+
+    lines = [ &
+      string_t(indent // '"hyperparameters": {'), &
+      string_t(indent // indent // '"' // mini_batches_key  // '" : '  // trim(adjustl(mini_batches_string))  // "," ), &
+      string_t(indent // indent // '"' // learning_rate_key // '" : '  // trim(adjustl(learning_rate_string)) // "," ), &
+      string_t(indent // indent // '"' // optimizer_key     // '" : "' // trim(adjustl(self%optimizer_     )) // '"'), &
+      string_t(indent // '}') &
+    ]
+  end procedure
+
+  module procedure default_real_mini_batches
+    num_mini_batches = self%mini_batches_
+  end procedure
+
+  module procedure double_precision_mini_batches
     num_mini_batches = self%mini_batches_
   end procedure
 
-  module procedure optimizer_name
+  module procedure default_real_optimizer_name
     identifier = string_t(self%optimizer_)
   end procedure
 
-  module procedure learning_rate
+  module procedure double_precision_optimizer_name
+    identifier = string_t(self%optimizer_)
+  end procedure
+
+  module procedure default_real_learning_rate
+    rate = self%learning_rate_
+  end procedure
+
+  module procedure double_precision_learning_rate
     rate = self%learning_rate_
   end procedure
 

From d3635876211b8f35a80fb4af116c9e8969ccd17f Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 00:36:08 -0700
Subject: [PATCH 030/105] fix(tensor_t): set component kind parameter

---
 src/inference_engine/tensor_m.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/inference_engine/tensor_m.f90 b/src/inference_engine/tensor_m.f90
index bea936ad3..859331f1c 100644
--- a/src/inference_engine/tensor_m.f90
+++ b/src/inference_engine/tensor_m.f90
@@ -9,7 +9,7 @@ module tensor_m
 
   type tensor_t(k)
     integer, kind :: k = default_real 
-    real, allocatable, private :: values_(:)
+    real(k), allocatable, private :: values_(:)
   contains
     generic   :: values => default_real_values, double_precision_values
     procedure, private ::  default_real_values, double_precision_values

From d3da4e64c59f4cd3c0633e1eeeacf93801d37bd6 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 06:52:35 -0700
Subject: [PATCH 031/105] feat(training_configuration_t): add kind parameter

---
 src/inference_engine/training_configuration_m.f90 | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/inference_engine/training_configuration_m.f90 b/src/inference_engine/training_configuration_m.f90
index 7805675a3..552864630 100644
--- a/src/inference_engine/training_configuration_m.f90
+++ b/src/inference_engine/training_configuration_m.f90
@@ -3,17 +3,17 @@ module training_configuration_m
   use julienne_file_m, only : file_t
   use hyperparameters_m, only : hyperparameters_t
   use network_configuration_m, only : network_configuration_t
-  use kind_parameters_m, only  : rkind
+  use kind_parameters_m, only  : default_real
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
   implicit none
 
   private
   public :: training_configuration_t
 
-  type, extends(file_t) :: training_configuration_t
-    private
-    type(hyperparameters_t) hyperparameters_
-    type(network_configuration_t) network_configuration_
+  type, extends(file_t) :: training_configuration_t(k)
+    integer, kind :: k = default_real
+    type(hyperparameters_t(k)),    private :: hyperparameters_
+    type(network_configuration_t), private :: network_configuration_
   contains
     procedure :: to_json
     procedure :: equals
@@ -72,7 +72,7 @@ elemental module function optimizer_name(self) result(identifier)
     elemental module function learning_rate(self) result(rate)
       implicit none
       class(training_configuration_t), intent(in) :: self
-      real(rkind) rate
+      real rate
     end function
  
     module function differentiable_activation_strategy(self) result(strategy)

From 0d6da762493f1a7a530630e232b59c78e1ca120f Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 06:59:23 -0700
Subject: [PATCH 032/105] refac(training_configuration): mk bindings generic

---
 .../training_configuration_m.f90              | 45 +++++++++++--------
 .../training_configuration_s.F90              | 20 ++++-----
 2 files changed, 36 insertions(+), 29 deletions(-)

diff --git a/src/inference_engine/training_configuration_m.f90 b/src/inference_engine/training_configuration_m.f90
index 552864630..3f658b210 100644
--- a/src/inference_engine/training_configuration_m.f90
+++ b/src/inference_engine/training_configuration_m.f90
@@ -15,27 +15,34 @@ module training_configuration_m
     type(hyperparameters_t(k)),    private :: hyperparameters_
     type(network_configuration_t), private :: network_configuration_
   contains
-    procedure :: to_json
-    procedure :: equals
-    generic :: operator(==) => equals
-    procedure :: mini_batches
-    procedure :: optimizer_name
-    procedure :: learning_rate
-    procedure :: differentiable_activation_strategy
-    procedure :: nodes_per_layer
-    procedure :: skip_connections
+    generic :: operator(==) => default_real_equals
+    procedure ::               default_real_equals
+    generic :: to_json => default_real_to_json
+    procedure, private :: default_real_to_json
+    generic :: mini_batches => default_real_mini_batches
+    procedure, private ::      default_real_mini_batches
+    generic :: optimizer_name => default_real_optimizer_name
+    procedure, private ::        default_real_optimizer_name
+    generic :: learning_rate => default_real_learning_rate
+    procedure, private ::       default_real_learning_rate
+    generic :: differentiable_activation_strategy => default_real_differentiable_activation_strategy
+    procedure, private ::                            default_real_differentiable_activation_strategy
+    generic :: nodes_per_layer => default_real_nodes_per_layer
+    procedure, private ::         default_real_nodes_per_layer
+    generic :: skip_connections => default_real_skip_connections
+    procedure, private ::          default_real_skip_connections
   end type
 
   interface training_configuration_t
 
-    module function from_components(hyperparameters, network_configuration) result(training_configuration)
+    module function default_real_from_components(hyperparameters, network_configuration) result(training_configuration)
       implicit none
       type(hyperparameters_t), intent(in) :: hyperparameters
       type(network_configuration_t), intent(in) :: network_configuration
       type(training_configuration_t) training_configuration
     end function
 
-    module function from_file(file_object) result(training_configuration)
+    module function default_real_from_file(file_object) result(training_configuration)
       implicit none
       type(file_t), intent(in) :: file_object
       type(training_configuration_t) training_configuration
@@ -45,49 +52,49 @@ module function from_file(file_object) result(training_configuration)
 
   interface
 
-    pure module function to_json(self) result(json_lines)
+    pure module function default_real_to_json(self) result(json_lines)
       implicit none
       class(training_configuration_t), intent(in) :: self
       type(string_t), allocatable :: json_lines(:)
     end function
 
-    elemental module function equals(lhs, rhs) result(lhs_eq_rhs)
+    elemental module function default_real_equals(lhs, rhs) result(lhs_eq_rhs)
       implicit none
       class(training_configuration_t), intent(in) :: lhs, rhs
       logical lhs_eq_rhs
     end function
 
-    elemental module function mini_batches(self) result(num_mini_batches)
+    elemental module function default_real_mini_batches(self) result(num_mini_batches)
       implicit none
       class(training_configuration_t), intent(in) :: self
       integer num_mini_batches
     end function
 
-    elemental module function optimizer_name(self) result(identifier)
+    elemental module function default_real_optimizer_name(self) result(identifier)
       implicit none
       class(training_configuration_t), intent(in) :: self
       type(string_t) identifier
     end function
 
-    elemental module function learning_rate(self) result(rate)
+    elemental module function default_real_learning_rate(self) result(rate)
       implicit none
       class(training_configuration_t), intent(in) :: self
       real rate
     end function
  
-    module function differentiable_activation_strategy(self) result(strategy)
+    module function default_real_differentiable_activation_strategy(self) result(strategy)
       implicit none
       class(training_configuration_t), intent(in) :: self
       class(differentiable_activation_strategy_t), allocatable :: strategy
     end function
  
-    pure module function nodes_per_layer(self) result(nodes)
+    pure module function default_real_nodes_per_layer(self) result(nodes)
       implicit none
       class(training_configuration_t), intent(in) :: self
       integer, allocatable :: nodes(:)
     end function
  
-    elemental module function skip_connections(self) result(using_skip)
+    elemental module function default_real_skip_connections(self) result(using_skip)
       implicit none
       class(training_configuration_t), intent(in) :: self
       logical using_skip
diff --git a/src/inference_engine/training_configuration_s.F90 b/src/inference_engine/training_configuration_s.F90
index 3ffbe1882..9d73d8b0f 100644
--- a/src/inference_engine/training_configuration_s.F90
+++ b/src/inference_engine/training_configuration_s.F90
@@ -7,7 +7,7 @@
 
 contains
 
-  module procedure from_components
+  module procedure default_real_from_components
 
     training_configuration%hyperparameters_ = hyperparameters
     training_configuration%network_configuration_ = network_configuration
@@ -20,7 +20,7 @@
     ])
   end procedure
 
-  module procedure from_file
+  module procedure default_real_from_file
     integer, parameter :: hyperparameters_start=2, hyperparameters_end=6, separator_line=7   ! line numbers
     integer, parameter :: net_config_start=8, net_config_end=12                         ! line numbers
     integer, parameter :: file_start=hyperparameters_start-1, file_end=net_config_end+1 ! line numbers
@@ -48,37 +48,37 @@
 
   end procedure
 
-  module procedure to_json
+  module procedure default_real_to_json
     json_lines = self%lines()
   end procedure
 
-  module procedure equals
+  module procedure default_real_equals
     lhs_eq_rhs = &
       lhs%hyperparameters_ == rhs%hyperparameters_ .and. &
       lhs%network_configuration_ == rhs%network_configuration_
   end procedure
 
-  module procedure mini_batches
+  module procedure default_real_mini_batches
     num_mini_batches = self%hyperparameters_%mini_batches()
   end procedure
 
-  module procedure optimizer_name
+  module procedure default_real_optimizer_name
     identifier = self%hyperparameters_%optimizer_name()
   end procedure
 
-  module procedure learning_rate
+  module procedure default_real_learning_rate
     rate = self%hyperparameters_%learning_rate()
   end procedure
 
-  module procedure nodes_per_layer
+  module procedure default_real_nodes_per_layer
     nodes = self%network_configuration_%nodes_per_layer()
   end procedure
 
-  module procedure skip_connections
+  module procedure default_real_skip_connections
     using_skip = self%network_configuration_%skip_connections()
   end procedure
 
-  module procedure differentiable_activation_strategy
+  module procedure default_real_differentiable_activation_strategy
 #if defined __INTEL_COMPILER || _CRAYFTN
     type(string_t) :: activation_name
     activation_name = self%network_configuration_%activation_name()

From 533ce135a39c5cc5fe8647bee0b476d0469b7ad1 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 14:01:53 -0700
Subject: [PATCH 033/105] feat(dble_prec_file):extend file_t, produce dp_str

---
 .../double_precision_file_m.f90               |  22 +++
 .../double_precision_file_s.f90               |  10 ++
 .../double_precision_string_m.f90             |  10 ++
 .../double_precision_string_s.f90             |   9 ++
 .../network_configuration_m.f90               |   7 +
 .../network_configuration_s.F90               |  20 +++
 .../training_configuration_m.f90              | 100 ++++++++++---
 .../training_configuration_s.F90              | 134 +++++++++++++++++-
 8 files changed, 290 insertions(+), 22 deletions(-)
 create mode 100644 src/inference_engine/double_precision_file_m.f90
 create mode 100644 src/inference_engine/double_precision_file_s.f90
 create mode 100644 src/inference_engine/double_precision_string_s.f90

diff --git a/src/inference_engine/double_precision_file_m.f90 b/src/inference_engine/double_precision_file_m.f90
new file mode 100644
index 000000000..90b50cd98
--- /dev/null
+++ b/src/inference_engine/double_precision_file_m.f90
@@ -0,0 +1,22 @@
+module double_precision_file_m
+  use julienne_m, only : file_t
+  use double_precision_string_m, only : double_precision_string_t
+
+  implicit none
+
+  type, extends(file_t) :: double_precision_file_t
+  contains
+    procedure double_precision_lines
+  end type
+
+  interface
+
+    module function double_precision_lines(self) result(lines)
+      implicit none
+      class(double_precision_file_t), intent(in) :: self
+      type(double_precision_string_t), allocatable :: lines(:)
+    end function
+
+  end interface
+
+end module double_precision_file_m
diff --git a/src/inference_engine/double_precision_file_s.f90 b/src/inference_engine/double_precision_file_s.f90
new file mode 100644
index 000000000..9d8eec923
--- /dev/null
+++ b/src/inference_engine/double_precision_file_s.f90
@@ -0,0 +1,10 @@
+submodule(double_precision_file_m) double_precision_file_s
+  implicit none
+
+contains
+
+  module procedure double_precision_lines
+    lines = double_precision_string_t(self%file_t%lines())
+  end procedure
+
+end submodule double_precision_file_s
diff --git a/src/inference_engine/double_precision_string_m.f90 b/src/inference_engine/double_precision_string_m.f90
index 54edde09d..14f0cdea0 100644
--- a/src/inference_engine/double_precision_string_m.f90
+++ b/src/inference_engine/double_precision_string_m.f90
@@ -5,4 +5,14 @@ module double_precision_string_m
   type, extends(string_t) :: double_precision_string_t
   end type
 
+  interface double_precision_string_t
+
+    elemental module function construct_from_string(string) result(double_precision_string)
+      implicit none
+      type(string_t), intent(in) :: string
+      type(double_precision_string_t) double_precision_string
+    end function
+
+  end interface
+
 end module double_precision_string_m
diff --git a/src/inference_engine/double_precision_string_s.f90 b/src/inference_engine/double_precision_string_s.f90
new file mode 100644
index 000000000..9e4ff72d3
--- /dev/null
+++ b/src/inference_engine/double_precision_string_s.f90
@@ -0,0 +1,9 @@
+submodule(double_precision_string_m)double_precision_string_s
+  implicit none
+contains
+
+    module procedure construct_from_string
+      double_precision_string%string_t = string
+    end procedure
+
+end submodule double_precision_string_s
diff --git a/src/inference_engine/network_configuration_m.f90 b/src/inference_engine/network_configuration_m.f90
index 453c912d1..5d2f34cc5 100644
--- a/src/inference_engine/network_configuration_m.f90
+++ b/src/inference_engine/network_configuration_m.f90
@@ -1,5 +1,6 @@
 module network_configuration_m
   use julienne_string_m, only : string_t
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
 
   private
@@ -27,6 +28,12 @@ pure module function from_json(lines) result(network_configuration)
       type(network_configuration_t) network_configuration
     end function
 
+    pure module function from_double_precision_string_json(lines) result(network_configuration)
+      implicit none
+      type(double_precision_string_t), intent(in) :: lines(:)
+      type(network_configuration_t) network_configuration
+    end function
+
     pure module function from_components(skip_connections, nodes_per_layer, activation_name) result(network_configuration)
       implicit none
       logical, intent(in) :: skip_connections
diff --git a/src/inference_engine/network_configuration_s.F90 b/src/inference_engine/network_configuration_s.F90
index ef6ff7f69..8100d9fa1 100644
--- a/src/inference_engine/network_configuration_s.F90
+++ b/src/inference_engine/network_configuration_s.F90
@@ -46,6 +46,26 @@
     call assert(network_configuration_key_found, "network_configuration_s(from_json): network_configuration_found")
   end procedure
 
+  module procedure from_double_precision_string_json
+    integer l
+    logical network_configuration_key_found
+
+    network_configuration_key_found = .false.
+
+    do l=1,size(lines)
+      if (lines(l)%get_json_key() == "network configuration") then
+        network_configuration_key_found = .true.
+        network_configuration%skip_connections_  = lines(l+1)%get_json_value(string_t(skip_connections_key), mold=.true.)
+        network_configuration%nodes_per_layer_ = lines(l+2)%get_json_value(string_t(nodes_per_layer_key), mold=[integer::])
+        network_configuration%activation_name_ = lines(l+3)%get_json_value(string_t(activation_name_key), mold=string_t(""))
+        return
+      end if
+    end do
+
+    call assert(network_configuration_key_found, &
+      "network_configuration_s(from_double_precision_string_json): network_configuration_found")
+  end procedure
+
   module procedure to_json
     character(len=*), parameter :: indent = repeat(" ",ncopies=4)
     integer, parameter :: max_logical_width= 6, char_per_elem = 10, brackets = 2
diff --git a/src/inference_engine/training_configuration_m.f90 b/src/inference_engine/training_configuration_m.f90
index 3f658b210..2c610b295 100644
--- a/src/inference_engine/training_configuration_m.f90
+++ b/src/inference_engine/training_configuration_m.f90
@@ -3,34 +3,37 @@ module training_configuration_m
   use julienne_file_m, only : file_t
   use hyperparameters_m, only : hyperparameters_t
   use network_configuration_m, only : network_configuration_t
-  use kind_parameters_m, only  : default_real
+  use kind_parameters_m, only  : default_real, double_precision
   use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
+  use double_precision_file_m, only : double_precision_file_t
   implicit none
 
   private
   public :: training_configuration_t
 
-  type, extends(file_t) :: training_configuration_t(k)
+  type, extends(double_precision_file_t) :: training_configuration_t(k)
     integer, kind :: k = default_real
     type(hyperparameters_t(k)),    private :: hyperparameters_
     type(network_configuration_t), private :: network_configuration_
   contains
-    generic :: operator(==) => default_real_equals
-    procedure ::               default_real_equals
-    generic :: to_json => default_real_to_json
-    procedure, private :: default_real_to_json
-    generic :: mini_batches => default_real_mini_batches
-    procedure, private ::      default_real_mini_batches
-    generic :: optimizer_name => default_real_optimizer_name
-    procedure, private ::        default_real_optimizer_name
-    generic :: learning_rate => default_real_learning_rate
-    procedure, private ::       default_real_learning_rate
-    generic :: differentiable_activation_strategy => default_real_differentiable_activation_strategy
-    procedure, private ::                            default_real_differentiable_activation_strategy
-    generic :: nodes_per_layer => default_real_nodes_per_layer
-    procedure, private ::         default_real_nodes_per_layer
-    generic :: skip_connections => default_real_skip_connections
-    procedure, private ::          default_real_skip_connections
+    generic :: operator(==) => default_real_equals, double_precision_equals
+    procedure, private ::      default_real_equals, double_precision_equals
+    generic :: to_json => default_real_to_json, double_precision_to_json
+    procedure, private :: default_real_to_json, double_precision_to_json
+    generic :: mini_batches => default_real_mini_batches, double_precision_mini_batches
+    procedure, private ::      default_real_mini_batches, double_precision_mini_batches
+    generic :: optimizer_name => default_real_optimizer_name, double_precision_optimizer_name
+    procedure, private ::        default_real_optimizer_name, double_precision_optimizer_name
+    generic :: learning_rate => default_real_learning_rate, double_precision_learning_rate
+    procedure, private ::       default_real_learning_rate, double_precision_learning_rate
+    generic :: differentiable_activation_strategy => &
+      default_real_differentiable_activation_strategy, double_precision_differentiable_activation_strategy
+    procedure, private :: &
+      default_real_differentiable_activation_strategy, double_precision_differentiable_activation_strategy
+    generic :: nodes_per_layer => default_real_nodes_per_layer, double_precision_nodes_per_layer
+    procedure, private ::         default_real_nodes_per_layer, double_precision_nodes_per_layer
+    generic :: skip_connections => default_real_skip_connections, double_precision_skip_connections
+    procedure, private ::          default_real_skip_connections, double_precision_skip_connections
   end type
 
   interface training_configuration_t
@@ -42,12 +45,25 @@ module function default_real_from_components(hyperparameters, network_configurat
       type(training_configuration_t) training_configuration
     end function
 
+    module function double_precision_from_components(hyperparameters, network_configuration) result(training_configuration)
+      implicit none
+      type(hyperparameters_t(double_precision)), intent(in) :: hyperparameters
+      type(network_configuration_t), intent(in) :: network_configuration
+      type(training_configuration_t(double_precision)) training_configuration
+    end function
+
     module function default_real_from_file(file_object) result(training_configuration)
       implicit none
       type(file_t), intent(in) :: file_object
       type(training_configuration_t) training_configuration
     end function
 
+    module function double_precision_from_file(file_object) result(training_configuration)
+      implicit none
+      type(double_precision_file_t), intent(in) :: file_object
+      type(training_configuration_t(double_precision)) training_configuration
+    end function
+
   end interface
 
   interface
@@ -58,47 +74,95 @@ pure module function default_real_to_json(self) result(json_lines)
       type(string_t), allocatable :: json_lines(:)
     end function
 
+    pure module function double_precision_to_json(self) result(json_lines)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      type(string_t), allocatable :: json_lines(:)
+    end function
+
     elemental module function default_real_equals(lhs, rhs) result(lhs_eq_rhs)
       implicit none
       class(training_configuration_t), intent(in) :: lhs, rhs
       logical lhs_eq_rhs
     end function
 
+    elemental module function double_precision_equals(lhs, rhs) result(lhs_eq_rhs)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: lhs, rhs
+      logical lhs_eq_rhs
+    end function
+
     elemental module function default_real_mini_batches(self) result(num_mini_batches)
       implicit none
       class(training_configuration_t), intent(in) :: self
       integer num_mini_batches
     end function
 
+    elemental module function double_precision_mini_batches(self) result(num_mini_batches)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      integer num_mini_batches
+    end function
+
     elemental module function default_real_optimizer_name(self) result(identifier)
       implicit none
       class(training_configuration_t), intent(in) :: self
       type(string_t) identifier
     end function
 
+    elemental module function double_precision_optimizer_name(self) result(identifier)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      type(string_t) identifier
+    end function
+
     elemental module function default_real_learning_rate(self) result(rate)
       implicit none
       class(training_configuration_t), intent(in) :: self
       real rate
     end function
  
+    elemental module function double_precision_learning_rate(self) result(rate)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      double precision rate
+    end function
+
     module function default_real_differentiable_activation_strategy(self) result(strategy)
       implicit none
       class(training_configuration_t), intent(in) :: self
       class(differentiable_activation_strategy_t), allocatable :: strategy
     end function
+
+    module function double_precision_differentiable_activation_strategy(self) result(strategy)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      class(differentiable_activation_strategy_t), allocatable :: strategy
+    end function
  
     pure module function default_real_nodes_per_layer(self) result(nodes)
       implicit none
       class(training_configuration_t), intent(in) :: self
       integer, allocatable :: nodes(:)
     end function
+
+    pure module function double_precision_nodes_per_layer(self) result(nodes)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      integer, allocatable :: nodes(:)
+    end function
  
     elemental module function default_real_skip_connections(self) result(using_skip)
       implicit none
       class(training_configuration_t), intent(in) :: self
       logical using_skip
     end function
+
+    elemental module function double_precision_skip_connections(self) result(using_skip)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      logical using_skip
+    end function
  
   end interface
 
diff --git a/src/inference_engine/training_configuration_s.F90 b/src/inference_engine/training_configuration_s.F90
index 9d73d8b0f..9a9651e76 100644
--- a/src/inference_engine/training_configuration_s.F90
+++ b/src/inference_engine/training_configuration_s.F90
@@ -1,6 +1,7 @@
 submodule(training_configuration_m) training_configuration_s
   use assert_m, only : assert
   use inference_engine_m, only : gelu_t, relu_t, sigmoid_t, swish_t
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
 
   character(len=*), parameter :: header="{", footer="}", separator = ","
@@ -20,6 +21,19 @@
     ])
   end procedure
 
+  module procedure double_precision_from_components
+
+    training_configuration%hyperparameters_ = hyperparameters
+    training_configuration%network_configuration_ = network_configuration
+    training_configuration%file_t = file_t([ &
+      string_t(header), &
+      training_configuration%hyperparameters_%to_json(), &
+      string_t(separator), &
+      training_configuration%network_configuration_%to_json(), &
+      string_t(footer) &
+    ])
+  end procedure
+
   module procedure default_real_from_file
     integer, parameter :: hyperparameters_start=2, hyperparameters_end=6, separator_line=7   ! line numbers
     integer, parameter :: net_config_start=8, net_config_end=12                         ! line numbers
@@ -32,18 +46,75 @@
 
 #if defined __INTEL_COMPILER || _CRAYFTN
     lines = training_configuration%file_t%lines()
+    call assert(trim(adjustl(lines(file_start)%string()))==header, &
+      "training_configuration_s(default_precision_from_file): header",lines(file_start))
+    training_configuration%hyperparameters_ = hyperparameters_t(lines(hyperparameters_start:hyperparameters_end))
+    call assert(trim(adjustl(lines(separator_line)%string()))==separator, &
+      "training_configuration_s(default_precision_from_file): separator", &
+      lines(file_start))
+    training_configuration%network_configuration_= network_configuration_t(lines(net_config_start:net_config_end))
+    call assert(trim(adjustl(lines(file_end)%string()))==footer, &
+      "training_configuration_s(default_precision_from_file): footer", lines(file_end))
 #else
     associate(lines => training_configuration%file_t%lines())
-#endif
-      call assert(trim(adjustl(lines(file_start)%string()))==header,"training_configuration_s(from_file): header",lines(file_start))
+      call assert(trim(adjustl(lines(file_start)%string()))==header, &
+        "training_configuration_s(default_precision_from_file): header",lines(file_start))
       training_configuration%hyperparameters_ = hyperparameters_t(lines(hyperparameters_start:hyperparameters_end))
-      call assert(trim(adjustl(lines(separator_line)%string()))==separator,"training_configuration_s(from_file): separator", &
+      call assert(trim(adjustl(lines(separator_line)%string()))==separator, &
+        "training_configuration_s(default_precision_from_file): separator", &
         lines(file_start))
       training_configuration%network_configuration_= network_configuration_t(lines(net_config_start:net_config_end))
-      call assert(trim(adjustl(lines(file_end)%string()))==footer, "training_configuration_s(from_file): footer", lines(file_end))
+      call assert(trim(adjustl(lines(file_end)%string()))==footer, &
+        "training_configuration_s(default_precision_from_file): footer", lines(file_end))
+    end associate
+#endif
+
+  end procedure
+
+  module procedure double_precision_from_file
+    integer, parameter :: hyperparameters_start=2, hyperparameters_end=6, separator_line=7   ! line numbers
+    integer, parameter :: net_config_start=8, net_config_end=12                         ! line numbers
+    integer, parameter :: file_start=hyperparameters_start-1, file_end=net_config_end+1 ! line numbers
+#if defined __INTEL_COMPILER || _CRAYFTN
+    type(double_precision_string_t), allocatable :: lines(:)
+#endif
+
+    training_configuration%double_precision_file_t = file_object
+
 #if defined __INTEL_COMPILER || _CRAYFTN
+    lines = training_configuration%double_precision_file_t%double_precision_lines()
+
+    call assert(adjustl(lines(file_start)%string()) == header, &
+      "training_configuration_s(double_precision_from_file): header",lines(file_start))
+
+    training_configuration%hyperparameters_ = hyperparameters_t(lines(hyperparameters_start:hyperparameters_end))
+
+    call assert(adjustl(lines(separator_line)%string()) == separator, &
+      "training_configuration_s(double_precision_from_file): separator", lines(file_start))
+
+    training_configuration%network_configuration_= network_configuration_t(lines(net_config_start:net_config_end))
+
+    call assert(adjustl(lines(file_end)%string()) == footer, &
+      "training_configuration_s(double_precision_from_file): footer", lines(file_end))
 #else
+
+    associate(lines => training_configuration%double_precision_file_t%double_precision_lines())
+
+      call assert(adjustl(lines(file_start)%string()) == header, &
+        "training_configuration_s(double_precision_from_file): header", lines(file_start))
+
+      training_configuration%hyperparameters_ = hyperparameters_t(lines(hyperparameters_start:hyperparameters_end))
+
+      call assert(adjustl(lines(separator_line)%string()) == separator, &
+        "training_configuration_s(double_precision_from_file): separator", lines(file_start))
+
+      training_configuration%network_configuration_= network_configuration_t(lines(net_config_start:net_config_end))
+
+      call assert(adjustl(lines(file_end)%string()) == footer, &
+        "training_configuration_s(double_precision_from_file): footer", lines(file_end))
+
     end associate
+
 #endif
 
   end procedure
@@ -52,32 +123,62 @@
     json_lines = self%lines()
   end procedure
 
+  module procedure double_precision_to_json
+    json_lines = self%lines()
+  end procedure
+
   module procedure default_real_equals
     lhs_eq_rhs = &
       lhs%hyperparameters_ == rhs%hyperparameters_ .and. &
       lhs%network_configuration_ == rhs%network_configuration_
   end procedure
 
+  module procedure double_precision_equals
+    lhs_eq_rhs = &
+      lhs%hyperparameters_ == rhs%hyperparameters_ .and. &
+      lhs%network_configuration_ == rhs%network_configuration_
+  end procedure
+
   module procedure default_real_mini_batches
     num_mini_batches = self%hyperparameters_%mini_batches()
   end procedure
 
+  module procedure double_precision_mini_batches
+    num_mini_batches = self%hyperparameters_%mini_batches()
+  end procedure
+
   module procedure default_real_optimizer_name
     identifier = self%hyperparameters_%optimizer_name()
   end procedure
 
+  module procedure double_precision_optimizer_name
+    identifier = self%hyperparameters_%optimizer_name()
+  end procedure
+
   module procedure default_real_learning_rate
     rate = self%hyperparameters_%learning_rate()
   end procedure
 
+  module procedure double_precision_learning_rate
+    rate = self%hyperparameters_%learning_rate()
+  end procedure
+
   module procedure default_real_nodes_per_layer
     nodes = self%network_configuration_%nodes_per_layer()
   end procedure
 
+  module procedure double_precision_nodes_per_layer
+    nodes = self%network_configuration_%nodes_per_layer()
+  end procedure
+
   module procedure default_real_skip_connections
     using_skip = self%network_configuration_%skip_connections()
   end procedure
 
+  module procedure double_precision_skip_connections
+    using_skip = self%network_configuration_%skip_connections()
+  end procedure
+
   module procedure default_real_differentiable_activation_strategy
 #if defined __INTEL_COMPILER || _CRAYFTN
     type(string_t) :: activation_name
@@ -98,6 +199,31 @@
           error stop 'activation_strategy_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
       end select
 #if defined __INTEL_COMPILER || _CRAYFTN
+#else
+    end associate
+#endif
+  end procedure
+
+  module procedure double_precision_differentiable_activation_strategy
+#if defined __INTEL_COMPILER || _CRAYFTN
+    type(string_t) :: activation_name
+    activation_name = self%network_configuration_%activation_name()
+#else
+    associate(activation_name => self%network_configuration_%activation_name())
+#endif
+      select case(activation_name%string())
+        case ("gelu")
+          strategy = gelu_t()
+        case ("relu")
+          strategy = relu_t()
+        case ("sigmoid")
+          strategy = sigmoid_t()
+        case ("swish")
+          strategy = swish_t()
+        case default
+          error stop 'activation_strategy_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
+      end select
+#if defined __INTEL_COMPILER || _CRAYFTN
 #else
     end associate
 #endif

From e292caee892666e4276a786aa9c3f5ac1363ef58 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 15:47:42 -0700
Subject: [PATCH 034/105] chore: rm global "rkind" real kind parameter

The recent introduction of parameterized derived types supports
default real and double precision kinds in a more flexible way
that makes it less useful to have one kind parameter setting the
real-value precision globally.  This commit therefore removes all
occurences of the "rkind" parameter from the repository.
---
 cloud-microphysics/app/tensor-statistics.f90  |  4 +-
 .../app/train-cloud-microphysics.f90          | 10 ++--
 cloud-microphysics/src/histogram_s.f90        | 16 ++----
 cloud-microphysics/src/phase_space_bin_m.f90  |  7 +--
 example/read-query-infer.f90                  |  1 -
 example/write-read-infer.F90                  | 11 ++--
 src/inference_engine/kind_parameters_m.f90    |  2 -
 src/inference_engine/step_s.f90               |  1 -
 src/inference_engine_m.f90                    |  1 -
 test/asymmetric_engine_test_m.F90             |  5 +-
 test/inference_engine_test_m.F90              | 17 +++---
 test/trainable_engine_test_m.F90              | 55 +++++++++----------
 12 files changed, 58 insertions(+), 72 deletions(-)

diff --git a/cloud-microphysics/app/tensor-statistics.f90 b/cloud-microphysics/app/tensor-statistics.f90
index cb72e7492..3a216205a 100644
--- a/cloud-microphysics/app/tensor-statistics.f90
+++ b/cloud-microphysics/app/tensor-statistics.f90
@@ -9,7 +9,7 @@ program tensor_statistics
   ! External dependencies:
   use julienne_m, only : command_line_t, file_t, string_t
   use assert_m, only : assert, intrinsic_array_t
-  use inference_engine_m, only : rkind, ubounds_t
+  use inference_engine_m, only : ubounds_t
   use ieee_arithmetic, only : ieee_is_nan
   use iso_fortran_env, only : int64, real64
     
@@ -195,7 +195,7 @@ subroutine compute_histograms(base_name, raw)
       allocate(dqr_dt, mold = qr_out)
       allocate(dqs_dt, mold = qs_out)
 
-      associate(dt => real(time_out - time_in, rkind))
+      associate(dt => real(time_out - time_in))
         do concurrent(t = 1:t_end)
           dpt_dt(:,:,:,t) = (potential_temperature_out(:,:,:,t) - potential_temperature_in(:,:,:,t))/dt(t)
           dqv_dt(:,:,:,t) = (qv_out(:,:,:,t)- qv_in(:,:,:,t))/dt(t)
diff --git a/cloud-microphysics/app/train-cloud-microphysics.f90 b/cloud-microphysics/app/train-cloud-microphysics.f90
index 50a923b6e..998be2966 100644
--- a/cloud-microphysics/app/train-cloud-microphysics.f90
+++ b/cloud-microphysics/app/train-cloud-microphysics.f90
@@ -13,7 +13,7 @@ program train_on_flat_distribution
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use inference_engine_m, only : &
-    inference_engine_t, mini_batch_t, input_output_pair_t, tensor_t, trainable_engine_t, rkind, tensor_map_t, &
+    inference_engine_t, mini_batch_t, input_output_pair_t, tensor_t, trainable_engine_t, tensor_map_t, &
     training_configuration_t, shuffle
 
   !! Internal dependencies;
@@ -230,7 +230,7 @@ subroutine read_train_write(training_configuration, base_name, plot_unit, previo
       allocate(dqr_dt, mold = qr_out)
       allocate(dqs_dt, mold = qs_out)
 
-      associate(dt => real(time_out - time_in, rkind))
+      associate(dt => real(time_out - time_in))
         do concurrent(t = 1:t_end)
           dpt_dt(:,:,:,t) = (potential_temperature_out(:,:,:,t) - potential_temperature_in(:,:,:,t))/dt(t)
           dqv_dt(:,:,:,t) = (qv_out(:,:,:,t)- qv_in(:,:,:,t))/dt(t)
@@ -253,7 +253,7 @@ subroutine read_train_write(training_configuration, base_name, plot_unit, previo
         type(bin_t), allocatable :: bins(:)
         type(input_output_pair_t), allocatable :: input_output_pairs(:)
         type(tensor_t), allocatable, dimension(:) :: inputs, outputs
-        real(rkind), allocatable :: cost(:)
+        real, allocatable :: cost(:)
         integer i, lon, lat, level, time, network_unit, io_status, epoch
         integer(int64) start_training, finish_training
 
@@ -421,8 +421,8 @@ subroutine read_train_write(training_configuration, base_name, plot_unit, previo
   end subroutine read_train_write
 
   pure function normalize(x, x_min, x_max) result(x_normalized)
-    real(rkind), intent(in) :: x(:,:,:,:), x_min, x_max
-    real(rkind), allocatable :: x_normalized(:,:,:,:)
+    real, intent(in) :: x(:,:,:,:), x_min, x_max
+    real, allocatable :: x_normalized(:,:,:,:)
     call assert(x_min/=x_max, "train_cloud_microphysics(normaliz): x_min/=x_max")
     x_normalized = (x - x_min)/(x_max - x_min)
   end function
diff --git a/cloud-microphysics/src/histogram_s.f90 b/cloud-microphysics/src/histogram_s.f90
index f88b51efc..a8936118d 100644
--- a/cloud-microphysics/src/histogram_s.f90
+++ b/cloud-microphysics/src/histogram_s.f90
@@ -2,12 +2,9 @@
 ! Terms of use are as specified in LICENSE.txt
 submodule(histogram_m) histogram_s
   use assert_m, only : assert, intrinsic_array_t
-  use kind_parameters_m, only : rkind
   use julienne_m, only : string_t, operator(.cat.)
   implicit none
 
-  real(rkind), parameter :: zero = 0._rkind, one = 1._rkind, two=2._rkind, half = one/two
-
 contains
 
   module procedure variable_name
@@ -96,9 +93,8 @@
   end procedure
 
   pure function normalize(x, x_min, x_max) result(x_normalized)
-    implicit none
-    real(rkind), intent(in) :: x(:,:,:,:), x_min, x_max
-    real(rkind), allocatable :: x_normalized(:,:,:,:)
+    real, intent(in) :: x(:,:,:,:), x_min, x_max
+    real, allocatable :: x_normalized(:,:,:,:)
     call assert(x_min/=x_max, "histogram_m(normalize): x_min/=x_max", intrinsic_array_t([x_min, x_max]))
     x_normalized = (x - x_min)/(x_max - x_min)
   end function
@@ -108,7 +104,7 @@ pure function normalize(x, x_min, x_max) result(x_normalized)
     integer i, j, k, n
     integer, allocatable :: bin_count(:)
     integer, parameter :: performance_threshold = 80
-    real, parameter :: capture_maxval = 1.0001_rkind ! ensure maxval(v_max) falls within the highest bin
+    real, parameter :: capture_maxval = 1.0001 ! ensure maxval(v_max) falls within the highest bin
     real, allocatable :: v_mapped(:,:,:,:)
 
     histogram%variable_name_ = variable_name
@@ -125,11 +121,11 @@ pure function normalize(x, x_min, x_max) result(x_normalized)
       else
         v_mapped = normalize(v, v_min, v_max)
       end if
-      associate(v_mapped_min => merge(v_min, zero, raw), v_mapped_max => capture_maxval*merge(v_max, one, raw))
+      associate(v_mapped_min => merge(v_min, 0., raw), v_mapped_max => capture_maxval*merge(v_max, 1., raw))
         associate(dv => (v_mapped_max - v_mapped_min)/real(num_bins))
           associate(v_bin_min => [(v_mapped_min + (i-1)*dv, i=1,num_bins)])
             associate(v_bin_max => [v_bin_min(2:), v_mapped_max])
-              histogram%bin_value_ = half*[v_bin_min + v_bin_max] ! switching to average yields problems likely related to roundoff
+              histogram%bin_value_ = 0.5*[v_bin_min + v_bin_max] ! switching to average yields problems likely related to roundoff
               if (num_bins < performance_threshold) then
                 do concurrent(i = 1:num_bins)
                   bin_count(i) = count(v_mapped >= v_bin_min(i) .and. v_mapped < v_bin_max(i))
@@ -147,7 +143,7 @@ pure function normalize(x, x_min, x_max) result(x_normalized)
                     end do
                   end do
                 end do
-                histogram%bin_frequency_ = real(bin_count,rkind) / real(cardinality,rkind)
+                histogram%bin_frequency_ = real(bin_count) / real(cardinality)
               end if
             end associate
           end associate
diff --git a/cloud-microphysics/src/phase_space_bin_m.f90 b/cloud-microphysics/src/phase_space_bin_m.f90
index 7ad1f0a61..f9ca94a24 100644
--- a/cloud-microphysics/src/phase_space_bin_m.f90
+++ b/cloud-microphysics/src/phase_space_bin_m.f90
@@ -1,5 +1,4 @@
 module phase_space_bin_m
-  use kind_parameters_m, only : rkind
   use tensor_map_m, only : tensor_map_t
   use tensor_m, only : tensor_t
   implicit none
@@ -15,7 +14,7 @@ module phase_space_bin_m
     pure module function bin(tensor, minima, maxima, num_bins) result(phase_space_bin)
       implicit none
       type(tensor_t), intent(in) :: tensor
-      real(rkind), intent(in) :: minima(:), maxima(:)
+      real, intent(in) :: minima(:), maxima(:)
       integer, intent(in) :: num_bins
       type(phase_space_bin_t) phase_space_bin
     end function
@@ -26,9 +25,9 @@ pure module function bin(tensor, minima, maxima, num_bins) result(phase_space_bi
 
   module procedure bin
 
-    real(rkind), parameter :: half = 0.5_rkind
+    real, parameter :: half = 0.5
 
-    associate(bin_widths => (maxima - minima)/real(num_bins,rkind))
+    associate(bin_widths => (maxima - minima)/real(num_bins))
       associate(tensor_values => min(tensor%values(), maxima - half*bin_widths))
         phase_space_bin%loc = (tensor_values - minima)/bin_widths + 1
       end associate
diff --git a/example/read-query-infer.f90 b/example/read-query-infer.f90
index 0fe66a003..45a593914 100644
--- a/example/read-query-infer.f90
+++ b/example/read-query-infer.f90
@@ -6,7 +6,6 @@ program read_query_infer
   !! and use the network to perform inference.  
   use inference_engine_m, only : inference_engine_t, relu_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
-  use kind_parameters_m, only : rkind
   implicit none
 
   type(command_line_t) command_line
diff --git a/example/write-read-infer.F90 b/example/write-read-infer.F90
index e6a7a907e..f6880cd3a 100644
--- a/example/write-read-infer.F90
+++ b/example/write-read-infer.F90
@@ -9,7 +9,6 @@ program write_read_infer
   !! function.
   use inference_engine_m, only : inference_engine_t, relu_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
-  use kind_parameters_m, only : rkind
   implicit none
 
   type(string_t) file_name
@@ -31,19 +30,19 @@ function identity_network() result(inference_engine)
     integer, parameter :: nodes_per_layer(*) = [2, 2, 2]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
 #ifdef _CRAYFTN
-    real(rkind), allocatable :: weights(:,:,:)
-    weights = reshape([real(rkind):: [1,0, 0,1], [1,0, 0,1]], [max_n, max_n, layers-1])
+    real, allocatable :: weights(:,:,:)
+    weights = reshape([real :: [1,0, 0,1], [1,0, 0,1]], [max_n, max_n, layers-1])
     inference_engine = inference_engine_t( &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")], &
       weights = weights, &
-      biases = reshape([real(rkind):: [0,0], [0,0]], [max_n, layers-1]), &
+      biases = reshape([real:: [0,0], [0,0]], [max_n, layers-1]), &
       nodes = nodes_per_layer &
     )
 #else
     inference_engine = inference_engine_t( &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")], &
-      weights = reshape([real(rkind):: [1,0, 0,1], [1,0, 0,1]], [max_n, max_n, layers-1]), &
-      biases = reshape([real(rkind):: [0,0], [0,0]], [max_n, layers-1]), &
+      weights = reshape([real :: [1,0, 0,1], [1,0, 0,1]], [max_n, max_n, layers-1]), &
+      biases = reshape([real :: [0,0], [0,0]], [max_n, layers-1]), &
       nodes = nodes_per_layer &
     )
 #endif
diff --git a/src/inference_engine/kind_parameters_m.f90 b/src/inference_engine/kind_parameters_m.f90
index 973319d4d..f312a66b6 100644
--- a/src/inference_engine/kind_parameters_m.f90
+++ b/src/inference_engine/kind_parameters_m.f90
@@ -3,11 +3,9 @@
 module kind_parameters_m
   implicit none
   private
-  public :: rkind
   public :: default_real
   public :: double_precision
 
-  integer, parameter :: rkind = kind(1.0)
   integer, parameter :: default_real = kind(1.)
   integer, parameter :: double_precision = kind(1D0)
 end module kind_parameters_m
diff --git a/src/inference_engine/step_s.f90 b/src/inference_engine/step_s.f90
index 380dbf617..e98d8d756 100644
--- a/src/inference_engine/step_s.f90
+++ b/src/inference_engine/step_s.f90
@@ -1,7 +1,6 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 submodule(step_m) step_s
-  use kind_parameters_m, only : rkind
   implicit none
 
 contains
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index efd0562bc..bc6ea2271 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -7,7 +7,6 @@ module inference_engine_m
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
  use inference_engine_m_, only : inference_engine_t, infer
- use kind_parameters_m, only : rkind
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t
  use network_configuration_m, only : network_configuration_t
diff --git a/test/asymmetric_engine_test_m.F90 b/test/asymmetric_engine_test_m.F90
index ee0d40e32..522bff898 100644
--- a/test/asymmetric_engine_test_m.F90
+++ b/test/asymmetric_engine_test_m.F90
@@ -10,7 +10,6 @@ module asymmetric_engine_test_m
 
   ! Internal dependencies
   use inference_engine_m, only : inference_engine_t, tensor_t
-  use kind_parameters_m, only : rkind
 
   implicit none
 
@@ -63,7 +62,7 @@ function results() result(test_results)
 
   function xor_and_2nd_input_network() result(inference_engine)
     type(inference_engine_t) inference_engine
-    real(rkind), allocatable :: biases(:,:),  weights(:,:,:)
+    real, allocatable :: biases(:,:),  weights(:,:,:)
     type(string_t), allocatable :: metadata(:)
     integer, parameter :: n(0:*) = [2,4,4,1]
     integer, parameter :: layers = size(n), n_max = maxval(n)
@@ -120,7 +119,7 @@ function xor_and_2nd_input_truth_table() result(test_passes)
     asymmetric = xor_and_2nd_input_network()
 
     block
-      real(rkind), parameter :: tolerance = 1.E-08_rkind, false = 0._rkind, true = 1._rkind
+      real, parameter :: tolerance = 1.E-08, false = 0., true = 1.
       type(tensor_t) true_true, true_false, false_true, false_false
 
       true_true = asymmetric%infer(tensor_t([true,true]))
diff --git a/test/inference_engine_test_m.F90 b/test/inference_engine_test_m.F90
index a2ecb10b1..a2c1bfe1d 100644
--- a/test/inference_engine_test_m.F90
+++ b/test/inference_engine_test_m.F90
@@ -5,7 +5,6 @@ module inference_engine_test_m
 
   ! External dependencies
   use assert_m, only : assert
-  use kind_parameters_m, only : rkind
   use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t, file_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i
@@ -77,7 +76,7 @@ function single_hidden_layer_xor_network() result(inference_engine)
 
     inference_engine = inference_engine_t( &
       metadata = [string_t("XOR"), string_t("Damian Rouson"), string_t("2023-07-02"), string_t("step"), string_t("false")], &
-      weights = reshape([real(rkind):: [1,1,0, 0,1,1, 0,0,0], [1,0,0, -2,0,0, 1,0,0]], [max_n, max_n, layers-1]), &
+      weights = reshape([real:: [1,1,0, 0,1,1, 0,0,0], [1,0,0, -2,0,0, 1,0,0]], [max_n, max_n, layers-1]), &
       biases = reshape([[0.,-1.99,0.], [0., 0., 0.]], [max_n, layers-1]), &
       nodes = nodes_per_layer &
     )
@@ -90,7 +89,7 @@ function multi_layer_xor_network() result(inference_engine)
 
     inference_engine = inference_engine_t( &
       metadata = [string_t("XOR"), string_t("Damian Rouson"), string_t("2023-07-02"), string_t("step"), string_t("false")], &
-      weights = reshape([real(rkind):: [1,1,0, 0,1,1, 1,0,0, 1,0,0, 0,1,0, 0,0,1], [1,0,0, -2,0,0, 1,0,0]], &
+      weights = reshape([real:: [1,1,0, 0,1,1, 1,0,0, 1,0,0, 0,1,0, 0,0,1], [1,0,0, -2,0,0, 1,0,0]], &
         [max_n, max_n, layers-1]), &
       biases = reshape([[0.,-1.99,0.], [0., 0., 0.], [0., 0., 0.]], [max_n, layers-1]), &
       nodes = nodes_per_layer &
@@ -108,7 +107,7 @@ function decrement_split_combine_increment() result(inference_engine)
     integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n) ! max layer width, number of layers
     integer, parameter :: w_shape(*) = [n_max, n_max, layers-1], b_shape(*) = [n_max, n_max]
-    real(rkind), parameter :: & 
+    real, parameter :: & 
       w(*,*,*) = reshape( [1.,0.,0., 0.,1.,0., 0.,0.,0., 1.,0.,0., 0.,.5,.5, 0.,0.,0., 1.,0.,0., 0.,1.,0., 0.,1.,1.], w_shape), &
       b(*,*) = reshape( [0.,-1.,0., 0.,0.,0., 0.,1.,0.], b_shape)
 
@@ -130,7 +129,7 @@ function varying_width() result(inference_engine)
     integer, parameter :: n_max = maxval(n), layers=size(n)   ! max layer width, number of layers
     integer, parameter :: w_shape(*) = [n_max, n_max, layers-1], b_shape(*) = [n_max, n_max]
     integer i
-    real(rkind), allocatable :: w(:,:,:), b(:,:)
+    real, allocatable :: w(:,:,:), b(:,:)
 
     w = reshape( [(i, i=1,product(w_shape))], w_shape)
     b = reshape( [(maxval(w) + i, i=1,product(b_shape))], b_shape)
@@ -148,7 +147,7 @@ function distinct_parameters() result(inference_engine)
     integer, parameter :: n_max = maxval(n), layers=size(n)   ! max layer width, number of layers
     integer, parameter :: w_shape(*) = [n_max, n_max, layers-1], b_shape(*) = [n_max, n_max]
     integer i
-    real(rkind), allocatable :: w(:,:,:), b(:,:)
+    real, allocatable :: w(:,:,:), b(:,:)
 
     w = reshape( [(i, i=1,product(w_shape))], w_shape)
     b = reshape( [(maxval(w) + i, i=1,product(b_shape))], b_shape)
@@ -184,7 +183,7 @@ function infer_with_varying_width_net() result(test_passes)
     logical test_passes
     type(inference_engine_t) inference_engine
     type(tensor_t) inputs, outputs
-    real(rkind), parameter :: tolerance = 1.E-08_rkind
+    real, parameter :: tolerance = 1.E-08
 
     inference_engine = decrement_split_combine_increment()
     inputs = tensor_t([1.1, 2.7])
@@ -200,7 +199,7 @@ function elemental_infer_with_1_hidden_layer_xor_net() result(test_passes)
 
     block
       type(tensor_t), allocatable :: truth_table(:)
-      real(rkind), parameter :: tolerance = 1.E-08_rkind, false = 0._rkind, true = 1._rkind
+      real, parameter :: tolerance = 1.E-08, false = 0., true = 1.
       integer i
 
       associate(array_of_inputs => [tensor_t([true,true]), tensor_t([true,false]), tensor_t([false,true]), tensor_t([false,false])])
@@ -221,7 +220,7 @@ function elemental_infer_with_2_hidden_layer_xor_net() result(test_passes)
 
     block
       type(tensor_t), allocatable :: truth_table(:)
-      real(rkind), parameter :: tolerance = 1.E-08_rkind, false = 0._rkind, true = 1._rkind
+      real, parameter :: tolerance = 1.E-08, false = 0., true = 1.
       integer i
 
       associate(array_of_inputs => [tensor_t([true,true]), tensor_t([true,false]), tensor_t([false,true]), tensor_t([false,false])])
diff --git a/test/trainable_engine_test_m.F90 b/test/trainable_engine_test_m.F90
index 6fc622686..23a7fd694 100644
--- a/test/trainable_engine_test_m.F90
+++ b/test/trainable_engine_test_m.F90
@@ -13,7 +13,6 @@ module trainable_engine_test_m
 
   ! Internal dependencies
   use inference_engine_m, only : trainable_engine_t, tensor_t, sigmoid_t, input_output_pair_t, mini_batch_t, relu_t, shuffle
-  use kind_parameters_m, only : rkind
   implicit none
 
   private
@@ -45,7 +44,7 @@ module trainable_engine_test_m
     procedure, nopass :: results
   end type
 
-  real(rkind), parameter :: false = 0._rkind, true = 1._rkind
+  real, parameter :: false = 0., true = 1.
 
   abstract interface
 
@@ -179,7 +178,7 @@ function two_zeroed_hidden_layers() result(trainable_engine)
     integer, parameter :: inputs = 2, outputs = 1, hidden = 3 ! number of neurons in input, output, and hidden layers
     integer, parameter :: neurons(*) = [inputs, hidden, hidden, outputs] ! neurons per layer
     integer, parameter :: max_neurons = maxval(neurons), layers=size(neurons) ! max layer width, number of layers
-    real(rkind) w(max_neurons, max_neurons, layers-1), b(max_neurons, max_neurons)
+    real w(max_neurons, max_neurons, layers-1), b(max_neurons, max_neurons)
 
     w = 0.
     b = 0.
@@ -195,7 +194,7 @@ function two_random_hidden_layers() result(trainable_engine)
     integer, parameter :: inputs = 2, outputs = 1, hidden = 3 ! number of neurons in input, output, and hidden layers
     integer, parameter :: neurons(*) = [inputs, hidden, hidden, outputs] ! neurons per layer
     integer, parameter :: max_neurons = maxval(neurons), layers=size(neurons) ! max layer width, number of layers
-    real(rkind) w(max_neurons, max_neurons, layers-1), b(max_neurons, max_neurons)
+    real w(max_neurons, max_neurons, layers-1), b(max_neurons, max_neurons)
 
     call random_number(b)
     call random_number(w)
@@ -214,8 +213,8 @@ function and_gate_with_skewed_training_data() result(test_passes)
     type(tensor_t), allocatable, dimension(:,:) :: training_inputs, training_outputs
     type(tensor_t), allocatable, dimension(:) :: tmp, tmp2, test_inputs, expected_test_outputs, actual_outputs
     type(trainable_engine_t) trainable_engine
-    real(rkind), parameter :: tolerance = 1.E-02_rkind
-    real(rkind), allocatable :: harvest(:,:,:)
+    real, parameter :: tolerance = 1.E-02
+    real, allocatable :: harvest(:,:,:)
     integer, parameter :: num_inputs=2, mini_batch_size = 1, num_iterations=20000
     integer batch, iter, i
 
@@ -246,7 +245,7 @@ function and_gate_with_skewed_training_data() result(test_passes)
     elemental function and(inputs_object) result(expected_outputs_object)
       type(tensor_t), intent(in) :: inputs_object 
       type(tensor_t) expected_outputs_object 
-      expected_outputs_object = tensor_t([merge(true, false, sum(inputs_object%values()) > 1.99_rkind)])
+      expected_outputs_object = tensor_t([merge(true, false, sum(inputs_object%values()) > 1.99)])
     end function
 
   end function
@@ -258,8 +257,8 @@ function not_and_gate_with_skewed_training_data() result(test_passes)
     type(tensor_t), allocatable :: training_outputs(:,:), expected_test_outputs(:), tmp2(:)
     type(trainable_engine_t) trainable_engine
     type(tensor_t), allocatable :: actual_outputs(:)
-    real(rkind), parameter :: tolerance = 1.E-02_rkind
-    real(rkind), allocatable :: harvest(:,:,:)
+    real, parameter :: tolerance = 1.E-02
+    real, allocatable :: harvest(:,:,:)
     integer, parameter :: num_inputs=2, mini_batch_size = 1, num_iterations=30000
     integer batch, iter, i
 
@@ -301,8 +300,8 @@ function or_gate_with_random_weights() result(test_passes)
     type(tensor_t), allocatable :: training_inputs(:,:), test_inputs(:), actual_outputs(:)
     type(tensor_t), allocatable :: training_outputs(:,:), expected_test_outputs(:)
     type(trainable_engine_t) trainable_engine
-    real(rkind), parameter :: tolerance = 1.E-02_rkind
-    real(rkind), allocatable :: harvest(:,:,:)
+    real, parameter :: tolerance = 1.E-02
+    real, allocatable :: harvest(:,:,:)
     integer, parameter :: num_inputs=2, mini_batch_size = 1, num_iterations=50000
     integer batch, iter, i
 
@@ -346,8 +345,8 @@ function xor_gate_with_random_weights() result(test_passes)
     type(tensor_t), allocatable, dimension(:,:) :: training_inputs, training_outputs 
     type(tensor_t), allocatable, dimension(:) :: actual_outputs, test_inputs, expected_test_outputs
     type(trainable_engine_t) trainable_engine
-    real(rkind), parameter :: tolerance = 1.E-02_rkind
-    real(rkind), allocatable :: harvest(:,:,:)
+    real, parameter :: tolerance = 1.E-02
+    real, allocatable :: harvest(:,:,:)
 #ifdef __flang__
       !! Reducing num_iterations yields a less robust test, but moving away from local minima by
       !! increasing num_iterations causes this test to crash when compiled with the flang or ifx compilers.
@@ -403,19 +402,19 @@ pure function local_xor(inputs) result(expected_outputs)
 
   function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
     type(trainable_engine_t) trainable_engine
-    real(rkind), intent(in) :: perturbation_magnitude
+    real, intent(in) :: perturbation_magnitude
     integer, parameter :: nodes_per_layer(*) = [2, 2, 2, 2]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
 #ifndef _CRAYFTN
-    real(rkind), parameter :: identity(*,*,*) = &
-      reshape([real(rkind):: [1,0], [0,1] ,[1,0], [0,1], [1,0], [0,1]], [max_n, max_n, layers-1])
+    real, parameter :: identity(*,*,*) = &
+      reshape([real:: [1,0], [0,1] ,[1,0], [0,1], [1,0], [0,1]], [max_n, max_n, layers-1])
 #else
-    real(rkind), allocatable :: identity(:,:,:)
+    real, allocatable :: identity(:,:,:)
 #endif
-    real(rkind) harvest(size(identity,1), size(identity,2), size(identity,3))
+    real harvest(size(identity,1), size(identity,2), size(identity,3))
 
 #ifdef _CRAYFTN
-    identity = reshape([real(rkind):: [1,0], [0,1] ,[1,0], [0,1], [1,0], [0,1]], [max_n, max_n, layers-1])
+    identity = reshape([real:: [1,0], [0,1] ,[1,0], [0,1], [1,0], [0,1]], [max_n, max_n, layers-1])
 #endif
 
     call random_number(harvest)
@@ -424,7 +423,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     trainable_engine = trainable_engine_t( &
       nodes = nodes_per_layer, &
       weights = identity + harvest , & 
-      biases = reshape([real(rkind):: [0,0], [0,0], [0,0]], [max_n, layers-1]), &
+      biases = reshape([real:: [0,0], [0,0], [0,0]], [max_n, layers-1]), &
       differentiable_activation_strategy = relu_t(), &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")] &
     )
@@ -437,7 +436,7 @@ function preserves_identity_mapping() result(test_passes)
     type(tensor_t), allocatable :: inputs(:)
     type(trainable_engine_t)  trainable_engine
     type(bin_t), allocatable :: bins(:)
-    real(rkind), allocatable :: cost(:)
+    real, allocatable :: cost(:)
     integer, parameter :: num_pairs = 100, num_epochs = 100, n_bins = 3
     integer i, bin, epoch
 
@@ -451,10 +450,10 @@ function preserves_identity_mapping() result(test_passes)
 #ifdef _CRAYFTN
       allocate(inputs(num_pairs))
       do i = 1, num_pairs
-         inputs(i) = tensor_t(real([i,2*i], rkind)/num_pairs)
+         inputs(i) = tensor_t(real([i,2*i])/num_pairs)
       end do
 #else
-      inputs = [(tensor_t(real([i,2*i], rkind)/num_pairs), i = 1, num_pairs)]
+      inputs = [(tensor_t(real([i,2*i])/num_pairs), i = 1, num_pairs)]
 #endif
       associate(outputs => inputs)
         input_output_pairs = input_output_pair_t(inputs, outputs)
@@ -467,7 +466,7 @@ function preserves_identity_mapping() result(test_passes)
       end do
 
       block
-        real(rkind), parameter :: tolerance = 1.E-06
+        real, parameter :: tolerance = 1.E-06
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
         network_outputs = trainable_engine%infer(inputs)
@@ -496,7 +495,7 @@ function perturbed_identity_converges() result(test_passes)
     type(tensor_t), allocatable :: inputs(:)
     type(trainable_engine_t)  trainable_engine
     type(bin_t), allocatable :: bins(:)
-    real(rkind), allocatable :: cost(:)
+    real, allocatable :: cost(:)
     integer, parameter :: num_pairs = 6
     integer, parameter :: num_epochs = 180
     integer, parameter :: num_bins = 5 
@@ -512,10 +511,10 @@ function perturbed_identity_converges() result(test_passes)
 #ifdef _CRAYFTN
       allocate(inputs(num_pairs))
       do i = 1, num_pairs
-        inputs(i) = tensor_t(real([i,2*i], rkind)/(2*num_pairs))
+        inputs(i) = tensor_t(real([i,2*i])/(2*num_pairs))
       end do
 #else
-      inputs = [(tensor_t(real([i,2*i], rkind)/(2*num_pairs)), i = 1, num_pairs)]
+      inputs = [(tensor_t(real([i,2*i])/(2*num_pairs)), i = 1, num_pairs)]
 #endif
       associate(outputs => inputs)
         input_output_pairs = input_output_pair_t(inputs, outputs)
@@ -529,7 +528,7 @@ function perturbed_identity_converges() result(test_passes)
       end do
 
       block
-        real(rkind), parameter :: tolerance = 1.E-06
+        real, parameter :: tolerance = 1.E-06
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
         network_outputs = trainable_engine%infer(inputs)

From 1e386420843507c87da91d2685d2fc147a19053d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 19:09:43 -0700
Subject: [PATCH 035/105] chore(fpm): update to julienne 1.2.1 dependency

---
 fpm.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fpm.toml b/fpm.toml
index 033569f9d..7e22c0bb6 100644
--- a/fpm.toml
+++ b/fpm.toml
@@ -8,4 +8,4 @@ maintainer = "rouson@lbl.gov"
 assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.7.0"}
 
 [dev-dependencies]
-julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.0"}
+julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.1"}

From 211f9cbc18f37019c1a87e89852ca330ba6e1db5 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 20:02:23 -0700
Subject: [PATCH 036/105] feat(metadata):dble_prec_string_t json constructor

This commit adds a metadata_t constructor that takes a
double_precision_string_t array of lines containing JSON syntax.
---
 src/inference_engine/metadata_m.f90 |  7 +++++++
 src/inference_engine/metadata_s.f90 | 28 ++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/src/inference_engine/metadata_m.f90 b/src/inference_engine/metadata_m.f90
index 09713c83a..09683fc57 100644
--- a/src/inference_engine/metadata_m.f90
+++ b/src/inference_engine/metadata_m.f90
@@ -1,5 +1,6 @@
 module metadata_m
   use julienne_string_m, only : string_t
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
 
   private
@@ -23,6 +24,12 @@ pure module function from_json(lines) result(metadata)
       type(metadata_t) metadata
     end function
 
+    pure module function double_precision_from_json(lines) result(metadata)
+      implicit none
+      type(double_precision_string_t), intent(in) :: lines(:)
+      type(metadata_t) metadata
+    end function
+
     pure module function from_components(modelName, modelAuthor, compilationDate, activationFunction, usingSkipConnections) &
       result(metadata)
       implicit none
diff --git a/src/inference_engine/metadata_s.f90 b/src/inference_engine/metadata_s.f90
index f50b18bce..92597e2c6 100644
--- a/src/inference_engine/metadata_s.f90
+++ b/src/inference_engine/metadata_s.f90
@@ -43,6 +43,34 @@
     call assert(any(trim(adjustl(lines(size(lines))%string())) == ["},","} "]), "metadata_s(from_json): metadata object end found")
   end procedure
 
+  module procedure double_precision_from_json
+    integer l
+
+    call assert(lines(1)%get_json_key() == "metadata", "metadata_s(double_precision_from_json): metadata found")
+
+    do l = 2, size(lines)-1
+      associate(key => lines(l)%get_json_key())
+        select case (key%string())
+          case("modelName")
+            metadata%modelName_ = lines(l)%get_json_value(key, mold=string_t(""))
+          case("modelAuthor")
+            metadata%modelAuthor_ = lines(l)%get_json_value(key, mold=string_t(""))
+          case("compilationDate")
+            metadata%compilationDate_ = lines(l)%get_json_value(key, mold=string_t(""))
+          case("activationFunction")
+            metadata%activationFunction_ = lines(l)%get_json_value(key, mold=string_t(""))
+          case("usingSkipConnections")
+            metadata%usingSkipConnections_ = lines(l)%get_json_value(key, mold=string_t(""))
+          case default
+            error stop "metadata_s(double_precision_from_json): missing key " // key%string()
+        end select
+      end associate
+    end do
+
+    call assert(any(trim(adjustl(lines(size(lines))%string())) == ["},","} "]), &
+      "metadata_s(double_precision_from_json): metadata object end found")
+  end procedure
+
   module procedure to_json
 
     character(len=*), parameter :: indent = repeat(" ",ncopies=4)

From 0e26cb0320812d972aae0dc99d960af0d12b0b5b Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 21:44:33 -0700
Subject: [PATCH 037/105] feat(tensor_map): add double_precision_from_json

---
 src/inference_engine/tensor_map_m.f90 |  7 +++++++
 src/inference_engine/tensor_map_s.f90 | 19 +++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/src/inference_engine/tensor_map_m.f90 b/src/inference_engine/tensor_map_m.f90
index 792327f00..b11457030 100644
--- a/src/inference_engine/tensor_map_m.f90
+++ b/src/inference_engine/tensor_map_m.f90
@@ -4,6 +4,7 @@ module tensor_map_m
   use tensor_m, only : tensor_t
   use julienne_m, only : string_t
   use kind_parameters_m, only : default_real, double_precision
+  use double_precision_string_m, only : double_precision_string_t
   implicit none
   
   private
@@ -47,6 +48,12 @@ module function from_json(lines) result(tensor_map)
       type(tensor_map_t) tensor_map
     end function
 
+    module function double_precision_from_json(lines) result(tensor_map)
+      implicit none
+      type(double_precision_string_t), intent(in) :: lines(:)
+      type(tensor_map_t(double_precision)) tensor_map
+    end function
+
   end interface
 
   interface
diff --git a/src/inference_engine/tensor_map_s.f90 b/src/inference_engine/tensor_map_s.f90
index 92ea17daa..c0e504d81 100644
--- a/src/inference_engine/tensor_map_s.f90
+++ b/src/inference_engine/tensor_map_s.f90
@@ -41,6 +41,25 @@
     call assert(tensor_map_key_found, "tensor_map_s(from_json): 'tensor_map' key found")
   end procedure
 
+  module procedure double_precision_from_json
+    logical tensor_map_key_found
+    integer l
+
+    tensor_map_key_found = .false.
+
+    do l=1,size(lines)
+      if (lines(l)%get_json_key() == "inputs_map" .or. lines(l)%get_json_key() == "outputs_map") then
+        tensor_map_key_found = .true.
+        tensor_map%layer_  = lines(l+1)%get_json_value(key=string_t("layer"), mold=string_t(""))
+        tensor_map%intercept_ = lines(l+2)%get_json_value(key=string_t("intercept"), mold=[0D0])
+        tensor_map%slope_ = lines(l+3)%get_json_value(key=string_t("slope"), mold=[0D0])
+        return
+      end if
+    end do
+
+    call assert(tensor_map_key_found, "tensor_map_s(from_json): 'tensor_map' key found")
+  end procedure
+
   module procedure default_real_equals
     real, parameter :: tolerance = 1.E-08
 

From 7a1db6605fd99ef70cef994bad0761144f872eb3 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 22:06:02 -0700
Subject: [PATCH 038/105] chore(example): rm public non-type-bound infer

---
 example/concurrent-inferences.f90 | 10 +---------
 src/inference_engine_m.f90        |  2 +-
 2 files changed, 2 insertions(+), 10 deletions(-)

diff --git a/example/concurrent-inferences.f90 b/example/concurrent-inferences.f90
index 996d4c97d..7a1426096 100644
--- a/example/concurrent-inferences.f90
+++ b/example/concurrent-inferences.f90
@@ -3,7 +3,7 @@
 program concurrent_inferences
   !! This program demonstrates how to read a neural network from a JSON file
   !! and use the network to perform concurrent inferences.
-  use inference_engine_m, only : inference_engine_t, tensor_t, infer
+  use inference_engine_m, only : inference_engine_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
   use assert_m, only : assert
   use iso_fortran_env, only : int64, real64
@@ -69,14 +69,6 @@ program concurrent_inferences
       call system_clock(t_finish)
       print *,"Concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
 
-      print *,"Performing concurrent inference with a non-type-bound inference procedure"
-      call system_clock(t_start)
-      do concurrent(i=1:lat, j=1:lon, k=1:lev)
-        outputs(i,j,k) = infer(inference_engine, inputs(i,j,k))           
-      end do
-      call system_clock(t_finish)
-      print *,"Concurrent inference time with non-type-bound procedure: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
-
     end block
   end block
 
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index bc6ea2271..ca7a8c1eb 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -6,7 +6,7 @@ module inference_engine_m
  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
- use inference_engine_m_, only : inference_engine_t, infer
+ use inference_engine_m_, only : inference_engine_t
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t
  use network_configuration_m, only : network_configuration_t

From d11a3776d18a95263aad722d5bc669ca97ef35f2 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 22:09:42 -0700
Subject: [PATCH 039/105] chore(inference_engine): generic bindings|priv tbp

---
 src/inference_engine/inference_engine_m_.f90 | 71 +++++++++++---------
 src/inference_engine/inference_engine_s.F90  | 36 +++++-----
 2 files changed, 59 insertions(+), 48 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index f7413dd12..9076dc572 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -14,7 +14,6 @@ module inference_engine_m_
   private
   public :: inference_engine_t
   public :: exchange_t
-  public :: infer
 
   type inference_engine_t(k)
     !! Encapsulate the minimal information needed to perform inference
@@ -25,20 +24,32 @@ module inference_engine_m_
     integer, allocatable, private :: nodes_(:)
     class(activation_strategy_t), allocatable, private :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   contains
-    procedure :: infer
-    procedure :: to_json
-    procedure :: map_to_input_range
-    procedure :: map_from_output_range
-    procedure :: num_hidden_layers
-    procedure :: num_inputs
-    procedure :: num_outputs
-    procedure :: nodes_per_layer
-    procedure :: assert_conformable_with
-    procedure :: skip
-    procedure, private :: approximately_equal
-    generic :: operator(==) => approximately_equal
-    procedure :: activation_function_name
-    procedure :: to_exchange
+    generic :: operator(==)             => default_real_approximately_equal
+    generic :: infer                    => default_real_infer
+    generic :: to_json                  => default_real_to_json
+    generic :: map_to_input_range       => default_real_map_to_input_range
+    generic :: map_from_output_range    => default_real_map_from_output_range
+    generic :: num_hidden_layers        => default_real_num_hidden_layers
+    generic :: num_inputs               => default_real_num_inputs
+    generic :: num_outputs              => default_real_num_outputs
+    generic :: nodes_per_layer          => default_real_nodes_per_layer
+    generic :: assert_conformable_with  => default_real_assert_conformable_with
+    generic :: skip                     => default_real_skip
+    generic :: activation_function_name => default_real_activation_function_name
+    generic :: to_exchange              => default_real_to_exchange
+    procedure, private :: default_real_approximately_equal
+    procedure, private :: default_real_infer
+    procedure, private :: default_real_to_json
+    procedure, private :: default_real_map_to_input_range
+    procedure, private :: default_real_map_from_output_range
+    procedure, private :: default_real_num_hidden_layers
+    procedure, private :: default_real_num_inputs
+    procedure, private :: default_real_num_outputs
+    procedure, private :: default_real_nodes_per_layer
+    procedure, private :: default_real_assert_conformable_with
+    procedure, private :: default_real_skip
+    procedure, private :: default_real_activation_function_name
+    procedure, private :: default_real_to_exchange
   end type
 
   type exchange_t(k)
@@ -52,7 +63,7 @@ module inference_engine_m_
 
   interface inference_engine_t
 
-    impure module function construct_from_padded_arrays(metadata, weights, biases, nodes, input_map, output_map) &
+    impure module function default_real_construct_from_padded_arrays(metadata, weights, biases, nodes, input_map, output_map) &
       result(inference_engine)
       implicit none
       type(string_t), intent(in) :: metadata(:)
@@ -62,7 +73,7 @@ impure module function construct_from_padded_arrays(metadata, weights, biases, n
       type(inference_engine_t) inference_engine
     end function
 
-    impure elemental module function from_json(file_) result(inference_engine)
+    impure elemental module function default_real_from_json(file_) result(inference_engine)
       implicit none
       type(file_t), intent(in) :: file_
       type(inference_engine_t) inference_engine
@@ -72,14 +83,14 @@ impure elemental module function from_json(file_) result(inference_engine)
 
   interface
 
-    elemental module function approximately_equal(lhs, rhs) result(lhs_eq_rhs)
+    elemental module function default_real_approximately_equal(lhs, rhs) result(lhs_eq_rhs)
       !! The result is true if lhs and rhs are the same to within a tolerance
       implicit none
       class(inference_engine_t), intent(in) :: lhs, rhs
       logical lhs_eq_rhs
     end function
 
-    elemental module function map_to_input_range(self, tensor) result(normalized_tensor)
+    elemental module function default_real_map_to_input_range(self, tensor) result(normalized_tensor)
       !! The result contains the input tensor values normalized to fall on the range used during training
       implicit none
       class(inference_engine_t), intent(in) :: self
@@ -87,7 +98,7 @@ elemental module function map_to_input_range(self, tensor) result(normalized_ten
       type(tensor_t) normalized_tensor
     end function
 
-    elemental module function map_from_output_range(self, normalized_tensor) result(tensor)
+    elemental module function default_real_map_from_output_range(self, normalized_tensor) result(tensor)
       !! The result contains the output tensor values unnormalized via the inverse of the mapping used in training
       implicit none
       class(inference_engine_t), intent(in) :: self
@@ -95,62 +106,62 @@ elemental module function map_from_output_range(self, normalized_tensor) result(
       type(tensor_t) tensor
     end function
 
-    impure module function to_exchange(self) result(exchange)
+    impure module function default_real_to_exchange(self) result(exchange)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(exchange_t) exchange
     end function
 
-    impure elemental module function to_json(self) result(json_file)
+    impure elemental module function default_real_to_json(self) result(json_file)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(file_t) json_file
     end function
 
-    elemental module subroutine assert_conformable_with(self, inference_engine)
+    elemental module subroutine default_real_assert_conformable_with(self, inference_engine)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(inference_engine_t), intent(in) :: inference_engine
     end subroutine
 
-    elemental module function infer(self, inputs) result(outputs)
+    elemental module function default_real_infer(self, inputs) result(outputs)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: inputs
       type(tensor_t) outputs
     end function
 
-    elemental module function num_outputs(self) result(output_count)
+    elemental module function default_real_num_outputs(self) result(output_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer output_count
     end function
 
-    elemental module function num_hidden_layers(self) result(hidden_layer_count)
+    elemental module function default_real_num_hidden_layers(self) result(hidden_layer_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer hidden_layer_count
     end function
 
-    elemental module function num_inputs(self) result(input_count)
+    elemental module function default_real_num_inputs(self) result(input_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer input_count
     end function
 
-    pure module function nodes_per_layer(self) result(node_count)
+    pure module function default_real_nodes_per_layer(self) result(node_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer, allocatable :: node_count(:)
     end function
 
-    elemental module function activation_function_name(self) result(activation_name)
+    elemental module function default_real_activation_function_name(self) result(activation_name)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(string_t) activation_name
     end function
 
-    pure module function skip(self) result(use_skip_connections)
+    pure module function default_real_skip(self) result(use_skip_connections)
       implicit none
       class(inference_engine_t), intent(in) :: self
       logical use_skip_connections
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 9ffa67580..00f2fd542 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -19,15 +19,15 @@
 
 contains
 
-  module procedure map_to_input_range
+  module procedure default_real_map_to_input_range
     normalized_tensor = self%input_map_%map_to_training_range(tensor)
   end procedure
 
-  module procedure map_from_output_range
+  module procedure default_real_map_from_output_range
     tensor = self%output_map_%map_from_training_range(normalized_tensor)
   end procedure
 
-  module procedure to_exchange
+  module procedure default_real_to_exchange
     exchange%input_map_ = self%input_map_
     exchange%output_map_ = self%output_map_
     associate(strings => self%metadata_%strings())
@@ -39,7 +39,7 @@
     exchange%activation_strategy_ = self%activation_strategy_ 
   end procedure
 
-  module procedure infer
+  module procedure default_real_infer
 
     real, allocatable :: a(:,:)
     integer, parameter :: input_layer = 0
@@ -133,7 +133,7 @@ impure function activation_factory_method(activation_name) result(activation)
     end select
   end function
 
-  module procedure construct_from_padded_arrays
+  module procedure default_real_construct_from_padded_arrays
 
     inference_engine%metadata_ = metadata_t(metadata(1),metadata(2),metadata(3),metadata(4),metadata(5))
     inference_engine%weights_ = weights
@@ -169,9 +169,9 @@ impure function activation_factory_method(activation_name) result(activation)
 
     call assert_consistency(inference_engine)
 
-  end procedure construct_from_padded_arrays
+  end procedure default_real_construct_from_padded_arrays
 
-  module procedure to_json
+  module procedure default_real_to_json
 
 #ifdef _CRAYFTN
     type(tensor_map_t) proto_map
@@ -285,9 +285,9 @@ impure function activation_factory_method(activation_name) result(activation)
         end block
       end associate
     end associate
-  end procedure to_json
+  end procedure default_real_to_json
 
-  module procedure from_json
+  module procedure default_real_from_json
 
     character(len=:), allocatable :: justified_line
     integer l, num_file_lines
@@ -386,9 +386,9 @@ impure function activation_factory_method(activation_name) result(activation)
 
     call assert_consistency(inference_engine)
 
-  end procedure from_json
+  end procedure default_real_from_json
 
-  module procedure assert_conformable_with
+  module procedure default_real_assert_conformable_with
 
     call assert_consistency(self)
     call assert_consistency(inference_engine)
@@ -405,7 +405,7 @@ impure function activation_factory_method(activation_name) result(activation)
     
   end procedure
 
-  module procedure approximately_equal
+  module procedure default_real_approximately_equal
 
     logical nodes_eq
 
@@ -446,12 +446,12 @@ impure function activation_factory_method(activation_name) result(activation)
 
   end procedure
 
-  module procedure num_outputs
+  module procedure default_real_num_outputs
     call assert_consistency(self)
     output_count = self%nodes_(ubound(self%nodes_,1))
   end procedure
 
-  module procedure num_hidden_layers
+  module procedure default_real_num_hidden_layers
     integer, parameter :: input_layer = 1, output_layer = 1
     call assert_consistency(self)
     associate(num_layers => size(self%nodes_))
@@ -459,23 +459,23 @@ impure function activation_factory_method(activation_name) result(activation)
     end associate
   end procedure
 
-  module procedure num_inputs
+  module procedure default_real_num_inputs
     call assert_consistency(self)
     input_count = self%nodes_(lbound(self%nodes_,1))
   end procedure
 
-  module procedure nodes_per_layer
+  module procedure default_real_nodes_per_layer
     call assert_consistency(self)
     node_count = self%nodes_
   end procedure
 
-  module procedure skip
+  module procedure default_real_skip
     associate(strings => self%metadata_%strings())
       use_skip_connections = merge(.true., .false.,  strings(5) == "true")
     end associate
   end procedure
 
-  module procedure activation_function_name
+  module procedure default_real_activation_function_name
     associate(strings => self%metadata_%strings())
       activation_name = strings(4)
     end associate

From a6ccaee3a364ce6eb82d52357c2f96cc1280bb9f Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 1 Sep 2024 23:23:17 -0700
Subject: [PATCH 040/105] feat(inference_engine_t):double-precision bindings

---
 src/inference_engine/inference_engine_m_.f90 | 140 +++++++--
 src/inference_engine/inference_engine_s.F90  | 310 ++++++++++++++++++-
 2 files changed, 419 insertions(+), 31 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index 9076dc572..294ee2b2c 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -5,7 +5,7 @@ module inference_engine_m_
   use activation_strategy_m, only : activation_strategy_t
   use julienne_file_m, only : file_t
   use julienne_string_m, only : string_t
-  use kind_parameters_m, only : default_real
+  use kind_parameters_m, only : default_real, double_precision
   use metadata_m, only : metadata_t
   use tensor_m, only : tensor_t
   use tensor_map_m, only : tensor_map_t
@@ -24,32 +24,32 @@ module inference_engine_m_
     integer, allocatable, private :: nodes_(:)
     class(activation_strategy_t), allocatable, private :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   contains
-    generic :: operator(==)             => default_real_approximately_equal
-    generic :: infer                    => default_real_infer
-    generic :: to_json                  => default_real_to_json
-    generic :: map_to_input_range       => default_real_map_to_input_range
-    generic :: map_from_output_range    => default_real_map_from_output_range
-    generic :: num_hidden_layers        => default_real_num_hidden_layers
-    generic :: num_inputs               => default_real_num_inputs
-    generic :: num_outputs              => default_real_num_outputs
-    generic :: nodes_per_layer          => default_real_nodes_per_layer
-    generic :: assert_conformable_with  => default_real_assert_conformable_with
-    generic :: skip                     => default_real_skip
-    generic :: activation_function_name => default_real_activation_function_name
-    generic :: to_exchange              => default_real_to_exchange
-    procedure, private :: default_real_approximately_equal
-    procedure, private :: default_real_infer
-    procedure, private :: default_real_to_json
-    procedure, private :: default_real_map_to_input_range
-    procedure, private :: default_real_map_from_output_range
-    procedure, private :: default_real_num_hidden_layers
-    procedure, private :: default_real_num_inputs
-    procedure, private :: default_real_num_outputs
-    procedure, private :: default_real_nodes_per_layer
-    procedure, private :: default_real_assert_conformable_with
-    procedure, private :: default_real_skip
-    procedure, private :: default_real_activation_function_name
-    procedure, private :: default_real_to_exchange
+    generic :: operator(==)             => default_real_approximately_equal,     double_precision_approximately_equal
+    generic :: infer                    => default_real_infer,                   double_precision_infer
+    generic :: to_json                  => default_real_to_json,                 double_precision_to_json
+    generic :: map_to_input_range       => default_real_map_to_input_range,      double_precision_map_to_input_range
+    generic :: map_from_output_range    => default_real_map_from_output_range,   double_precision_map_from_output_range
+    generic :: num_hidden_layers        => default_real_num_hidden_layers,       double_precision_num_hidden_layers
+    generic :: num_inputs               => default_real_num_inputs,              double_precision_num_inputs
+    generic :: num_outputs              => default_real_num_outputs,             double_precision_num_outputs
+    generic :: nodes_per_layer          => default_real_nodes_per_layer,         double_precision_nodes_per_layer
+    generic :: assert_conformable_with  => default_real_assert_conformable_with, double_precision_assert_conformable_with
+    generic :: skip                     => default_real_skip,                    double_precision_skip
+    generic :: activation_function_name => default_real_activation_name,         double_precision_activation_name
+    generic :: to_exchange              => default_real_to_exchange,             double_precision_to_exchange
+    procedure, private :: default_real_approximately_equal,     double_precision_approximately_equal
+    procedure, private :: default_real_infer,                   double_precision_infer
+    procedure, private :: default_real_to_json,                 double_precision_to_json
+    procedure, private :: default_real_map_to_input_range,      double_precision_map_to_input_range
+    procedure, private :: default_real_map_from_output_range,   double_precision_map_from_output_range
+    procedure, private :: default_real_num_hidden_layers,       double_precision_num_hidden_layers
+    procedure, private :: default_real_num_inputs,              double_precision_num_inputs
+    procedure, private :: default_real_num_outputs,             double_precision_num_outputs
+    procedure, private :: default_real_nodes_per_layer,         double_precision_nodes_per_layer
+    procedure, private :: default_real_assert_conformable_with, double_precision_assert_conformable_with
+    procedure, private :: default_real_skip,                    double_precision_skip
+    procedure, private :: default_real_activation_name,         double_precision_activation_name
+    procedure, private :: default_real_to_exchange,             double_precision_to_exchange
   end type
 
   type exchange_t(k)
@@ -90,6 +90,13 @@ elemental module function default_real_approximately_equal(lhs, rhs) result(lhs_
       logical lhs_eq_rhs
     end function
 
+    elemental module function double_precision_approximately_equal(lhs, rhs) result(lhs_eq_rhs)
+      !! The result is true if lhs and rhs are the same to within a tolerance
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: lhs, rhs
+      logical lhs_eq_rhs
+    end function
+
     elemental module function default_real_map_to_input_range(self, tensor) result(normalized_tensor)
       !! The result contains the input tensor values normalized to fall on the range used during training
       implicit none
@@ -98,6 +105,14 @@ elemental module function default_real_map_to_input_range(self, tensor) result(n
       type(tensor_t) normalized_tensor
     end function
 
+    elemental module function double_precision_map_to_input_range(self, tensor) result(normalized_tensor)
+      !! The result contains the input tensor values normalized to fall on the range used during training
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: tensor
+      type(tensor_t(double_precision)) normalized_tensor
+    end function
+
     elemental module function default_real_map_from_output_range(self, normalized_tensor) result(tensor)
       !! The result contains the output tensor values unnormalized via the inverse of the mapping used in training
       implicit none
@@ -106,24 +121,50 @@ elemental module function default_real_map_from_output_range(self, normalized_te
       type(tensor_t) tensor
     end function
 
+    elemental module function double_precision_map_from_output_range(self, normalized_tensor) result(tensor)
+      !! The result contains the output tensor values unnormalized via the inverse of the mapping used in training
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: normalized_tensor
+      type(tensor_t(double_precision)) tensor
+    end function
+
     impure module function default_real_to_exchange(self) result(exchange)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(exchange_t) exchange
     end function
 
+    impure module function double_precision_to_exchange(self) result(exchange)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(exchange_t(double_precision)) exchange
+    end function
+
     impure elemental module function default_real_to_json(self) result(json_file)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(file_t) json_file
     end function
 
+    impure elemental module function double_precision_to_json(self) result(json_file)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(file_t) json_file
+    end function
+
     elemental module subroutine default_real_assert_conformable_with(self, inference_engine)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(inference_engine_t), intent(in) :: inference_engine
     end subroutine
 
+    elemental module subroutine double_precision_assert_conformable_with(self, inference_engine)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(inference_engine_t(double_precision)), intent(in) :: inference_engine
+    end subroutine
+
     elemental module function default_real_infer(self, inputs) result(outputs)
       implicit none
       class(inference_engine_t), intent(in) :: self
@@ -131,42 +172,85 @@ elemental module function default_real_infer(self, inputs) result(outputs)
       type(tensor_t) outputs
     end function
 
+    elemental module function double_precision_infer(self, inputs) result(outputs)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: inputs
+      type(tensor_t(double_precision)) outputs
+    end function
+
     elemental module function default_real_num_outputs(self) result(output_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer output_count
     end function
 
+    elemental module function double_precision_num_outputs(self) result(output_count)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      integer output_count
+    end function
+
     elemental module function default_real_num_hidden_layers(self) result(hidden_layer_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer hidden_layer_count
     end function
 
+    elemental module function double_precision_num_hidden_layers(self) result(hidden_layer_count)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      integer hidden_layer_count
+    end function
+
     elemental module function default_real_num_inputs(self) result(input_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer input_count
     end function
 
+    elemental module function double_precision_num_inputs(self) result(input_count)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      integer input_count
+    end function
+
     pure module function default_real_nodes_per_layer(self) result(node_count)
       implicit none
       class(inference_engine_t), intent(in) :: self
       integer, allocatable :: node_count(:)
     end function
 
-    elemental module function default_real_activation_function_name(self) result(activation_name)
+    pure module function double_precision_nodes_per_layer(self) result(node_count)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      integer, allocatable :: node_count(:)
+    end function
+
+    elemental module function default_real_activation_name(self) result(activation_name)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(string_t) activation_name
     end function
 
+    elemental module function double_precision_activation_name(self) result(activation_name)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(string_t) activation_name
+    end function
+
     pure module function default_real_skip(self) result(use_skip_connections)
       implicit none
       class(inference_engine_t), intent(in) :: self
       logical use_skip_connections
     end function
 
+    pure module function double_precision_skip(self) result(use_skip_connections)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      logical use_skip_connections
+    end function
+
   end interface
 
 end module inference_engine_m_
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 00f2fd542..d20e556a1 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -12,7 +12,8 @@
   implicit none
 
   interface assert_consistency
-    procedure inference_engine_consistency
+    procedure default_real_consistency
+    procedure double_precision_consistency
   end interface
 
   character(len=*), parameter :: acceptable_engine_tag = "0.13.0" ! git tag capable of reading the current json file format
@@ -23,10 +24,18 @@
     normalized_tensor = self%input_map_%map_to_training_range(tensor)
   end procedure
 
+  module procedure double_precision_map_to_input_range
+    normalized_tensor = self%input_map_%map_to_training_range(tensor)
+  end procedure
+
   module procedure default_real_map_from_output_range
     tensor = self%output_map_%map_from_training_range(normalized_tensor)
   end procedure
 
+  module procedure double_precision_map_from_output_range
+    tensor = self%output_map_%map_from_training_range(normalized_tensor)
+  end procedure
+
   module procedure default_real_to_exchange
     exchange%input_map_ = self%input_map_
     exchange%output_map_ = self%output_map_
@@ -39,6 +48,18 @@
     exchange%activation_strategy_ = self%activation_strategy_ 
   end procedure
 
+  module procedure double_precision_to_exchange
+    exchange%input_map_ = self%input_map_
+    exchange%output_map_ = self%output_map_
+    associate(strings => self%metadata_%strings())
+      exchange%metadata_ = metadata_t(strings(1),strings(2),strings(3),strings(4),strings(5))
+    end associate
+    exchange%weights_ = self%weights_
+    exchange%biases_ = self%biases_
+    exchange%nodes_ = self%nodes_
+    exchange%activation_strategy_ = self%activation_strategy_ 
+  end procedure
+
   module procedure default_real_infer
 
     real, allocatable :: a(:,:)
@@ -88,7 +109,56 @@
 
   end procedure
 
-  pure subroutine inference_engine_consistency(self)
+  module procedure double_precision_infer
+
+    double precision, allocatable :: a(:,:)
+    integer, parameter :: input_layer = 0
+    integer l
+
+    call assert_consistency(self)
+
+    associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
+
+      allocate(a(maxval(n), input_layer:output_layer))
+
+#ifndef _CRAYFTN
+      associate(normalized_inputs => self%input_map_%map_to_training_range(inputs))
+        a(1:n(input_layer),input_layer) = normalized_inputs%values()
+      end associate
+#else
+      block
+        type(tensor_t) normalized_inputs
+        normalized_inputs = self%input_map_%map_to_training_range(inputs)
+        a(1:n(input_layer),input_layer) = normalized_inputs%values()
+      end block
+#endif
+
+      feed_forward: &
+      do l = input_layer+1, output_layer
+        associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
+          a(1:n(l),l) = self%activation_strategy_%activation(z)
+        end associate
+      end do feed_forward
+
+#ifdef _CRAYFTN
+      block
+        type(tensor_t) :: normalized_outputs
+        normalized_outputs = tensor_t(a(1:n(output_layer), output_layer))
+#else
+      associate(normalized_outputs => tensor_t(a(1:n(output_layer), output_layer)))
+#endif
+        outputs = self%output_map_%map_from_training_range(normalized_outputs)
+#ifdef _CRAYFTN
+      end block
+#else
+      end associate
+#endif
+
+    end associate
+
+  end procedure
+
+  pure subroutine default_real_consistency(self)
 
     type(inference_engine_t), intent(in) :: self
 
@@ -113,6 +183,31 @@ pure subroutine inference_engine_consistency(self)
 
   end subroutine
 
+  pure subroutine double_precision_consistency(self)
+
+    type(inference_engine_t(double_precision)), intent(in) :: self
+
+    integer, parameter :: input_layer=0
+
+    associate( &
+      all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_),allocated(self%activation_strategy_)]&
+    )   
+      call assert(all(all_allocated),"inference_engine_s(inference_engine_consistency): fully_allocated", &
+        intrinsic_array_t(all_allocated))
+    end associate
+
+    associate(max_width=>maxval(self%nodes_), component_dims=>[size(self%biases_,1), size(self%weights_,1), size(self%weights_,2)])
+      call assert(all(component_dims == max_width), "inference_engine_s(inference_engine_consistency): conformable arrays", &
+        intrinsic_array_t([max_width,component_dims]))
+    end associate
+
+    associate(input_subscript => lbound(self%nodes_,1))
+      call assert(input_subscript == input_layer, "inference_engine_s(inference_engine_consistency): n base subsscript", &
+        input_subscript)
+    end associate
+
+  end subroutine
+
   impure function activation_factory_method(activation_name) result(activation)
     character(len=*), intent(in) :: activation_name
     class(activation_strategy_t), allocatable :: activation
@@ -287,6 +382,122 @@ impure function activation_factory_method(activation_name) result(activation)
     end associate
   end procedure default_real_to_json
 
+  module procedure double_precision_to_json
+
+#ifdef _CRAYFTN
+    type(tensor_map_t) proto_map
+    type(metadata_t) proto_meta
+    type(neuron_t) proto_neuron
+    proto_map = tensor_map_t("",[0.],[1.])
+    proto_meta = metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t(""))
+    proto_neuron = neuron_t([0.],1.)
+#endif
+
+    call assert_consistency(self)
+
+    associate( &
+       num_hidden_layers => self%num_hidden_layers() &
+      ,num_outputs => self%num_outputs() &
+      ,num_inputs => self%num_inputs() &
+      ,first_hidden => lbound(self%nodes_,1) + 1 &
+      ,last_hidden => ubound(self%nodes_,1) - 1 &
+#ifndef _CRAYFTN
+      ,proto_map => tensor_map_t("",[0.],[1.]) &
+      ,proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")) &
+      ,proto_neuron => neuron_t([0.],0.) &
+#endif
+    )
+      associate( &
+        metadata_lines => size(proto_meta%to_json()), &
+        tensor_map_lines => size(proto_map%to_json()), &
+        neuron_lines => size(proto_neuron%to_json()) &
+      )
+        block
+          type(string_t), allocatable :: lines(:)
+          integer layer, n, line
+          integer, parameter :: &
+            brace = 1, bracket_hidden_layers_array = 1, bracket_layer = 1, bracket_output_layer = 1, file_version_lines = 1
+               
+          associate( json_lines => &
+            brace + &                                                          ! { 
+              file_version_lines + &                                           !   "acceptable_engine_tag": ...
+              metadata_lines + &                                               !   "metadata": ...
+              tensor_map_lines + &                                             !   "inputs_tensor_map": ...
+              tensor_map_lines + &                                             !   "outputs_tensor_map": ...
+                bracket_hidden_layers_array + &                                !   "hidden_layers": [
+                  bracket_layer*num_hidden_layers + &                          !      [
+                    neuron_lines*sum(self%nodes_(first_hidden:last_hidden))+ & !        neuron ...
+                  bracket_layer*num_hidden_layers + &                          !      ] ...
+                bracket_hidden_layers_array + &                                !   ],
+                bracket_output_layer + &                                       !   "output_layer": [
+                  neuron_lines*num_outputs + &                                 !        neurons
+                bracket_output_layer + &                                       !    ]
+            brace &                                                            ! }
+          )
+            allocate(lines(json_lines))
+            lines(brace) = string_t('{')
+            lines(brace+1:brace+file_version_lines)= string_t('    "acceptable_engine_tag": "')//acceptable_engine_tag//'",'
+            associate(meta_start => brace + file_version_lines + 1)
+              associate(meta_end => meta_start + metadata_lines - 1)
+              lines(meta_start:meta_end) = self%metadata_%to_json()
+              lines(meta_end) = lines(meta_end) // ","
+                associate(input_map_start => meta_end + 1,  input_map_end => meta_end + tensor_map_lines)
+                  lines(input_map_start:input_map_end) =  self%input_map_%to_json()
+                  lines(input_map_end) = lines(input_map_end) // ","
+                  associate(output_map_start => input_map_end + 1,  output_map_end => input_map_end + tensor_map_lines)
+                    lines(output_map_start:output_map_end) =  self%output_map_%to_json()
+                    lines(output_map_end) = lines(output_map_end) // ","
+                    lines(output_map_end + 1) = string_t('     "hidden_layers": [')
+                    line= output_map_end + 1
+                  end associate
+                end associate
+              end associate
+            end associate
+            do layer = first_hidden, last_hidden
+              line = line + 1
+              lines(line) = string_t('         [')
+              do n = 1, self%nodes_(layer)
+                associate( &
+                  neuron => neuron_t(weights=self%weights_(n,1:self%nodes_(layer-1),layer), bias=self%biases_(n,layer)), &
+                  neuron_start => line + 1, &
+                  neuron_end => line + neuron_lines &
+                )
+                  lines(neuron_start:neuron_end) = neuron%to_json()
+                  lines(neuron_end) = lines(neuron_end) // trim(merge(" ", ",", n==self%nodes_(layer)))
+                end associate
+                line = line + neuron_lines
+              end do
+              line = line + 1
+              lines(line) = string_t('         ]') // trim(merge(" ", ",", layer==last_hidden))
+            end do
+            line = line + 1
+            lines(line) = string_t('    ],')
+            line = line + 1
+            lines(line) = string_t('     "output_layer": [')
+            layer = last_hidden + 1
+            do n = 1, self%nodes_(layer)
+              associate( &
+                  neuron => neuron_t(weights=self%weights_(n,1:self%nodes_(layer-1),layer), bias=self%biases_(n,layer)), &
+                  neuron_start=>line+1, &
+                  neuron_end=>line+neuron_lines &
+              )
+                lines(neuron_start:neuron_end) = neuron%to_json()
+                lines(neuron_end) = lines(neuron_end) // trim(merge(" ", ",", n==self%nodes_(layer)))
+              end associate
+              line = line + neuron_lines
+            end do
+            line = line + 1
+            lines(line) = string_t('         ]')
+            line = line + 1
+            lines(line) = string_t('}')
+            call assert(line == json_lines, "inference_engine_t%to_json: all lines defined", intrinsic_array_t([json_lines, line]))
+          end associate
+          json_file = file_t(lines)
+        end block
+      end associate
+    end associate
+  end procedure double_precision_to_json
+
   module procedure default_real_from_json
 
     character(len=:), allocatable :: justified_line
@@ -405,6 +616,23 @@ impure function activation_factory_method(activation_name) result(activation)
     
   end procedure
 
+  module procedure double_precision_assert_conformable_with
+
+    call assert_consistency(self)
+    call assert_consistency(inference_engine)
+
+    associate(equal_shapes => [ &
+      shape(self%weights_) == shape(inference_engine%weights_), &
+      shape(self%biases_) == shape(inference_engine%biases_), &
+      shape(self%nodes_) == shape(inference_engine%nodes_)  &
+     ])
+      call assert(all(equal_shapes), "assert_conformable_with: all(equal_shapes)", intrinsic_array_t(equal_shapes))
+    end associate
+
+    call assert(same_type_as(self%activation_strategy_, inference_engine%activation_strategy_), "assert_conformable_with: types)")
+    
+  end procedure
+
   module procedure default_real_approximately_equal
 
     logical nodes_eq
@@ -446,11 +674,57 @@ impure function activation_factory_method(activation_name) result(activation)
 
   end procedure
 
+  module procedure double_precision_approximately_equal
+
+    logical nodes_eq
+
+    nodes_eq = all(lhs%nodes_ == rhs%nodes_)
+
+    call assert_consistency(lhs)
+    call assert_consistency(rhs)
+    call lhs%assert_conformable_with(rhs)
+
+    block
+      integer l
+      logical layer_eq(ubound(lhs%nodes_,1))
+      real, parameter :: tolerance = 1.D-12
+
+      associate(n => lhs%nodes_)
+#ifndef __INTEL_COMPILER
+        do concurrent(l = 1:ubound(n,1))
+            layer_eq(l) = all(abs(lhs%weights_(1:n(l),1:n(l-1),l) - rhs%weights_(1:n(l),1:n(l-1),l)) < tolerance) .and. &
+                          all(abs(lhs%biases_(1:n(l),l)           - rhs%biases_(1:n(l),l)) < tolerance)
+        end do
+#else
+        block
+          integer j, k
+          do l = 1, ubound(n,1)
+            do j = 1, n(l)
+              do k = 1, n(l-1)
+                layer_eq(l) = all(abs(lhs%weights_(j,k,l) - rhs%weights_(j,k,l)) < tolerance) .and. &
+                              all(abs(lhs%biases_(j,l)    - rhs%biases_(j,l)) < tolerance)
+              end do
+            end do
+          end do
+        end block
+#endif
+      end associate
+
+      lhs_eq_rhs = nodes_eq .and. all(layer_eq)
+    end block
+
+  end procedure
+
   module procedure default_real_num_outputs
     call assert_consistency(self)
     output_count = self%nodes_(ubound(self%nodes_,1))
   end procedure
 
+  module procedure double_precision_num_outputs
+    call assert_consistency(self)
+    output_count = self%nodes_(ubound(self%nodes_,1))
+  end procedure
+
   module procedure default_real_num_hidden_layers
     integer, parameter :: input_layer = 1, output_layer = 1
     call assert_consistency(self)
@@ -459,23 +733,53 @@ impure function activation_factory_method(activation_name) result(activation)
     end associate
   end procedure
 
+  module procedure double_precision_num_hidden_layers
+    integer, parameter :: input_layer = 1, output_layer = 1
+    call assert_consistency(self)
+    associate(num_layers => size(self%nodes_))
+      hidden_layer_count =  num_layers - (input_layer + output_layer)
+    end associate
+  end procedure
+
   module procedure default_real_num_inputs
     call assert_consistency(self)
     input_count = self%nodes_(lbound(self%nodes_,1))
   end procedure
 
+  module procedure double_precision_num_inputs
+    call assert_consistency(self)
+    input_count = self%nodes_(lbound(self%nodes_,1))
+  end procedure
+
   module procedure default_real_nodes_per_layer
     call assert_consistency(self)
     node_count = self%nodes_
   end procedure
 
+  module procedure double_precision_nodes_per_layer
+    call assert_consistency(self)
+    node_count = self%nodes_
+  end procedure
+
   module procedure default_real_skip
     associate(strings => self%metadata_%strings())
       use_skip_connections = merge(.true., .false.,  strings(5) == "true")
     end associate
   end procedure
 
-  module procedure default_real_activation_function_name
+  module procedure double_precision_skip
+    associate(strings => self%metadata_%strings())
+      use_skip_connections = merge(.true., .false.,  strings(5) == "true")
+    end associate
+  end procedure
+
+  module procedure default_real_activation_name
+    associate(strings => self%metadata_%strings())
+      activation_name = strings(4)
+    end associate
+  end procedure
+
+  module procedure double_precision_activation_name
     associate(strings => self%metadata_%strings())
       activation_name = strings(4)
     end associate

From cd3acd6d25f3352a6cf5f4fac16ec6f75ee94dc2 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 2 Sep 2024 12:20:39 -0700
Subject: [PATCH 041/105] test(inference_engine): double-precision inference

This is the first commit with a passing test of double-precision
inference using parameterized derived types.
---
 src/inference_engine/inference_engine_m_.f90 | 12 ++++-
 src/inference_engine/inference_engine_s.F90  | 56 +++++++++++++++++---
 src/inference_engine/metadata_m.f90          | 11 +++-
 src/inference_engine/metadata_s.f90          |  4 ++
 test/inference_engine_test_m.F90             | 44 +++++++++++++--
 5 files changed, 113 insertions(+), 14 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index 294ee2b2c..46985483a 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -63,7 +63,7 @@ module inference_engine_m_
 
   interface inference_engine_t
 
-    impure module function default_real_construct_from_padded_arrays(metadata, weights, biases, nodes, input_map, output_map) &
+    impure module function default_real_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
       result(inference_engine)
       implicit none
       type(string_t), intent(in) :: metadata(:)
@@ -73,6 +73,16 @@ impure module function default_real_construct_from_padded_arrays(metadata, weigh
       type(inference_engine_t) inference_engine
     end function
 
+    impure module function double_precision_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
+      result(inference_engine)
+      implicit none
+      type(metadata_t), intent(in) :: metadata
+      double precision, intent(in) :: weights(:,:,:), biases(:,:)
+      integer, intent(in) :: nodes(0:)
+      type(tensor_map_t(double_precision)), intent(in), optional :: input_map, output_map
+      type(inference_engine_t(double_precision)) inference_engine
+    end function
+
     impure elemental module function default_real_from_json(file_) result(inference_engine)
       implicit none
       type(file_t), intent(in) :: file_
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index d20e556a1..4ac1b5871 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -228,7 +228,7 @@ impure function activation_factory_method(activation_name) result(activation)
     end select
   end function
 
-  module procedure default_real_construct_from_padded_arrays
+  module procedure default_real_construct_from_components
 
     inference_engine%metadata_ = metadata_t(metadata(1),metadata(2),metadata(3),metadata(4),metadata(5))
     inference_engine%weights_ = weights
@@ -264,7 +264,47 @@ impure function activation_factory_method(activation_name) result(activation)
 
     call assert_consistency(inference_engine)
 
-  end procedure default_real_construct_from_padded_arrays
+  end procedure default_real_construct_from_components
+
+  module procedure double_precision_construct_from_components
+
+    inference_engine%metadata_ = metadata
+    inference_engine%weights_ = weights
+    inference_engine%biases_ = biases
+    inference_engine%nodes_ = nodes
+
+    block
+      integer i
+
+      if (present(input_map)) then
+        inference_engine%input_map_ = input_map
+      else
+        associate(num_inputs => nodes(lbound(nodes,1)))
+          associate(default_intercept => [(0D0, i=1,num_inputs)], default_slope => [(1D0, i=1,num_inputs)])
+            inference_engine%input_map_ = tensor_map_t("inputs", default_intercept, default_slope)
+          end associate
+        end associate
+      end if
+
+      if (present(output_map)) then
+        inference_engine%output_map_ = output_map
+      else
+        associate(num_outputs => nodes(ubound(nodes,1)))
+          associate(default_intercept => [(0D0, i=1,num_outputs)], default_slope => [(1D0, i=1,num_outputs)])
+            inference_engine%output_map_ = tensor_map_t("outputs", default_intercept, default_slope)
+          end associate
+        end associate
+      end if
+    end block
+
+    if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
+    associate(function_name => metadata%activation_name())
+      allocate(inference_engine%activation_strategy_, source = activation_factory_method(function_name%string()))
+    end associate
+
+    call assert_consistency(inference_engine)
+
+  end procedure double_precision_construct_from_components
 
   module procedure default_real_to_json
 
@@ -388,9 +428,9 @@ impure function activation_factory_method(activation_name) result(activation)
     type(tensor_map_t) proto_map
     type(metadata_t) proto_meta
     type(neuron_t) proto_neuron
-    proto_map = tensor_map_t("",[0.],[1.])
+    proto_map = tensor_map_t("",[0D0],[1D0])
     proto_meta = metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t(""))
-    proto_neuron = neuron_t([0.],1.)
+    proto_neuron = neuron_t([0D0],1D0)
 #endif
 
     call assert_consistency(self)
@@ -402,9 +442,9 @@ impure function activation_factory_method(activation_name) result(activation)
       ,first_hidden => lbound(self%nodes_,1) + 1 &
       ,last_hidden => ubound(self%nodes_,1) - 1 &
 #ifndef _CRAYFTN
-      ,proto_map => tensor_map_t("",[0.],[1.]) &
+      ,proto_map => tensor_map_t("",[0D0],[0D0]) &
       ,proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")) &
-      ,proto_neuron => neuron_t([0.],0.) &
+      ,proto_neuron => neuron_t([0D0],0D0) &
 #endif
     )
       associate( &
@@ -590,7 +630,9 @@ impure function activation_factory_method(activation_name) result(activation)
         associate(metadata_strings => metadata%strings())
           inference_engine = hidden_layers%inference_engine(metadata_strings, output_layer, input_map, output_map)
           if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
-          allocate(inference_engine%activation_strategy_, source = activation_factory_method(metadata_strings(4)%string()))
+          associate(function_name => metadata%activation_name())
+            allocate(inference_engine%activation_strategy_, source = activation_factory_method(function_name%string()))
+          end associate
         end associate
       end associate
     end associate read_metadata
diff --git a/src/inference_engine/metadata_m.f90 b/src/inference_engine/metadata_m.f90
index 09683fc57..6dcf5c909 100644
--- a/src/inference_engine/metadata_m.f90
+++ b/src/inference_engine/metadata_m.f90
@@ -10,10 +10,11 @@ module metadata_m
     private
     type(string_t) modelName_, modelAuthor_, compilationDate_, activationFunction_, usingSkipConnections_
   contains
+    generic :: operator(==) => equals
     procedure :: strings
     procedure :: to_json
-    procedure :: equals
-    generic :: operator(==) => equals
+    procedure :: activation_name
+    procedure, private :: equals
   end type
 
   interface metadata_t
@@ -47,6 +48,12 @@ pure module function strings(self) result(components)
       type(string_t), allocatable :: components(:)
     end function
 
+    pure module function activation_name(self) result(function_name)
+      implicit none
+      class(metadata_t), intent(in) :: self
+      type(string_t) function_name
+    end function
+
     pure module function to_json(self) result(lines)
       implicit none
       class(metadata_t), intent(in) :: self
diff --git a/src/inference_engine/metadata_s.f90 b/src/inference_engine/metadata_s.f90
index 92597e2c6..c2702bcd4 100644
--- a/src/inference_engine/metadata_s.f90
+++ b/src/inference_engine/metadata_s.f90
@@ -8,6 +8,10 @@
     components = [self%modelName_, self%modelAuthor_, self%compilationDate_, self%activationFunction_, self%usingSkipConnections_]
   end procedure 
   
+  module procedure activation_name
+    function_name = self%activationFunction_
+  end procedure
+
   module procedure from_components
     metadata%modelName_ = modelName
     metadata%modelAuthor_ = modelAuthor
diff --git a/test/inference_engine_test_m.F90 b/test/inference_engine_test_m.F90
index a2c1bfe1d..128598ae4 100644
--- a/test/inference_engine_test_m.F90
+++ b/test/inference_engine_test_m.F90
@@ -5,13 +5,14 @@ module inference_engine_test_m
 
   ! External dependencies
   use assert_m, only : assert
+  use kind_parameters_m, only : double_precision
   use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t, file_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : inference_engine_t, tensor_t
+  use inference_engine_m, only : inference_engine_t, tensor_t, metadata_t
 
   implicit none
 
@@ -42,22 +43,26 @@ function results() result(test_results)
       ,test_description_t("converting a network with 2 hidden layers to and from JSON format", multi_hidden_layer_net_to_from_json)&
       ,test_description_t("converting a network with varying-width hidden layers to/from JSON", varying_width_net_to_from_json) &
       ,test_description_t("performing inference with a network with hidden layers of varying width", infer_with_varying_width_net) &
+      ,test_description_t("double-precision inference", double_precision_inference) &
     ]
 #else
-    procedure(test_function_i), pointer :: &
-      elemental_infer_1_ptr, elemental_infer_2_ptr, multi_hidden_ptr, vary_width_ptr, vary_width_infer_ptr
+    procedure(test_function_i), pointer :: elemental_infer_1_ptr, elemental_infer_2_ptr, multi_hidden_ptr, vary_width_ptr, &
+      vary_width_infer_ptr, double_precision_inference_ptr
+
     elemental_infer_1_ptr => elemental_infer_with_1_hidden_layer_xor_net
     elemental_infer_2_ptr => elemental_infer_with_2_hidden_layer_xor_net
     multi_hidden_ptr => multi_hidden_layer_net_to_from_json
     vary_width_ptr => varying_width_net_to_from_json
     vary_width_infer_ptr => infer_with_varying_width_net
+    double_precision_inference_ptr => double_precision_inference
 
     test_descriptions = [ &
        test_description_t("performing elemental inference with 1 hidden layer", elemental_infer_1_ptr) &
       ,test_description_t("performing elemental inference with 2 hidden layers", elemental_infer_2_ptr) &
       ,test_description_t("converting a network with 2 hidden layers to and from JSON format", multi_hidden_ptr) &
       ,test_description_t("converting a network with varying-width hidden layers to/from JSON", vary_width_ptr) &
-      ,test_description_t("performing inference with varyring-width hidden layers", vary_width_infer_ptr)  &
+      ,test_description_t("performing inference with varying-width hidden layers", vary_width_infer_ptr)  &
+      ,test_description_t("double-precision inference", double_precision_inference_ptr) &
     ]
 #endif
     associate( &
@@ -122,6 +127,25 @@ function decrement_split_combine_increment() result(inference_engine)
     )
   end function
 
+  function double_precision_network() result(inference_engine)
+    !! The result is a double-precision version of the this network defined in the decrement_split_combine_increment function
+    type(inference_engine_t(double_precision)) inference_engine
+    integer, parameter :: inputs = 2, hidden(*) = [2,3], outputs = 2 ! number of neurons in input, output, and hidden layers
+    integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
+    integer, parameter :: n_max = maxval(n), layers=size(n) ! max layer width, number of layers
+    integer, parameter :: w_shape(*) = [n_max, n_max, layers-1], b_shape(*) = [n_max, n_max]
+    double precision, parameter :: & 
+      w(*,*,*) = reshape( &
+        [double precision :: 1.,0.,0., 0.,1.,0., 0.,0.,0., 1.,0.,0., 0.,.5,.5, 0.,0.,0., 1.,0.,0., 0.,1.,0., 0.,1.,1.], w_shape &
+      ), b(*,*) = reshape( [double precision :: 0.,-1.,0., 0.,0.,0., 0.,1.,0.], b_shape)
+      
+    inference_engine = inference_engine_t( &
+      metadata = metadata_t( &
+        string_t("Double-Precision"), string_t("Damian Rouson"), string_t("2024-09-02"), string_t("relu"), string_t("false") &
+     ), weights = w, biases = b, nodes = n &
+    )
+  end function
+
   function varying_width() result(inference_engine)
     type(inference_engine_t) inference_engine
     integer, parameter :: inputs = 2, hidden(*) = [2,3], outputs = 2 ! number of neurons in input, output, and hidden layers
@@ -191,6 +215,18 @@ function infer_with_varying_width_net() result(test_passes)
     test_passes = all(abs(inputs%values() - outputs%values()) < tolerance)
   end function
 
+  function double_precision_inference() result(test_passes)
+    logical test_passes
+    type(inference_engine_t(double_precision)) inference_engine
+    type(tensor_t(double_precision)) inputs, outputs
+    real, parameter :: tolerance = 1.D-08
+
+    inference_engine = double_precision_network()
+    inputs = tensor_t([1.1D0, 2.7D0])
+    outputs = inference_engine%infer(inputs)
+    test_passes = all(abs(inputs%values() - outputs%values()) < tolerance)
+  end function
+
   function elemental_infer_with_1_hidden_layer_xor_net() result(test_passes)
     logical test_passes
     type(inference_engine_t) inference_engine

From bf1c126410ee74170686dd5e676002e424c4c788 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 2 Sep 2024 12:52:26 -0700
Subject: [PATCH 042/105] test(inference_engine): whitespace edits

---
 test/inference_engine_test_m.F90 | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/inference_engine_test_m.F90 b/test/inference_engine_test_m.F90
index 128598ae4..2d7d3071c 100644
--- a/test/inference_engine_test_m.F90
+++ b/test/inference_engine_test_m.F90
@@ -112,7 +112,7 @@ function decrement_split_combine_increment() result(inference_engine)
     integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n) ! max layer width, number of layers
     integer, parameter :: w_shape(*) = [n_max, n_max, layers-1], b_shape(*) = [n_max, n_max]
-    real, parameter :: & 
+    real, parameter :: &
       w(*,*,*) = reshape( [1.,0.,0., 0.,1.,0., 0.,0.,0., 1.,0.,0., 0.,.5,.5, 0.,0.,0., 1.,0.,0., 0.,1.,0., 0.,1.,1.], w_shape), &
       b(*,*) = reshape( [0.,-1.,0., 0.,0.,0., 0.,1.,0.], b_shape)
 
@@ -134,11 +134,11 @@ function double_precision_network() result(inference_engine)
     integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n) ! max layer width, number of layers
     integer, parameter :: w_shape(*) = [n_max, n_max, layers-1], b_shape(*) = [n_max, n_max]
-    double precision, parameter :: & 
+    double precision, parameter :: &
       w(*,*,*) = reshape( &
         [double precision :: 1.,0.,0., 0.,1.,0., 0.,0.,0., 1.,0.,0., 0.,.5,.5, 0.,0.,0., 1.,0.,0., 0.,1.,0., 0.,1.,1.], w_shape &
       ), b(*,*) = reshape( [double precision :: 0.,-1.,0., 0.,0.,0., 0.,1.,0.], b_shape)
-      
+
     inference_engine = inference_engine_t( &
       metadata = metadata_t( &
         string_t("Double-Precision"), string_t("Damian Rouson"), string_t("2024-09-02"), string_t("relu"), string_t("false") &

From 3abb069a58501a0a72bd6e89bcff9f8c04cac557 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 2 Sep 2024 17:31:42 -0700
Subject: [PATCH 043/105] fix(inference_engine_m): make entities public

This commit exposes the double_precision kind parameter and the
double_precision_string_t in the inference_engine_m module.
---
 src/inference_engine_m.f90 | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index ca7a8c1eb..5f961b85b 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -4,13 +4,15 @@ module inference_engine_m
  !! Specify the user-facing modules, derived types, and type parameters
  use activation_strategy_m, only : activation_strategy_t
  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
+ use double_precision_string_m, only : double_precision_string_t
+ use gelu_m, only : gelu_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
  use inference_engine_m_, only : inference_engine_t
+ use kind_parameters_m, only : default_real, double_precision
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t
  use network_configuration_m, only : network_configuration_t
- use gelu_m, only : gelu_t
  use relu_m, only : relu_t
  use sigmoid_m, only : sigmoid_t
  use step_m, only : step_t

From 35585740a65b6f3d8d9c4d91cc34cc363ab3ce0b Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 2 Sep 2024 21:24:13 -0700
Subject: [PATCH 044/105] build(fpm): update dependency to julienne 1.2.2

---
 fpm.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fpm.toml b/fpm.toml
index 7e22c0bb6..cd88caba8 100644
--- a/fpm.toml
+++ b/fpm.toml
@@ -8,4 +8,4 @@ maintainer = "rouson@lbl.gov"
 assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.7.0"}
 
 [dev-dependencies]
-julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.1"}
+julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.2"}

From f4c0c6ffd2dbeb68fbfecaf7d0a89559256c22e7 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 2 Sep 2024 21:25:34 -0700
Subject: [PATCH 045/105] doc(example):double-precision concurrent inference

---
 example/concurrent-inferences.f90             |  83 ++++++++----
 .../double_precision_file_m.f90               |  20 ++-
 .../double_precision_file_s.f90               |   8 ++
 src/inference_engine/inference_engine_m_.f90  |  10 +-
 src/inference_engine/inference_engine_s.F90   | 125 ++++++++++++++++--
 src/inference_engine/layer_m.f90              |  22 ++-
 src/inference_engine/layer_s.f90              |  74 ++++++++++-
 src/inference_engine_m.f90                    |   1 +
 8 files changed, 296 insertions(+), 47 deletions(-)

diff --git a/example/concurrent-inferences.f90 b/example/concurrent-inferences.f90
index 7a1426096..5036e67b7 100644
--- a/example/concurrent-inferences.f90
+++ b/example/concurrent-inferences.f90
@@ -3,7 +3,7 @@
 program concurrent_inferences
   !! This program demonstrates how to read a neural network from a JSON file
   !! and use the network to perform concurrent inferences.
-  use inference_engine_m, only : inference_engine_t, tensor_t
+  use inference_engine_m, only : inference_engine_t, tensor_t, double_precision, double_precision_string_t, double_precision_file_t
   use julienne_m, only : string_t, command_line_t, file_t
   use assert_m, only : assert
   use iso_fortran_env, only : int64, real64
@@ -19,35 +19,38 @@ program concurrent_inferences
       'Usage: fpm run --example concurrent-inferences --profile release --flag "-fopenmp" -- --network "<file-name>"'
   end if
 
-  block 
-    type(inference_engine_t) inference_engine
-    type(tensor_t), allocatable :: inputs(:,:,:), outputs(:,:,:)
-    real, allocatable :: input_components(:,:,:,:)
+  block
     integer, parameter :: lat=263, lon=317, lev=15 ! latitudes, longitudes, levels (elevations)
     integer i, j, k
 
-    print *, "Constructing a new inference_engine_t object from the file " // network_file_name%string()
-    inference_engine = inference_engine_t(file_t(network_file_name))
+    single_precision_inference: &
+    block
+      integer(int64) t_start, t_finish, clock_rate
 
-    print *,"Defining an array of tensor_t input objects with random normalized components"
-    allocate(inputs(lat,lon,lev))
-    allocate(input_components(lat,lon,lev,inference_engine%num_inputs()))
-    call random_number(input_components)
+      type(inference_engine_t) inference_engine
+      type(tensor_t), allocatable :: inputs(:,:,:), outputs(:,:,:)
+      real, allocatable :: input_components(:,:,:,:)
 
-    do concurrent(i=1:lat, j=1:lon, k=1:lev)
-      inputs(i,j,k) = tensor_t(input_components(i,j,k,:))
-    end do
+      print *, "Constructing a new inference_engine_t object from the file " // network_file_name%string()
+      inference_engine = inference_engine_t(file_t(network_file_name))
 
-    block 
-      integer(int64) t_start, t_finish, clock_rate
+      print *,"Defining an array of tensor_t input objects with random normalized components"
+      allocate(outputs(lat,lon,lev))
+      allocate( inputs(lat,lon,lev))
+      allocate(input_components(lat,lon,lev,inference_engine%num_inputs()))
+      call random_number(input_components)
 
-      print *,"Performing elemental inferences"
+      do concurrent(i=1:lat, j=1:lon, k=1:lev)
+        inputs(i,j,k) = tensor_t(input_components(i,j,k,:))
+      end do
+
+      print *,"Performing concurrent inference"
       call system_clock(t_start, clock_rate)
-      outputs = inference_engine%infer(inputs)  ! implicit allocation of outputs array
+      do concurrent(i=1:lat, j=1:lon, k=1:lev)
+        outputs(i,j,k) = inference_engine%infer(inputs(i,j,k))
+      end do
       call system_clock(t_finish)
-      print *,"Elemental inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
-
-      call assert(all(shape(outputs) == shape(inputs)), "all(shape(outputs) == shape(inputs))")
+      print *,"Concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
 
       print *,"Performing loop-based inference"
       call system_clock(t_start)
@@ -61,15 +64,45 @@ program concurrent_inferences
       call system_clock(t_finish)
       print *,"Looping inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
 
-      print *,"Performing concurrent inference"
-      call system_clock(t_start)
+      print *,"Performing elemental inferences"
+      call system_clock(t_start, clock_rate)
+      outputs = inference_engine%infer(inputs)  ! implicit (re)allocation of outputs array only if shape(inputs) /= shape(outputs)
+      call system_clock(t_finish)
+      print *,"Elemental inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
+
+    end block single_precision_inference
+
+    double_precision_inference: &
+    block
+      integer(int64) t_start, t_finish, clock_rate
+
+      type(inference_engine_t(double_precision)) inference_engine
+      type(tensor_t(double_precision)), allocatable :: inputs(:,:,:), outputs(:,:,:)
+      double precision, allocatable :: input_components(:,:,:,:)
+
+      print *, "Constructing a new inference_engine_t object from the file " // network_file_name%string()
+      inference_engine = inference_engine_t(double_precision_file_t(network_file_name))
+
+      print *,"Defining an array of tensor_t input objects with random normalized components"
+      allocate(outputs(lat,lon,lev))
+      allocate( inputs(lat,lon,lev))
+      allocate(input_components(lat,lon,lev,inference_engine%num_inputs()))
+      call random_number(input_components)
+
+      do concurrent(i=1:lat, j=1:lon, k=1:lev)
+        inputs(i,j,k) = tensor_t(input_components(i,j,k,:))
+      end do
+
+      print *,"Performing double-precision concurrent inference"
+      call system_clock(t_start, clock_rate)
       do concurrent(i=1:lat, j=1:lon, k=1:lev)
         outputs(i,j,k) = inference_engine%infer(inputs(i,j,k))           
       end do
       call system_clock(t_finish)
-      print *,"Concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
+      print *,"Double-precision concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
+
+    end block double_precision_inference
 
-    end block
   end block
 
 end program
diff --git a/src/inference_engine/double_precision_file_m.f90 b/src/inference_engine/double_precision_file_m.f90
index 90b50cd98..6264116ef 100644
--- a/src/inference_engine/double_precision_file_m.f90
+++ b/src/inference_engine/double_precision_file_m.f90
@@ -1,5 +1,5 @@
 module double_precision_file_m
-  use julienne_m, only : file_t
+  use julienne_m, only : file_t, string_t
   use double_precision_string_m, only : double_precision_string_t
 
   implicit none
@@ -9,9 +9,25 @@ module double_precision_file_m
     procedure double_precision_lines
   end type
 
+  interface double_precision_file_t
+
+    impure elemental module function construct_from_string(file_name) result(double_precision_file)
+      implicit none
+      type(string_t), intent(in) :: file_name
+      type(double_precision_file_t) double_precision_file
+    end function
+
+    impure elemental module function construct_from_character(file_name) result(double_precision_file)
+      implicit none
+      character(len=*), intent(in) :: file_name
+      type(double_precision_file_t) double_precision_file
+    end function
+
+  end interface
+
   interface
 
-    module function double_precision_lines(self) result(lines)
+    pure module function double_precision_lines(self) result(lines)
       implicit none
       class(double_precision_file_t), intent(in) :: self
       type(double_precision_string_t), allocatable :: lines(:)
diff --git a/src/inference_engine/double_precision_file_s.f90 b/src/inference_engine/double_precision_file_s.f90
index 9d8eec923..961bfde48 100644
--- a/src/inference_engine/double_precision_file_s.f90
+++ b/src/inference_engine/double_precision_file_s.f90
@@ -3,6 +3,14 @@
 
 contains
 
+  module procedure construct_from_string
+    double_precision_file%file_t = file_t(file_name)
+  end procedure
+
+  module procedure construct_from_character
+    double_precision_file%file_t = file_t(file_name)
+  end procedure
+
   module procedure double_precision_lines
     lines = double_precision_string_t(self%file_t%lines())
   end procedure
diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index 46985483a..136a143aa 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -3,9 +3,9 @@
 module inference_engine_m_
   !! Define an abstraction that supports inference operationsn on a neural network
   use activation_strategy_m, only : activation_strategy_t
-  use julienne_file_m, only : file_t
-  use julienne_string_m, only : string_t
+  use double_precision_file_m, only : double_precision_file_t
   use kind_parameters_m, only : default_real, double_precision
+  use julienne_m, only : file_t, string_t
   use metadata_m, only : metadata_t
   use tensor_m, only : tensor_t
   use tensor_map_m, only : tensor_map_t
@@ -89,6 +89,12 @@ impure elemental module function default_real_from_json(file_) result(inference_
       type(inference_engine_t) inference_engine
     end function
 
+    impure elemental module function double_precision_from_json(file) result(inference_engine)
+      implicit none
+      type(double_precision_file_t), intent(in) :: file
+      type(inference_engine_t(double_precision)) inference_engine
+    end function
+
   end interface
 
   interface
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 4ac1b5871..4bd3d6cd5 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -2,13 +2,15 @@
 ! Terms of use are as specified in LICENSE.txt
 submodule(inference_engine_m_) inference_engine_s
   use assert_m, only : assert, intrinsic_array_t
-  use step_m, only : step_t
-  use swish_m, only : swish_t
-  use sigmoid_m, only : sigmoid_t
+  use double_precision_string_m, only : double_precision_string_t
   use gelu_m, only : gelu_t
-  use relu_m, only : relu_t
+  use kind_parameters_m, only : double_precision
   use layer_m, only : layer_t
   use neuron_m, only : neuron_t
+  use relu_m, only : relu_t
+  use step_m, only : step_t
+  use swish_m, only : swish_t
+  use sigmoid_m, only : sigmoid_t
   implicit none
 
   interface assert_consistency
@@ -547,7 +549,7 @@ impure function activation_factory_method(activation_name) result(activation)
     type(layer_t) hidden_layers, output_layer
 
     lines = file_%lines()
-    call assert(adjustl(lines(1)%string())=="{", "inference_engine_s(from_json): expected outermost object '{'")
+    call assert(adjustl(lines(1)%string())=="{", "inference_engine_s(default_real_from_json): expected outermost object '{'")
  
     check_git_tag: &
     block 
@@ -556,7 +558,7 @@ impure function activation_factory_method(activation_name) result(activation)
       tag = lines(2)%get_json_value("acceptable_engine_tag", mold="")
       call assert( &
         tag == acceptable_engine_tag &
-        ,"inference_engine_s(from_json): acceptable_engine_tag" &
+        ,"inference_engine_s(default_real_from_json): acceptable_engine_tag" &
         ,tag //"(expected " //acceptable_engine_tag // ")" &
       )
     end block check_git_tag
@@ -573,7 +575,7 @@ impure function activation_factory_method(activation_name) result(activation)
            if (justified_line == '"inputs_map": {') exit
          end do find_inputs_map
 
-         call assert(justified_line =='"inputs_map": {', 'from_json: expecting "inputs_map": {', justified_line)
+         call assert(justified_line =='"inputs_map": {', 'default_real_from_json: expecting "inputs_map": {', justified_line)
          input_map = tensor_map_t(lines(l:l+num_map_lines-1))
 
          find_outputs_map: &
@@ -582,7 +584,7 @@ impure function activation_factory_method(activation_name) result(activation)
            if (justified_line == '"outputs_map": {') exit
          end do find_outputs_map
 
-         call assert(justified_line =='"outputs_map": {', 'from_json: expecting "outputs_map": {', justified_line)
+         call assert(justified_line =='"outputs_map": {', 'default_real_from_json: expecting "outputs_map": {', justified_line)
          output_map = tensor_map_t(lines(l:l+num_map_lines-1))
 
       end associate
@@ -593,7 +595,7 @@ impure function activation_factory_method(activation_name) result(activation)
       justified_line = adjustl(lines(l)%string())
       if (justified_line == '"hidden_layers": [') exit
     end do find_hidden_layers
-    call assert(justified_line=='"hidden_layers": [', 'from_json: expecting "hidden_layers": [', justified_line)
+    call assert(justified_line=='"hidden_layers": [', 'default_real_from_json: expecting "hidden_layers": [', justified_line)
 
     read_hidden_layers: &
     block
@@ -609,7 +611,7 @@ impure function activation_factory_method(activation_name) result(activation)
 
           output_layer_line = lines(output_layer_line_number)%string()
 
-          call assert(adjustl(output_layer_line)=='"output_layer": [', 'from_json: expecting "output_layer": [', &
+          call assert(adjustl(output_layer_line)=='"output_layer": [', 'default_real_from_json: expecting "output_layer": [', &
             lines(output_layer_line_number)%string())
 
           output_layer = layer_t(lines, start=output_layer_line_number)
@@ -622,7 +624,7 @@ impure function activation_factory_method(activation_name) result(activation)
       justified_line = adjustl(lines(l)%string())
       if (justified_line == '"metadata": {') exit
     end do find_metadata
-    call assert(justified_line=='"metadata": {', 'from_json: expecting "metadata": {', justified_line)
+    call assert(justified_line=='"metadata": {', 'default_real_from_json: expecting "metadata": {', justified_line)
 
     read_metadata: &
     associate(proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")))
@@ -641,6 +643,107 @@ impure function activation_factory_method(activation_name) result(activation)
 
   end procedure default_real_from_json
 
+  module procedure double_precision_from_json
+
+    character(len=:), allocatable :: justified_line
+    integer l, num_file_lines
+    type(double_precision_string_t), allocatable :: lines(:)
+    type(tensor_map_t(double_precision)) input_map, output_map
+    type(layer_t(double_precision)) hidden_layers, output_layer
+
+    lines = file%double_precision_lines()
+    call assert(adjustl(lines(1)%string())=="{", "inference_engine_s(double_precision_from_json): expected outermost object '{'")
+
+    check_git_tag: &
+    block
+      character(len=:), allocatable :: tag
+
+      tag = lines(2)%get_json_value("acceptable_engine_tag", mold="")
+      call assert( &
+        tag == acceptable_engine_tag &
+        ,"inference_engine_s(double_precision_from_json): acceptable_engine_tag" &
+        ,tag //"(expected " //acceptable_engine_tag // ")" &
+      )
+    end block check_git_tag
+
+    num_file_lines = size(lines)
+
+    read_tensor_maps: &
+    associate(proto_map => tensor_map_t("",[0D0],[1D0]))
+      associate(num_map_lines => size(proto_map%to_json()))
+
+         find_inputs_map: &
+         do l = 1, num_file_lines
+           justified_line = adjustl(lines(l)%string())
+           if (justified_line == '"inputs_map": {') exit
+         end do find_inputs_map
+
+         call assert(justified_line =='"inputs_map": {', 'double_precision_from_json: expecting "inputs_map": {', justified_line)
+         input_map = tensor_map_t(lines(l:l+num_map_lines-1))
+
+         find_outputs_map: &
+         do l = 1, num_file_lines
+           justified_line = adjustl(lines(l)%string())
+           if (justified_line == '"outputs_map": {') exit
+         end do find_outputs_map
+
+         call assert(justified_line =='"outputs_map": {', 'double_precision_from_json: expecting "outputs_map": {', justified_line)
+         output_map = tensor_map_t(lines(l:l+num_map_lines-1))
+
+      end associate
+    end associate read_tensor_maps
+
+    find_hidden_layers: &
+    do l = 1, num_file_lines
+      justified_line = adjustl(lines(l)%string())
+      if (justified_line == '"hidden_layers": [') exit
+    end do find_hidden_layers
+    call assert(justified_line=='"hidden_layers": [', 'double_precision_from_json: expecting "hidden_layers": [', justified_line)
+
+    read_hidden_layers: &
+    block
+      integer, parameter :: bracket_lines_per_layer=2
+      character(len=:), allocatable :: output_layer_line
+
+      hidden_layers = layer_t(lines, start=l+1)
+
+      read_layers_of_neurons: &
+      associate(proto_neuron => neuron_t(weights=[0D0], bias=0D0))
+        associate(output_layer_line_number => l + 1 + size(proto_neuron%to_json())*sum(hidden_layers%count_neurons()) &
+          + bracket_lines_per_layer*hidden_layers%count_layers() + 1)
+
+          output_layer_line = lines(output_layer_line_number)%string()
+
+          call assert(adjustl(output_layer_line)=='"output_layer": [', 'double_precision_from_json: expecting "output_layer": [', &
+            lines(output_layer_line_number)%string())
+
+          output_layer = layer_t(lines, start=output_layer_line_number)
+        end associate
+      end associate read_layers_of_neurons
+    end block read_hidden_layers
+
+    find_metadata: &
+    do l = 1, num_file_lines
+      justified_line = adjustl(lines(l)%string())
+      if (justified_line == '"metadata": {') exit
+    end do find_metadata
+    call assert(justified_line=='"metadata": {', 'double_precision_from_json: expecting "metadata": {', justified_line)
+
+    read_metadata: &
+    associate(proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")))
+      associate(metadata => metadata_t(lines(l : l + size(proto_meta%to_json()) - 1)))
+        inference_engine = hidden_layers%inference_engine(metadata, output_layer, input_map, output_map)
+        if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
+        associate(function_name => metadata%activation_name())
+          allocate(inference_engine%activation_strategy_, source = activation_factory_method(function_name%string()))
+        end associate
+      end associate
+    end associate read_metadata
+
+    call assert_consistency(inference_engine)
+
+  end procedure double_precision_from_json
+
   module procedure default_real_assert_conformable_with
 
     call assert_consistency(self)
diff --git a/src/inference_engine/layer_m.f90 b/src/inference_engine/layer_m.f90
index 47065eef1..cc418e8d6 100644
--- a/src/inference_engine/layer_m.f90
+++ b/src/inference_engine/layer_m.f90
@@ -1,12 +1,13 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 module layer_m
-  use neuron_m, only : neuron_t
+  use double_precision_string_m, only : double_precision_string_t
+  use kind_parameters_m, only : default_real, double_precision
   use julienne_string_m, only : string_t
   use inference_engine_m_, only : inference_engine_t
-  use tensor_map_m, only :  tensor_map_t
-  use kind_parameters_m, only : default_real, double_precision
-  use double_precision_string_m, only : double_precision_string_t
+  use metadata_m, only : metadata_t
+  use neuron_m, only : neuron_t
+  use tensor_map_m, only : tensor_map_t
   implicit none
 
   private
@@ -18,8 +19,8 @@ module layer_m
     type(neuron_t(k)),             private :: neuron !! linked list of this layer's neurons 
     type(layer_t(k)), allocatable, private :: next !! next layer
   contains
-    generic :: inference_engine => default_real_inference_engine 
-    procedure, private ::          default_real_inference_engine
+    generic :: inference_engine => default_real_inference_engine, double_precision_inference_engine
+    procedure, private ::          default_real_inference_engine, double_precision_inference_engine
     generic :: count_layers => default_real_count_layers, double_precision_count_layers
     procedure, private ::      default_real_count_layers, double_precision_count_layers
     generic :: count_neurons => default_real_count_neurons, double_precision_count_neurons
@@ -65,6 +66,15 @@ module function default_real_inference_engine(hidden_layers, metadata, output_la
       type(inference_engine_t) inference_engine_
     end function
 
+    module function double_precision_inference_engine(hidden_layers, metadata, output_layer, input_map, output_map) result(inference_engine_)
+      implicit none
+      class(layer_t(double_precision)), intent(in), target :: hidden_layers
+      type(layer_t(double_precision)), intent(in), target :: output_layer
+      type(metadata_t), intent(in) :: metadata
+      type(tensor_map_t(double_precision)), intent(in) :: input_map, output_map
+      type(inference_engine_t(double_precision)) inference_engine_
+    end function
+
     module function default_real_count_layers(layer) result(num_layers)
       implicit none
       class(layer_t), intent(in), target :: layer
diff --git a/src/inference_engine/layer_s.f90 b/src/inference_engine/layer_s.f90
index 1fdb858f9..231514e2f 100644
--- a/src/inference_engine/layer_s.f90
+++ b/src/inference_engine/layer_s.f90
@@ -138,7 +138,79 @@
       end associate
     end associate
     
-  end procedure
+  end procedure default_real_inference_engine
+
+  module procedure double_precision_inference_engine
+
+    associate( &
+      num_inputs => hidden_layers%count_inputs(), &
+      num_outputs => output_layer%count_neurons(), &
+      neurons_per_hidden_layer => hidden_layers%count_neurons(), &
+      num_hidden_layers =>  hidden_layers%count_layers(), &
+      num_output_layers => output_layer%count_layers() &
+    )   
+      call assert(num_output_layers==1, "inference_engine_s(double_precision_inference_engine): 1 output layer", num_output_layers)
+
+      associate(nodes => [num_inputs, neurons_per_hidden_layer, num_outputs])
+        associate(n_max => maxval(nodes))
+          block
+            double precision, allocatable :: weights(:,:,:), biases(:,:)
+            type(layer_t(double_precision)), pointer :: layer_ptr
+            type(neuron_t(double_precision)), pointer :: neuron_ptr
+            integer j, l
+
+            allocate(weights(n_max, n_max, num_hidden_layers + num_output_layers))
+            allocate(biases(n_max, num_hidden_layers + num_output_layers))
+
+            layer_ptr => hidden_layers
+            l = 0 
+            loop_over_hidden_Layers: &
+            do  
+              l = l + 1
+              neuron_ptr => layer_ptr%neuron
+              j = 0
+              loop_over_hidden_neurons: &
+              do  
+                j = j + 1
+                associate(w => neuron_ptr%weights())
+                  weights(j,1:size(w,1),l) = w
+                end associate
+                biases(j,l) = neuron_ptr%bias()
+
+                if (.not. neuron_ptr%next_allocated()) exit
+                neuron_ptr => neuron_ptr%next_pointer()
+
+              end do loop_over_hidden_neurons
+
+              if (.not. allocated(layer_ptr%next)) exit
+              layer_ptr => layer_ptr%next_pointer()
+
+            end do loop_over_hidden_Layers
+
+            layer_ptr => output_layer
+            l = l + 1
+            neuron_ptr => layer_ptr%neuron
+            j = 0
+            loop_over_output_neurons: &
+            do  
+              j = j + 1
+              associate(w => neuron_ptr%weights())
+                weights(j,1:size(w,1),l) = w
+              end associate
+              biases(j,l) = neuron_ptr%bias()
+
+              if (.not. neuron_ptr%next_allocated()) exit
+              neuron_ptr => neuron_ptr%next_pointer()
+
+            end do loop_over_output_neurons
+
+            inference_engine_ = inference_engine_t(metadata, weights, biases, nodes, input_map, output_map)
+          end block
+        end associate
+      end associate
+    end associate
+    
+  end procedure double_precision_inference_engine
 
   module procedure default_real_count_layers
 
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 5f961b85b..06ec6d98f 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -4,6 +4,7 @@ module inference_engine_m
  !! Specify the user-facing modules, derived types, and type parameters
  use activation_strategy_m, only : activation_strategy_t
  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
+ use double_precision_file_m, only : double_precision_file_t
  use double_precision_string_m, only : double_precision_string_t
  use gelu_m, only : gelu_t
  use hyperparameters_m, only : hyperparameters_t

From b8468bce231f290496442ccf55290ae5e69e04fb Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Tue, 3 Sep 2024 14:23:54 -0700
Subject: [PATCH 046/105] Add new Github CI actions yaml file to build with
 flang

---
 .github/workflows/build-with-flang.yml | 32 ++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 .github/workflows/build-with-flang.yml

diff --git a/.github/workflows/build-with-flang.yml b/.github/workflows/build-with-flang.yml
new file mode 100644
index 000000000..7bd2849dc
--- /dev/null
+++ b/.github/workflows/build-with-flang.yml
@@ -0,0 +1,32 @@
+name: Build with LLVM-Flang
+
+on: [push, pull_request]
+
+
+jobs:
+  Build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-24.04]
+      fail-fast: true
+    container: gmao/llvm-flang:latest
+    env:
+      FC: flang-new
+      CC: clang
+
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v2
+
+    - uses: fortran-lang/setup-fpm@v4
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+
+    - name: Build and Test
+      if: contains(matrix.os, 'ubuntu')
+      run: |
+        fpm --version
+        export FPM_FC=$FC
+        export FPM_CC=$CC
+        fpm test --flag "-mmlir -allow-assumed-rank -O3"

From 71860db1330b3b9071b36c6e7efdd7edbf7d8fda Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Tue, 3 Sep 2024 14:27:26 -0700
Subject: [PATCH 047/105] Replace CI yaml file that builds with gfortran with
 new CI yaml file that builds with Flang

---
 .github/workflows/CI.yml               | 35 +++++++-------------------
 .github/workflows/build-with-flang.yml | 32 -----------------------
 2 files changed, 9 insertions(+), 58 deletions(-)
 delete mode 100644 .github/workflows/build-with-flang.yml

diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index cabff7e84..ac2e16395 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -1,4 +1,4 @@
-name: CI
+name: Build with LLVM Flang
 
 on: [push, pull_request]
 
@@ -8,11 +8,12 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        os: [macOS-12, ubuntu-24.04]
+        os: [ubuntu-24.04]
       fail-fast: true
+    container: gmao/llvm-flang:latest
     env:
-      FC: gfortran
-      GCC_V: 14
+      FC: flang-new
+      CC: clang
 
     steps:
     - name: Checkout code
@@ -22,28 +23,10 @@ jobs:
       with:
         github-token: ${{ secrets.GITHUB_TOKEN }}
 
-    - name: Install Dependencies MacOS
-      if: contains(matrix.os, 'mac')
-      run: |
-        brew install gcc@${GCC_V}
-        sudo ln -s $(which gfortran-${GCC_V}) $(dirname $(which gfortran-${GCC_V}))/gfortran
-
-    - name: Install on Ubuntu
-      if: contains(matrix.os, 'ubuntu')
-      run: |
-        sudo apt update
-        sudo apt install -y build-essential gfortran-14 gcc-14 g++-14
-
-    - name: Build and Test MacOS
-      if: contains(matrix.os, 'mac')
-      run: |
-        export PATH="${HOME}/.local/bin:$PATH"
-        ./setup.sh
-
-    - name: Build and Test Ubuntu
+    - name: Build and Test with LLVM Flang
       if: contains(matrix.os, 'ubuntu')
       run: |
         fpm --version
-        export FPM_FC=gfortran-14
-        export FPM_CC=gcc-14
-        fpm test
+        export FPM_FC=$FC
+        export FPM_CC=$CC
+        fpm test --flag "-mmlir -allow-assumed-rank -O3"
diff --git a/.github/workflows/build-with-flang.yml b/.github/workflows/build-with-flang.yml
deleted file mode 100644
index 7bd2849dc..000000000
--- a/.github/workflows/build-with-flang.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Build with LLVM-Flang
-
-on: [push, pull_request]
-
-
-jobs:
-  Build:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os: [ubuntu-24.04]
-      fail-fast: true
-    container: gmao/llvm-flang:latest
-    env:
-      FC: flang-new
-      CC: clang
-
-    steps:
-    - name: Checkout code
-      uses: actions/checkout@v2
-
-    - uses: fortran-lang/setup-fpm@v4
-      with:
-        github-token: ${{ secrets.GITHUB_TOKEN }}
-
-    - name: Build and Test
-      if: contains(matrix.os, 'ubuntu')
-      run: |
-        fpm --version
-        export FPM_FC=$FC
-        export FPM_CC=$CC
-        fpm test --flag "-mmlir -allow-assumed-rank -O3"

From d134548d02b53a8db4bb7baf43f22edd3173ebfe Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Tue, 3 Sep 2024 15:00:54 -0700
Subject: [PATCH 048/105] Update .github/workflows/CI.yml

Co-authored-by: Damian Rouson <rouson@lbl.gov>
---
 .github/workflows/CI.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index ac2e16395..27dbc968b 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -27,6 +27,7 @@ jobs:
       if: contains(matrix.os, 'ubuntu')
       run: |
         fpm --version
+        flang-new --version
         export FPM_FC=$FC
         export FPM_CC=$CC
         fpm test --flag "-mmlir -allow-assumed-rank -O3"

From 0221ee08dd78ce95a33d343a5f6740cf9d320dcb Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Tue, 3 Sep 2024 15:08:01 -0700
Subject: [PATCH 049/105] Add version check to CI

---
 .github/workflows/CI.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 27dbc968b..021833e93 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -27,7 +27,8 @@ jobs:
       if: contains(matrix.os, 'ubuntu')
       run: |
         fpm --version
-        flang-new --version
+        $FC --version
+        $CC --version
         export FPM_FC=$FC
         export FPM_CC=$CC
         fpm test --flag "-mmlir -allow-assumed-rank -O3"

From 867ae6fe3faa1a66957068eb60927f476e6cf804 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 4 Sep 2024 12:29:17 -0700
Subject: [PATCH 050/105] refac(inference_engine_s): edit Cray workaround

---
 src/inference_engine/inference_engine_s.F90 | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 4bd3d6cd5..17005ba3f 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -93,18 +93,16 @@
         end associate
       end do feed_forward
 
-#ifdef _CRAYFTN
+#ifndef _CRAYFTN
+      associate(normalized_outputs => tensor_t(a(1:n(output_layer), output_layer)))
+        outputs = self%output_map_%map_from_training_range(normalized_outputs)
+      end associate
+#else
       block
         type(tensor_t) :: normalized_outputs
         normalized_outputs = tensor_t(a(1:n(output_layer), output_layer))
-#else
-      associate(normalized_outputs => tensor_t(a(1:n(output_layer), output_layer)))
-#endif
         outputs = self%output_map_%map_from_training_range(normalized_outputs)
-#ifdef _CRAYFTN
       end block
-#else
-      end associate
 #endif
 
     end associate

From eb4e067766712276681dc81e6f07ca077b48c206 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 4 Sep 2024 14:09:48 -0700
Subject: [PATCH 051/105] feat(pdt): merge infer_a proc from aerosol branch

---
 src/inference_engine/inference_engine_m_.f90 | 20 ++++++
 src/inference_engine/inference_engine_s.F90  | 66 +++++++++++++++++++-
 2 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index 136a143aa..1a763cd9d 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -52,6 +52,12 @@ module inference_engine_m_
     procedure, private :: default_real_to_exchange,             double_precision_to_exchange
   end type
 
+  type, extends(inference_engine_t) :: unnormalized_engine_t
+  contains
+    generic :: infer =>   default_real_infer_unnormalized, double_precision_infer_unnormalized
+    procedure, private :: default_real_infer_unnormalized, double_precision_infer_unnormalized
+  end type
+
   type exchange_t(k)
     integer, kind :: k = default_real
     type(tensor_map_t(k)) input_map_, output_map_
@@ -188,6 +194,20 @@ elemental module function default_real_infer(self, inputs) result(outputs)
       type(tensor_t) outputs
     end function
 
+    elemental module function default_real_infer_unnormalized(self, inputs) result(outputs)
+      implicit none
+      class(unnormalized_engine_t), intent(in) :: self
+      type(tensor_t), intent(in) :: inputs
+      type(tensor_t) outputs
+    end function
+
+    elemental module function double_precision_infer_unnormalized(self, inputs) result(outputs)
+      implicit none
+      class(unnormalized_engine_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: inputs
+      type(tensor_t(double_precision)) outputs
+    end function
+
     elemental module function double_precision_infer(self, inputs) result(outputs)
       implicit none
       class(inference_engine_t(double_precision)), intent(in) :: self
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 17005ba3f..f94327dcc 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -62,6 +62,68 @@
     exchange%activation_strategy_ = self%activation_strategy_ 
   end procedure
 
+  module procedure default_real_infer_unnormalized
+
+    real, allocatable :: a(:,:)
+    integer, parameter :: input_layer = 0 
+    integer l
+
+    call assert_consistency(self)
+
+    associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
+
+      allocate(a(maxval(n), input_layer:output_layer))
+
+      a(1:n(input_layer),input_layer) = inputs%values()
+
+      feed_forward: &
+      do l = input_layer+1, output_layer
+      associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
+          if (l .lt. output_layer) then
+             a(1:n(l),l) = self%activation_strategy_%activation(z)
+          else
+             a(1:n(l),l) = z(1:n(l))
+          end if
+        end associate
+      end do feed_forward
+
+      outputs = tensor_t(a(1:n(output_layer), output_layer))
+
+    end associate
+
+  end procedure
+
+  module procedure double_precision_infer_unnormalized
+
+    double precision, allocatable :: a(:,:)
+    integer, parameter :: input_layer = 0 
+    integer l
+
+    call assert_consistency(self)
+
+    associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
+
+      allocate(a(maxval(n), input_layer:output_layer))
+
+      a(1:n(input_layer),input_layer) = inputs%values()
+
+      feed_forward: &
+      do l = input_layer+1, output_layer
+      associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
+          if (l .lt. output_layer) then
+             a(1:n(l),l) = self%activation_strategy_%activation(z)
+          else
+             a(1:n(l),l) = z(1:n(l))
+          end if
+        end associate
+      end do feed_forward
+
+      outputs = tensor_t(a(1:n(output_layer), output_layer))
+
+    end associate
+
+  end procedure
+
   module procedure default_real_infer
 
     real, allocatable :: a(:,:)
@@ -160,7 +222,7 @@
 
   pure subroutine default_real_consistency(self)
 
-    type(inference_engine_t), intent(in) :: self
+    class(inference_engine_t), intent(in) :: self
 
     integer, parameter :: input_layer=0
 
@@ -185,7 +247,7 @@ pure subroutine default_real_consistency(self)
 
   pure subroutine double_precision_consistency(self)
 
-    type(inference_engine_t(double_precision)), intent(in) :: self
+    class(inference_engine_t(double_precision)), intent(in) :: self
 
     integer, parameter :: input_layer=0
 

From c900539baecac1e891c3cf429f409773e64d41fc Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 5 Sep 2024 15:58:43 -0700
Subject: [PATCH 052/105] refac(inference_engine): unnormalized -> unmapped

---
 src/inference_engine/inference_engine_m_.f90 | 18 +++++++++---------
 src/inference_engine/inference_engine_s.F90  |  4 ++--
 src/inference_engine_m.f90                   |  2 +-
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index 1a763cd9d..df9d6dee9 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -52,10 +52,10 @@ module inference_engine_m_
     procedure, private :: default_real_to_exchange,             double_precision_to_exchange
   end type
 
-  type, extends(inference_engine_t) :: unnormalized_engine_t
+  type, extends(inference_engine_t) :: unmapped_engine_t
   contains
-    generic :: infer =>   default_real_infer_unnormalized, double_precision_infer_unnormalized
-    procedure, private :: default_real_infer_unnormalized, double_precision_infer_unnormalized
+    generic :: infer =>   default_real_infer_unmapped, double_precision_infer_unmapped
+    procedure, private :: default_real_infer_unmapped, double_precision_infer_unmapped
   end type
 
   type exchange_t(k)
@@ -136,7 +136,7 @@ elemental module function double_precision_map_to_input_range(self, tensor) resu
     end function
 
     elemental module function default_real_map_from_output_range(self, normalized_tensor) result(tensor)
-      !! The result contains the output tensor values unnormalized via the inverse of the mapping used in training
+      !! The result contains the output tensor values unmapped via the inverse of the mapping used in training
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: normalized_tensor
@@ -144,7 +144,7 @@ elemental module function default_real_map_from_output_range(self, normalized_te
     end function
 
     elemental module function double_precision_map_from_output_range(self, normalized_tensor) result(tensor)
-      !! The result contains the output tensor values unnormalized via the inverse of the mapping used in training
+      !! The result contains the output tensor values unmapped via the inverse of the mapping used in training
       implicit none
       class(inference_engine_t(double_precision)), intent(in) :: self
       type(tensor_t(double_precision)), intent(in) :: normalized_tensor
@@ -194,16 +194,16 @@ elemental module function default_real_infer(self, inputs) result(outputs)
       type(tensor_t) outputs
     end function
 
-    elemental module function default_real_infer_unnormalized(self, inputs) result(outputs)
+    elemental module function default_real_infer_unmapped(self, inputs) result(outputs)
       implicit none
-      class(unnormalized_engine_t), intent(in) :: self
+      class(unmapped_engine_t), intent(in) :: self
       type(tensor_t), intent(in) :: inputs
       type(tensor_t) outputs
     end function
 
-    elemental module function double_precision_infer_unnormalized(self, inputs) result(outputs)
+    elemental module function double_precision_infer_unmapped(self, inputs) result(outputs)
       implicit none
-      class(unnormalized_engine_t(double_precision)), intent(in) :: self
+      class(unmapped_engine_t(double_precision)), intent(in) :: self
       type(tensor_t(double_precision)), intent(in) :: inputs
       type(tensor_t(double_precision)) outputs
     end function
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index f94327dcc..e19c3701c 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -62,7 +62,7 @@
     exchange%activation_strategy_ = self%activation_strategy_ 
   end procedure
 
-  module procedure default_real_infer_unnormalized
+  module procedure default_real_infer_unmapped
 
     real, allocatable :: a(:,:)
     integer, parameter :: input_layer = 0 
@@ -93,7 +93,7 @@
 
   end procedure
 
-  module procedure double_precision_infer_unnormalized
+  module procedure double_precision_infer_unmapped
 
     double precision, allocatable :: a(:,:)
     integer, parameter :: input_layer = 0 
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 06ec6d98f..2b9c2a040 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -9,7 +9,7 @@ module inference_engine_m
  use gelu_m, only : gelu_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
- use inference_engine_m_, only : inference_engine_t
+ use inference_engine_m_, only : inference_engine_t, unmapped_engine_t
  use kind_parameters_m, only : default_real, double_precision
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t

From d06f14f7c05bd89fa401062ed68d44f208072b74 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 5 Sep 2024 16:34:42 -0700
Subject: [PATCH 053/105] fix(unmapped_engine_t): make public

---
 src/inference_engine/inference_engine_m_.f90 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index df9d6dee9..ead1eed60 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -13,6 +13,7 @@ module inference_engine_m_
 
   private
   public :: inference_engine_t
+  public :: unmapped_engine_t
   public :: exchange_t
 
   type inference_engine_t(k)

From 80b9e23f0f1c55f44b132b25c3113b03dc65811e Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Thu, 5 Sep 2024 16:27:36 -0700
Subject: [PATCH 054/105] Support building cloud-microphysics with LLVM Flang

---
 cloud-microphysics/fpm.toml                  | 2 +-
 cloud-microphysics/setup.sh                  | 9 ++++++++-
 cloud-microphysics/src/phase_space_bin_m.f90 | 6 +++++-
 cloud-microphysics/test/main.f90             | 7 +++++++
 4 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/cloud-microphysics/fpm.toml b/cloud-microphysics/fpm.toml
index 4e76ace76..da9e0206b 100644
--- a/cloud-microphysics/fpm.toml
+++ b/cloud-microphysics/fpm.toml
@@ -5,6 +5,6 @@ maintainer = "(Please see inference-engine/fpm.toml.)"
 
 [dependencies]
 assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.7.0"}
-julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.1.1"}
+julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.2"}
 inference-engine = {path = "../"}
 netcdf-interfaces = {git = "https://github.com/LKedward/netcdf-interfaces.git", rev = "d2bbb71ac52b4e346b62572b1ca1620134481096"}
diff --git a/cloud-microphysics/setup.sh b/cloud-microphysics/setup.sh
index 724282fab..a2d130d82 100755
--- a/cloud-microphysics/setup.sh
+++ b/cloud-microphysics/setup.sh
@@ -63,10 +63,17 @@ HDF5_LIB_PATH="`brew --prefix hdf5`/lib"
 NETCDFF_LIB_PATH="`brew --prefix netcdf-fortran`/lib"
 
 FPM_LD_FLAG=" -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH -L$NETCDFF_LIB_PATH"
-FPM_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
 FPM_FC=${FC:-"gfortran-14"}
 FPM_CC=${CC:-"gcc-14"}
 
+if [ $FPM_FC = "gfortran-14" ]; then
+    FPM_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
+elif [ $FPM_FC = "flang-new" ]; then
+    FPM_FLAG="-mmlir -allow-assumed-rank -O3 -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
+else
+    FPM_FLAG=""
+fi
+
 mkdir -p build
 
 CI=${CI:-"false"} # GitHub Actions workflows set CI=true
diff --git a/cloud-microphysics/src/phase_space_bin_m.f90 b/cloud-microphysics/src/phase_space_bin_m.f90
index f9ca94a24..d48655599 100644
--- a/cloud-microphysics/src/phase_space_bin_m.f90
+++ b/cloud-microphysics/src/phase_space_bin_m.f90
@@ -20,6 +20,10 @@ pure module function bin(tensor, minima, maxima, num_bins) result(phase_space_bi
     end function
 
   end interface
+end module phase_space_bin_m
+
+submodule (phase_space_bin_m) phase_space_bin_s
+  implicit none
 
 contains
 
@@ -35,4 +39,4 @@ pure module function bin(tensor, minima, maxima, num_bins) result(phase_space_bi
 
   end procedure
 
-end module phase_space_bin_m
+end submodule
diff --git a/cloud-microphysics/test/main.f90 b/cloud-microphysics/test/main.f90
index c0c5685e6..778d90443 100644
--- a/cloud-microphysics/test/main.f90
+++ b/cloud-microphysics/test/main.f90
@@ -1,5 +1,10 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
+
+#if defined(__flang__)
+# define SINGLE_IMAGE_ONLY
+#endif
+
 program main
   use netCDF_file_test_m, only : netCDF_file_test_t
   implicit none
@@ -19,7 +24,9 @@ program main
   print *,"Test suite execution time: ",t_finish - t_start
   print *
   print '(*(a,:,g0))',"_________ In total, ",passes," of ",tests, " tests pass. _________"
+#ifndef SINGLE_IMAGE_ONLY
   sync all
+#endif
   print *
   if (passes/=tests) error stop "-------- One or more tests failed. See the above report. ---------"
 end program

From 27be2039ec78f8df028822cb1cf51293e0472466 Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Thu, 5 Sep 2024 18:07:01 -0700
Subject: [PATCH 055/105] Improve setup script changes in cloud microphysics

---
 cloud-microphysics/setup.sh | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/cloud-microphysics/setup.sh b/cloud-microphysics/setup.sh
index a2d130d82..49686bf1c 100755
--- a/cloud-microphysics/setup.sh
+++ b/cloud-microphysics/setup.sh
@@ -66,9 +66,10 @@ FPM_LD_FLAG=" -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH -L$NETCDFF_LIB_PATH"
 FPM_FC=${FC:-"gfortran-14"}
 FPM_CC=${CC:-"gcc-14"}
 
-if [ $FPM_FC = "gfortran-14" ]; then
+FPM_FC_NAME_ONLY=${FPM_FC##*/}
+if [ $FPM_FC_NAME_ONLY} = "gfortran-14" ]; then
     FPM_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
-elif [ $FPM_FC = "flang-new" ]; then
+elif [ $FPM_FC_NAME_ONLY = "flang-new" ]; then
     FPM_FLAG="-mmlir -allow-assumed-rank -O3 -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
 else
     FPM_FLAG=""

From 1534ee2c1add4651faeffd3677d94b6bdf5ed82c Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 12 Sep 2024 16:34:19 -0700
Subject: [PATCH 056/105] chore: git mv cloud-microphysics/ demo/

---
 {cloud-microphysics => demo}/app/plot-normalized-histograms.gnu | 0
 {cloud-microphysics => demo}/app/plot-raw-histograms.gnu        | 0
 {cloud-microphysics => demo}/app/tensor-statistics.f90          | 0
 {cloud-microphysics => demo}/app/train-cloud-microphysics.f90   | 0
 {cloud-microphysics => demo}/fpm.toml                           | 0
 {cloud-microphysics => demo}/setup.sh                           | 0
 {cloud-microphysics => demo}/src/NetCDF_file_m.f90              | 0
 {cloud-microphysics => demo}/src/NetCDF_file_s.f90              | 0
 {cloud-microphysics => demo}/src/histogram_m.f90                | 0
 {cloud-microphysics => demo}/src/histogram_s.f90                | 0
 {cloud-microphysics => demo}/src/phase_space_bin_m.f90          | 0
 {cloud-microphysics => demo}/src/run-fpm.sh-header              | 0
 {cloud-microphysics => demo}/test/main.f90                      | 0
 {cloud-microphysics => demo}/test/netCDF_file_test_m.f90        | 0
 {cloud-microphysics => demo}/train.sh                           | 0
 {cloud-microphysics => demo}/training_configuration.json        | 0
 16 files changed, 0 insertions(+), 0 deletions(-)
 rename {cloud-microphysics => demo}/app/plot-normalized-histograms.gnu (100%)
 rename {cloud-microphysics => demo}/app/plot-raw-histograms.gnu (100%)
 rename {cloud-microphysics => demo}/app/tensor-statistics.f90 (100%)
 rename {cloud-microphysics => demo}/app/train-cloud-microphysics.f90 (100%)
 rename {cloud-microphysics => demo}/fpm.toml (100%)
 rename {cloud-microphysics => demo}/setup.sh (100%)
 rename {cloud-microphysics => demo}/src/NetCDF_file_m.f90 (100%)
 rename {cloud-microphysics => demo}/src/NetCDF_file_s.f90 (100%)
 rename {cloud-microphysics => demo}/src/histogram_m.f90 (100%)
 rename {cloud-microphysics => demo}/src/histogram_s.f90 (100%)
 rename {cloud-microphysics => demo}/src/phase_space_bin_m.f90 (100%)
 rename {cloud-microphysics => demo}/src/run-fpm.sh-header (100%)
 rename {cloud-microphysics => demo}/test/main.f90 (100%)
 rename {cloud-microphysics => demo}/test/netCDF_file_test_m.f90 (100%)
 rename {cloud-microphysics => demo}/train.sh (100%)
 rename {cloud-microphysics => demo}/training_configuration.json (100%)

diff --git a/cloud-microphysics/app/plot-normalized-histograms.gnu b/demo/app/plot-normalized-histograms.gnu
similarity index 100%
rename from cloud-microphysics/app/plot-normalized-histograms.gnu
rename to demo/app/plot-normalized-histograms.gnu
diff --git a/cloud-microphysics/app/plot-raw-histograms.gnu b/demo/app/plot-raw-histograms.gnu
similarity index 100%
rename from cloud-microphysics/app/plot-raw-histograms.gnu
rename to demo/app/plot-raw-histograms.gnu
diff --git a/cloud-microphysics/app/tensor-statistics.f90 b/demo/app/tensor-statistics.f90
similarity index 100%
rename from cloud-microphysics/app/tensor-statistics.f90
rename to demo/app/tensor-statistics.f90
diff --git a/cloud-microphysics/app/train-cloud-microphysics.f90 b/demo/app/train-cloud-microphysics.f90
similarity index 100%
rename from cloud-microphysics/app/train-cloud-microphysics.f90
rename to demo/app/train-cloud-microphysics.f90
diff --git a/cloud-microphysics/fpm.toml b/demo/fpm.toml
similarity index 100%
rename from cloud-microphysics/fpm.toml
rename to demo/fpm.toml
diff --git a/cloud-microphysics/setup.sh b/demo/setup.sh
similarity index 100%
rename from cloud-microphysics/setup.sh
rename to demo/setup.sh
diff --git a/cloud-microphysics/src/NetCDF_file_m.f90 b/demo/src/NetCDF_file_m.f90
similarity index 100%
rename from cloud-microphysics/src/NetCDF_file_m.f90
rename to demo/src/NetCDF_file_m.f90
diff --git a/cloud-microphysics/src/NetCDF_file_s.f90 b/demo/src/NetCDF_file_s.f90
similarity index 100%
rename from cloud-microphysics/src/NetCDF_file_s.f90
rename to demo/src/NetCDF_file_s.f90
diff --git a/cloud-microphysics/src/histogram_m.f90 b/demo/src/histogram_m.f90
similarity index 100%
rename from cloud-microphysics/src/histogram_m.f90
rename to demo/src/histogram_m.f90
diff --git a/cloud-microphysics/src/histogram_s.f90 b/demo/src/histogram_s.f90
similarity index 100%
rename from cloud-microphysics/src/histogram_s.f90
rename to demo/src/histogram_s.f90
diff --git a/cloud-microphysics/src/phase_space_bin_m.f90 b/demo/src/phase_space_bin_m.f90
similarity index 100%
rename from cloud-microphysics/src/phase_space_bin_m.f90
rename to demo/src/phase_space_bin_m.f90
diff --git a/cloud-microphysics/src/run-fpm.sh-header b/demo/src/run-fpm.sh-header
similarity index 100%
rename from cloud-microphysics/src/run-fpm.sh-header
rename to demo/src/run-fpm.sh-header
diff --git a/cloud-microphysics/test/main.f90 b/demo/test/main.f90
similarity index 100%
rename from cloud-microphysics/test/main.f90
rename to demo/test/main.f90
diff --git a/cloud-microphysics/test/netCDF_file_test_m.f90 b/demo/test/netCDF_file_test_m.f90
similarity index 100%
rename from cloud-microphysics/test/netCDF_file_test_m.f90
rename to demo/test/netCDF_file_test_m.f90
diff --git a/cloud-microphysics/train.sh b/demo/train.sh
similarity index 100%
rename from cloud-microphysics/train.sh
rename to demo/train.sh
diff --git a/cloud-microphysics/training_configuration.json b/demo/training_configuration.json
similarity index 100%
rename from cloud-microphysics/training_configuration.json
rename to demo/training_configuration.json

From 45448c198f1a2ef030df0dcf1db820efb407d7aa Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 12 Sep 2024 16:42:24 -0700
Subject: [PATCH 057/105] chore: copy infer-aersol to pdt branch

---
 demo/app/infer-aerosol.f90 | 154 +++++++++++++++++++++++++++++++++++++
 1 file changed, 154 insertions(+)
 create mode 100644 demo/app/infer-aerosol.f90

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
new file mode 100644
index 000000000..1fa90d582
--- /dev/null
+++ b/demo/app/infer-aerosol.f90
@@ -0,0 +1,154 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
+
+program infer_aerosol
+
+  ! Intrinsic modules:
+  use iso_fortran_env, only : int64, real64
+
+  ! External dependencies:
+  use inference_engine_m, only : inference_engine_t, tensor_t, infer
+  use julienne_m, only : string_t, file_t, command_line_t
+  use kind_parameters_m, only : rkind
+  use omp_lib
+
+  ! Internal dependencies:
+  use NetCDF_file_m, only: NetCDF_file_t
+  implicit none
+
+  type tensor_statistics_t
+    real(rkind), allocatable, dimension(:) :: mean, standard_deviation
+  end type
+
+  character(len=*), parameter :: usage_info = & 
+    new_line('') // &
+    'Usage:  ./build/run-fpm.sh run infer-aerosol --  --file-path <string>' // &
+    'where angular brackets (<>) denote user-provided input.' // &
+    new_line('')
+
+  call read_stats_and_perform_inference( file_path( stop_code = usage_info ) )
+
+  print *,new_line('') // "______infer-aerosol done _______"
+
+contains
+
+  function file_path(stop_code) result(path)
+    character(len=:), allocatable :: path
+    character(len=*), intent(in) :: stop_code
+    type(command_line_t) command_line
+
+    path = command_line%flag_value("--file-path") // "/"
+    if (len(path)==0) error stop stop_code
+
+  end function
+
+  subroutine read_stats_and_perform_inference(path)
+    character(len=*), intent(in) :: path
+    integer,          parameter :: num_inputs = 80, num_outputs = 31
+    character(len=*), parameter :: network_file_name = "training_network.json"
+    character(len=*), parameter :: inputs_tensor_file_name = "training_input.nc"
+    real(rkind) cube_root
+    real(rkind), allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
+    type(tensor_statistics_t) :: input_stats, output_stats
+    type(inference_engine_t) inference_engine
+    integer i, j
+
+    input_stats  = read_tensor_statistics(path // "meanxp.txt", path // "stdxp.txt", num_inputs)  !for pre-processing normalization
+    output_stats = read_tensor_statistics(path // "meanyp.txt", path // "stdyp.txt", num_outputs) !for post-processing normalization
+
+    associate(inputs_tensor_file => NetCDF_file_t(path // inputs_tensor_file_name))
+      print *,"Reading network inputs from ", inputs_tensor_file_name
+      call inputs_tensor_file%input("input_vars", aerosol_data)
+    end associate    
+
+    ! print*,'shape aerosol array = ',shape(aerosol_data) ! convert to an asssertion
+    
+    allocate(input_components(size(aerosol_data,2),size(aerosol_data,1)))
+    allocate(output_components(size(aerosol_data,2),num_outputs))
+
+    !$omp parallel do shared(aerosol_data,input_components,input_stats) private(i,j,cube_root)
+    pre_process: &
+    do j = 1,size(aerosol_data,2)
+      do i = 1,size(aerosol_data,1)
+        cube_root = (abs(aerosol_data(i,j))**(1.d0/3.d0))*sign(1.0,aerosol_data(i,j))
+        input_components(j,i) = (cube_root - input_stats%mean(i))/input_stats%standard_deviation(i)
+      end do
+    end do pre_process
+    !$omp end parallel do   
+      
+    print *, "Reading the neural network from " // network_file_name
+    inference_engine = inference_engine_t(file_t(path // network_file_name))
+
+    time_inference: &
+    block
+      integer(int64) t_start, t_finish, clock_rate
+      type(tensor_t), allocatable :: inputs(:), outputs(:)
+      double precision start_time,end_time
+      real(rkind), allocatable :: output_slice(:)
+      integer i, icc
+
+      allocate(inputs(size(input_components,1)))
+      allocate(outputs(size(input_components,1)))      
+
+      !$omp parallel do shared(inputs,input_components) 
+      do i = 1,size(input_components,1)
+        inputs(i) = tensor_t(input_components(i,:))
+      end do
+      !$omp end parallel do         
+
+      print*, "Starting inference."
+      call system_clock(t_start, clock_rate)      
+
+      icc = size(input_components,1)
+
+      !$ start_time = omp_get_wtime()
+      !$omp parallel do shared(inputs,outputs,icc)
+      do i = 1,icc
+         outputs(i) = inference_engine%infer_na(inputs(i))
+      end do
+      !$omp end parallel do   
+      !$    end_time = omp_get_wtime()       
+
+      call system_clock(t_finish)
+      print*, "Finished inference."
+      print *,"System clock time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
+      !$    print*,'OMP Total time = ',end_time - start_time
+
+      allocate(output_slice(num_outputs))
+
+      !$omp parallel do shared(outputs,output_components,icc,output_stats) private(output_slice,i,j)
+      post_process: &
+      do i = 1,icc
+         output_slice = outputs(i)%values()
+         do j = 1,num_outputs
+            output_components(i,j) = (output_stats%standard_deviation(j)*output_slice(j)+output_stats%mean(j))**3
+         end do
+      end do post_process
+      !$omp end parallel do
+
+    end block time_inference
+   
+  end subroutine read_stats_and_perform_inference
+
+  function read_tensor_statistics(mean_file, standard_deviation_file, n) result(tensor_statistics)
+    type(tensor_statistics_t) tensor_statistics
+    character(len=*), intent(in) :: mean_file, standard_deviation_file
+    integer, intent(in) :: n
+    integer i, mean_unit, standard_deviation_unit
+  
+    open(newunit = mean_unit, file = mean_file, status="old")
+    open(newunit = standard_deviation_unit , file = standard_deviation_file, status="old")
+
+    allocate(tensor_statistics%mean(n))
+    allocate(tensor_statistics%standard_deviation(n))
+  
+    do i = 1, n
+      read(mean_unit, *) tensor_statistics%mean(i)
+      read(standard_deviation_unit , *) tensor_statistics%standard_deviation(i)
+    end do
+
+    close(mean_unit)
+    close(standard_deviation_unit)
+  end function
+
+end program infer_aerosol

From 4c905b570d2055fc35c52d65fafae8bfcb9163ad Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 14 Sep 2024 19:36:00 -0700
Subject: [PATCH 058/105] feat(demo/app/infer-aerosol): add double precision

---
 demo/app/infer-aerosol.f90 | 23 +++++++++++------------
 demo/src/NetCDF_file_m.f90 | 11 +++++++++--
 demo/src/NetCDF_file_s.f90 | 25 +++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index 1fa90d582..ef83e340c 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -7,9 +7,8 @@ program infer_aerosol
   use iso_fortran_env, only : int64, real64
 
   ! External dependencies:
-  use inference_engine_m, only : inference_engine_t, tensor_t, infer
-  use julienne_m, only : string_t, file_t, command_line_t
-  use kind_parameters_m, only : rkind
+  use inference_engine_m, only : inference_engine_t, tensor_t, double_precision, double_precision_file_t
+  use julienne_m, only : string_t, command_line_t
   use omp_lib
 
   ! Internal dependencies:
@@ -17,7 +16,7 @@ program infer_aerosol
   implicit none
 
   type tensor_statistics_t
-    real(rkind), allocatable, dimension(:) :: mean, standard_deviation
+    double precision, allocatable, dimension(:) :: mean, standard_deviation
   end type
 
   character(len=*), parameter :: usage_info = & 
@@ -47,10 +46,10 @@ subroutine read_stats_and_perform_inference(path)
     integer,          parameter :: num_inputs = 80, num_outputs = 31
     character(len=*), parameter :: network_file_name = "training_network.json"
     character(len=*), parameter :: inputs_tensor_file_name = "training_input.nc"
-    real(rkind) cube_root
-    real(rkind), allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
-    type(tensor_statistics_t) :: input_stats, output_stats
-    type(inference_engine_t) inference_engine
+    double precision cube_root
+    double precision, allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
+    type(tensor_statistics_t) input_stats, output_stats
+    type(inference_engine_t(double_precision)) inference_engine
     integer i, j
 
     input_stats  = read_tensor_statistics(path // "meanxp.txt", path // "stdxp.txt", num_inputs)  !for pre-processing normalization
@@ -77,14 +76,14 @@ subroutine read_stats_and_perform_inference(path)
     !$omp end parallel do   
       
     print *, "Reading the neural network from " // network_file_name
-    inference_engine = inference_engine_t(file_t(path // network_file_name))
+    inference_engine = inference_engine_t(double_precision_file_t(path // network_file_name))
 
     time_inference: &
     block
       integer(int64) t_start, t_finish, clock_rate
-      type(tensor_t), allocatable :: inputs(:), outputs(:)
+      type(tensor_t(double_precision)), allocatable :: inputs(:), outputs(:)
       double precision start_time,end_time
-      real(rkind), allocatable :: output_slice(:)
+      real, allocatable :: output_slice(:)
       integer i, icc
 
       allocate(inputs(size(input_components,1)))
@@ -104,7 +103,7 @@ subroutine read_stats_and_perform_inference(path)
       !$ start_time = omp_get_wtime()
       !$omp parallel do shared(inputs,outputs,icc)
       do i = 1,icc
-         outputs(i) = inference_engine%infer_na(inputs(i))
+         outputs(i) = inference_engine%infer(inputs(i))
       end do
       !$omp end parallel do   
       !$    end_time = omp_get_wtime()       
diff --git a/demo/src/NetCDF_file_m.f90 b/demo/src/NetCDF_file_m.f90
index c898efb17..826552464 100644
--- a/demo/src/NetCDF_file_m.f90
+++ b/demo/src/NetCDF_file_m.f90
@@ -13,8 +13,8 @@ module NetCDF_file_m
     private
     character(len=:), allocatable :: file_name_
   contains
-    procedure :: input_2D_integer, input_1D_double, input_4D_real, input_3D_real, input_real_scalar
-    generic :: input => input_2D_integer, input_1D_double, input_4D_real, input_3D_real, input_real_scalar
+    generic :: input =>   input_2D_integer, input_1D_double, input_4D_real, input_3D_real, input_real_scalar, input_2D_double
+    procedure, private :: input_2D_integer, input_1D_double, input_4D_real, input_3D_real, input_real_scalar, input_2D_double
   end type
 
   interface NetCDF_file_t
@@ -50,6 +50,13 @@ module subroutine input_1D_double(self, varname, values)
       double precision, intent(out), allocatable :: values(:)
     end subroutine
 
+    module subroutine input_2D_double(self, varname, values)
+      implicit none
+      class(NetCDF_file_t), intent(in) :: self
+      character(len=*), intent(in) :: varname
+      double precision, intent(out), allocatable :: values(:,:)
+    end subroutine
+
     module subroutine input_4D_real(self, varname, values)
       implicit none
       class(NetCDF_file_t), intent(in) :: self
diff --git a/demo/src/NetCDF_file_s.f90 b/demo/src/NetCDF_file_s.f90
index 4c0ecba59..b3aa7b9ec 100644
--- a/demo/src/NetCDF_file_s.f90
+++ b/demo/src/NetCDF_file_s.f90
@@ -124,6 +124,31 @@ function get_shape(ncid, varname) result(array_shape)
     end associate
   end procedure
     
+  module procedure input_2D_double
+    character(len=32) varid_string
+    integer ncid, varid
+
+    associate( nf_status => nf90_open(self%file_name_, nf90_nowrite, ncid) ) ! open file with read-only acces
+      call assert(nf_status == nf90_noerr, "nf90_open(self%file_name_, NF90_NOWRITE, ncid)", &
+        trim(nf90_strerror(nf_status)) // self%file_name_)
+    end associate
+
+    associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
+      write(varid_string, *) varid
+      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_2D_double): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
+        diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
+    end associate
+
+    associate(array_shape => get_shape(ncid, varname))
+      call assert(size(array_shape)==rank(values), "netCDF_file_s(input_2D_double): size(array_shape)==rank(values)", &
+        intrinsic_array_t([size(array_shape),rank(values)]))
+      allocate(values(array_shape(1),array_shape(2)))
+      associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+        call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+      end associate
+    end associate
+  end procedure
+    
   module procedure input_4D_real
 
     character(len=32) varid_string

From 4595a8ccc041bea3e4de14df5e6a7429bea678f0 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 14 Sep 2024 20:37:23 -0700
Subject: [PATCH 059/105] refac(NetCDF_file): use generic assumed-rank I/O

This commit replaces the rank-specific NetCDF_file_t type-bound
input procedures with assumed-rank versions so that the interface
is now rank-agnostic and expands the matrix of supported types
and ranks to include
1. Integer: ranks 0-4
1. Real: ranks 0-4
1. Double precision: ranks 0-4
---
 demo/src/NetCDF_file_m.f90 |  37 ++----
 demo/src/NetCDF_file_s.f90 | 233 ++++++++++++++++++++-----------------
 2 files changed, 136 insertions(+), 134 deletions(-)

diff --git a/demo/src/NetCDF_file_m.f90 b/demo/src/NetCDF_file_m.f90
index 826552464..7bba60ab0 100644
--- a/demo/src/NetCDF_file_m.f90
+++ b/demo/src/NetCDF_file_m.f90
@@ -13,8 +13,8 @@ module NetCDF_file_m
     private
     character(len=:), allocatable :: file_name_
   contains
-    generic :: input =>   input_2D_integer, input_1D_double, input_4D_real, input_3D_real, input_real_scalar, input_2D_double
-    procedure, private :: input_2D_integer, input_1D_double, input_4D_real, input_3D_real, input_real_scalar, input_2D_double
+    generic :: input =>   input_integer, input_double_precision, input_real
+    procedure, private :: input_integer, input_double_precision, input_real
   end type
 
   interface NetCDF_file_t
@@ -29,46 +29,25 @@ pure module function construct(file_name) result(NetCDF_file)
 
   interface
 
-    module subroutine input_real_scalar(self, varname, scalar)
+    module subroutine input_real(self, varname, values)
       implicit none
       class(NetCDF_file_t), intent(in) :: self
       character(len=*), intent(in) :: varname
-      real, intent(out) :: scalar
+      real, intent(out), allocatable :: values(..)
     end subroutine
 
-    module subroutine input_2D_integer(self, varname, values)
+    module subroutine input_integer(self, varname, values)
       implicit none
       class(NetCDF_file_t), intent(in) :: self
       character(len=*), intent(in) :: varname
-      integer, intent(out), allocatable :: values(:,:)
+      integer, intent(out), allocatable :: values(..)
     end subroutine
 
-    module subroutine input_1D_double(self, varname, values)
+    module subroutine input_double_precision(self, varname, values)
       implicit none
       class(NetCDF_file_t), intent(in) :: self
       character(len=*), intent(in) :: varname
-      double precision, intent(out), allocatable :: values(:)
-    end subroutine
-
-    module subroutine input_2D_double(self, varname, values)
-      implicit none
-      class(NetCDF_file_t), intent(in) :: self
-      character(len=*), intent(in) :: varname
-      double precision, intent(out), allocatable :: values(:,:)
-    end subroutine
-
-    module subroutine input_4D_real(self, varname, values)
-      implicit none
-      class(NetCDF_file_t), intent(in) :: self
-      character(len=*), intent(in) :: varname
-      real, intent(out), allocatable :: values(:,:,:,:)
-    end subroutine
-
-    module subroutine input_3D_real(self, varname, values)
-      implicit none
-      class(NetCDF_file_t), intent(in) :: self
-      character(len=*), intent(in) :: varname
-      real, intent(out), allocatable :: values(:,:,:)
+      double precision, intent(out), allocatable :: values(..)
     end subroutine
 
   end interface
diff --git a/demo/src/NetCDF_file_s.f90 b/demo/src/NetCDF_file_s.f90
index b3aa7b9ec..0200cfc89 100644
--- a/demo/src/NetCDF_file_s.f90
+++ b/demo/src/NetCDF_file_s.f90
@@ -49,57 +49,63 @@ function get_shape(ncid, varname) result(array_shape)
     array_shape = dims(2:var_rank+1)
   end function 
 
-  module procedure input_real_scalar
-
-    character(len=32) varid_string
-    integer ncid, varid
-    
-    associate( nf_status => nf90_open(self%file_name_, nf90_nowrite, ncid) ) ! open file with read-only acces
-      call assert(nf_status == nf90_noerr, &
-        "Net_CDF_file_m(input_real_scalar): nf90_open " // trim(nf90_strerror(nf_status)), &
-        diagnostic_data = trim(nf90_strerror(nf_status)) // self%file_name_)
-    end associate
-
-    associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
-      write(varid_string, *) varid
-      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_real_scalar): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
-        diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
-    end associate
-
-    associate( nf_status => nf90_get_var(ncid, varid, scalar)) ! read data
-      call assert(nf_status == nf90_noerr, "NetCDF_file_s(input_real_scalar): nf90_get_var ", trim(nf90_strerror(nf_status)))
-    end associate
-
-  end procedure
-
-  module procedure input_2D_integer
+  module procedure input_integer
 
     character(len=32) varid_string
     integer ncid, varid
 
     associate( nf_status => nf90_open(self%file_name_, nf90_nowrite, ncid) ) ! open file with read-only acces
       call assert(nf_status == nf90_noerr, &
-        "Net_CDF_file_m(input_2D_integer): nf90_open" // trim(nf90_strerror(nf_status)), &
+        "Net_CDF_file_m(input_integer): nf90_open" // trim(nf90_strerror(nf_status)), &
         diagnostic_data = trim(nf90_strerror(nf_status)) // self%file_name_)
     end associate
 
     associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
       write(varid_string, *) varid
-      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_2D_integer): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
+      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_integer): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
         diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
     end associate
 
-    associate(array_shape => get_shape(ncid, varname))
-      call assert(size(array_shape)==rank(values), "netCDF_file_s(input_2D_integer): size(array_shape)==rank(values)")
-      allocate(values(array_shape(1), array_shape(2)))
-      associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
-        call assert(nf_status == nf90_noerr, "NetCDF_file_s(input_2D_integer): nf90_get_var", trim(nf90_strerror(nf_status)))
-      end associate
-    end associate
+    select rank(values)
+      rank(1)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_integer): size(array_shape)==rank(values)")
+          allocate(values(array_shape(1)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "NetCDF_file_s(input_integer): nf90_get_var", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(2)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_integer): size(array_shape)==rank(values)")
+          allocate(values(array_shape(1), array_shape(2)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "NetCDF_file_s(input_integer): nf90_get_var", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(3)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_integer): size(array_shape)==rank(values)")
+          allocate(values(array_shape(1), array_shape(2), array_shape(3)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "NetCDF_file_s(input_integer): nf90_get_var", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(4)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_integer): size(array_shape)==rank(values)")
+          allocate(values(array_shape(1), array_shape(2), array_shape(3), array_shape(4)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "NetCDF_file_s(input_integer): nf90_get_var", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank default
+        error stop "NetCDF_file_s(input_integer): unsupported rank"
+    end select
 
   end procedure
 
-  module procedure input_1D_double
+  module procedure input_double_precision
     character(len=32) varid_string
     integer ncid, varid
 
@@ -110,73 +116,53 @@ function get_shape(ncid, varname) result(array_shape)
 
     associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
       write(varid_string, *) varid
-      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_1D_double): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
+      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_double): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
         diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
     end associate
 
-    associate(array_shape => get_shape(ncid, varname))
-      call assert(size(array_shape)==rank(values), "netCDF_file_s(input_1D_double): size(array_shape)==rank(values)", &
-        intrinsic_array_t([size(array_shape),rank(values)]))
-      allocate(values(array_shape(1)))
-      associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
-        call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
-      end associate
-    end associate
+    select rank(values)
+      rank(1)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_double): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(2)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_double): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1),array_shape(2)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(3)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_double): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1),array_shape(2),array_shape(3)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(4)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_double): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1),array_shape(2),array_shape(3),array_shape(4)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank default
+        error stop "NetCDF_file_s(input_double): unsupported rank"
+    end select
   end procedure
-    
-  module procedure input_2D_double
-    character(len=32) varid_string
-    integer ncid, varid
 
-    associate( nf_status => nf90_open(self%file_name_, nf90_nowrite, ncid) ) ! open file with read-only acces
-      call assert(nf_status == nf90_noerr, "nf90_open(self%file_name_, NF90_NOWRITE, ncid)", &
-        trim(nf90_strerror(nf_status)) // self%file_name_)
-    end associate
-
-    associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
-      write(varid_string, *) varid
-      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_2D_double): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
-        diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
-    end associate
-
-    associate(array_shape => get_shape(ncid, varname))
-      call assert(size(array_shape)==rank(values), "netCDF_file_s(input_2D_double): size(array_shape)==rank(values)", &
-        intrinsic_array_t([size(array_shape),rank(values)]))
-      allocate(values(array_shape(1),array_shape(2)))
-      associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
-        call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
-      end associate
-    end associate
-  end procedure
-    
-  module procedure input_4D_real
-
-    character(len=32) varid_string
-    integer ncid, varid
-
-    associate( nf_status => nf90_open(self%file_name_, nf90_nowrite, ncid) ) ! open file with read-only acces
-      call assert(nf_status == nf90_noerr, "nf90_open(self%file_name_, NF90_NOWRITE, ncid)", &
-        trim(nf90_strerror(nf_status)) // self%file_name_)
-    end associate
-
-    associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
-      write(varid_string, *) varid
-      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_4D_real): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
-        diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
-    end associate
-
-    associate(array_shape => get_shape(ncid, varname))
-      call assert(size(array_shape)==rank(values), "netCDF_file_s(input_4D_real): size(array_shape)==rank(values)", &
-        intrinsic_array_t([size(array_shape),rank(values)]))
-      allocate(values(array_shape(1), array_shape(2), array_shape(3), array_shape(4)))
-      associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
-        call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
-      end associate
-    end associate
-
-  end procedure
-
-  module procedure input_3D_real
+  module procedure input_real
 
     character(len=32) varid_string
     integer ncid, varid
@@ -188,18 +174,55 @@ function get_shape(ncid, varname) result(array_shape)
 
     associate( nf_status => nf90_inq_varid(ncid, varname, varid)) ! get variable's ID
       write(varid_string, *) varid
-      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_3D_real): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
+      call assert(nf_status == nf90_noerr, "Net_CDF_file_m(input_real): nf90_inq_varid " // trim(nf90_strerror(nf_status)), &
         diagnostic_data = "varname '" // varname // "', varid " // trim(adjustl(varid_string)))
     end associate
 
-    associate(array_shape => get_shape(ncid, varname))
-      call assert(size(array_shape)==rank(values), "netCDF_file_s(input_3D_real): size(array_shape)==rank(values)", &
-        intrinsic_array_t([size(array_shape),rank(values)]))
-      allocate(values(array_shape(1), array_shape(2), array_shape(3)))
-      associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
-        call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
-      end associate
-    end associate
+    select rank(values)
+      rank(0)
+        allocate(values)
+        associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+          call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, values)", trim(nf90_strerror(nf_status)))
+        end associate
+      rank(1)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_real): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(2)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_real): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1), array_shape(2)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(3)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_real): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1), array_shape(2), array_shape(3)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank(4)
+        associate(array_shape => get_shape(ncid, varname))
+          call assert(size(array_shape)==rank(values), "netCDF_file_s(input_real): size(array_shape)==rank(values)", &
+            intrinsic_array_t([size(array_shape),rank(values)]))
+          allocate(values(array_shape(1), array_shape(2), array_shape(3), array_shape(4)))
+          associate( nf_status => nf90_get_var(ncid, varid, values)) ! read data
+            call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, array)", trim(nf90_strerror(nf_status)))
+          end associate
+        end associate
+      rank default
+        error stop "NetCDF_file_s(input_real_array): unsupported rank"
+    end select
 
   end procedure
 

From a092e02a68b01e35d42531cbf147dfc22a1d9b03 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sat, 14 Sep 2024 21:53:45 -0700
Subject: [PATCH 060/105] fix(infer-aerosol): handle missing req'd arg

---
 demo/app/infer-aerosol.f90 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index ef83e340c..1a3d42302 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -36,9 +36,9 @@ function file_path(stop_code) result(path)
     character(len=*), intent(in) :: stop_code
     type(command_line_t) command_line
 
-    path = command_line%flag_value("--file-path") // "/"
+    path = command_line%flag_value("--file-path") 
     if (len(path)==0) error stop stop_code
-
+    path = path // "/"
   end function
 
   subroutine read_stats_and_perform_inference(path)

From e7bc20a39c240a5d204b2a89a8b6ab9dfe345608 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 15 Sep 2024 20:48:13 -0700
Subject: [PATCH 061/105] fix(demo): revise setup.sh for Linux

---
 demo/setup.sh | 83 +++++++++++++++++++++++++++++----------------------
 1 file changed, 47 insertions(+), 36 deletions(-)

diff --git a/demo/setup.sh b/demo/setup.sh
index 49686bf1c..a7e19ce14 100755
--- a/demo/setup.sh
+++ b/demo/setup.sh
@@ -39,40 +39,54 @@ done
 
 set -u # error on use of undefined variable
 
-if ! command -v brew > /dev/null ; then
-  if ! command -v curl > /dev/null ; then
-    echo "Please install curl and then rerun ./setup.sh"
-    exit 1
-  fi
-  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-  if [ $(uname) = "Linux" ]; then
-    if [ -z "$PATH" ]; then
-      PATH=/home/linuxbrew/.linuxbrew/bin/
-    else
-      PATH=/home/linuxbrew/.linuxbrew/bin/:"$PATH"
+FPM_FC=${FPM_FC:-"flang-new"}
+FPM_CC=${FPM_CC:-"clang"}
+
+if [ $(uname) = "Darwin" ]; then
+  if command -v brew ; then
+    brew install netcdf netcdf-fortran pkg-config coreutils # coreutils supports `realpath` below
+    NETCDF_LIB_PATH="`brew --prefix netcdf`/lib"
+    HDF5_LIB_PATH="`brew --prefix hdf5`/lib"
+    NETCDFF_LIB_PATH="`brew --prefix netcdf-fortran`/lib"
+    fpm_cc_version=$($FPM_CC --version)
+    if [[ $fpm_cc_version = Apple* ]]; then
+      echo "$FPM_CC appears to be an Apple compiler.  Please set FPM_CC to the location of LLVM clang."
+      exit 1
     fi
+  else
+    cat <<'EOF'
+
+      Command 'brew' not found. On macOS, this script uses Homebrew (https://brew.sh) to 
+      install the prerequisite packages netcdf, netcdf-fortran, pkg-config, and coreutils.
+      Please install Homebrew and restart this script."
+EOF
   fi
+elif [ $(uname) = "Linux" ]; then
+  echo 
+  # TODO: Unless NETCDF_LIB_PATH, HDF5_LIB_PATH, and NETCDFF_LIB_PATH are set, build
+  # NetCDF, NetCDF-Fortran and HDF5 set the aforementioned environment variables.
 fi
 
-brew install netcdf netcdf-fortran pkg-config coreutils # coreutils supports `realpath` below
+[ -z ${HDF5_LIB_PATH:-}    ] && printf "Please set HDF5_LIB_PATH to the HDF5 library path and restart this script.\n\n"; exit 1
+[ -z ${NETCDF_LIB_PATH:-}  ] && printf "Please set NETCDF_LIB_PATH to the NetCDF library path and restart this script.\n\n"; exit 1
+[ -z ${NETCDFF_LIB_PATH:-} ] && printf "Please set NETCDFF_LIB_PATH to the NetCDF-Fortran library path and restart this script.\n\n"; exit 1
 
-PREFIX=`realpath $PREFIX`
+FPM_LD_FLAG=" -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH -L$NETCDFF_LIB_PATH"
 
-NETCDF_LIB_PATH="`brew --prefix netcdf`/lib"
-HDF5_LIB_PATH="`brew --prefix hdf5`/lib"
-NETCDFF_LIB_PATH="`brew --prefix netcdf-fortran`/lib"
+PREFIX=`realpath $PREFIX`
 
-FPM_LD_FLAG=" -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH -L$NETCDFF_LIB_PATH"
-FPM_FC=${FC:-"gfortran-14"}
-FPM_CC=${CC:-"gcc-14"}
-
-FPM_FC_NAME_ONLY=${FPM_FC##*/}
-if [ $FPM_FC_NAME_ONLY} = "gfortran-14" ]; then
-    FPM_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
-elif [ $FPM_FC_NAME_ONLY = "flang-new" ]; then
-    FPM_FLAG="-mmlir -allow-assumed-rank -O3 -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
+fpm_fc_version=$($FPM_FC --version)
+if [[ $fpm_fc_version = flang* ]]; then
+  FPM_FC_FLAG="-mmlir -allow-assumed-rank -O3 -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
+  FPM_LD_FLAG=""
+elif [[ $fpm_fc_version = GNU* ]]; then
+  echo
+  echo "$FPM_FC appears to be gfortran, which is currently unsupported due to compiler bugs for parameterized derived types."
+  echo
+  exit 1
+  FPM_FC_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
 else
-    FPM_FLAG=""
+  FPM_FC_FLAG=""
 fi
 
 mkdir -p build
@@ -100,10 +114,10 @@ echo "INFERENCE_ENGINE_FPM_FLAG=\"$FPM_FLAG\""              >> $INFERENCE_ENGINE
 echo "Name: inference-engine"                               >> $INFERENCE_ENGINE_PC
 echo "Description: Inference Engine"                        >> $INFERENCE_ENGINE_PC
 echo "URL: https://github.com/berkeleylab/inference-engine" >> $INFERENCE_ENGINE_PC
-echo "Version: 0.1.2"                                       >> $INFERENCE_ENGINE_PC
+echo "Version: 0.13.0"                                      >> $INFERENCE_ENGINE_PC
 if [ $CI = true ]; then
   echo "---------------"
-  echo "cat $INFERENCE_ENGINE_PC"
+  echo "cat \$INFERENCE_ENGINE_PC"
   cat $INFERENCE_ENGINE_PC
   echo "---------------"
 fi
@@ -126,16 +140,13 @@ if [ $CI = true ]; then
   echo "---------------"
 fi
 
-echo "$RUN_FPM_SH test"
-$RUN_FPM_SH test
+$RUN_FPM_SH build
 
 echo ""
-echo "____________________ cloud-microphysics has been set up! _______________________"
+echo "____________________ The inference-engine demo apps build succeeded! _______________________"
 echo ""
-echo "Usage:"
+echo "Run the following command to see a list of available apps:"
 echo ""
-echo "./build/run-fpm.sh run train-cloud-microphysics -- \ "
-echo "  --base <string> --epochs <integer> \ "
-echo "  [--start <integer>] [--end <integer>] [--stride <integer>] [--bins <integer]"
+echo "./build/run-fpm.sh run"
 echo ""
-echo "where angular brackets denote user-provided values and square brackets denote optional arguments"
+echo "Append a space followe by an app's name to see basic app usage information."

From c5eb64b7c392e14e6fb70cbf8c3798faa59d9d4d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 16 Sep 2024 13:17:28 -0700
Subject: [PATCH 062/105] fix(setup.sh): works on macOS

---
 demo/setup.sh | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/demo/setup.sh b/demo/setup.sh
index a7e19ce14..57eb0d932 100755
--- a/demo/setup.sh
+++ b/demo/setup.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 set -e # exit on error
 
@@ -67,9 +67,18 @@ elif [ $(uname) = "Linux" ]; then
   # NetCDF, NetCDF-Fortran and HDF5 set the aforementioned environment variables.
 fi
 
-[ -z ${HDF5_LIB_PATH:-}    ] && printf "Please set HDF5_LIB_PATH to the HDF5 library path and restart this script.\n\n"; exit 1
-[ -z ${NETCDF_LIB_PATH:-}  ] && printf "Please set NETCDF_LIB_PATH to the NetCDF library path and restart this script.\n\n"; exit 1
-[ -z ${NETCDFF_LIB_PATH:-} ] && printf "Please set NETCDFF_LIB_PATH to the NetCDF-Fortran library path and restart this script.\n\n"; exit 1
+if [[ -z ${HDF5_LIB_PATH:-}    ]]; then 
+  printf "Please set HDF5_LIB_PATH to the HDF5 library path and restart this script.\n\n"
+  exit 1
+fi
+if [ -z ${NETCDF_LIB_PATH:-}  ]; then
+  printf "Please set NETCDF_LIB_PATH to the NetCDF library path and restart this script.\n\n"
+   exit 1
+fi
+if [ -z ${NETCDFF_LIB_PATH:-} ]; then
+  printf "Please set NETCDFF_LIB_PATH to the NetCDF-Fortran library path and restart this script.\n\n"
+   exit 1
+fi
 
 FPM_LD_FLAG=" -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH -L$NETCDFF_LIB_PATH"
 
@@ -77,16 +86,15 @@ PREFIX=`realpath $PREFIX`
 
 fpm_fc_version=$($FPM_FC --version)
 if [[ $fpm_fc_version = flang* ]]; then
-  FPM_FC_FLAG="-mmlir -allow-assumed-rank -O3 -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
-  FPM_LD_FLAG=""
+  FPM_FLAG="-mmlir -allow-assumed-rank -O3 -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
 elif [[ $fpm_fc_version = GNU* ]]; then
   echo
   echo "$FPM_FC appears to be gfortran, which is currently unsupported due to compiler bugs for parameterized derived types."
   echo
   exit 1
-  FPM_FC_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
+  FPM_FLAG="-fcoarray=single -O3 -fallow-argument-mismatch -ffree-line-length-none -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH"
 else
-  FPM_FC_FLAG=""
+  FPM_FLAG=""
 fi
 
 mkdir -p build

From 2d908a600de8236d588aba14c96c9386152ed691 Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Mon, 16 Sep 2024 14:06:18 -0700
Subject: [PATCH 063/105] Update filenames being read in by infer_aerosol

---
 demo/app/infer-aerosol.f90 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index 1a3d42302..319710db0 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -44,8 +44,8 @@ function file_path(stop_code) result(path)
   subroutine read_stats_and_perform_inference(path)
     character(len=*), intent(in) :: path
     integer,          parameter :: num_inputs = 80, num_outputs = 31
-    character(len=*), parameter :: network_file_name = "training_network.json"
-    character(len=*), parameter :: inputs_tensor_file_name = "training_input.nc"
+    character(len=*), parameter :: network_file_name = "model.json"
+    character(len=*), parameter :: inputs_tensor_file_name = "aerosol_input.nc"
     double precision cube_root
     double precision, allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
     type(tensor_statistics_t) input_stats, output_stats

From 5f4d8f980d0a0de126536feb8df0fa178e46a999 Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Mon, 16 Sep 2024 15:00:03 -0700
Subject: [PATCH 064/105] Update actions/upload-artifact version for github
 deploy documentation workflow

---
 .github/workflows/deploy-docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 183d5bfc1..5714ed292 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -26,7 +26,7 @@ jobs:
         ford ford.md
         cp ./README.md ./doc/html
     - name: Upload Documentation
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v4
       with:
         name: documentation
         path: doc/html

From 47f9ef45579d76a8213ef73b70e84520664dc1ba Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 20 Sep 2024 14:42:05 -0700
Subject: [PATCH 065/105] feat(demo): add unmapped_engine_t constructor

The new constructor is used demo/app/infer-aerosol.f90.
---
 demo/app/infer-aerosol.f90                   |  6 +++---
 src/inference_engine/inference_engine_m_.f90 | 11 +++++++++++
 src/inference_engine/inference_engine_s.F90  |  4 ++++
 3 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index 319710db0..5cd369b61 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -7,7 +7,7 @@ program infer_aerosol
   use iso_fortran_env, only : int64, real64
 
   ! External dependencies:
-  use inference_engine_m, only : inference_engine_t, tensor_t, double_precision, double_precision_file_t
+  use inference_engine_m, only : unmapped_engine_t, tensor_t, double_precision, double_precision_file_t
   use julienne_m, only : string_t, command_line_t
   use omp_lib
 
@@ -49,7 +49,7 @@ subroutine read_stats_and_perform_inference(path)
     double precision cube_root
     double precision, allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
     type(tensor_statistics_t) input_stats, output_stats
-    type(inference_engine_t(double_precision)) inference_engine
+    type(unmapped_engine_t(double_precision)) inference_engine
     integer i, j
 
     input_stats  = read_tensor_statistics(path // "meanxp.txt", path // "stdxp.txt", num_inputs)  !for pre-processing normalization
@@ -76,7 +76,7 @@ subroutine read_stats_and_perform_inference(path)
     !$omp end parallel do   
       
     print *, "Reading the neural network from " // network_file_name
-    inference_engine = inference_engine_t(double_precision_file_t(path // network_file_name))
+    inference_engine = unmapped_engine_t(double_precision_file_t(path // network_file_name))
 
     time_inference: &
     block
diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index ead1eed60..f87f1a3fd 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -104,6 +104,17 @@ impure elemental module function double_precision_from_json(file) result(inferen
 
   end interface
 
+  interface unmapped_engine_t
+
+    impure elemental module function double_precision_unmapped_from_json(file) result(unmapped_engine)
+      implicit none
+      type(double_precision_file_t), intent(in) :: file
+      type(unmapped_engine_t(double_precision)) unmapped_engine
+    end function
+
+  end interface
+
+
   interface
 
     elemental module function default_real_approximately_equal(lhs, rhs) result(lhs_eq_rhs)
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index e19c3701c..64fde4e8c 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -703,6 +703,10 @@ impure function activation_factory_method(activation_name) result(activation)
 
   end procedure default_real_from_json
 
+  module procedure double_precision_unmapped_from_json
+    unmapped_engine%inference_engine_t = double_precision_from_json(file)
+  end procedure
+
   module procedure double_precision_from_json
 
     character(len=:), allocatable :: justified_line

From 98a68a4cf25a1c45fe66407d666ddd19d4813a7d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 20 Sep 2024 15:09:17 -0700
Subject: [PATCH 066/105] fix(infer-aerosol): double-prec. sign() invocation

---
 demo/app/infer-aerosol.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index 5cd369b61..6810af0cc 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -69,7 +69,7 @@ subroutine read_stats_and_perform_inference(path)
     pre_process: &
     do j = 1,size(aerosol_data,2)
       do i = 1,size(aerosol_data,1)
-        cube_root = (abs(aerosol_data(i,j))**(1.d0/3.d0))*sign(1.0,aerosol_data(i,j))
+        cube_root = (abs(aerosol_data(i,j))**(1.d0/3.d0))*sign(1.d0,aerosol_data(i,j))
         input_components(j,i) = (cube_root - input_stats%mean(i))/input_stats%standard_deviation(i)
       end do
     end do pre_process

From f3872905bf1da98cafa3e29873740f0e97fd4a76 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 20 Sep 2024 17:56:53 -0700
Subject: [PATCH 067/105] chore: add missing copyright statements to files

---
 demo/src/phase_space_bin_m.f90                     | 2 ++
 example/print-training-configuration.F90           | 2 ++
 src/inference_engine/double_precision_file_m.f90   | 2 ++
 src/inference_engine/double_precision_file_s.f90   | 2 ++
 src/inference_engine/double_precision_string_m.f90 | 2 ++
 src/inference_engine/double_precision_string_s.f90 | 2 ++
 src/inference_engine/hyperparameters_m.f90         | 2 ++
 src/inference_engine/hyperparameters_s.f90         | 2 ++
 src/inference_engine/metadata_m.f90                | 2 ++
 src/inference_engine/metadata_s.f90                | 2 ++
 src/inference_engine/network_configuration_m.f90   | 2 ++
 src/inference_engine/network_configuration_s.F90   | 2 ++
 src/inference_engine/training_configuration_m.f90  | 2 ++
 src/inference_engine/training_configuration_s.F90  | 2 ++
 14 files changed, 28 insertions(+)

diff --git a/demo/src/phase_space_bin_m.f90 b/demo/src/phase_space_bin_m.f90
index d48655599..1e164fea1 100644
--- a/demo/src/phase_space_bin_m.f90
+++ b/demo/src/phase_space_bin_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module phase_space_bin_m
   use tensor_map_m, only : tensor_map_t
   use tensor_m, only : tensor_t
diff --git a/example/print-training-configuration.F90 b/example/print-training-configuration.F90
index 6643318bd..cf6f4996b 100644
--- a/example/print-training-configuration.F90
+++ b/example/print-training-configuration.F90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 program print_training_configuration
   !! Demonstrate how to construct and print a training_configuration_t object
   use inference_engine_m, only : training_configuration_t, hyperparameters_t, network_configuration_t
diff --git a/src/inference_engine/double_precision_file_m.f90 b/src/inference_engine/double_precision_file_m.f90
index 6264116ef..f62506a42 100644
--- a/src/inference_engine/double_precision_file_m.f90
+++ b/src/inference_engine/double_precision_file_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module double_precision_file_m
   use julienne_m, only : file_t, string_t
   use double_precision_string_m, only : double_precision_string_t
diff --git a/src/inference_engine/double_precision_file_s.f90 b/src/inference_engine/double_precision_file_s.f90
index 961bfde48..620e95291 100644
--- a/src/inference_engine/double_precision_file_s.f90
+++ b/src/inference_engine/double_precision_file_s.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 submodule(double_precision_file_m) double_precision_file_s
   implicit none
 
diff --git a/src/inference_engine/double_precision_string_m.f90 b/src/inference_engine/double_precision_string_m.f90
index 14f0cdea0..01b2f19d2 100644
--- a/src/inference_engine/double_precision_string_m.f90
+++ b/src/inference_engine/double_precision_string_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module double_precision_string_m
   use julienne_m, only : string_t
   implicit none
diff --git a/src/inference_engine/double_precision_string_s.f90 b/src/inference_engine/double_precision_string_s.f90
index 9e4ff72d3..b0cecd45c 100644
--- a/src/inference_engine/double_precision_string_s.f90
+++ b/src/inference_engine/double_precision_string_s.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 submodule(double_precision_string_m)double_precision_string_s
   implicit none
 contains
diff --git a/src/inference_engine/hyperparameters_m.f90 b/src/inference_engine/hyperparameters_m.f90
index e6d41a529..7d485be73 100644
--- a/src/inference_engine/hyperparameters_m.f90
+++ b/src/inference_engine/hyperparameters_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module hyperparameters_m
   use julienne_string_m, only : string_t
   use kind_parameters_m, only : default_real, double_precision
diff --git a/src/inference_engine/hyperparameters_s.f90 b/src/inference_engine/hyperparameters_s.f90
index 229d1d637..e6df53996 100644
--- a/src/inference_engine/hyperparameters_s.f90
+++ b/src/inference_engine/hyperparameters_s.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 submodule(hyperparameters_m) hyperparameters_s
   use assert_m, only : assert
   implicit none
diff --git a/src/inference_engine/metadata_m.f90 b/src/inference_engine/metadata_m.f90
index 6dcf5c909..a5d9df6c3 100644
--- a/src/inference_engine/metadata_m.f90
+++ b/src/inference_engine/metadata_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module metadata_m
   use julienne_string_m, only : string_t
   use double_precision_string_m, only : double_precision_string_t
diff --git a/src/inference_engine/metadata_s.f90 b/src/inference_engine/metadata_s.f90
index c2702bcd4..ca521d0ff 100644
--- a/src/inference_engine/metadata_s.f90
+++ b/src/inference_engine/metadata_s.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 submodule(metadata_m) metadata_s
   use assert_m, only : assert
   implicit none
diff --git a/src/inference_engine/network_configuration_m.f90 b/src/inference_engine/network_configuration_m.f90
index 5d2f34cc5..2822173bc 100644
--- a/src/inference_engine/network_configuration_m.f90
+++ b/src/inference_engine/network_configuration_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module network_configuration_m
   use julienne_string_m, only : string_t
   use double_precision_string_m, only : double_precision_string_t
diff --git a/src/inference_engine/network_configuration_s.F90 b/src/inference_engine/network_configuration_s.F90
index 8100d9fa1..750ca0cc2 100644
--- a/src/inference_engine/network_configuration_s.F90
+++ b/src/inference_engine/network_configuration_s.F90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 submodule(network_configuration_m) network_configuration_s
   use assert_m, only : assert
   use julienne_formats_m, only : csv
diff --git a/src/inference_engine/training_configuration_m.f90 b/src/inference_engine/training_configuration_m.f90
index 2c610b295..3e5179d2f 100644
--- a/src/inference_engine/training_configuration_m.f90
+++ b/src/inference_engine/training_configuration_m.f90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 module training_configuration_m
   use julienne_string_m, only : string_t
   use julienne_file_m, only : file_t
diff --git a/src/inference_engine/training_configuration_s.F90 b/src/inference_engine/training_configuration_s.F90
index 9a9651e76..24b926ca1 100644
--- a/src/inference_engine/training_configuration_s.F90
+++ b/src/inference_engine/training_configuration_s.F90
@@ -1,3 +1,5 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
 submodule(training_configuration_m) training_configuration_s
   use assert_m, only : assert
   use inference_engine_m, only : gelu_t, relu_t, sigmoid_t, swish_t

From d6154f7bd74fe87a7f434e145ceafa91e177f306 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 20 Sep 2024 18:01:01 -0700
Subject: [PATCH 068/105] doc: update subproject name in demo/fpm.toml

---
 demo/fpm.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/demo/fpm.toml b/demo/fpm.toml
index da9e0206b..ae7a32a68 100644
--- a/demo/fpm.toml
+++ b/demo/fpm.toml
@@ -1,4 +1,4 @@
-name       = "cloud-microphysics-trainer"
+name       = "Inference-Engine Demonstration Applications"
 license    = "(Please see inference-engine/LICENSE.txt.)"
 author     = "(Please see inference-engine/fpm.toml.)"
 maintainer = "(Please see inference-engine/fpm.toml.)"

From f4a28c25421e27b11dfd421ef710544ea7bd38e1 Mon Sep 17 00:00:00 2001
From: Katherine Rasmussen <krasmussen@lbl.gov>
Date: Mon, 23 Sep 2024 13:29:22 -0700
Subject: [PATCH 069/105] Remove unallowed whitespace from project name in
 `demo/fpm.toml`

---
 demo/fpm.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/demo/fpm.toml b/demo/fpm.toml
index ae7a32a68..eefd579dd 100644
--- a/demo/fpm.toml
+++ b/demo/fpm.toml
@@ -1,4 +1,4 @@
-name       = "Inference-Engine Demonstration Applications"
+name       = "Inference-Engine-Demonstration-Applications"
 license    = "(Please see inference-engine/LICENSE.txt.)"
 author     = "(Please see inference-engine/fpm.toml.)"
 maintainer = "(Please see inference-engine/fpm.toml.)"

From ffd1c7caa5f2a9f3defb931580e22629554670cc Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 27 Sep 2024 05:49:18 -0700
Subject: [PATCH 070/105] fix(inference_engine_t): conforming real literal

This commit fixes a non-standard-conforming declaration allowed by
flang as an extension but reported as an error by nagfor.
---
 src/inference_engine/hyperparameters_m.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/inference_engine/hyperparameters_m.f90 b/src/inference_engine/hyperparameters_m.f90
index 7d485be73..d7fc5c464 100644
--- a/src/inference_engine/hyperparameters_m.f90
+++ b/src/inference_engine/hyperparameters_m.f90
@@ -11,7 +11,7 @@ module hyperparameters_m
   type hyperparameters_t(k)
     integer, kind :: k = default_real
     integer, private:: mini_batches_ = 10
-    real(k), private :: learning_rate_ = 1.5_k
+    real(k), private :: learning_rate_ = real(1.5,k)
     character(len=:), allocatable :: optimizer_
   contains
     generic :: to_json => default_real_to_json, double_precision_to_json

From 5a06c4d3adf27a9e7ba4f598ffe645448ccdef89 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 9 Oct 2024 17:30:39 -0700
Subject: [PATCH 071/105] fix(sigmoid): make real literals double precision

---
 src/inference_engine/sigmoid_s.f90 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/sigmoid_s.f90 b/src/inference_engine/sigmoid_s.f90
index ebbb4efbe..11d88dd6d 100644
--- a/src/inference_engine/sigmoid_s.f90
+++ b/src/inference_engine/sigmoid_s.f90
@@ -10,7 +10,7 @@
     end procedure
 
     module procedure double_precision_activation
-      y =  1./(1.+exp(-x))
+      y =  1.D0/(1.D0+exp(-x))
     end procedure
 
     module procedure default_real_activation_derivative
@@ -18,7 +18,7 @@
     end procedure
 
     module procedure double_precision_activation_derivative
-      y =  exp(-x)/(1.+exp(-x))**2
+      y =  exp(-x)/(1.D0+exp(-x))**2
     end procedure
 
     module procedure function_name

From 08f425404333959b794c79201ea7337d50296ec2 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 9 Oct 2024 20:30:34 -0700
Subject: [PATCH 072/105] fix(gelu): make constants double-precision

---
 src/inference_engine/gelu_s.f90 | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/inference_engine/gelu_s.f90 b/src/inference_engine/gelu_s.f90
index c684a1721..6e926ff34 100644
--- a/src/inference_engine/gelu_s.f90
+++ b/src/inference_engine/gelu_s.f90
@@ -7,9 +7,9 @@
   real, parameter :: half = 0.5, two = 2.D0
   real, parameter :: sqrt_2_pi = sqrt(2*pi), sqrt_2 = sqrt(2.)
 
-  real, parameter :: pi_dp = 3.141592653589793D0
-  real, parameter :: half_dp = 0.5D0, two_dp = 2.D0
-  real, parameter :: sqrt_2_pi_dp = sqrt(two_dp*pi_dp), sqrt_2_dp = sqrt(2.D0)
+  double precision, parameter :: pi_dp = 3.141592653589793D0
+  double precision, parameter :: half_dp = 0.5D0, two_dp = 2.D0
+  double precision, parameter :: sqrt_2_pi_dp = sqrt(two_dp*pi_dp), sqrt_2_dp = sqrt(2.D0)
 
 contains
 

From f848d4399ba718b7f4f5dac948a88658d6e84353 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 9 Oct 2024 14:35:20 -0700
Subject: [PATCH 073/105] feat: make inference non_overridable

---
 src/inference_engine/inference_engine_m_.f90 | 2 +-
 src/inference_engine/tensor_m.f90            | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index f87f1a3fd..b9b2a9c60 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -39,7 +39,7 @@ module inference_engine_m_
     generic :: activation_function_name => default_real_activation_name,         double_precision_activation_name
     generic :: to_exchange              => default_real_to_exchange,             double_precision_to_exchange
     procedure, private :: default_real_approximately_equal,     double_precision_approximately_equal
-    procedure, private :: default_real_infer,                   double_precision_infer
+    procedure, private, non_overridable :: default_real_infer,                   double_precision_infer
     procedure, private :: default_real_to_json,                 double_precision_to_json
     procedure, private :: default_real_map_to_input_range,      double_precision_map_to_input_range
     procedure, private :: default_real_map_from_output_range,   double_precision_map_from_output_range
diff --git a/src/inference_engine/tensor_m.f90 b/src/inference_engine/tensor_m.f90
index 859331f1c..425a899bb 100644
--- a/src/inference_engine/tensor_m.f90
+++ b/src/inference_engine/tensor_m.f90
@@ -12,7 +12,7 @@ module tensor_m
     real(k), allocatable, private :: values_(:)
   contains
     generic   :: values => default_real_values, double_precision_values
-    procedure, private ::  default_real_values, double_precision_values
+    procedure, private, non_overridable ::  default_real_values, double_precision_values
     generic :: num_components => default_real_num_components, double_precision_num_components
     procedure, private ::        default_real_num_components, double_precision_num_components
   end type

From 6a1c601e60e842887b75e307d02be0e4a023823d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 9 Oct 2024 22:48:43 -0700
Subject: [PATCH 074/105] refac(inference_engine): define activation_t

---
 src/inference_engine/activation_m.f90        |  73 ++++++++++++++
 src/inference_engine/activation_s.f90        | 101 +++++++++++++++++++
 src/inference_engine/inference_engine_m_.f90 |   2 +
 3 files changed, 176 insertions(+)
 create mode 100644 src/inference_engine/activation_m.f90
 create mode 100644 src/inference_engine/activation_s.f90

diff --git a/src/inference_engine/activation_m.f90 b/src/inference_engine/activation_m.f90
new file mode 100644
index 000000000..c2f51bf42
--- /dev/null
+++ b/src/inference_engine/activation_m.f90
@@ -0,0 +1,73 @@
+module activation_m
+  use iso_c_binding, only :  c_int
+  use julienne_m, only : string_t
+  implicit none
+
+  private
+  public :: activation_t
+  public :: gelu, relu, sigmoid, step, swish
+
+  enum, bind(C)
+    enumerator :: gelu, relu, sigmoid, step, swish
+  end enum
+  
+  type activation_t
+    private
+    integer(c_int) selection_ = sigmoid
+  contains
+    generic :: evaluate        => default_real_evaluate     , double_precision_evaluate
+    procedure, non_overridable :: default_real_evaluate     , double_precision_evaluate
+    generic :: differentiate   => default_real_differentiate, double_precision_differentiate
+    procedure, non_overridable :: default_real_differentiate, double_precision_differentiate
+    procedure, non_overridable :: function_name
+  end type
+
+  interface activation_t
+
+    elemental module function construct(selection) result(activation)
+      implicit none
+      integer(c_int), intent(in) :: selection
+      type(activation_t) activation
+    end function
+    
+  end interface
+
+  interface
+
+    elemental module function default_real_evaluate(self, x) result(y)
+      implicit none
+      class(activation_t), intent(in) :: self
+      real, intent(in) :: x 
+      real y 
+    end function
+
+    elemental module function double_precision_evaluate(self, x) result(y)
+      implicit none
+      class(activation_t), intent(in) :: self
+      double precision, intent(in) :: x 
+      double precision y 
+    end function 
+
+    elemental module function default_real_differentiate(self, x) result(dy_dx)
+      implicit none
+      class(activation_t), intent(in) :: self
+      real, intent(in) :: x 
+      real dy_dx 
+    end function
+
+    elemental module function double_precision_differentiate(self, x) result(dy_dx)
+      implicit none
+      class(activation_t), intent(in) :: self
+      double precision, intent(in) :: x 
+      double precision dy_dx 
+    end function
+
+    elemental module function function_name(self) result(string)
+      implicit none
+      class(activation_t), intent(in) :: self
+      type(string_t) string
+    end function
+
+  end interface
+
+end module activation_m
diff --git a/src/inference_engine/activation_s.f90 b/src/inference_engine/activation_s.f90
new file mode 100644
index 000000000..63b672ef6
--- /dev/null
+++ b/src/inference_engine/activation_s.f90
@@ -0,0 +1,101 @@
+submodule(activation_m) activation_s
+  implicit none
+
+  real            , parameter :: pi    = 3.141592653589793
+  double precision, parameter :: pi_dp = 3.141592653589793D0
+
+contains
+
+    module procedure default_real_evaluate
+      select case(self%selection_)
+        case(gelu)
+          y = .5*x*(1. + erf(x/sqrt(2.)))
+        case(relu) 
+          y = max(0., x)
+        case(sigmoid)
+          y =  1./(1.+exp(-x))
+        case(step)
+          y = merge(1., 0., x>0.)
+        case(swish)
+          associate(sigmoid_activation => 1./(1.+exp(-x)))
+            y = x*sigmoid_activation
+          end associate
+        case default
+          error stop "activation_s(default_real_evaluate): unknown activation"
+      end select
+    end procedure
+
+    module procedure double_precision_evaluate
+      select case(self%selection_)
+        case(gelu)
+          y = .5D0*x*(1D0 + erf(x/sqrt(2D0)))
+        case(relu) 
+          y = max(0D0, x)
+        case(sigmoid)
+          y =  1D0/(1D0+exp(-x))
+        case(step)
+          y = merge(1D0, 0D0, x>0D0)
+        case(swish)
+          associate(sigmoid_activation => 1D0/(1D0+exp(-x)))
+            y = x*sigmoid_activation
+          end associate
+        case default
+          error stop "activation_s(double_precision_evaluate): unknown activation"
+      end select
+    end procedure
+  
+    module procedure default_real_differentiate
+      select case(self%selection_)
+        case(gelu)
+          dy_dx = .5*(1. + erf(x/sqrt(2.))) + x*exp(-x**2/2.)/sqrt(2*pi)
+        case(relu) 
+          dy_dx = merge(1., 0., x>0.)
+        case(sigmoid)
+          dy_dx = exp(-x)/(1.+exp(-x))**2
+        case(step)
+          error stop "activation_s(default_real_differentiate): non-differentiable activation"
+        case(swish)
+          associate(sigmoid_activation => 1./(1.+exp(-x)), sigmoid_differentiate => exp(-x)/(1.+exp(-x))**2)
+            dy_dx = sigmoid_activation + x * sigmoid_differentiate
+          end associate
+        case default
+          error stop "activation_s(default_real_differentiate): unknown activation"
+      end select
+    end procedure
+
+    module procedure double_precision_differentiate
+      select case(self%selection_)
+        case(gelu)
+          dy_dx = .5D0*(1D0 + erf(x/sqrt(2D0))) + x*exp(-x**2/2D0)/sqrt(2D0*pi_dp)
+        case(relu) 
+          dy_dx = merge(1D0, 0D0, x>0D0)
+        case(sigmoid)
+          dy_dx = exp(-x)/(1D0+exp(-x))**2
+        case(step)
+          error stop "activation_s(double_precision_differentiate): non-differentiable activation"
+        case(swish)
+          associate(sigmoid_activation => 1D0/(1D0+exp(-x)), sigmoid_differentiate => exp(-x)/(1D0+exp(-x))**2)
+            dy_dx = sigmoid_activation + x * sigmoid_differentiate
+          end associate
+        case default
+          error stop "activation_s(double_precision_differentiate): unknown activation"
+      end select
+    end procedure
+
+    module procedure function_name
+      select case(self%selection_)
+        case(gelu)
+          string = "gelu"
+        case(relu) 
+          string = "relu"
+        case(sigmoid)
+          string = "sigmoid"
+        case(step)
+          string = "step"
+        case(swish)
+          string = "swish"
+        case default
+          error stop "activation_s(function_name): unknown activation"
+      end select
+    end procedure
+end submodule activation_s
diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index b9b2a9c60..0736f0d11 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -2,6 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module inference_engine_m_
   !! Define an abstraction that supports inference operationsn on a neural network
+  use activation_m, only : activation_t
   use activation_strategy_m, only : activation_strategy_t
   use double_precision_file_m, only : double_precision_file_t
   use kind_parameters_m, only : default_real, double_precision
@@ -23,6 +24,7 @@ module inference_engine_m_
     type(metadata_t), private :: metadata_
     real(k), allocatable, private :: weights_(:,:,:), biases_(:,:)
     integer, allocatable, private :: nodes_(:)
+    type(activation_t), private :: activation_
     class(activation_strategy_t), allocatable, private :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   contains
     generic :: operator(==)             => default_real_approximately_equal,     double_precision_approximately_equal

From 3fe8fb74688706bdab2e9ea0e2628a1b2a4d1e6f Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 9 Oct 2024 23:11:59 -0700
Subject: [PATCH 075/105] feat(activation_t): add construct_from_name

---
 src/inference_engine/activation_m.f90       |  8 +++++++-
 src/inference_engine/activation_s.f90       | 21 +++++++++++++++++++++
 src/inference_engine/inference_engine_s.F90 |  1 +
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/src/inference_engine/activation_m.f90 b/src/inference_engine/activation_m.f90
index c2f51bf42..a95294197 100644
--- a/src/inference_engine/activation_m.f90
+++ b/src/inference_engine/activation_m.f90
@@ -24,12 +24,18 @@ module activation_m
 
   interface activation_t
 
-    elemental module function construct(selection) result(activation)
+    elemental module function construct_from_component(selection) result(activation)
       implicit none
       integer(c_int), intent(in) :: selection
       type(activation_t) activation
     end function
     
+    elemental module function construct_from_name(name) result(activation)
+      implicit none
+      character(len=*), intent(in) :: name
+      type(activation_t) activation
+    end function
+
   end interface
 
   interface
diff --git a/src/inference_engine/activation_s.f90 b/src/inference_engine/activation_s.f90
index 63b672ef6..4c820d299 100644
--- a/src/inference_engine/activation_s.f90
+++ b/src/inference_engine/activation_s.f90
@@ -6,6 +6,27 @@
 
 contains
 
+    module procedure construct_from_component
+      activation%selection_ = selection
+    end procedure
+
+    module procedure construct_from_name
+      select case(name)
+        case("gelu")
+          activation%selection_ = gelu
+        case("relu")
+          activation%selection_ = relu
+        case("sigmoid")
+          activation%selection_ = sigmoid
+        case("step")
+          activation%selection_ = step
+        case("swish")
+          activation%selection_ = swish
+        case default
+          error stop "activation_s(construct_from_name): unknown name"
+      end select
+    end procedure
+
     module procedure default_real_evaluate
       select case(self%selection_)
         case(gelu)
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 64fde4e8c..201e7d013 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -323,6 +323,7 @@ impure function activation_factory_method(activation_name) result(activation)
 
     if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
     allocate(inference_engine%activation_strategy_, source = activation_factory_method(metadata(4)%string()))
+    inference_engine%activation_ = activation_t(metadata(4)%string())
 
     call assert_consistency(inference_engine)
 

From 8d6f3b89945bd7eb70b911f6d392a0a2d72a8076 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 10 Oct 2024 13:02:24 -0700
Subject: [PATCH 076/105] feat(tensor_map): mk map functions non_overridable

---
 src/inference_engine/tensor_map_m.f90 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/inference_engine/tensor_map_m.f90 b/src/inference_engine/tensor_map_m.f90
index b11457030..b105f4613 100644
--- a/src/inference_engine/tensor_map_m.f90
+++ b/src/inference_engine/tensor_map_m.f90
@@ -16,9 +16,9 @@ module tensor_map_m
     real(k), dimension(:), allocatable, private :: intercept_, slope_
   contains
     generic :: map_to_training_range => default_real_map_to_training_range, double_precision_map_to_training_range
-    procedure, private ::               default_real_map_to_training_range, double_precision_map_to_training_range
+    procedure, private, non_overridable ::               default_real_map_to_training_range, double_precision_map_to_training_range
     generic :: map_from_training_range => default_real_map_from_training_range, double_precision_map_from_training_range
-    procedure, private ::                 default_real_map_from_training_range, double_precision_map_from_training_range
+    procedure, private, non_overridable ::                 default_real_map_from_training_range, double_precision_map_from_training_range
     generic :: to_json => default_real_to_json, double_precision_to_json
     procedure, private :: default_real_to_json, double_precision_to_json
     generic :: operator(==) => default_real_equals, double_precision_equals

From eba29a068976b7c0f5b4ec06cbf8383e5fedefed Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Thu, 10 Oct 2024 19:32:30 -0700
Subject: [PATCH 077/105] feat(trainable_engine): non-polymorphic activation

---
 example/learn-addition.F90                   |  3 +-
 example/learn-exponentiation.F90             |  3 +-
 example/learn-microphysics-procedures.F90    |  3 +-
 example/learn-multiplication.F90             |  3 +-
 example/learn-power-series.F90               |  3 +-
 example/learn-saturated-mixing-ratio.F90     |  3 +-
 example/train-and-write.F90                  |  3 +-
 src/inference_engine/activation_m.f90        | 11 ++++-
 src/inference_engine/activation_s.f90        |  4 ++
 src/inference_engine/inference_engine_m_.f90 |  4 +-
 src/inference_engine/inference_engine_s.F90  | 51 +++++---------------
 src/inference_engine/trainable_engine_m.F90  | 14 ++----
 src/inference_engine/trainable_engine_s.F90  | 33 +++++--------
 test/trainable_engine_test_m.F90             |  5 +-
 14 files changed, 53 insertions(+), 90 deletions(-)

diff --git a/example/learn-addition.F90 b/example/learn-addition.F90
index 8d5a35510..358e15fbb 100644
--- a/example/learn-addition.F90
+++ b/example/learn-addition.F90
@@ -142,8 +142,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
       trainable_engine = trainable_engine_t( &
-        nodes = n, weights = w, biases = b, differentiable_activation_strategy = relu_t(), &
-        metadata = &
+        nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       )
 
diff --git a/example/learn-exponentiation.F90 b/example/learn-exponentiation.F90
index 9cdd0efba..3bcbe2b96 100644
--- a/example/learn-exponentiation.F90
+++ b/example/learn-exponentiation.F90
@@ -142,8 +142,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
       trainable_engine = trainable_engine_t( &
-        nodes = n, weights = w, biases = b, differentiable_activation_strategy = relu_t(), &
-        metadata = &
+        nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       )
 
diff --git a/example/learn-microphysics-procedures.F90 b/example/learn-microphysics-procedures.F90
index fe4ac50e8..a76bfd2e8 100644
--- a/example/learn-microphysics-procedures.F90
+++ b/example/learn-microphysics-procedures.F90
@@ -190,8 +190,7 @@ function perturbed_identity_network(perturbation_magnitude, n) result(trainable_
       associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
         trainable_engine = trainable_engine_t( &
-          nodes = n, weights = w, biases = b, differentiable_activation_strategy = sigmoid_t(), &
-          metadata = &
+          nodes = n, weights = w, biases = b, metadata = &
             [string_t("Thompson microphysics procedures"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("sigmoid"), &
              string_t("false")] &
         )
diff --git a/example/learn-multiplication.F90 b/example/learn-multiplication.F90
index 0c6a8349d..88d43633c 100644
--- a/example/learn-multiplication.F90
+++ b/example/learn-multiplication.F90
@@ -142,8 +142,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
       trainable_engine = trainable_engine_t( &
-        nodes = n, weights = w, biases = b, differentiable_activation_strategy = relu_t(), &
-        metadata = &
+        nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       )
 
diff --git a/example/learn-power-series.F90 b/example/learn-power-series.F90
index e730fde1e..1fbe93973 100644
--- a/example/learn-power-series.F90
+++ b/example/learn-power-series.F90
@@ -144,8 +144,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
       trainable_engine = trainable_engine_t( &
-        nodes = n, weights = w, biases = b, differentiable_activation_strategy = relu_t(), &
-        metadata = &
+        nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       )
 
diff --git a/example/learn-saturated-mixing-ratio.F90 b/example/learn-saturated-mixing-ratio.F90
index 1302bde97..9799c53d7 100644
--- a/example/learn-saturated-mixing-ratio.F90
+++ b/example/learn-saturated-mixing-ratio.F90
@@ -189,8 +189,7 @@ function perturbed_identity_network(perturbation_magnitude, n) result(trainable_
       associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
         trainable_engine = trainable_engine_t( &
-          nodes = n, weights = w, biases = b, differentiable_activation_strategy = relu_t(), &
-          metadata = &
+          nodes = n, weights = w, biases = b, metadata = &
             [string_t("Saturated Mixing Ratio"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), &
              string_t("false")] &
         )
diff --git a/example/train-and-write.F90 b/example/train-and-write.F90
index d7e8b2b14..c770f088b 100644
--- a/example/train-and-write.F90
+++ b/example/train-and-write.F90
@@ -130,8 +130,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
       trainable_engine = trainable_engine_t( &
-        nodes = nodes_per_layer, weights = w, biases = b, differentiable_activation_strategy = relu_t(), &
-        metadata = &
+        nodes = nodes_per_layer, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       )
 
diff --git a/src/inference_engine/activation_m.f90 b/src/inference_engine/activation_m.f90
index a95294197..d326659db 100644
--- a/src/inference_engine/activation_m.f90
+++ b/src/inference_engine/activation_m.f90
@@ -15,11 +15,13 @@ module activation_m
     private
     integer(c_int) selection_ = sigmoid
   contains
+    procedure, non_overridable :: function_name
+    generic :: operator(==) => equals
+    procedure, private      :: equals
     generic :: evaluate        => default_real_evaluate     , double_precision_evaluate
     procedure, non_overridable :: default_real_evaluate     , double_precision_evaluate
     generic :: differentiate   => default_real_differentiate, double_precision_differentiate
     procedure, non_overridable :: default_real_differentiate, double_precision_differentiate
-    procedure, non_overridable :: function_name
   end type
 
   interface activation_t
@@ -40,6 +42,13 @@ elemental module function construct_from_name(name) result(activation)
 
   interface
 
+    elemental module function equals(self, rhs) result(self_eq_rhs)
+      implicit none
+      class(activation_t), intent(in) :: self
+      type(activation_t), intent(in) :: rhs
+      logical self_eq_rhs
+    end function
+
     elemental module function default_real_evaluate(self, x) result(y)
       implicit none
       class(activation_t), intent(in) :: self
diff --git a/src/inference_engine/activation_s.f90 b/src/inference_engine/activation_s.f90
index 4c820d299..d9721555e 100644
--- a/src/inference_engine/activation_s.f90
+++ b/src/inference_engine/activation_s.f90
@@ -10,6 +10,10 @@
       activation%selection_ = selection
     end procedure
 
+    module procedure equals
+      self_eq_rhs = self%selection_ == rhs%selection_
+    end procedure
+
     module procedure construct_from_name
       select case(name)
         case("gelu")
diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index 0736f0d11..d07f2779b 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -3,7 +3,6 @@
 module inference_engine_m_
   !! Define an abstraction that supports inference operationsn on a neural network
   use activation_m, only : activation_t
-  use activation_strategy_m, only : activation_strategy_t
   use double_precision_file_m, only : double_precision_file_t
   use kind_parameters_m, only : default_real, double_precision
   use julienne_m, only : file_t, string_t
@@ -25,7 +24,6 @@ module inference_engine_m_
     real(k), allocatable, private :: weights_(:,:,:), biases_(:,:)
     integer, allocatable, private :: nodes_(:)
     type(activation_t), private :: activation_
-    class(activation_strategy_t), allocatable, private :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
   contains
     generic :: operator(==)             => default_real_approximately_equal,     double_precision_approximately_equal
     generic :: infer                    => default_real_infer,                   double_precision_infer
@@ -67,7 +65,7 @@ module inference_engine_m_
     type(metadata_t) metadata_
     real(k), allocatable :: weights_(:,:,:), biases_(:,:)
     integer, allocatable :: nodes_(:)
-    class(activation_strategy_t), allocatable :: activation_strategy_ ! Strategy Pattern facilitates elemental activation
+    type(activation_t) activation_
   end type
 
   interface inference_engine_t
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 201e7d013..566aadd7b 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -47,7 +47,7 @@
     exchange%weights_ = self%weights_
     exchange%biases_ = self%biases_
     exchange%nodes_ = self%nodes_
-    exchange%activation_strategy_ = self%activation_strategy_ 
+    exchange%activation_ = self%activation_
   end procedure
 
   module procedure double_precision_to_exchange
@@ -59,7 +59,7 @@
     exchange%weights_ = self%weights_
     exchange%biases_ = self%biases_
     exchange%nodes_ = self%nodes_
-    exchange%activation_strategy_ = self%activation_strategy_ 
+    exchange%activation_ = self%activation_
   end procedure
 
   module procedure default_real_infer_unmapped
@@ -80,7 +80,7 @@
       do l = input_layer+1, output_layer
       associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
           if (l .lt. output_layer) then
-             a(1:n(l),l) = self%activation_strategy_%activation(z)
+             a(1:n(l),l) = self%activation_%evaluate(z)
           else
              a(1:n(l),l) = z(1:n(l))
           end if
@@ -111,7 +111,7 @@
       do l = input_layer+1, output_layer
       associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
           if (l .lt. output_layer) then
-             a(1:n(l),l) = self%activation_strategy_%activation(z)
+             a(1:n(l),l) = self%activation_%evaluate(z)
           else
              a(1:n(l),l) = z(1:n(l))
           end if
@@ -151,7 +151,7 @@
       feed_forward: &
       do l = input_layer+1, output_layer
         associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
-          a(1:n(l),l) = self%activation_strategy_%activation(z)
+          a(1:n(l),l) = self%activation_%evaluate(z)
         end associate
       end do feed_forward
 
@@ -198,7 +198,7 @@
       feed_forward: &
       do l = input_layer+1, output_layer
         associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
-          a(1:n(l),l) = self%activation_strategy_%activation(z)
+          a(1:n(l),l) = self%activation_%evaluate(z)
         end associate
       end do feed_forward
 
@@ -227,7 +227,7 @@ pure subroutine default_real_consistency(self)
     integer, parameter :: input_layer=0
 
     associate( &
-      all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_),allocated(self%activation_strategy_)]&
+      all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
       call assert(all(all_allocated),"inference_engine_s(inference_engine_consistency): fully_allocated", &
         intrinsic_array_t(all_allocated))
@@ -252,7 +252,7 @@ pure subroutine double_precision_consistency(self)
     integer, parameter :: input_layer=0
 
     associate( &
-      all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_),allocated(self%activation_strategy_)]&
+      all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
       call assert(all(all_allocated),"inference_engine_s(inference_engine_consistency): fully_allocated", &
         intrinsic_array_t(all_allocated))
@@ -270,26 +270,6 @@ pure subroutine double_precision_consistency(self)
 
   end subroutine
 
-  impure function activation_factory_method(activation_name) result(activation)
-    character(len=*), intent(in) :: activation_name
-    class(activation_strategy_t), allocatable :: activation
-
-    select case(activation_name)
-      case("swish")
-        activation = swish_t()
-      case("sigmoid")
-        activation = sigmoid_t()
-      case("step")
-        activation = step_t()
-      case("gelu")
-        activation = gelu_t()
-      case("relu")
-        activation = relu_t()
-      case default
-        error stop "inference_engine_s(activation_factory_method): unrecognized activation strategy '"//activation_name//"'"
-    end select
-  end function
-
   module procedure default_real_construct_from_components
 
     inference_engine%metadata_ = metadata_t(metadata(1),metadata(2),metadata(3),metadata(4),metadata(5))
@@ -321,8 +301,6 @@ impure function activation_factory_method(activation_name) result(activation)
       end if
     end block
 
-    if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
-    allocate(inference_engine%activation_strategy_, source = activation_factory_method(metadata(4)%string()))
     inference_engine%activation_ = activation_t(metadata(4)%string())
 
     call assert_consistency(inference_engine)
@@ -360,9 +338,8 @@ impure function activation_factory_method(activation_name) result(activation)
       end if
     end block
 
-    if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
     associate(function_name => metadata%activation_name())
-      allocate(inference_engine%activation_strategy_, source = activation_factory_method(function_name%string()))
+      inference_engine%activation_ = activation_t(function_name%string())
     end associate
 
     call assert_consistency(inference_engine)
@@ -692,9 +669,8 @@ impure function activation_factory_method(activation_name) result(activation)
       associate(metadata => metadata_t(lines(l : l + size(proto_meta%to_json()) - 1)))
         associate(metadata_strings => metadata%strings())
           inference_engine = hidden_layers%inference_engine(metadata_strings, output_layer, input_map, output_map)
-          if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
           associate(function_name => metadata%activation_name())
-            allocate(inference_engine%activation_strategy_, source = activation_factory_method(function_name%string()))
+            inference_engine%activation_ = activation_t(function_name%string())
           end associate
         end associate
       end associate
@@ -798,9 +774,8 @@ impure function activation_factory_method(activation_name) result(activation)
     associate(proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")))
       associate(metadata => metadata_t(lines(l : l + size(proto_meta%to_json()) - 1)))
         inference_engine = hidden_layers%inference_engine(metadata, output_layer, input_map, output_map)
-        if (allocated(inference_engine%activation_strategy_)) deallocate(inference_engine%activation_strategy_)
         associate(function_name => metadata%activation_name())
-          allocate(inference_engine%activation_strategy_, source = activation_factory_method(function_name%string()))
+          inference_engine%activation_ = activation_t(function_name%string())
         end associate
       end associate
     end associate read_metadata
@@ -822,7 +797,7 @@ impure function activation_factory_method(activation_name) result(activation)
       call assert(all(equal_shapes), "assert_conformable_with: all(equal_shapes)", intrinsic_array_t(equal_shapes))
     end associate
 
-    call assert(same_type_as(self%activation_strategy_, inference_engine%activation_strategy_), "assert_conformable_with: types)")
+    call assert(self%activation_ == inference_engine%activation_, "assert_conformable_with: activation_")
     
   end procedure
 
@@ -839,7 +814,7 @@ impure function activation_factory_method(activation_name) result(activation)
       call assert(all(equal_shapes), "assert_conformable_with: all(equal_shapes)", intrinsic_array_t(equal_shapes))
     end associate
 
-    call assert(same_type_as(self%activation_strategy_, inference_engine%activation_strategy_), "assert_conformable_with: types)")
+    call assert(self%activation_ == inference_engine%activation_, "assert_conformable_with: activation_")
     
   end procedure
 
diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
index 13ae2baf8..2b6eb8467 100644
--- a/src/inference_engine/trainable_engine_m.F90
+++ b/src/inference_engine/trainable_engine_m.F90
@@ -2,9 +2,9 @@
 ! Terms of use are as specified in LICENSE.txt
 module trainable_engine_m
   !! Define an abstraction that supports training a neural network
+  use activation_m, only : activation_t
   use julienne_string_m, only : string_t
   use inference_engine_m_, only : inference_engine_t
-  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
   use kind_parameters_m, only : default_real
   use metadata_m, only : metadata_t
   use tensor_m, only :  tensor_t
@@ -21,9 +21,8 @@ module trainable_engine_m
     !! Encapsulate the information needed to perform training
     integer, kind :: k = default_real
     type(tensor_map_t(k)), private :: input_map_, output_map_ !! mappings to/from data ranges used during training
-    class(differentiable_activation_strategy_t), allocatable :: differentiable_activation_strategy_ 
+    type(activation_t), private :: activation_
     type(metadata_t) metadata_       !! metadata_ encapsulates strings for which default-kind suffices
-      !! stateless and thus not paremeterized; generic resolution supports different kinds
     real(k), allocatable :: w(:,:,:) !! weights
     real(k), allocatable :: b(:,:)   !! biases
     integer, allocatable :: n(:)     !! nodes per layer
@@ -62,19 +61,14 @@ module trainable_engine_m
 
   interface trainable_engine_t
 #ifdef __INTEL_COMPILER
-     impure module function construct_trainable_engine_from_padded_arrays( &
-       nodes, weights, biases, differentiable_activation_strategy, metadata, input_map, output_map &
-     ) &
+     impure module function construct_trainable_engine_from_padded_arrays(nodes, weights, biases, metadata, input_map, output_map) &
 #else
-     impure module function construct_from_padded_arrays( &
-       nodes, weights, biases, differentiable_activation_strategy, metadata, input_map, output_map &
-     ) &
+     impure module function construct_from_padded_arrays( nodes, weights, biases, metadata, input_map, output_map) &
 #endif
       result(trainable_engine)
       implicit none
       integer, intent(in) :: nodes(input_layer:)
       real, intent(in)  :: weights(:,:,:), biases(:,:)
-      class(differentiable_activation_strategy_t), intent(in) :: differentiable_activation_strategy
       type(string_t), intent(in) :: metadata(:)
       type(tensor_map_t), intent(in), optional :: input_map, output_map
       type(trainable_engine_t) trainable_engine
diff --git a/src/inference_engine/trainable_engine_s.F90 b/src/inference_engine/trainable_engine_s.F90
index 1ce4f2d51..155a084cc 100644
--- a/src/inference_engine/trainable_engine_s.F90
+++ b/src/inference_engine/trainable_engine_s.F90
@@ -43,13 +43,7 @@
       trainable_engine%w = exchange%weights_
       trainable_engine%b = exchange%biases_
       trainable_engine%n = exchange%nodes_
-      select type(activation => exchange%activation_strategy_)
-        class is(differentiable_activation_strategy_t)
-           trainable_engine%differentiable_activation_strategy_ = activation
-        class default
-           error stop &
-           "trainable_engine_s(from_inference_engine): activation strategy must be a differentiable_activation_stragegy_t"
-      end select
+      trainable_engine%activation_ = exchange%activation_
 #ifndef _CRAYFTN
     end associate
 #endif
@@ -59,7 +53,7 @@
   module procedure default_real_assert_consistent
 
     associate( &
-      fully_allocated=>[allocated(self%w),allocated(self%b),allocated(self%n),allocated(self%differentiable_activation_strategy_)] &
+      fully_allocated=>[allocated(self%w),allocated(self%b),allocated(self%n)] &
     )
       call assert(all(fully_allocated),"trainable_engine_s(assert_consistent): fully_allocated",intrinsic_array_t(fully_allocated))
     end associate
@@ -100,9 +94,9 @@
 
       feed_forward: &
       do l = 1,output_layer
-        a(1:n(l),l) = self%differentiable_activation_strategy_%activation( &
-          matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l) &
-        )
+        associate(z=>matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
+          a(1:n(l),l) = self%activation_%evaluate(z)
+        end associate
       end do feed_forward
  
       associate(normalized_outputs => tensor_t(a(1:n(output_layer), output_layer)))
@@ -213,22 +207,21 @@
                 feed_forward: &
                 do l = 1,output_layer
                   z(1:n(l),l) = matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l)
-                  a(1:n(l),l) = self%differentiable_activation_strategy_%activation(z(1:n(l),l))
+                  a(1:n(l),l) = self%activation_%evaluate(z(1:n(l),l))
                 end do feed_forward
 
                 associate(y => expected_outputs(pair)%values())
                   if (present(cost)) pair_cost(pair) = sum((y(1:n(output_layer))-a(1:n(output_layer),output_layer))**2)
               
-                  delta(1:n(output_layer),output_layer) = &
-                    (a(1:n(output_layer),output_layer) - y(1:n(output_layer))) &
-                    * self%differentiable_activation_strategy_%activation_derivative(z(1:n(output_layer),output_layer))
+                  delta(1:n(output_layer),output_layer) = (a(1:n(output_layer),output_layer) - y(1:n(output_layer))) &
+                                                         * self%activation_%differentiate(z(1:n(output_layer),output_layer))
                 end associate
                 
                 associate(n_hidden => self%num_layers()-2)
                   back_propagate_error: &
                   do l = n_hidden,1,-1
                     delta(1:n(l),l) = matmul(transpose(w(1:n(l+1),1:n(l),l+1)), delta(1:n(l+1),l+1)) &
-                      * self%differentiable_activation_strategy_%activation_derivative(z(1:n(l),l))
+                                     * self%activation_%differentiate(z(1:n(l),l))
                   end do back_propagate_error
                 end associate
 
@@ -325,7 +318,7 @@
     trainable_engine%n = nodes
     trainable_engine%w = weights
     trainable_engine%b = biases
-    trainable_engine%differentiable_activation_strategy_ = differentiable_activation_strategy
+    trainable_engine%activation_ = activation_t(metadata(4)%string())
 
     block 
       integer i
@@ -370,12 +363,10 @@
 
         associate( &
           w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, &
-          b => perturbation_magnitude*(b_harvest-0.5)/0.5, &
-          activation => training_configuration%differentiable_activation_strategy() &
+          b => perturbation_magnitude*(b_harvest-0.5)/0.5 &
         )
           trainable_engine = trainable_engine_t( &
-            nodes = n, weights = w, biases = b, differentiable_activation_strategy = activation, metadata = metadata, &
-            input_map = input_map, output_map = output_map &
+            nodes = n, weights = w, biases = b, metadata = metadata, input_map = input_map, output_map = output_map &
           )
         end associate
       end associate
diff --git a/test/trainable_engine_test_m.F90 b/test/trainable_engine_test_m.F90
index 23a7fd694..b6a363ba3 100644
--- a/test/trainable_engine_test_m.F90
+++ b/test/trainable_engine_test_m.F90
@@ -184,7 +184,7 @@ function two_zeroed_hidden_layers() result(trainable_engine)
     b = 0.
 
     trainable_engine = trainable_engine_t( &
-      nodes = neurons, weights = w, biases = b, differentiable_activation_strategy = sigmoid_t(), metadata = &
+      nodes = neurons, weights = w, biases = b, metadata = &
       [string_t("2-hide|3-wide"), string_t("Damian Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
     )   
   end function
@@ -202,7 +202,7 @@ function two_random_hidden_layers() result(trainable_engine)
     w = 2*w
 
     trainable_engine = trainable_engine_t( &
-      nodes = neurons, weights = w, biases = b, differentiable_activation_strategy = sigmoid_t(), metadata = &
+      nodes = neurons, weights = w, biases = b, metadata = &
       [string_t("2-hide|3-wide"), string_t("Damian Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
     )   
   end function
@@ -424,7 +424,6 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
       nodes = nodes_per_layer, &
       weights = identity + harvest , & 
       biases = reshape([real:: [0,0], [0,0], [0,0]], [max_n, layers-1]), &
-      differentiable_activation_strategy = relu_t(), &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")] &
     )
   end function

From 63ab0414fd52bd8e51282044ed63bcd65dc57d30 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 11 Oct 2024 21:48:54 -0700
Subject: [PATCH 078/105] refac: rm activation_strategy class hierachy

---
 example/learn-addition.F90                    |  2 +-
 example/learn-exponentiation.F90              |  2 +-
 example/learn-microphysics-procedures.F90     |  2 +-
 example/learn-multiplication.F90              |  2 +-
 example/learn-power-series.F90                |  2 +-
 example/learn-saturated-mixing-ratio.F90      |  2 +-
 example/read-query-infer.f90                  |  2 +-
 example/train-and-write.F90                   |  2 +-
 example/write-read-infer.F90                  |  2 +-
 src/inference_engine/activation_s.f90         |  1 +
 .../activation_strategy_m.f90                 | 46 ----------------
 .../differentiable_activation_strategy_m.f90  | 17 ------
 src/inference_engine/gelu_m.f90               | 52 -------------------
 src/inference_engine/gelu_s.f90               | 36 -------------
 src/inference_engine/inference_engine_s.F90   |  5 --
 src/inference_engine/relu_m.f90               | 52 -------------------
 src/inference_engine/relu_s.f90               | 28 ----------
 src/inference_engine/sigmoid_m.f90            | 52 -------------------
 src/inference_engine/sigmoid_s.f90            | 28 ----------
 src/inference_engine/step_m.f90               | 39 --------------
 src/inference_engine/step_s.f90               | 20 -------
 src/inference_engine/swish_m.f90              | 52 -------------------
 src/inference_engine/swish_s.f90              | 33 ------------
 .../training_configuration_m.f90              | 16 +++---
 .../training_configuration_s.F90              | 26 +++++-----
 src/inference_engine_m.f90                    |  7 ---
 test/trainable_engine_test_m.F90              |  2 +-
 27 files changed, 31 insertions(+), 499 deletions(-)
 delete mode 100644 src/inference_engine/activation_strategy_m.f90
 delete mode 100644 src/inference_engine/differentiable_activation_strategy_m.f90
 delete mode 100644 src/inference_engine/gelu_m.f90
 delete mode 100644 src/inference_engine/gelu_s.f90
 delete mode 100644 src/inference_engine/relu_m.f90
 delete mode 100644 src/inference_engine/relu_s.f90
 delete mode 100644 src/inference_engine/sigmoid_m.f90
 delete mode 100644 src/inference_engine/sigmoid_s.f90
 delete mode 100644 src/inference_engine/step_m.f90
 delete mode 100644 src/inference_engine/step_s.f90
 delete mode 100644 src/inference_engine/swish_m.f90
 delete mode 100644 src/inference_engine/swish_s.f90

diff --git a/example/learn-addition.F90 b/example/learn-addition.F90
index 358e15fbb..0e2efdf3f 100644
--- a/example/learn-addition.F90
+++ b/example/learn-addition.F90
@@ -21,7 +21,7 @@ elemental function y(x_tensor) result(a_tensor)
 program learn_addition
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, relu_t
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use addition_m, only : y
diff --git a/example/learn-exponentiation.F90 b/example/learn-exponentiation.F90
index 3bcbe2b96..6b8ba0624 100644
--- a/example/learn-exponentiation.F90
+++ b/example/learn-exponentiation.F90
@@ -21,7 +21,7 @@ elemental function y(x_tensor) result(a_tensor)
 program learn_exponentiation
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, relu_t
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use exponentiation_m, only : y
diff --git a/example/learn-microphysics-procedures.F90 b/example/learn-microphysics-procedures.F90
index a76bfd2e8..552bd9ac0 100644
--- a/example/learn-microphysics-procedures.F90
+++ b/example/learn-microphysics-procedures.F90
@@ -4,7 +4,7 @@ program learn_microphysics_procedures
   !! Train a neural network proxies for procedures in the Thompson microphysics model
   !! in of ICAR (https://github.com/BerkeleyLab/icar).
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, sigmoid_t 
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t, csv
   use assert_m, only : assert, intrinsic_array_t
   use thompson_tensors_m, only : y, T, p
diff --git a/example/learn-multiplication.F90 b/example/learn-multiplication.F90
index 88d43633c..797335dd9 100644
--- a/example/learn-multiplication.F90
+++ b/example/learn-multiplication.F90
@@ -21,7 +21,7 @@ elemental function y(x_tensor) result(a_tensor)
 program learn_multiplication
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, relu_t
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use multiply_inputs, only : y
diff --git a/example/learn-power-series.F90 b/example/learn-power-series.F90
index 1fbe93973..c0490669e 100644
--- a/example/learn-power-series.F90
+++ b/example/learn-power-series.F90
@@ -21,7 +21,7 @@ elemental function y(x_in) result(a)
 program learn_power_series
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, relu_t
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use power_series, only : y
diff --git a/example/learn-saturated-mixing-ratio.F90 b/example/learn-saturated-mixing-ratio.F90
index 9799c53d7..ba8bd599c 100644
--- a/example/learn-saturated-mixing-ratio.F90
+++ b/example/learn-saturated-mixing-ratio.F90
@@ -3,7 +3,7 @@
 program train_saturated_mixture_ratio
   !! This program trains a neural network to learn the saturated mixing ratio function of ICAR.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, relu_t
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t, csv
   use assert_m, only : assert, intrinsic_array_t
   use saturated_mixing_ratio_m, only : y, T, p
diff --git a/example/read-query-infer.f90 b/example/read-query-infer.f90
index 45a593914..86889192b 100644
--- a/example/read-query-infer.f90
+++ b/example/read-query-infer.f90
@@ -4,7 +4,7 @@ program read_query_infer
   !! This program demonstrates how to read a neural network from a JSON file,
   !! query the network object for some of its properties, print those properties,
   !! and use the network to perform inference.  
-  use inference_engine_m, only : inference_engine_t, relu_t, tensor_t
+  use inference_engine_m, only : inference_engine_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
   implicit none
 
diff --git a/example/train-and-write.F90 b/example/train-and-write.F90
index c770f088b..04df61a55 100644
--- a/example/train-and-write.F90
+++ b/example/train-and-write.F90
@@ -9,7 +9,7 @@ program train_and_write
   !! The initial condition corresponds to the desired network with all weights and biases perturbed by a random variable
   !! that is uniformly distributed on the range [0,0.1].
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle, relu_t
+    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   implicit none
diff --git a/example/write-read-infer.F90 b/example/write-read-infer.F90
index f6880cd3a..8786d4b24 100644
--- a/example/write-read-infer.F90
+++ b/example/write-read-infer.F90
@@ -7,7 +7,7 @@ program write_read_infer
   !! perform inference.  The network performs an identity mapping from any
   !! non-negative inputs to the corresponding outputs using a RELU activation
   !! function.
-  use inference_engine_m, only : inference_engine_t, relu_t, tensor_t
+  use inference_engine_m, only : inference_engine_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
   implicit none
 
diff --git a/src/inference_engine/activation_s.f90 b/src/inference_engine/activation_s.f90
index d9721555e..0e346f7a8 100644
--- a/src/inference_engine/activation_s.f90
+++ b/src/inference_engine/activation_s.f90
@@ -123,4 +123,5 @@
           error stop "activation_s(function_name): unknown activation"
       end select
     end procedure
+
 end submodule activation_s
diff --git a/src/inference_engine/activation_strategy_m.f90 b/src/inference_engine/activation_strategy_m.f90
deleted file mode 100644
index 8bca0a026..000000000
--- a/src/inference_engine/activation_strategy_m.f90
+++ /dev/null
@@ -1,46 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module activation_strategy_m
-
-  ! External dependencies
-  use julienne_string_m, only : string_t
-  implicit none
-
-  private
-  public :: activation_strategy_t
-  public :: default_real_activation_i
-  public :: double_precision_activation_i
-  public :: function_name_i
-
-  type, abstract :: activation_strategy_t
-  contains
-     procedure(default_real_activation_i), nopass, deferred :: default_real_activation
-     procedure(double_precision_activation_i), nopass, deferred :: double_precision_activation
-     generic :: activation => default_real_activation, double_precision_activation
-     procedure(function_name_i), deferred :: function_name
-  end type
-
-  abstract interface
-
-    elemental function default_real_activation_i(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental function double_precision_activation_i(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental function function_name_i(self) result(string)
-      import string_t, activation_strategy_t
-      implicit none
-      class(activation_strategy_t), intent(in) :: self
-      type(string_t) string
-    end function
-
-  end interface
-
-end module activation_strategy_m
diff --git a/src/inference_engine/differentiable_activation_strategy_m.f90 b/src/inference_engine/differentiable_activation_strategy_m.f90
deleted file mode 100644
index b1e226472..000000000
--- a/src/inference_engine/differentiable_activation_strategy_m.f90
+++ /dev/null
@@ -1,17 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module differentiable_activation_strategy_m
-  use activation_strategy_m, only : activation_strategy_t, default_real_activation_i, double_precision_activation_i
-  implicit none
-
-  private
-  public :: differentiable_activation_strategy_t
-
-  type, extends(activation_strategy_t), abstract :: differentiable_activation_strategy_t
-  contains
-     procedure(default_real_activation_i), nopass, deferred :: default_real_activation_derivative
-     procedure(double_precision_activation_i), nopass, deferred :: double_precision_activation_derivative
-     generic :: activation_derivative => default_real_activation_derivative, double_precision_activation_derivative
-  end type
-
-end module differentiable_activation_strategy_m
diff --git a/src/inference_engine/gelu_m.f90 b/src/inference_engine/gelu_m.f90
deleted file mode 100644
index 51a872c46..000000000
--- a/src/inference_engine/gelu_m.f90
+++ /dev/null
@@ -1,52 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module gelu_m
-  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use julienne_string_m, only : string_t
-  implicit none
-
-  private
-  public :: gelu_t 
-
-  type, extends(differentiable_activation_strategy_t) :: gelu_t
-  contains
-     procedure, nopass :: default_real_activation,            double_precision_activation
-     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
-     procedure :: function_name
-  end type
-
-  interface
-
-    elemental module function default_real_activation(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function default_real_activation_derivative(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation_derivative(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function function_name(self) result(string)
-      implicit none
-      class(gelu_t), intent(in) :: self
-      type(string_t) string
-    end function
-
-  end interface
-
-end module gelu_m
diff --git a/src/inference_engine/gelu_s.f90 b/src/inference_engine/gelu_s.f90
deleted file mode 100644
index 6e926ff34..000000000
--- a/src/inference_engine/gelu_s.f90
+++ /dev/null
@@ -1,36 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-submodule(gelu_m) gelu_s
-  implicit none
-
-  real, parameter :: pi = 3.141592653589793
-  real, parameter :: half = 0.5, two = 2.D0
-  real, parameter :: sqrt_2_pi = sqrt(2*pi), sqrt_2 = sqrt(2.)
-
-  double precision, parameter :: pi_dp = 3.141592653589793D0
-  double precision, parameter :: half_dp = 0.5D0, two_dp = 2.D0
-  double precision, parameter :: sqrt_2_pi_dp = sqrt(two_dp*pi_dp), sqrt_2_dp = sqrt(2.D0)
-
-contains
-
-    module procedure default_real_activation
-      y = half*x*(1. + erf(x/sqrt_2))
-    end procedure
-
-    module procedure double_precision_activation
-      y = half_dp*x*(1.D0 + erf(x/sqrt_2_dp))
-    end procedure
-
-    module procedure default_real_activation_derivative
-      y = half*(1. + erf(x/sqrt_2)) + x*exp(-x**2/two)/sqrt_2_pi
-    end procedure
-
-    module procedure double_precision_activation_derivative
-      y = half_dp*(1.D0 + erf(x/sqrt_2_dp)) + x*exp(-x**2/2.D0)/sqrt_2_pi_dp
-    end procedure
-
-    module procedure function_name
-      string = string_t("gelu")
-    end procedure
-
-end submodule gelu_s
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 566aadd7b..9050888d3 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -3,14 +3,9 @@
 submodule(inference_engine_m_) inference_engine_s
   use assert_m, only : assert, intrinsic_array_t
   use double_precision_string_m, only : double_precision_string_t
-  use gelu_m, only : gelu_t
   use kind_parameters_m, only : double_precision
   use layer_m, only : layer_t
   use neuron_m, only : neuron_t
-  use relu_m, only : relu_t
-  use step_m, only : step_t
-  use swish_m, only : swish_t
-  use sigmoid_m, only : sigmoid_t
   implicit none
 
   interface assert_consistency
diff --git a/src/inference_engine/relu_m.f90 b/src/inference_engine/relu_m.f90
deleted file mode 100644
index d537dee01..000000000
--- a/src/inference_engine/relu_m.f90
+++ /dev/null
@@ -1,52 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module relu_m
-  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use julienne_string_m, only : string_t
-  implicit none
-
-  private
-  public :: relu_t 
-
-  type, extends(differentiable_activation_strategy_t) :: relu_t
-  contains
-     procedure, nopass :: default_real_activation,            double_precision_activation
-     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
-     procedure :: function_name
-  end type
-
-  interface
-
-    elemental module function default_real_activation(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function default_real_activation_derivative(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation_derivative(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function function_name(self) result(string)
-      implicit none
-      class(relu_t), intent(in) :: self
-      type(string_t) string
-    end function
-
-  end interface
-
-end module relu_m
diff --git a/src/inference_engine/relu_s.f90 b/src/inference_engine/relu_s.f90
deleted file mode 100644
index 29c3980e5..000000000
--- a/src/inference_engine/relu_s.f90
+++ /dev/null
@@ -1,28 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-submodule(relu_m) relu_s
-  implicit none
-
-contains
-
-    module procedure default_real_activation
-      y = max(0., x)
-    end procedure
-
-    module procedure double_precision_activation
-      y = max(0.D0, x)
-    end procedure
-
-    module procedure default_real_activation_derivative
-      y = merge(1., 0., x>0.)
-    end procedure
-
-    module procedure double_precision_activation_derivative
-      y = merge(1.D0, 0.D0, x>0.D0)
-    end procedure
-
-    module procedure function_name
-      string = string_t("relu")
-    end procedure
-
-end submodule relu_s
diff --git a/src/inference_engine/sigmoid_m.f90 b/src/inference_engine/sigmoid_m.f90
deleted file mode 100644
index 64160bc73..000000000
--- a/src/inference_engine/sigmoid_m.f90
+++ /dev/null
@@ -1,52 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module sigmoid_m
-  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use julienne_string_m, only : string_t
-  implicit none
-
-  private
-  public :: sigmoid_t 
-
-  type, extends(differentiable_activation_strategy_t) :: sigmoid_t
-  contains
-     procedure, nopass :: default_real_activation,            double_precision_activation
-     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
-     procedure :: function_name
-  end type
-
-  interface
-
-    elemental module function default_real_activation(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function default_real_activation_derivative(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation_derivative(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function function_name(self) result(string)
-      implicit none
-      class(sigmoid_t), intent(in) :: self
-      type(string_t) string
-    end function
-
-  end interface
-
-end module sigmoid_m
diff --git a/src/inference_engine/sigmoid_s.f90 b/src/inference_engine/sigmoid_s.f90
deleted file mode 100644
index 11d88dd6d..000000000
--- a/src/inference_engine/sigmoid_s.f90
+++ /dev/null
@@ -1,28 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-submodule(sigmoid_m) sigmoid_s
-  implicit none
-
-contains
-
-    module procedure default_real_activation
-      y =  1./(1.+exp(-x))
-    end procedure
-
-    module procedure double_precision_activation
-      y =  1.D0/(1.D0+exp(-x))
-    end procedure
-
-    module procedure default_real_activation_derivative
-      y =  exp(-x)/(1.+exp(-x))**2
-    end procedure
-
-    module procedure double_precision_activation_derivative
-      y =  exp(-x)/(1.D0+exp(-x))**2
-    end procedure
-
-    module procedure function_name
-      string = string_t("sigmoid")
-    end procedure
-
-end submodule sigmoid_s
diff --git a/src/inference_engine/step_m.f90 b/src/inference_engine/step_m.f90
deleted file mode 100644
index 90abab9cb..000000000
--- a/src/inference_engine/step_m.f90
+++ /dev/null
@@ -1,39 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module step_m
-  use activation_strategy_m, only : activation_strategy_t
-  use julienne_string_m, only : string_t
-  implicit none
-
-  private
-  public :: step_t 
-
-  type, extends(activation_strategy_t) :: step_t
-  contains
-     procedure, nopass :: default_real_activation, double_precision_activation
-     procedure :: function_name
-  end type
-
-  interface
-
-    elemental module function default_real_activation(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function function_name(self) result(string)
-      implicit none
-      class(step_t), intent(in) :: self
-      type(string_t) string
-    end function
-
-  end interface
-
-end module step_m
diff --git a/src/inference_engine/step_s.f90 b/src/inference_engine/step_s.f90
deleted file mode 100644
index e98d8d756..000000000
--- a/src/inference_engine/step_s.f90
+++ /dev/null
@@ -1,20 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-submodule(step_m) step_s
-  implicit none
-
-contains
-
-    module procedure default_real_activation
-      y = merge(1., 0., x>0.)
-    end procedure
-
-    module procedure double_precision_activation
-      y = merge(1.D0, 0.D0, x>0.D0)
-    end procedure
-
-    module procedure function_name
-      string = string_t("step")
-    end procedure
-
-end submodule step_s
diff --git a/src/inference_engine/swish_m.f90 b/src/inference_engine/swish_m.f90
deleted file mode 100644
index 476764004..000000000
--- a/src/inference_engine/swish_m.f90
+++ /dev/null
@@ -1,52 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module swish_m
-  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
-  use julienne_string_m, only : string_t
-  implicit none
-
-  private
-  public :: swish_t 
-
-  type, extends(differentiable_activation_strategy_t) :: swish_t
-  contains
-     procedure, nopass :: default_real_activation           , double_precision_activation
-     procedure, nopass :: default_real_activation_derivative, double_precision_activation_derivative
-     procedure :: function_name
-  end type
-
-  interface
-
-    elemental module function default_real_activation(x) result(y)
-      implicit none
-      real, intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation(x) result(y)
-      implicit none
-      double precision, intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function default_real_activation_derivative(x) result(y)
-      implicit none
-      real , intent(in) :: x
-      real y
-    end function
-
-    elemental module function double_precision_activation_derivative(x) result(y)
-      implicit none
-      double precision , intent(in) :: x
-      double precision y
-    end function
-
-    elemental module function function_name(self) result(string)
-      implicit none
-      class(swish_t), intent(in) :: self
-      type(string_t) string
-    end function
-
-  end interface
-
-end module swish_m
diff --git a/src/inference_engine/swish_s.f90 b/src/inference_engine/swish_s.f90
deleted file mode 100644
index d85f020cf..000000000
--- a/src/inference_engine/swish_s.f90
+++ /dev/null
@@ -1,33 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-submodule(swish_m) swish_s
-  use sigmoid_m, only : sigmoid_t
-  implicit none
-
-contains
-
-    module procedure default_real_activation
-      type(sigmoid_t) sigmoid
-      y =  x*sigmoid%activation(x)
-    end procedure
-
-    module procedure double_precision_activation
-      type(sigmoid_t) sigmoid
-      y =  x*sigmoid%activation(x)
-    end procedure
-
-    module procedure default_real_activation_derivative
-      type(sigmoid_t) sigmoid
-      y =  sigmoid%activation(x) + x * sigmoid%activation_derivative(x)
-    end procedure
-
-    module procedure double_precision_activation_derivative
-      type(sigmoid_t) sigmoid
-      y =  sigmoid%activation(x) + x * sigmoid%activation_derivative(x)
-    end procedure
-
-    module procedure function_name
-      string = string_t("swish")
-    end procedure
-
-end submodule swish_s
diff --git a/src/inference_engine/training_configuration_m.f90 b/src/inference_engine/training_configuration_m.f90
index 3e5179d2f..eb20a30f0 100644
--- a/src/inference_engine/training_configuration_m.f90
+++ b/src/inference_engine/training_configuration_m.f90
@@ -1,12 +1,12 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 module training_configuration_m
+  use activation_m, only : activation_t
   use julienne_string_m, only : string_t
   use julienne_file_m, only : file_t
   use hyperparameters_m, only : hyperparameters_t
   use network_configuration_m, only : network_configuration_t
   use kind_parameters_m, only  : default_real, double_precision
-  use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
   use double_precision_file_m, only : double_precision_file_t
   implicit none
 
@@ -28,10 +28,8 @@ module training_configuration_m
     procedure, private ::        default_real_optimizer_name, double_precision_optimizer_name
     generic :: learning_rate => default_real_learning_rate, double_precision_learning_rate
     procedure, private ::       default_real_learning_rate, double_precision_learning_rate
-    generic :: differentiable_activation_strategy => &
-      default_real_differentiable_activation_strategy, double_precision_differentiable_activation_strategy
-    procedure, private :: &
-      default_real_differentiable_activation_strategy, double_precision_differentiable_activation_strategy
+    generic :: differentiable_activation => default_real_differentiable_activation, double_precision_differentiable_activation
+    procedure, private ::                   default_real_differentiable_activation, double_precision_differentiable_activation
     generic :: nodes_per_layer => default_real_nodes_per_layer, double_precision_nodes_per_layer
     procedure, private ::         default_real_nodes_per_layer, double_precision_nodes_per_layer
     generic :: skip_connections => default_real_skip_connections, double_precision_skip_connections
@@ -130,16 +128,16 @@ elemental module function double_precision_learning_rate(self) result(rate)
       double precision rate
     end function
 
-    module function default_real_differentiable_activation_strategy(self) result(strategy)
+    module function default_real_differentiable_activation(self) result(activation)
       implicit none
       class(training_configuration_t), intent(in) :: self
-      class(differentiable_activation_strategy_t), allocatable :: strategy
+      type(activation_t) activation
     end function
 
-    module function double_precision_differentiable_activation_strategy(self) result(strategy)
+    module function double_precision_differentiable_activation(self) result(activation)
       implicit none
       class(training_configuration_t(double_precision)), intent(in) :: self
-      class(differentiable_activation_strategy_t), allocatable :: strategy
+      type(activation_t) activation
     end function
  
     pure module function default_real_nodes_per_layer(self) result(nodes)
diff --git a/src/inference_engine/training_configuration_s.F90 b/src/inference_engine/training_configuration_s.F90
index 24b926ca1..3b982a202 100644
--- a/src/inference_engine/training_configuration_s.F90
+++ b/src/inference_engine/training_configuration_s.F90
@@ -2,8 +2,8 @@
 ! Terms of use are as specified in LICENSE.txt
 submodule(training_configuration_m) training_configuration_s
   use assert_m, only : assert
-  use inference_engine_m, only : gelu_t, relu_t, sigmoid_t, swish_t
   use double_precision_string_m, only : double_precision_string_t
+  use activation_m, only : activation_t, gelu, relu, sigmoid, swish
   implicit none
 
   character(len=*), parameter :: header="{", footer="}", separator = ","
@@ -181,7 +181,7 @@
     using_skip = self%network_configuration_%skip_connections()
   end procedure
 
-  module procedure default_real_differentiable_activation_strategy
+  module procedure default_real_differentiable_activation
 #if defined __INTEL_COMPILER || _CRAYFTN
     type(string_t) :: activation_name
     activation_name = self%network_configuration_%activation_name()
@@ -190,15 +190,15 @@
 #endif
       select case(activation_name%string())
         case ("gelu")
-          strategy = gelu_t()
+          activation = activation_t(gelu)
         case ("relu")
-          strategy = relu_t()
+          activation = activation_t(relu)
         case ("sigmoid")
-          strategy = sigmoid_t()
+          activation = activation_t(sigmoid)
         case ("swish")
-          strategy = swish_t()
+          activation = activation_t(swish)
         case default
-          error stop 'activation_strategy_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
+          error stop 'activation_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
       end select
 #if defined __INTEL_COMPILER || _CRAYFTN
 #else
@@ -206,7 +206,7 @@
 #endif
   end procedure
 
-  module procedure double_precision_differentiable_activation_strategy
+  module procedure double_precision_differentiable_activation
 #if defined __INTEL_COMPILER || _CRAYFTN
     type(string_t) :: activation_name
     activation_name = self%network_configuration_%activation_name()
@@ -215,15 +215,15 @@
 #endif
       select case(activation_name%string())
         case ("gelu")
-          strategy = gelu_t()
+          activation = activation_t(gelu)
         case ("relu")
-          strategy = relu_t()
+          activation = activation_t(relu)
         case ("sigmoid")
-          strategy = sigmoid_t()
+          activation = activation_t(sigmoid)
         case ("swish")
-          strategy = swish_t()
+          activation = activation_t(swish)
         case default
-          error stop 'activation_strategy_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
+          error stop 'activation_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
       end select
 #if defined __INTEL_COMPILER || _CRAYFTN
 #else
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 2b9c2a040..22f505671 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -2,11 +2,8 @@
 ! Terms of use are as specified in LICENSE.txt
 module inference_engine_m
  !! Specify the user-facing modules, derived types, and type parameters
- use activation_strategy_m, only : activation_strategy_t
- use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t
  use double_precision_file_m, only : double_precision_file_t
  use double_precision_string_m, only : double_precision_string_t
- use gelu_m, only : gelu_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
  use inference_engine_m_, only : inference_engine_t, unmapped_engine_t
@@ -14,10 +11,6 @@ module inference_engine_m
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t
  use network_configuration_m, only : network_configuration_t
- use relu_m, only : relu_t
- use sigmoid_m, only : sigmoid_t
- use step_m, only : step_t
- use swish_m, only : swish_t
  use tensor_m, only : tensor_t
  use tensor_map_m, only : tensor_map_t
  use trainable_engine_m, only : trainable_engine_t
diff --git a/test/trainable_engine_test_m.F90 b/test/trainable_engine_test_m.F90
index b6a363ba3..929d40ea6 100644
--- a/test/trainable_engine_test_m.F90
+++ b/test/trainable_engine_test_m.F90
@@ -12,7 +12,7 @@ module trainable_engine_test_m
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : trainable_engine_t, tensor_t, sigmoid_t, input_output_pair_t, mini_batch_t, relu_t, shuffle
+  use inference_engine_m, only : trainable_engine_t, tensor_t, input_output_pair_t, mini_batch_t, shuffle
   implicit none
 
   private

From 1c1e4872900ca3f98101ac4daa390f517391ae73 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 11 Oct 2024 22:53:59 -0700
Subject: [PATCH 079/105] refac(activation_s): simplify function_name getter

---
 src/inference_engine/activation_m.f90 |  6 ++++--
 src/inference_engine/activation_s.f90 | 27 ++++++++++-----------------
 2 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/src/inference_engine/activation_m.f90 b/src/inference_engine/activation_m.f90
index d326659db..3566fbff8 100644
--- a/src/inference_engine/activation_m.f90
+++ b/src/inference_engine/activation_m.f90
@@ -8,9 +8,11 @@ module activation_m
   public :: gelu, relu, sigmoid, step, swish
 
   enum, bind(C)
-    enumerator :: gelu, relu, sigmoid, step, swish
+    enumerator :: gelu=1, relu, sigmoid, step, swish
   end enum
-  
+
+  character(len=*), parameter :: activation_name(*) =  [character(len("sigmoid")) :: "gelu", "relu", "sigmoid", "step", "swish"]
+
   type activation_t
     private
     integer(c_int) selection_ = sigmoid
diff --git a/src/inference_engine/activation_s.f90 b/src/inference_engine/activation_s.f90
index 0e346f7a8..876417de9 100644
--- a/src/inference_engine/activation_s.f90
+++ b/src/inference_engine/activation_s.f90
@@ -1,4 +1,5 @@
 submodule(activation_m) activation_s
+  use assert_m, only : assert
   implicit none
 
   real            , parameter :: pi    = 3.141592653589793
@@ -14,6 +15,15 @@
       self_eq_rhs = self%selection_ == rhs%selection_
     end procedure
 
+    module procedure function_name
+
+      call assert( lbound(activation_name,1) <= self%selection_ .and. self%selection_ <= ubound(activation_name,1), &
+        "activation_s(function_name): bounds")
+
+      string = string_t(trim(activation_name(self%selection_)))
+
+    end procedure
+
     module procedure construct_from_name
       select case(name)
         case("gelu")
@@ -107,21 +117,4 @@
       end select
     end procedure
 
-    module procedure function_name
-      select case(self%selection_)
-        case(gelu)
-          string = "gelu"
-        case(relu) 
-          string = "relu"
-        case(sigmoid)
-          string = "sigmoid"
-        case(step)
-          string = "step"
-        case(swish)
-          string = "swish"
-        case default
-          error stop "activation_s(function_name): unknown activation"
-      end select
-    end procedure
-
 end submodule activation_s

From d9a90731b4025f45ca58bc4ba94361b9de625bda Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 11 Oct 2024 23:05:28 -0700
Subject: [PATCH 080/105] refac(engines): make component constructors pure

---
 src/inference_engine/inference_engine_m_.f90 | 8 ++++----
 src/inference_engine/trainable_engine_m.F90  | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index d07f2779b..daeeccf8e 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -70,7 +70,7 @@ module inference_engine_m_
 
   interface inference_engine_t
 
-    impure module function default_real_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
+    module function default_real_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
       result(inference_engine)
       implicit none
       type(string_t), intent(in) :: metadata(:)
@@ -80,7 +80,7 @@ impure module function default_real_construct_from_components(metadata, weights,
       type(inference_engine_t) inference_engine
     end function
 
-    impure module function double_precision_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
+    module function double_precision_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
       result(inference_engine)
       implicit none
       type(metadata_t), intent(in) :: metadata
@@ -163,13 +163,13 @@ elemental module function double_precision_map_from_output_range(self, normalize
       type(tensor_t(double_precision)) tensor
     end function
 
-    impure module function default_real_to_exchange(self) result(exchange)
+    module function default_real_to_exchange(self) result(exchange)
       implicit none
       class(inference_engine_t), intent(in) :: self
       type(exchange_t) exchange
     end function
 
-    impure module function double_precision_to_exchange(self) result(exchange)
+    module function double_precision_to_exchange(self) result(exchange)
       implicit none
       class(inference_engine_t(double_precision)), intent(in) :: self
       type(exchange_t(double_precision)) exchange
diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
index 2b6eb8467..6a0a46f9e 100644
--- a/src/inference_engine/trainable_engine_m.F90
+++ b/src/inference_engine/trainable_engine_m.F90
@@ -61,9 +61,9 @@ module trainable_engine_m
 
   interface trainable_engine_t
 #ifdef __INTEL_COMPILER
-     impure module function construct_trainable_engine_from_padded_arrays(nodes, weights, biases, metadata, input_map, output_map) &
+     module function construct_trainable_engine_from_padded_arrays(nodes, weights, biases, metadata, input_map, output_map) &
 #else
-     impure module function construct_from_padded_arrays( nodes, weights, biases, metadata, input_map, output_map) &
+     module function construct_from_padded_arrays( nodes, weights, biases, metadata, input_map, output_map) &
 #endif
       result(trainable_engine)
       implicit none
@@ -74,7 +74,7 @@ impure module function construct_from_padded_arrays( nodes, weights, biases, met
       type(trainable_engine_t) trainable_engine
     end function
 
-    impure module function construct_from_inference_engine(inference_engine) result(trainable_engine)
+    module function construct_from_inference_engine(inference_engine) result(trainable_engine)
       implicit none
       type(inference_engine_t), intent(in) :: inference_engine
       type(trainable_engine_t) trainable_engine

From 6ef2d1cb8a1d0d9c52450743d6b3682ebb23cae6 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Fri, 11 Oct 2024 23:21:38 -0700
Subject: [PATCH 081/105] feat: non_overridble for inference & training

---
 src/inference_engine/input_output_pair_m.f90 | 8 ++++----
 src/inference_engine/mini_batch_m.f90        | 2 +-
 src/inference_engine/trainable_engine_m.F90  | 8 ++++----
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/inference_engine/input_output_pair_m.f90 b/src/inference_engine/input_output_pair_m.f90
index fcbfab493..047f2ba5c 100644
--- a/src/inference_engine/input_output_pair_m.f90
+++ b/src/inference_engine/input_output_pair_m.f90
@@ -14,10 +14,10 @@ module input_output_pair_m
     integer, kind :: k = default_real
     type(tensor_t(k)), private :: inputs_, expected_outputs_
   contains
-    generic :: inputs =>  default_real_inputs, double_precision_inputs
-    procedure, private :: default_real_inputs, double_precision_inputs
-    generic :: expected_outputs => default_real_expected_outputs, double_precision_expected_outputs
-    procedure, private ::          default_real_expected_outputs, double_precision_expected_outputs
+    generic :: inputs                   => default_real_inputs, double_precision_inputs
+    procedure, private, non_overridable :: default_real_inputs, double_precision_inputs
+    generic :: expected_outputs         => default_real_expected_outputs, double_precision_expected_outputs
+    procedure, private, non_overridable :: default_real_expected_outputs, double_precision_expected_outputs
   end type
 
   interface input_output_pair_t
diff --git a/src/inference_engine/mini_batch_m.f90 b/src/inference_engine/mini_batch_m.f90
index 10fb0b567..7bb44d7cb 100644
--- a/src/inference_engine/mini_batch_m.f90
+++ b/src/inference_engine/mini_batch_m.f90
@@ -13,7 +13,7 @@ module mini_batch_m
     type(input_output_pair_t(k)), private, allocatable :: input_output_pairs_(:)
   contains
     generic :: input_output_pairs => default_real_input_output_pairs, double_precision_input_output_pairs
-    procedure ::                     default_real_input_output_pairs, double_precision_input_output_pairs
+    procedure, non_overridable    :: default_real_input_output_pairs, double_precision_input_output_pairs
   end type
 
   interface mini_batch_t
diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
index 6a0a46f9e..b7acc6e83 100644
--- a/src/inference_engine/trainable_engine_m.F90
+++ b/src/inference_engine/trainable_engine_m.F90
@@ -30,8 +30,8 @@ module trainable_engine_m
     real(k), allocatable, dimension(:,:,:) :: dcdw, vdw, sdw, vdwc, sdwc
     real(k), allocatable, dimension(:,:) :: z, delta, dcdb, vdb, sdb, vdbc, sdbc
   contains
-    generic :: train =>   default_real_train
-    procedure, private :: default_real_train
+    generic :: train                    => default_real_train
+    procedure, private, non_overridable :: default_real_train
     generic :: predict => default_real_predict
     generic :: infer =>   default_real_predict
     procedure, private :: default_real_predict
@@ -40,8 +40,8 @@ module trainable_engine_m
     generic :: num_layers => default_real_num_layers
     procedure, private ::    default_real_num_layers
     generic :: num_inputs => default_real_num_inputs
-    procedure, private ::    default_real_num_inputs
-    generic :: num_outputs => default_real_num_outputs
+    procedure, private, non_overridable :: default_real_num_inputs
+    generic :: num_outputs              => default_real_num_outputs
     procedure, private ::     default_real_num_outputs
     generic :: to_inference_engine => default_real_to_inference_engine
     procedure, private ::             default_real_to_inference_engine

From 9958bb781fd99a8f08dc981dcd997a2ad68189f4 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 13 Oct 2024 17:00:36 -0700
Subject: [PATCH 082/105] feat(trainable_network): extend inference_engine_t

---
 src/inference_engine/inference_engine_m_.f90 |  77 +++++--
 src/inference_engine/inference_engine_s.F90  | 205 ++++++++++++++++++-
 src/inference_engine/trainable_engine_s.F90  |   1 -
 src/inference_engine/trainable_network_m.f90 |  42 ++++
 src/inference_engine/trainable_network_s.f90 |  15 ++
 src/inference_engine/workspace_s.f90         | 117 +++++++++++
 src/inference_engine_m.f90                   |   1 +
 7 files changed, 434 insertions(+), 24 deletions(-)
 create mode 100644 src/inference_engine/trainable_network_m.f90
 create mode 100644 src/inference_engine/trainable_network_s.f90
 create mode 100644 src/inference_engine/workspace_s.f90

diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index daeeccf8e..a196fd6ee 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -7,6 +7,7 @@ module inference_engine_m_
   use kind_parameters_m, only : default_real, double_precision
   use julienne_m, only : file_t, string_t
   use metadata_m, only : metadata_t
+  use mini_batch_m, only : mini_batch_t
   use tensor_m, only : tensor_t
   use tensor_map_m, only : tensor_map_t
   implicit none
@@ -15,6 +16,7 @@ module inference_engine_m_
   public :: inference_engine_t
   public :: unmapped_engine_t
   public :: exchange_t
+  public :: workspace_t
 
   type inference_engine_t(k)
     !! Encapsulate the minimal information needed to perform inference
@@ -38,19 +40,33 @@ module inference_engine_m_
     generic :: skip                     => default_real_skip,                    double_precision_skip
     generic :: activation_function_name => default_real_activation_name,         double_precision_activation_name
     generic :: to_exchange              => default_real_to_exchange,             double_precision_to_exchange
-    procedure, private :: default_real_approximately_equal,     double_precision_approximately_equal
+    generic :: learn                    => default_real_learn
+    procedure, private, non_overridable :: default_real_approximately_equal,     double_precision_approximately_equal
     procedure, private, non_overridable :: default_real_infer,                   double_precision_infer
-    procedure, private :: default_real_to_json,                 double_precision_to_json
-    procedure, private :: default_real_map_to_input_range,      double_precision_map_to_input_range
-    procedure, private :: default_real_map_from_output_range,   double_precision_map_from_output_range
-    procedure, private :: default_real_num_hidden_layers,       double_precision_num_hidden_layers
-    procedure, private :: default_real_num_inputs,              double_precision_num_inputs
-    procedure, private :: default_real_num_outputs,             double_precision_num_outputs
-    procedure, private :: default_real_nodes_per_layer,         double_precision_nodes_per_layer
-    procedure, private :: default_real_assert_conformable_with, double_precision_assert_conformable_with
-    procedure, private :: default_real_skip,                    double_precision_skip
-    procedure, private :: default_real_activation_name,         double_precision_activation_name
-    procedure, private :: default_real_to_exchange,             double_precision_to_exchange
+    procedure, private, non_overridable :: default_real_learn
+    procedure, private, non_overridable :: default_real_to_json,                 double_precision_to_json
+    procedure, private, non_overridable :: default_real_map_to_input_range,      double_precision_map_to_input_range
+    procedure, private, non_overridable :: default_real_map_from_output_range,   double_precision_map_from_output_range
+    procedure, private, non_overridable :: default_real_num_hidden_layers,       double_precision_num_hidden_layers
+    procedure, private, non_overridable :: default_real_num_inputs,              double_precision_num_inputs
+    procedure, private, non_overridable :: default_real_num_outputs,             double_precision_num_outputs
+    procedure, private, non_overridable :: default_real_nodes_per_layer,         double_precision_nodes_per_layer
+    procedure, private, non_overridable :: default_real_assert_conformable_with, double_precision_assert_conformable_with
+    procedure, private, non_overridable :: default_real_skip,                    double_precision_skip
+    procedure, private, non_overridable :: default_real_activation_name,         double_precision_activation_name
+    procedure, private, non_overridable :: default_real_to_exchange,             double_precision_to_exchange
+  end type
+
+  type workspace_t(k)
+    integer, kind :: k = default_real
+    real(k), allocatable, dimension(:,:) :: a
+    real(k), allocatable, dimension(:,:,:) :: dcdw, vdw, sdw, vdwc, sdwc
+    real(k), allocatable, dimension(:,:) :: z, delta, dcdb, vdb, sdb, vdbc, sdbc
+  contains
+    generic :: fully_allocated          => default_real_allocated
+    generic :: allocate_if_necessary    => default_real_allocate
+    procedure, non_overridable, private :: default_real_allocated
+    procedure, non_overridable, private :: default_real_allocate
   end type
 
   type, extends(inference_engine_t) :: unmapped_engine_t
@@ -115,8 +131,35 @@ impure elemental module function double_precision_unmapped_from_json(file) resul
   end interface
 
 
+  interface workspace_t
+
+    pure module function default_real_workspace(inference_engine) result(workspace)
+      implicit none
+      type(inference_engine_t), intent(in) :: inference_engine
+      type(workspace_t) workspace
+    end function
+
+  end interface
+
   interface
 
+    module subroutine default_real_allocate(self, inference_engine)
+      implicit none
+      class(workspace_t), intent(inout) :: self
+      type(inference_engine_t), intent(in) :: inference_engine
+    end subroutine
+
+    pure module function default_real_allocated(self) result(all_allocated)
+      implicit none
+      class(workspace_t), intent(in) :: self
+      logical all_allocated
+    end function
+
+  end interface
+
+
+  interface ! inference_engine_t type-bound procedures
+
     elemental module function default_real_approximately_equal(lhs, rhs) result(lhs_eq_rhs)
       !! The result is true if lhs and rhs are the same to within a tolerance
       implicit none
@@ -299,6 +342,16 @@ pure module function double_precision_skip(self) result(use_skip_connections)
       logical use_skip_connections
     end function
 
+    pure module subroutine default_real_learn(self, mini_batches_arr, cost, adam, learning_rate, workspace)
+      implicit none
+      class(inference_engine_t), intent(inout) :: self
+      type(mini_batch_t), intent(in) :: mini_batches_arr(:)
+      real, intent(out), allocatable, optional :: cost(:)
+      logical, intent(in) :: adam
+      real, intent(in) :: learning_rate
+      type(workspace_t), intent(inout) :: workspace
+    end subroutine
+
   end interface
 
 end module inference_engine_m_
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index 9050888d3..ce8b5c7ea 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -14,6 +14,7 @@
   end interface
 
   character(len=*), parameter :: acceptable_engine_tag = "0.13.0" ! git tag capable of reading the current json file format
+  integer, parameter :: input_layer = 0 
 
 contains
 
@@ -60,7 +61,6 @@
   module procedure default_real_infer_unmapped
 
     real, allocatable :: a(:,:)
-    integer, parameter :: input_layer = 0 
     integer l
 
     call assert_consistency(self)
@@ -91,7 +91,6 @@
   module procedure double_precision_infer_unmapped
 
     double precision, allocatable :: a(:,:)
-    integer, parameter :: input_layer = 0 
     integer l
 
     call assert_consistency(self)
@@ -122,7 +121,6 @@
   module procedure default_real_infer
 
     real, allocatable :: a(:,:)
-    integer, parameter :: input_layer = 0
     integer l
 
     call assert_consistency(self)
@@ -169,7 +167,6 @@
   module procedure double_precision_infer
 
     double precision, allocatable :: a(:,:)
-    integer, parameter :: input_layer = 0
     integer l
 
     call assert_consistency(self)
@@ -219,8 +216,6 @@ pure subroutine default_real_consistency(self)
 
     class(inference_engine_t), intent(in) :: self
 
-    integer, parameter :: input_layer=0
-
     associate( &
       all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
@@ -244,7 +239,6 @@ pure subroutine double_precision_consistency(self)
 
     class(inference_engine_t(double_precision)), intent(in) :: self
 
-    integer, parameter :: input_layer=0
 
     associate( &
       all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
@@ -906,18 +900,18 @@ pure subroutine double_precision_consistency(self)
   end procedure
 
   module procedure default_real_num_hidden_layers
-    integer, parameter :: input_layer = 1, output_layer = 1
+    integer, parameter :: num_non_hidden_layers = 2
     call assert_consistency(self)
     associate(num_layers => size(self%nodes_))
-      hidden_layer_count =  num_layers - (input_layer + output_layer)
+      hidden_layer_count =  num_layers - num_non_hidden_layers
     end associate
   end procedure
 
   module procedure double_precision_num_hidden_layers
-    integer, parameter :: input_layer = 1, output_layer = 1
+    integer, parameter :: num_non_hidden_layers = 2
     call assert_consistency(self)
     associate(num_layers => size(self%nodes_))
-      hidden_layer_count =  num_layers - (input_layer + output_layer)
+      hidden_layer_count =  num_layers - num_non_hidden_layers
     end associate
   end procedure
 
@@ -965,4 +959,193 @@ pure subroutine double_precision_consistency(self)
     end associate
   end procedure
 
+  module procedure default_real_learn
+    integer l, batch, mini_batch_size, pair
+    type(tensor_t), allocatable :: inputs(:), expected_outputs(:)
+
+    call assert_consistency(self)
+    call assert(workspace%fully_allocated(), "inference_engine_s(default_real_learn): workspace%fully_allocated()")
+
+    associate(output_layer => ubound(self%nodes_,1))
+
+      associate( &
+        dcdw => workspace%dcdw, vdw => workspace%vdw, sdw   => workspace%sdw  , vdwc => workspace%vdwc, sdwc => workspace%sdwc &
+       ,dcdb => workspace%dcdb, vdb => workspace%vdb, sdb   => workspace%sdb  , vdbc => workspace%vdbc, sdbc => workspace%sdbc &
+       ,a    => workspace%a   , z   => workspace%z  , delta => workspace%delta &
+      )
+        vdw = 0.; sdw = 1.; vdb = 0.; sdb = 1.
+
+        associate(w => self%weights_, b => self%biases_, n => self%nodes_, num_mini_batches => size(mini_batches_arr))
+
+          if (present(cost)) allocate(cost(num_mini_batches))
+        
+          iterate_across_batches: &
+          do batch = 1, num_mini_batches
+
+            dcdw = 0.; dcdb = 0.
+
+#ifndef _CRAYFTN
+            associate(input_output_pairs => mini_batches_arr(batch)%input_output_pairs())
+#else
+            block
+              type(input_output_pair_t), allocatable :: input_output_pairs(:)
+              input_output_pairs = mini_batches_arr(batch)%input_output_pairs()
+#endif  
+              inputs = input_output_pairs%inputs()
+              expected_outputs = input_output_pairs%expected_outputs()
+              mini_batch_size = size(input_output_pairs)
+#ifndef _CRAYFTN
+            end associate
+#else
+            end block
+#endif  
+            sum_cost: &
+            block
+              real, allocatable :: pair_cost(:)
+              if (present(cost)) allocate(pair_cost(mini_batch_size))
+
+#if F2023_LOCALITY
+              iterate_through_batch: &
+              do concurrent (pair = 1:mini_batch_size) local(a,z,delta) reduce(+: dcdb, dcdw)
+
+#elif F2018_LOCALITY
+
+              reduce_gradients: &
+              block
+                real reduce_dcdb(size(dcdb,1),size(dcdb,2),mini_batch_size)
+                real reduce_dcdw(size(dcdw,1),size(dcdw,2),size(dcdw,3),mini_batch_size)
+                reduce_dcdb = 0.
+                reduce_dcdw = 0.
+
+                iterate_through_batch: &
+                do concurrent (pair = 1:mini_batch_size) local(a,z,delta)
+
+#else
+
+              reduce_gradients: &
+              block
+                real reduce_dcdb(size(dcdb,1),size(dcdb,2),mini_batch_size)
+                real reduce_dcdw(size(dcdw,1),size(dcdw,2),size(dcdw,3),mini_batch_size)
+                reduce_dcdb = 0.
+                reduce_dcdw = 0.
+              
+                iterate_through_batch: &
+                do concurrent (pair = 1:mini_batch_size)
+
+                  iteration: &
+                  block
+
+                    real a(maxval(self%nodes_), input_layer:output_layer) ! Activations
+                    real z(size(b,1),size(b,2)), delta(size(b,1),size(b,2))
+#endif
+
+                    a(1:self%num_inputs(), input_layer) = inputs(pair)%values()
+
+                    feed_forward: &
+                    do l = 1,output_layer
+                      z(1:n(l),l) = matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l) ! z_j^l =  sum_k(w_jk^{l} a_k^{l-1}) + b_j^l
+                      a(1:n(l),l) = self%activation_%evaluate(z(1:n(l),l))
+                    end do feed_forward
+
+                    associate(y => expected_outputs(pair)%values())
+                      if (present(cost)) pair_cost(pair) = sum((y(1:n(output_layer))-a(1:n(output_layer),output_layer))**2)
+              
+                      delta(1:n(output_layer),output_layer) = (a(1:n(output_layer),output_layer) - y(1:n(output_layer))) &
+                                                             * self%activation_%differentiate(z(1:n(output_layer),output_layer))
+                    end associate
+                
+                    associate(n_hidden => self%num_hidden_layers())
+                      back_propagate_error: &
+                      do l = n_hidden,1,-1
+                        delta(1:n(l),l) = matmul(transpose(w(1:n(l+1),1:n(l),l+1)), delta(1:n(l+1),l+1)) &
+                                         * self%activation_%differentiate(z(1:n(l),l))
+                      end do back_propagate_error
+                    end associate
+
+                    
+
+                    block
+                      integer j
+                      sum_gradients: &
+                      do l = 1,output_layer
+#if F2023_LOCALITY
+                        dcdb(1:n(l),l) = dcdb(1:n(l),l) + delta(1:n(l),l)
+                        do concurrent(j = 1:n(l)) reduce(+: dcdw)
+                          dcdw(j,1:n(l-1),l) = dcdw(j,1:n(l-1),l) + a(1:n(l-1),l-1)*delta(j,l)
+                        end do
+#else
+                        reduce_dcdb(1:n(l),l,pair) = reduce_dcdb(1:n(l),l,pair) + delta(1:n(l),l)
+                        do j = 1,n(l)
+                          reduce_dcdw(j,1:n(l-1),l,pair) = reduce_dcdw(j,1:n(l-1),l,pair) + a(1:n(l-1),l-1)*delta(j,l)
+                        end do
+#endif
+                      end do sum_gradients
+                    end block
+    
+#if F2023_LOCALITY
+              end do iterate_through_batch
+#elif F2018_LOCALITY
+
+                end do iterate_through_batch
+                dcdb = sum(reduce_dcdb,dim=3)
+                dcdw = sum(reduce_dcdw,dim=4)
+
+              end block reduce_gradients
+#else
+                  end block iteration
+                end do iterate_through_batch
+                dcdb = sum(reduce_dcdb,dim=3)
+                dcdw = sum(reduce_dcdw,dim=4)
+  
+              end block reduce_gradients
+#endif
+
+              if (present(cost)) cost(batch) = sum(pair_cost)/(2*mini_batch_size)
+            end block sum_cost
+          
+            if (adam) then
+              adam: &
+              block
+                ! Adam parameters  
+                real, parameter :: beta(*) = [.9, .999]
+                real, parameter :: obeta(*) = [1.- beta(1), 1.- beta(2)]
+                real, parameter :: epsilon = 1.E-08
+
+                associate(alpha => learning_rate)
+                  adam_adjust_weights_and_biases: &
+                  do concurrent(l = 1:output_layer)
+                    dcdw(1:n(l),1:n(l-1),l) = dcdw(1:n(l),1:n(l-1),l)/(mini_batch_size)
+                    vdw(1:n(l),1:n(l-1),l)  = beta(1)*vdw(1:n(l),1:n(l-1),l) + obeta(1)*dcdw(1:n(l),1:n(l-1),l)
+                    sdw (1:n(l),1:n(l-1),l) = beta(2)*sdw(1:n(l),1:n(l-1),l) + obeta(2)*(dcdw(1:n(l),1:n(l-1),l)**2)
+                    vdwc(1:n(l),1:n(l-1),l) = vdw(1:n(l),1:n(l-1),l)/(1.- beta(1)**num_mini_batches)
+                    sdwc(1:n(l),1:n(l-1),l) = sdw(1:n(l),1:n(l-1),l)/(1.- beta(2)**num_mini_batches)
+                    w(1:n(l),1:n(l-1),l) = w(1:n(l),1:n(l-1),l) &
+                      - alpha*vdwc(1:n(l),1:n(l-1),l)/(sqrt(sdwc(1:n(l),1:n(l-1),l))+epsilon) ! Adjust weights
+
+                    dcdb(1:n(l),l) = dcdb(1:n(l),l)/mini_batch_size
+                    vdb(1:n(l),l) = beta(1)*vdb(1:n(l),l) + obeta(1)*dcdb(1:n(l),l)
+                    sdb(1:n(l),l) = beta(2)*sdb(1:n(l),l) + obeta(2)*(dcdb(1:n(l),l)**2)
+                    vdbc(1:n(l),l) = vdb(1:n(l),l)/(1. - beta(1)**num_mini_batches)
+                    sdbc(1:n(l),l) = sdb(1:n(l),l)/(1. - beta(2)**num_mini_batches)
+                    b(1:n(l),l) = b(1:n(l),l) - alpha*vdbc(1:n(l),l)/(sqrt(sdbc(1:n(l),l))+epsilon) ! Adjust weights
+                  end do adam_adjust_weights_and_biases
+                end associate
+              end block adam
+            else
+              associate(eta => learning_rate)
+                adjust_weights_and_biases: &
+                do concurrent(l = 1:output_layer)
+                  dcdb(1:n(l),l) = dcdb(1:n(l),l)/mini_batch_size
+                  b(1:n(l),l) = b(1:n(l),l) - eta*dcdb(1:n(l),l) ! Adjust biases
+                  dcdw(1:n(l),1:n(l-1),l) = dcdw(1:n(l),1:n(l-1),l)/mini_batch_size
+                  w(1:n(l),1:n(l-1),l) = w(1:n(l),1:n(l-1),l) - eta*dcdw(1:n(l),1:n(l-1),l) ! Adjust weights
+                end do adjust_weights_and_biases
+              end associate
+            end if
+          end do iterate_across_batches
+        end associate
+      end associate
+    end associate
+  end procedure default_real_learn
+
 end submodule inference_engine_s
diff --git a/src/inference_engine/trainable_engine_s.F90 b/src/inference_engine/trainable_engine_s.F90
index 155a084cc..f72096fe6 100644
--- a/src/inference_engine/trainable_engine_s.F90
+++ b/src/inference_engine/trainable_engine_s.F90
@@ -412,6 +412,5 @@ pure function e(j,n) result(unit_vector)
   module procedure default_real_map_from_output_training_range
     unnormalized_tensor = self%output_map_%map_from_training_range(tensor)
   end procedure
-  
 
 end submodule trainable_engine_s
diff --git a/src/inference_engine/trainable_network_m.f90 b/src/inference_engine/trainable_network_m.f90
new file mode 100644
index 000000000..3338f0f69
--- /dev/null
+++ b/src/inference_engine/trainable_network_m.f90
@@ -0,0 +1,42 @@
+module trainable_network_m
+  use inference_engine_m_, only : inference_engine_t, workspace_t
+  use mini_batch_m, only : mini_batch_t
+  use kind_parameters_m, only : default_real
+  implicit none
+
+  private
+  public :: trainable_network_t 
+
+  type, extends(inference_engine_t) ::  trainable_network_t(k)
+    integer, kind :: k = default_real 
+    private
+    type(workspace_t), private :: workspace_
+  contains
+    generic :: train           => default_real_train                   
+    procedure, non_overridable :: default_real_train
+  end type
+
+  interface trainable_network_t 
+
+    pure module function default_real_network(inference_engine) result(trainable_network)
+      implicit none
+      type(inference_engine_t), intent(in) :: inference_engine
+      type(trainable_network_t) trainable_network
+    end function 
+ 
+  end interface
+
+  interface
+
+    pure module subroutine default_real_train(self, mini_batches_arr, cost, adam, learning_rate)
+      implicit none
+      class(trainable_network_t), intent(inout) :: self
+      type(mini_batch_t), intent(in) :: mini_batches_arr(:)
+      real, intent(out), allocatable, optional :: cost(:)
+      logical, intent(in) :: adam
+      real, intent(in) :: learning_rate
+    end subroutine
+
+  end interface
+
+end module trainable_network_m
diff --git a/src/inference_engine/trainable_network_s.f90 b/src/inference_engine/trainable_network_s.f90
new file mode 100644
index 000000000..d6e1d83a6
--- /dev/null
+++ b/src/inference_engine/trainable_network_s.f90
@@ -0,0 +1,15 @@
+submodule(trainable_network_m) trainable_network_s
+  implicit none 
+
+contains
+
+  module procedure default_real_network
+    trainable_network%inference_engine_t = inference_engine
+    trainable_network%workspace_ = workspace_t(inference_engine)
+  end procedure
+
+  module procedure default_real_train
+    call self%inference_engine_t%default_real_learn(mini_batches_arr, cost, adam, learning_rate, self%workspace_)
+  end procedure
+
+end submodule trainable_network_s
diff --git a/src/inference_engine/workspace_s.f90 b/src/inference_engine/workspace_s.f90
new file mode 100644
index 000000000..f3d80139c
--- /dev/null
+++ b/src/inference_engine/workspace_s.f90
@@ -0,0 +1,117 @@
+submodule(inference_engine_m_) workspace_s
+  use assert_m, only : assert
+  implicit none
+
+  integer, parameter :: input_layer = 0
+
+contains
+
+  module procedure default_real_workspace
+
+    allocate(workspace%dcdw, mold=inference_engine%weights_) ! Gradient of cost function with respect to weights
+    allocate(workspace%vdw , mold=inference_engine%weights_)
+    allocate(workspace%sdw , mold=inference_engine%weights_)
+    allocate(workspace%vdwc, mold=inference_engine%weights_)
+    allocate(workspace%sdwc, mold=inference_engine%weights_)
+
+    allocate(workspace%dcdb, mold=inference_engine%biases_ ) ! Gradient of cost function with respect with biases
+    allocate(workspace%vdb , mold=inference_engine%biases_ )
+    allocate(workspace%sdb , mold=inference_engine%biases_ )
+    allocate(workspace%vdbc, mold=inference_engine%biases_ )
+    allocate(workspace%sdbc, mold=inference_engine%biases_ )
+
+    ! TODO: #if ! (F2023_LOCALITY || F2018_LOCALITY)
+    !          then don't allocate a, z, and delta
+
+    allocate(workspace%z    , mold=inference_engine%biases_)
+    allocate(workspace%delta, mold=inference_engine%biases_)
+
+    associate(output_layer => ubound(inference_engine%nodes_,1))
+      allocate(workspace%a(maxval(inference_engine%nodes_), input_layer:output_layer)) ! Activations
+    end associate
+
+    call assert(workspace%fully_allocated(), "workspace_s(defalt_real_workspace): workspace allocated")
+
+  end procedure
+
+  module procedure default_real_allocated
+
+    ! TODO: #if ! (F2023_LOCALITY || F2018_LOCALITY)
+    !          then don't check a, z, and delta allocations
+
+    all_allocated = all( [ & 
+      allocated(self%a), allocated(self%dcdw), allocated(self%vdw), allocated(self%sdw), allocated(self%vdwc), allocated(self%sdwc)&
+     ,allocated(self%z), allocated(self%dcdb), allocated(self%vdb), allocated(self%sdb), allocated(self%vdbc), allocated(self%sdbc)&
+     ,allocated(self%delta) &
+    ])
+  end procedure
+
+
+  module procedure default_real_allocate
+
+    call allocate_if_necessary(self%dcdw,  mold=inference_engine%weights_) ! Gradient of cost function with respect to weights
+    call allocate_if_necessary(self%vdw ,  mold=inference_engine%weights_) 
+    call allocate_if_necessary(self%sdw ,  mold=inference_engine%weights_) 
+    call allocate_if_necessary(self%vdwc,  mold=inference_engine%weights_) 
+    call allocate_if_necessary(self%sdwc,  mold=inference_engine%weights_) 
+
+    call allocate_if_necessary(self%dcdb , mold=inference_engine%biases_) ! Gradient of cost function with respect with biases
+    call allocate_if_necessary(self%vdb  , mold=inference_engine%biases_) 
+    call allocate_if_necessary(self%sdb  , mold=inference_engine%biases_) 
+    call allocate_if_necessary(self%vdbc , mold=inference_engine%biases_) 
+    call allocate_if_necessary(self%sdbc , mold=inference_engine%biases_) 
+
+    ! TODO: #if ! (F2023_LOCALITY || F2018_LOCALITY)
+    !          then don't allocate a, z, and delta
+
+    call allocate_if_necessary(self%z    , mold=inference_engine%biases_) 
+    call allocate_if_necessary(self%delta, mold=inference_engine%biases_) 
+
+    associate(output_layer => ubound(inference_engine%nodes_,1))
+      allocate(self%a(maxval(inference_engine%nodes_), input_layer:output_layer)) ! Activations
+    end associate
+
+  contains
+
+    subroutine allocate_if_necessary(array, mold)
+      real, allocatable, intent(inout) :: array(..)
+      real, intent(in) :: mold(..)
+
+      select rank(array)
+        rank(2)
+          select rank(mold)
+            rank(2)
+              if (.not. allocated(array)) then
+                allocate(array, mold=mold)
+              else
+                if (any(shape(array) /= shape(mold))) then
+                  deallocate(array)
+                  allocate(array, mold=mold)
+                end if
+              end if
+            rank default
+              error stop "workspace_s(allocate_if_necessary): mold-rank mismatch with rank-2 'array'"
+          end select
+        rank(3)
+          select rank(mold)
+            rank(3)
+              if (.not. allocated(array)) then
+                allocate(array, mold=mold)
+              else
+                if (any(shape(array) /= shape(mold))) then
+                  deallocate(array)
+                  allocate(array, mold=mold)
+                end if
+              end if
+            rank default
+              error stop "workspace_s(allocate_if_necessary): mold-rank mismatch with rank-3 'array'"
+          end select
+        rank default
+          error stop "workspace_s(allocate_if_necessary): unsupported 'array' rank"
+      end select
+
+    end subroutine allocate_if_necessary
+
+  end procedure default_real_allocate
+
+end submodule workspace_s
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 22f505671..732b20ca0 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -13,6 +13,7 @@ module inference_engine_m
  use network_configuration_m, only : network_configuration_t
  use tensor_m, only : tensor_t
  use tensor_map_m, only : tensor_map_t
+ use trainable_network_m, only : trainable_network_t
  use trainable_engine_m, only : trainable_engine_t
  use training_configuration_m, only : training_configuration_t
  use ubounds_m, only : ubounds_t

From 5ef6625dc3cb747f7e96394fd91bdacb4e2a39ae Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 13 Oct 2024 20:43:20 -0700
Subject: [PATCH 083/105] feat(sat-mix-rat):adjust to use trainale_network_t

---
 example/learn-saturated-mixing-ratio.F90 | 45 +++++++++++-------------
 1 file changed, 21 insertions(+), 24 deletions(-)

diff --git a/example/learn-saturated-mixing-ratio.F90 b/example/learn-saturated-mixing-ratio.F90
index ba8bd599c..641f33f3e 100644
--- a/example/learn-saturated-mixing-ratio.F90
+++ b/example/learn-saturated-mixing-ratio.F90
@@ -3,7 +3,7 @@
 program train_saturated_mixture_ratio
   !! This program trains a neural network to learn the saturated mixing ratio function of ICAR.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t, csv
   use assert_m, only : assert, intrinsic_array_t
   use saturated_mixing_ratio_m, only : y, T, p
@@ -30,29 +30,29 @@ program train_saturated_mixture_ratio
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:), desired_outputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
     integer io_status, network_unit, plot_unit
-    integer, parameter :: io_success=0, diagnostics_print_interval = 1000, network_save_interval = 10000
+    integer, parameter :: io_success=0, diagnostics_print_interval = 100, network_save_interval = 1000
     integer, parameter :: nodes_per_layer(*) = [2, 4, 72, 2, 1]
-    real, parameter :: cost_tolerance = 1.E-08
+    real, parameter :: cost_tolerance = 1.E-06
 
     call random_init(image_distinct=.true., repeatable=.true.)
     open(newunit=network_unit, file=network_file%string(), form='formatted', status='old', iostat=io_status, action='read')
 
     if (io_status == io_success) then
       print *,"Reading network from file " // network_file%string()
-      trainable_engine = trainable_engine_t(inference_engine_t(file_t(network_file)))
+      trainable_network = trainable_network_t(inference_engine_t(file_t(network_file)))
       close(network_unit)
     else
       close(network_unit)
       print *,"Initializing a new network"
-      trainable_engine = perturbed_identity_network(perturbation_magnitude=0.05, n = nodes_per_layer)
+      trainable_network = perturbed_identity_network(perturbation_magnitude=0.05, n = nodes_per_layer)
     end if
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
+    call output(trainable_network, string_t("initial-network.json"))
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
       block
         integer i, j
@@ -84,7 +84,7 @@ program train_saturated_mixture_ratio
           call random_number(random_numbers)
           call shuffle(input_output_pairs)
           mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+          call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
           call system_clock(counter_end, clock_rate)
 
           associate( &
@@ -95,7 +95,7 @@ program train_saturated_mixture_ratio
             write_and_exit_if_converged: &
             if (cost_avg < cost_tolerance) then
               call print_diagnostics(plot_unit, e, cost_avg, cumulative_clock_time, nodes_per_layer)
-              call output(trainable_engine%to_inference_engine(), network_file)
+              call output(trainable_network, network_file)
               exit
             end if write_and_exit_if_converged
 
@@ -104,13 +104,13 @@ program train_saturated_mixture_ratio
             write_and_exit_if_stop_file_exists: &
             if (io_status==0) then
               call print_diagnostics(plot_unit, e, cost_avg, cumulative_clock_time, nodes_per_layer)
-              call output(trainable_engine%to_inference_engine(), network_file)
+              call output(trainable_network, network_file)
               exit
             end if write_and_exit_if_stop_file_exists
 
             if (mod(e,diagnostics_print_interval)==0 .or. loop_ending) &
               call print_diagnostics(plot_unit, e, cost_avg, cumulative_clock_time, nodes_per_layer)
-            if (mod(e,network_save_interval)==0 .or. loop_ending) call output(trainable_engine%to_inference_engine(), network_file)
+            if (mod(e,network_save_interval)==0 .or. loop_ending) call output(trainable_network, network_file)
           end associate
         end do
 
@@ -121,9 +121,9 @@ program train_saturated_mixture_ratio
           integer p
 #if defined _CRAYFTN || __GFORTRAN__
           type(tensor_t), allocatable :: network_outputs(:)
-          network_outputs = trainable_engine%infer(inputs)
+          network_outputs = trainable_network%infer(inputs)
 #else
-          associate(network_outputs => trainable_engine%infer(inputs))
+          associate(network_outputs => trainable_network%infer(inputs))
 #endif
             print *,"Inputs (normalized)          | Outputs      | Desired outputs"
             do p = 1, num_pairs
@@ -139,7 +139,7 @@ program train_saturated_mixture_ratio
 
    end associate
 
-   call output(trainable_engine%to_inference_engine(), network_file)
+   call output(trainable_network, network_file)
 
   end block
 
@@ -156,7 +156,7 @@ subroutine print_diagnostics(plot_file_unit, epoch, cost, clock, nodes)
   end subroutine
 
   subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
+    class(inference_engine_t), intent(in) :: inference_engine
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
     json_file = inference_engine%to_json()
@@ -170,8 +170,8 @@ pure function e(j,n) result(unit_vector)
     unit_vector = real([(merge(1,0,j==k),k=1,n)])
   end function
 
-  function perturbed_identity_network(perturbation_magnitude, n) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude, n) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, intent(in)  :: n(:)
     integer k, l
@@ -187,12 +187,9 @@ function perturbed_identity_network(perturbation_magnitude, n) result(trainable_
       call random_number(b_harvest)
 
       associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
-
-        trainable_engine = trainable_engine_t( &
-          nodes = n, weights = w, biases = b, metadata = &
-            [string_t("Saturated Mixing Ratio"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), &
-             string_t("false")] &
-        )
+        trainable_network = trainable_network_t( inference_engine_t(nodes=n, weights=w, biases=b, &
+          metadata=[string_t("Saturated Mixing Ratio"),string_t("Rouson"),string_t("20241013"),string_t("relu"),string_t("false")] &
+        ))
       end associate
     end associate
   end function

From d45c57eebf4ff5f1b3ff8716b0e528da04af11a8 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 13 Oct 2024 20:56:46 -0700
Subject: [PATCH 084/105] fix(activation_t):add :: in default initialization

---
 src/inference_engine/activation_m.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/inference_engine/activation_m.f90 b/src/inference_engine/activation_m.f90
index 3566fbff8..966f6beea 100644
--- a/src/inference_engine/activation_m.f90
+++ b/src/inference_engine/activation_m.f90
@@ -15,7 +15,7 @@ module activation_m
 
   type activation_t
     private
-    integer(c_int) selection_ = sigmoid
+    integer(c_int) :: selection_ = sigmoid
   contains
     procedure, non_overridable :: function_name
     generic :: operator(==) => equals

From 4c76b7a85a94f3dc031ad1f80f2236963dbc908c Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 13 Oct 2024 21:50:51 -0700
Subject: [PATCH 085/105] fix(unmapped_network): rm ambiguous specific proc

also adjust demo/app/infer-aerosol.f90 to reflect the
renaming of unmapped_engine_t to unmapped_network_t
---
 demo/app/infer-aerosol.f90                   |   6 +-
 src/inference_engine/inference_engine_m_.f90 | 158 +++++++++++--------
 src/inference_engine/inference_engine_s.F90  | 142 ++++-------------
 src/inference_engine/unmapped_network_s.f90  |  88 +++++++++++
 src/inference_engine_m.f90                   |   2 +-
 5 files changed, 216 insertions(+), 180 deletions(-)
 create mode 100644 src/inference_engine/unmapped_network_s.f90

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index 6810af0cc..d2211f2e9 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -7,7 +7,7 @@ program infer_aerosol
   use iso_fortran_env, only : int64, real64
 
   ! External dependencies:
-  use inference_engine_m, only : unmapped_engine_t, tensor_t, double_precision, double_precision_file_t
+  use inference_engine_m, only : unmapped_network_t, tensor_t, double_precision, double_precision_file_t
   use julienne_m, only : string_t, command_line_t
   use omp_lib
 
@@ -49,7 +49,7 @@ subroutine read_stats_and_perform_inference(path)
     double precision cube_root
     double precision, allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
     type(tensor_statistics_t) input_stats, output_stats
-    type(unmapped_engine_t(double_precision)) inference_engine
+    type(unmapped_network_t(double_precision)) inference_engine
     integer i, j
 
     input_stats  = read_tensor_statistics(path // "meanxp.txt", path // "stdxp.txt", num_inputs)  !for pre-processing normalization
@@ -76,7 +76,7 @@ subroutine read_stats_and_perform_inference(path)
     !$omp end parallel do   
       
     print *, "Reading the neural network from " // network_file_name
-    inference_engine = unmapped_engine_t(double_precision_file_t(path // network_file_name))
+    inference_engine = unmapped_network_t(double_precision_file_t(path // network_file_name))
 
     time_inference: &
     block
diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90
index a196fd6ee..40121b4cf 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/inference_engine/inference_engine_m_.f90
@@ -14,7 +14,7 @@ module inference_engine_m_
 
   private
   public :: inference_engine_t
-  public :: unmapped_engine_t
+  public :: unmapped_network_t
   public :: exchange_t
   public :: workspace_t
 
@@ -41,6 +41,8 @@ module inference_engine_m_
     generic :: activation_function_name => default_real_activation_name,         double_precision_activation_name
     generic :: to_exchange              => default_real_to_exchange,             double_precision_to_exchange
     generic :: learn                    => default_real_learn
+    generic :: assert_consistency       => default_real_consistency,             double_precision_consistency
+    procedure, private, non_overridable :: default_real_consistency,             double_precision_consistency
     procedure, private, non_overridable :: default_real_approximately_equal,     double_precision_approximately_equal
     procedure, private, non_overridable :: default_real_infer,                   double_precision_infer
     procedure, private, non_overridable :: default_real_learn
@@ -69,11 +71,31 @@ module inference_engine_m_
     procedure, non_overridable, private :: default_real_allocate
   end type
 
-  type, extends(inference_engine_t) :: unmapped_engine_t
-  contains
-    generic :: infer =>   default_real_infer_unmapped, double_precision_infer_unmapped
-    procedure, private :: default_real_infer_unmapped, double_precision_infer_unmapped
-  end type
+  interface workspace_t
+
+    pure module function default_real_workspace(inference_engine) result(workspace)
+      implicit none
+      type(inference_engine_t), intent(in) :: inference_engine
+      type(workspace_t) workspace
+    end function
+
+  end interface
+
+  interface
+
+    module subroutine default_real_allocate(self, inference_engine)
+      implicit none
+      class(workspace_t), intent(inout) :: self
+      type(inference_engine_t), intent(in) :: inference_engine
+    end subroutine
+
+    pure module function default_real_allocated(self) result(all_allocated)
+      implicit none
+      class(workspace_t), intent(in) :: self
+      logical all_allocated
+    end function
+
+  end interface
 
   type exchange_t(k)
     integer, kind :: k = default_real
@@ -84,6 +106,22 @@ module inference_engine_m_
     type(activation_t) activation_
   end type
 
+  interface
+
+    module function default_real_to_exchange(self) result(exchange)
+      implicit none
+      class(inference_engine_t), intent(in) :: self
+      type(exchange_t) exchange
+    end function
+
+    module function double_precision_to_exchange(self) result(exchange)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+      type(exchange_t(double_precision)) exchange
+    end function
+
+  end interface
+
   interface inference_engine_t
 
     module function default_real_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
@@ -120,42 +158,7 @@ impure elemental module function double_precision_from_json(file) result(inferen
 
   end interface
 
-  interface unmapped_engine_t
-
-    impure elemental module function double_precision_unmapped_from_json(file) result(unmapped_engine)
-      implicit none
-      type(double_precision_file_t), intent(in) :: file
-      type(unmapped_engine_t(double_precision)) unmapped_engine
-    end function
-
-  end interface
-
-
-  interface workspace_t
-
-    pure module function default_real_workspace(inference_engine) result(workspace)
-      implicit none
-      type(inference_engine_t), intent(in) :: inference_engine
-      type(workspace_t) workspace
-    end function
-
-  end interface
-
-  interface
-
-    module subroutine default_real_allocate(self, inference_engine)
-      implicit none
-      class(workspace_t), intent(inout) :: self
-      type(inference_engine_t), intent(in) :: inference_engine
-    end subroutine
-
-    pure module function default_real_allocated(self) result(all_allocated)
-      implicit none
-      class(workspace_t), intent(in) :: self
-      logical all_allocated
-    end function
 
-  end interface
 
 
   interface ! inference_engine_t type-bound procedures
@@ -206,18 +209,6 @@ elemental module function double_precision_map_from_output_range(self, normalize
       type(tensor_t(double_precision)) tensor
     end function
 
-    module function default_real_to_exchange(self) result(exchange)
-      implicit none
-      class(inference_engine_t), intent(in) :: self
-      type(exchange_t) exchange
-    end function
-
-    module function double_precision_to_exchange(self) result(exchange)
-      implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
-      type(exchange_t(double_precision)) exchange
-    end function
-
     impure elemental module function default_real_to_json(self) result(json_file)
       implicit none
       class(inference_engine_t), intent(in) :: self
@@ -249,20 +240,6 @@ elemental module function default_real_infer(self, inputs) result(outputs)
       type(tensor_t) outputs
     end function
 
-    elemental module function default_real_infer_unmapped(self, inputs) result(outputs)
-      implicit none
-      class(unmapped_engine_t), intent(in) :: self
-      type(tensor_t), intent(in) :: inputs
-      type(tensor_t) outputs
-    end function
-
-    elemental module function double_precision_infer_unmapped(self, inputs) result(outputs)
-      implicit none
-      class(unmapped_engine_t(double_precision)), intent(in) :: self
-      type(tensor_t(double_precision)), intent(in) :: inputs
-      type(tensor_t(double_precision)) outputs
-    end function
-
     elemental module function double_precision_infer(self, inputs) result(outputs)
       implicit none
       class(inference_engine_t(double_precision)), intent(in) :: self
@@ -352,6 +329,53 @@ pure module subroutine default_real_learn(self, mini_batches_arr, cost, adam, le
       type(workspace_t), intent(inout) :: workspace
     end subroutine
 
+    pure module subroutine default_real_consistency(self)
+      implicit none
+      class(inference_engine_t), intent(in) :: self
+    end subroutine
+
+    pure module subroutine double_precision_consistency(self)
+      implicit none
+      class(inference_engine_t(double_precision)), intent(in) :: self
+    end subroutine
+
+  end interface
+
+  type unmapped_network_t(k)
+    integer, kind :: k = default_real
+    private
+    type(inference_engine_t(k)) inference_engine_
+  contains
+    generic :: infer                    => default_real_infer_unmapped, double_precision_infer_unmapped
+    procedure, private, non_overridable :: default_real_infer_unmapped, double_precision_infer_unmapped
+  end type
+
+  interface unmapped_network_t
+
+    impure elemental module function double_precision_unmapped_from_json(file) result(unmapped_network)
+      implicit none
+      type(double_precision_file_t), intent(in) :: file
+      type(unmapped_network_t(double_precision)) unmapped_network
+    end function
+
+  end interface
+
+  interface
+
+    elemental module function default_real_infer_unmapped(self, inputs) result(outputs)
+      implicit none
+      class(unmapped_network_t), intent(in) :: self
+      type(tensor_t), intent(in) :: inputs
+      type(tensor_t) outputs
+    end function
+
+    elemental module function double_precision_infer_unmapped(self, inputs) result(outputs)
+      implicit none
+      class(unmapped_network_t(double_precision)), intent(in) :: self
+      type(tensor_t(double_precision)), intent(in) :: inputs
+      type(tensor_t(double_precision)) outputs
+    end function
+
   end interface
 
 end module inference_engine_m_
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90
index ce8b5c7ea..ac31642f6 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/inference_engine/inference_engine_s.F90
@@ -8,11 +8,6 @@
   use neuron_m, only : neuron_t
   implicit none
 
-  interface assert_consistency
-    procedure default_real_consistency
-    procedure double_precision_consistency
-  end interface
-
   character(len=*), parameter :: acceptable_engine_tag = "0.13.0" ! git tag capable of reading the current json file format
   integer, parameter :: input_layer = 0 
 
@@ -58,72 +53,12 @@
     exchange%activation_ = self%activation_
   end procedure
 
-  module procedure default_real_infer_unmapped
-
-    real, allocatable :: a(:,:)
-    integer l
-
-    call assert_consistency(self)
-
-    associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
-
-      allocate(a(maxval(n), input_layer:output_layer))
-
-      a(1:n(input_layer),input_layer) = inputs%values()
-
-      feed_forward: &
-      do l = input_layer+1, output_layer
-      associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
-          if (l .lt. output_layer) then
-             a(1:n(l),l) = self%activation_%evaluate(z)
-          else
-             a(1:n(l),l) = z(1:n(l))
-          end if
-        end associate
-      end do feed_forward
-
-      outputs = tensor_t(a(1:n(output_layer), output_layer))
-
-    end associate
-
-  end procedure
-
-  module procedure double_precision_infer_unmapped
-
-    double precision, allocatable :: a(:,:)
-    integer l
-
-    call assert_consistency(self)
-
-    associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
-
-      allocate(a(maxval(n), input_layer:output_layer))
-
-      a(1:n(input_layer),input_layer) = inputs%values()
-
-      feed_forward: &
-      do l = input_layer+1, output_layer
-      associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
-          if (l .lt. output_layer) then
-             a(1:n(l),l) = self%activation_%evaluate(z)
-          else
-             a(1:n(l),l) = z(1:n(l))
-          end if
-        end associate
-      end do feed_forward
-
-      outputs = tensor_t(a(1:n(output_layer), output_layer))
-
-    end associate
-
-  end procedure
-
   module procedure default_real_infer
 
     real, allocatable :: a(:,:)
     integer l
 
-    call assert_consistency(self)
+    call self%assert_consistency()
 
     associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
 
@@ -169,7 +104,7 @@
     double precision, allocatable :: a(:,:)
     integer l
 
-    call assert_consistency(self)
+    call self%assert_consistency()
 
     associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1))
 
@@ -212,52 +147,47 @@
 
   end procedure
 
-  pure subroutine default_real_consistency(self)
-
-    class(inference_engine_t), intent(in) :: self
+  module procedure default_real_consistency
 
     associate( &
       all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
-      call assert(all(all_allocated),"inference_engine_s(inference_engine_consistency): fully_allocated", &
+      call assert(all(all_allocated),"inference_engine_s(default_real_consistency): fully_allocated", &
         intrinsic_array_t(all_allocated))
     end associate
 
     associate(max_width=>maxval(self%nodes_), component_dims=>[size(self%biases_,1), size(self%weights_,1), size(self%weights_,2)])
-      call assert(all(component_dims == max_width), "inference_engine_s(inference_engine_consistency): conformable arrays", &
+      call assert(all(component_dims == max_width), "inference_engine_s(default_real_consistency): conformable arrays", &
         intrinsic_array_t([max_width,component_dims]))
     end associate
 
     associate(input_subscript => lbound(self%nodes_,1))
-      call assert(input_subscript == input_layer, "inference_engine_s(inference_engine_consistency): n base subsscript", &
+      call assert(input_subscript == input_layer, "inference_engine_s(default_real_consistency): n base subsscript", &
         input_subscript)
     end associate
 
-  end subroutine
-
-  pure subroutine double_precision_consistency(self)
-
-    class(inference_engine_t(double_precision)), intent(in) :: self
+  end procedure
 
+  module procedure double_precision_consistency
 
     associate( &
       all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
-      call assert(all(all_allocated),"inference_engine_s(inference_engine_consistency): fully_allocated", &
+      call assert(all(all_allocated),"inference_engine_s(default_real_consistency): fully_allocated", &
         intrinsic_array_t(all_allocated))
     end associate
 
     associate(max_width=>maxval(self%nodes_), component_dims=>[size(self%biases_,1), size(self%weights_,1), size(self%weights_,2)])
-      call assert(all(component_dims == max_width), "inference_engine_s(inference_engine_consistency): conformable arrays", &
+      call assert(all(component_dims == max_width), "inference_engine_s(default_real_consistency): conformable arrays", &
         intrinsic_array_t([max_width,component_dims]))
     end associate
 
     associate(input_subscript => lbound(self%nodes_,1))
-      call assert(input_subscript == input_layer, "inference_engine_s(inference_engine_consistency): n base subsscript", &
+      call assert(input_subscript == input_layer, "inference_engine_s(default_real_consistency): n base subsscript", &
         input_subscript)
     end associate
 
-  end subroutine
+  end procedure
 
   module procedure default_real_construct_from_components
 
@@ -292,7 +222,7 @@ pure subroutine double_precision_consistency(self)
 
     inference_engine%activation_ = activation_t(metadata(4)%string())
 
-    call assert_consistency(inference_engine)
+    call inference_engine%assert_consistency()
 
   end procedure default_real_construct_from_components
 
@@ -331,7 +261,7 @@ pure subroutine double_precision_consistency(self)
       inference_engine%activation_ = activation_t(function_name%string())
     end associate
 
-    call assert_consistency(inference_engine)
+    call inference_engine%assert_consistency()
 
   end procedure double_precision_construct_from_components
 
@@ -346,7 +276,7 @@ pure subroutine double_precision_consistency(self)
     proto_neuron = neuron_t([0.],1.)
 #endif
 
-    call assert_consistency(self)
+    call self%assert_consistency()
 
     associate( &
        num_hidden_layers => self%num_hidden_layers() &
@@ -462,7 +392,7 @@ pure subroutine double_precision_consistency(self)
     proto_neuron = neuron_t([0D0],1D0)
 #endif
 
-    call assert_consistency(self)
+    call self%assert_consistency()
 
     associate( &
        num_hidden_layers => self%num_hidden_layers() &
@@ -665,14 +595,10 @@ pure subroutine double_precision_consistency(self)
       end associate
     end associate read_metadata
 
-    call assert_consistency(inference_engine)
+    call inference_engine%assert_consistency()
 
   end procedure default_real_from_json
 
-  module procedure double_precision_unmapped_from_json
-    unmapped_engine%inference_engine_t = double_precision_from_json(file)
-  end procedure
-
   module procedure double_precision_from_json
 
     character(len=:), allocatable :: justified_line
@@ -769,14 +695,13 @@ pure subroutine double_precision_consistency(self)
       end associate
     end associate read_metadata
 
-    call assert_consistency(inference_engine)
+    call inference_engine%assert_consistency()
 
   end procedure double_precision_from_json
 
   module procedure default_real_assert_conformable_with
 
-    call assert_consistency(self)
-    call assert_consistency(inference_engine)
+    call self%assert_consistency()
 
     associate(equal_shapes => [ &
       shape(self%weights_) == shape(inference_engine%weights_), &
@@ -792,8 +717,7 @@ pure subroutine double_precision_consistency(self)
 
   module procedure double_precision_assert_conformable_with
 
-    call assert_consistency(self)
-    call assert_consistency(inference_engine)
+    call self%assert_consistency()
 
     associate(equal_shapes => [ &
       shape(self%weights_) == shape(inference_engine%weights_), &
@@ -813,8 +737,8 @@ pure subroutine double_precision_consistency(self)
 
     nodes_eq = all(lhs%nodes_ == rhs%nodes_)
 
-    call assert_consistency(lhs)
-    call assert_consistency(rhs)
+    call lhs%assert_consistency()
+    call rhs%assert_consistency()
     call lhs%assert_conformable_with(rhs)
 
     block
@@ -854,8 +778,8 @@ pure subroutine double_precision_consistency(self)
 
     nodes_eq = all(lhs%nodes_ == rhs%nodes_)
 
-    call assert_consistency(lhs)
-    call assert_consistency(rhs)
+    call lhs%assert_consistency()
+    call rhs%assert_consistency()
     call lhs%assert_conformable_with(rhs)
 
     block
@@ -890,18 +814,18 @@ pure subroutine double_precision_consistency(self)
   end procedure
 
   module procedure default_real_num_outputs
-    call assert_consistency(self)
+    call self%assert_consistency()
     output_count = self%nodes_(ubound(self%nodes_,1))
   end procedure
 
   module procedure double_precision_num_outputs
-    call assert_consistency(self)
+    call self%assert_consistency()
     output_count = self%nodes_(ubound(self%nodes_,1))
   end procedure
 
   module procedure default_real_num_hidden_layers
     integer, parameter :: num_non_hidden_layers = 2
-    call assert_consistency(self)
+    call self%assert_consistency()
     associate(num_layers => size(self%nodes_))
       hidden_layer_count =  num_layers - num_non_hidden_layers
     end associate
@@ -909,29 +833,29 @@ pure subroutine double_precision_consistency(self)
 
   module procedure double_precision_num_hidden_layers
     integer, parameter :: num_non_hidden_layers = 2
-    call assert_consistency(self)
+    call self%assert_consistency()
     associate(num_layers => size(self%nodes_))
       hidden_layer_count =  num_layers - num_non_hidden_layers
     end associate
   end procedure
 
   module procedure default_real_num_inputs
-    call assert_consistency(self)
+    call self%assert_consistency()
     input_count = self%nodes_(lbound(self%nodes_,1))
   end procedure
 
   module procedure double_precision_num_inputs
-    call assert_consistency(self)
+    call self%assert_consistency()
     input_count = self%nodes_(lbound(self%nodes_,1))
   end procedure
 
   module procedure default_real_nodes_per_layer
-    call assert_consistency(self)
+    call self%assert_consistency()
     node_count = self%nodes_
   end procedure
 
   module procedure double_precision_nodes_per_layer
-    call assert_consistency(self)
+    call self%assert_consistency()
     node_count = self%nodes_
   end procedure
 
@@ -963,7 +887,7 @@ pure subroutine double_precision_consistency(self)
     integer l, batch, mini_batch_size, pair
     type(tensor_t), allocatable :: inputs(:), expected_outputs(:)
 
-    call assert_consistency(self)
+    call self%assert_consistency()
     call assert(workspace%fully_allocated(), "inference_engine_s(default_real_learn): workspace%fully_allocated()")
 
     associate(output_layer => ubound(self%nodes_,1))
diff --git a/src/inference_engine/unmapped_network_s.f90 b/src/inference_engine/unmapped_network_s.f90
new file mode 100644
index 000000000..9ced289e5
--- /dev/null
+++ b/src/inference_engine/unmapped_network_s.f90
@@ -0,0 +1,88 @@
+! Copyright (c), The Regents of the University of California
+! Terms of use are as specified in LICENSE.txt
+submodule(inference_engine_m_) unmapped_network_s
+  implicit none
+
+  integer, parameter :: input_layer = 0 
+
+contains
+
+  module procedure double_precision_unmapped_from_json
+    unmapped_network%inference_engine_ = double_precision_from_json(file)
+  end procedure
+
+  module procedure default_real_infer_unmapped
+
+    real, allocatable :: a(:,:)
+    integer l
+
+    associate(inference_engine => self%inference_engine_)
+
+      call inference_engine%assert_consistency()
+
+      associate( &
+        w => inference_engine%weights_ &
+       ,b => inference_engine%biases_ &
+       ,n => inference_engine%nodes_ &
+       ,output_layer => ubound(inference_engine%nodes_,1) &
+      )
+        allocate(a(maxval(n), input_layer:output_layer))
+
+        a(1:n(input_layer),input_layer) = inputs%values()
+
+        feed_forward: &
+        do l = input_layer+1, output_layer
+          associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
+            if (l .lt. output_layer) then
+               a(1:n(l),l) = inference_engine%activation_%evaluate(z)
+            else
+               a(1:n(l),l) = z(1:n(l))
+            end if
+          end associate
+        end do feed_forward
+
+        outputs = tensor_t(a(1:n(output_layer), output_layer))
+
+      end associate
+    end associate
+
+  end procedure
+
+  module procedure double_precision_infer_unmapped
+
+    double precision, allocatable :: a(:,:)
+    integer l
+
+    associate(inference_engine => self%inference_engine_)
+
+      call inference_engine%assert_consistency()
+
+      associate( &
+        w => inference_engine%weights_ &
+       ,b => inference_engine%biases_ &
+       ,n => inference_engine%nodes_ &
+       ,output_layer => ubound(inference_engine%nodes_,1) &
+      )
+
+        allocate(a(maxval(n), input_layer:output_layer))
+
+        a(1:n(input_layer),input_layer) = inputs%values()
+
+        feed_forward: &
+        do l = input_layer+1, output_layer
+          associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
+            if (l .lt. output_layer) then
+               a(1:n(l),l) = inference_engine%activation_%evaluate(z)
+            else
+               a(1:n(l),l) = z(1:n(l))
+            end if
+          end associate
+        end do feed_forward
+
+        outputs = tensor_t(a(1:n(output_layer), output_layer))
+      end associate
+    end associate
+
+  end procedure
+
+end submodule unmapped_network_s
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 732b20ca0..5b6820d1e 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -6,7 +6,7 @@ module inference_engine_m
  use double_precision_string_m, only : double_precision_string_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
- use inference_engine_m_, only : inference_engine_t, unmapped_engine_t
+ use inference_engine_m_, only : inference_engine_t, unmapped_network_t
  use kind_parameters_m, only : default_real, double_precision
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t

From e4f8ba9c8784c373d98529b43c2966491c085582 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Sun, 13 Oct 2024 23:35:30 -0700
Subject: [PATCH 086/105] chore(trainable_network):replaces trainable_engine

---
 demo/app/train-cloud-microphysics.F90         |  20 +-
 example/learn-addition.F90                    |  29 +-
 example/learn-exponentiation.F90              |  29 +-
 example/learn-microphysics-procedures.F90     | 236 ----------
 example/learn-multiplication.F90              |  29 +-
 example/learn-power-series.F90                |  29 +-
 example/learn-saturated-mixing-ratio.F90      |   4 +-
 example/supporting-modules/mp_thompson.f90    | 102 -----
 .../supporting-modules/thompson_tensors_m.f90 |  33 --
 example/train-and-write.F90                   |  39 +-
 src/inference_engine/tmp                      |   0
 src/inference_engine/trainable_engine_m.F90   | 179 --------
 src/inference_engine/trainable_engine_s.F90   | 416 ------------------
 src/inference_engine/trainable_network_m.f90  |  29 +-
 src/inference_engine/trainable_network_s.f90  |  48 +-
 src/inference_engine_m.f90                    |   1 -
 test/main.F90                                 |   6 +-
 ...est_m.F90 => trainable_network_test_m.F90} | 106 ++---
 18 files changed, 216 insertions(+), 1119 deletions(-)
 delete mode 100644 example/learn-microphysics-procedures.F90
 delete mode 100644 example/supporting-modules/mp_thompson.f90
 delete mode 100644 example/supporting-modules/thompson_tensors_m.f90
 create mode 100644 src/inference_engine/tmp
 delete mode 100644 src/inference_engine/trainable_engine_m.F90
 delete mode 100644 src/inference_engine/trainable_engine_s.F90
 rename test/{trainable_engine_test_m.F90 => trainable_network_test_m.F90} (86%)

diff --git a/demo/app/train-cloud-microphysics.F90 b/demo/app/train-cloud-microphysics.F90
index fcb952056..e96038f5d 100644
--- a/demo/app/train-cloud-microphysics.F90
+++ b/demo/app/train-cloud-microphysics.F90
@@ -13,7 +13,7 @@ program train_on_flat_distribution
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use inference_engine_m, only : &
-    inference_engine_t, mini_batch_t, input_output_pair_t, tensor_t, trainable_engine_t, tensor_map_t, &
+    inference_engine_t, mini_batch_t, input_output_pair_t, tensor_t, trainable_network_t, tensor_map_t, &
     training_configuration_t, shuffle
 
   !! Internal dependencies:
@@ -271,7 +271,7 @@ subroutine read_train_write(training_configuration, args, plot_file)
 
       train_network: &
       block
-        type(trainable_engine_t) trainable_engine
+        type(trainable_network_t) trainable_network
         type(mini_batch_t), allocatable :: mini_batches(:)
         type(bin_t), allocatable :: bins(:)
         type(input_output_pair_t), allocatable :: input_output_pairs(:)
@@ -319,7 +319,7 @@ subroutine read_train_write(training_configuration, args, plot_file)
             read_or_initialize_engine: &
             if (io_status==0) then
               print *,"Reading network from file " // network_file
-              trainable_engine = trainable_engine_t(inference_engine_t(file_t(string_t(network_file))))
+              trainable_network = trainable_network_t(inference_engine_t(file_t(string_t(network_file))))
               close(network_unit)
             else
               close(network_unit)
@@ -339,9 +339,9 @@ subroutine read_train_write(training_configuration, args, plot_file)
                     maxima = [maxval(pressure_in), maxval(potential_temperature_in), maxval(temperature_in), &
                       maxval(qv_in), maxval(qc_in), maxval(qr_in), maxval(qs_in)] &
                 ) )
-                  associate(activation => training_configuration%differentiable_activation_strategy())
+                  associate(activation => training_configuration%differentiable_activation())
                     associate(residual_network=> string_t(trim(merge("true ", "false", training_configuration%skip_connections()))))
-                      trainable_engine = trainable_engine_t( &
+                      trainable_network = trainable_network_t( &
                         training_configuration,  &
                         perturbation_magnitude = 0.05, &
                         metadata = [ &
@@ -379,7 +379,7 @@ subroutine read_train_write(training_configuration, args, plot_file)
         end associate output_extrema
 
         print *,"Normalizing the remaining input and output tensors"
-        input_output_pairs = trainable_engine%map_to_training_ranges(input_output_pairs)
+        input_output_pairs = trainable_network%map_to_training_ranges(input_output_pairs)
 
         associate( &
           num_pairs => size(input_output_pairs), &
@@ -417,7 +417,7 @@ subroutine read_train_write(training_configuration, args, plot_file)
                 if (size(bins)>1) call shuffle(input_output_pairs) ! set up for stochastic gradient descent
                 mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
 
-                call trainable_engine%train(mini_batches, cost, adam, learning_rate)
+                call trainable_network%train(mini_batches, cost, adam, learning_rate)
 
                 associate(average_cost => sum(cost)/size(cost))
                   associate(converged => average_cost <= args%cost_tolerance)
@@ -432,10 +432,8 @@ subroutine read_train_write(training_configuration, args, plot_file)
                         integer net_unit
 
                         open(newunit=net_unit, file=network_file, form='formatted', status='unknown', iostat=io_status, action='write')
-                        associate(inference_engine => trainable_engine%to_inference_engine())
-                          associate(json_file => inference_engine%to_json())
-                            call json_file%write_lines(string_t(network_file))
-                          end associate
+                        associate(json_file => trainable_network%to_json())
+                          call json_file%write_lines(string_t(network_file))
                         end associate
                         close(net_unit)
                       end block
diff --git a/example/learn-addition.F90 b/example/learn-addition.F90
index 0e2efdf3f..b6174650f 100644
--- a/example/learn-addition.F90
+++ b/example/learn-addition.F90
@@ -21,7 +21,7 @@ elemental function y(x_tensor) result(a_tensor)
 program learn_addition
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use addition_m, only : y
@@ -43,15 +43,15 @@ program learn_addition
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:), desired_outputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
 
     call random_init(image_distinct=.true., repeatable=.true.)
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.05)
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.05)
+    call output(trainable_network, string_t("initial-network.json"))
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
       block
         integer i, j
@@ -76,19 +76,18 @@ program learn_addition
           call random_number(random_numbers)
           call shuffle(input_output_pairs)
           mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+          call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
           print *,sum(cost)/size(cost)
         end do
       end block
 
       block
-        real, parameter :: tolerance = 1.E-06
         integer p
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
           print "(a,69x,a)","  Outputs", "| Desired outputs"
           do p = 1, num_pairs
@@ -102,14 +101,14 @@ program learn_addition
 
    end associate
 
-   call output(trainable_engine%to_inference_engine(), final_network_file)
+   call output(trainable_network, final_network_file)
 
   end block
 
 contains
 
   subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
+    class(inference_engine_t), intent(in) :: inference_engine
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
     json_file = inference_engine%to_json()
@@ -123,8 +122,8 @@ pure function e(j,n) result(unit_vector)
     unit_vector = real([(merge(1,0,j==k),k=1,n)])
   end function
 
-  function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, parameter :: n(*) = [8, 64, 64, 64, 6]
     integer, parameter :: n_max = maxval(n), layers = size(n)
@@ -141,10 +140,10 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_engine = trainable_engine_t( &
+      trainable_network = trainable_network_t( inference_engine_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
-      )
+      ))
 
     end associate
   end function
diff --git a/example/learn-exponentiation.F90 b/example/learn-exponentiation.F90
index 6b8ba0624..a09b9359d 100644
--- a/example/learn-exponentiation.F90
+++ b/example/learn-exponentiation.F90
@@ -21,7 +21,7 @@ elemental function y(x_tensor) result(a_tensor)
 program learn_exponentiation
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use exponentiation_m, only : y
@@ -43,15 +43,15 @@ program learn_exponentiation
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:), desired_outputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
 
     call random_init(image_distinct=.true., repeatable=.true.)
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.05)
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.05)
+    call output(trainable_network, string_t("initial-network.json"))
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
       block
         integer i, j
@@ -76,19 +76,18 @@ program learn_exponentiation
           call random_number(random_numbers)
           call shuffle(input_output_pairs)
           mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+          call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
           print *,sum(cost)/size(cost)
         end do
       end block
 
       block
-        real, parameter :: tolerance = 1.E-06
         integer p
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
           print "(a,69x,a)","  Outputs", "| Desired outputs"
           do p = 1, num_pairs
@@ -102,14 +101,14 @@ program learn_exponentiation
 
    end associate
 
-   call output(trainable_engine%to_inference_engine(), final_network_file)
+   call output(trainable_network, final_network_file)
 
   end block
 
 contains
 
   subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
+    class(inference_engine_t), intent(in) :: inference_engine
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
     json_file = inference_engine%to_json()
@@ -123,8 +122,8 @@ pure function e(j,n) result(unit_vector)
     unit_vector = real([(merge(1,0,j==k),k=1,n)])
   end function
 
-  function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, parameter :: n(*) = [8, 64, 64, 64, 6] ! nodes per layer (first layer = input, last layer = output)
     integer, parameter :: n_max = maxval(n), layers = size(n)
@@ -141,10 +140,10 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_engine = trainable_engine_t( &
+      trainable_network = trainable_network_t( inference_engine_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
-      )
+      ))
 
     end associate
   end function
diff --git a/example/learn-microphysics-procedures.F90 b/example/learn-microphysics-procedures.F90
deleted file mode 100644
index 552bd9ac0..000000000
--- a/example/learn-microphysics-procedures.F90
+++ /dev/null
@@ -1,236 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-program learn_microphysics_procedures
-  !! Train a neural network proxies for procedures in the Thompson microphysics model
-  !! in of ICAR (https://github.com/BerkeleyLab/icar).
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
-  use julienne_m, only : string_t, file_t, command_line_t, bin_t, csv
-  use assert_m, only : assert, intrinsic_array_t
-  use thompson_tensors_m, only : y, T, p
-  use iso_fortran_env, only : int64, output_unit
-  implicit none
-
-  type(string_t) network_file
-  type(command_line_t) command_line
-  integer(int64) counter_start, counter_end, clock_rate
-
-  network_file = string_t(command_line%flag_value("--output-file"))
-
-  if (len(network_file%string())==0) then
-    error stop new_line('a') // new_line('a') // &
-      'Usage: fpm run learn-microphysics-procedures --profile release --flag "-fopenmp" -- --output-file "<file-name>"'
-  end if
-
-  call system_clock(counter_start, clock_rate)
-
-  block
-    integer, parameter :: max_num_epochs = 10000000, num_mini_batches = 10
-    integer num_pairs ! number of input/output pairs
-
-    type(mini_batch_t), allocatable :: mini_batches(:)
-    type(input_output_pair_t), allocatable :: input_output_pairs(:)
-    type(tensor_t), allocatable :: inputs(:), desired_outputs(:)
-    type(trainable_engine_t)  trainable_engine
-    type(bin_t), allocatable :: bins(:)
-    real, allocatable :: cost(:), random_numbers(:)
-    integer io_status, network_unit, plot_unit
-    integer, parameter :: io_success=0, diagnostics_print_interval = 1000, network_save_interval = 10000
-    integer, parameter :: nodes_per_layer(*) = [2, 72, 2]
-    real, parameter :: cost_tolerance = 1.E-08
-
-    call random_init(image_distinct=.true., repeatable=.true.)
-    open(newunit=network_unit, file=network_file%string(), form='formatted', status='old', iostat=io_status, action='read')
-
-    if (io_status == io_success) then
-      print *,"Reading network from file " // network_file%string()
-      trainable_engine = trainable_engine_t(inference_engine_t(file_t(network_file)))
-      close(network_unit)
-    else
-      close(network_unit)
-      print *,"Initializing a new network"
-      trainable_engine = perturbed_identity_network(perturbation_magnitude=0.05, n = nodes_per_layer)
-    end if
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
-
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
-
-      block
-        integer i, j
-        integer, allocatable :: output_sizes(:)
-        inputs = [( [(tensor_t([T(i), p(j)]), j=1,size(p))], i = 1,size(T))]
-        num_pairs = size(inputs)
-        call assert(num_pairs == size(T)*size(p), "train_cloud_microphysics: inputs tensor array complete")
-        desired_outputs = y(inputs)
-        output_sizes = [(size(desired_outputs(i)%values()),i=1,size(desired_outputs))]
-        call assert(all([num_outputs==output_sizes]), "fit-polynomials: # outputs", intrinsic_array_t([num_outputs,output_sizes])) 
-      end block
-
-      input_output_pairs = input_output_pair_t(inputs, desired_outputs)
-
-      block 
-        integer b
-        bins = [(bin_t(num_items=num_pairs, num_bins=num_mini_batches, bin_number=b), b = 1, num_mini_batches)]
-      end block
-
-      block
-        integer e, b, stop_unit, previous_epoch
-        real previous_clock_time
-
-        call open_plot_file_for_appending("cost.plt", plot_unit, previous_epoch, previous_clock_time)
-        print *, "        Epoch | Cost Function| System_Clock | Nodes per Layer"
-        allocate(random_numbers(2:size(input_output_pairs)))
-
-        do e = previous_epoch + 1, previous_epoch + max_num_epochs
-          call random_number(random_numbers)
-          call shuffle(input_output_pairs)
-          mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
-          call system_clock(counter_end, clock_rate)
-
-          associate( &
-            cost_avg => sum(cost)/size(cost), &
-            cumulative_clock_time => previous_clock_time +  real(counter_end - counter_start) / real(clock_rate), &
-            loop_ending => e == previous_epoch + max_num_epochs &
-          )
-            write_and_exit_if_converged: &
-            if (cost_avg < cost_tolerance) then
-              call print_diagnostics(plot_unit, e, cost_avg, cumulative_clock_time, nodes_per_layer)
-              call output(trainable_engine%to_inference_engine(), network_file)
-              exit
-            end if write_and_exit_if_converged
-
-            open(newunit=stop_unit, file="stop", form='formatted', status='old', iostat=io_status)
-
-            write_and_exit_if_stop_file_exists: &
-            if (io_status==0) then
-              call print_diagnostics(plot_unit, e, cost_avg, cumulative_clock_time, nodes_per_layer)
-              call output(trainable_engine%to_inference_engine(), network_file)
-              exit
-            end if write_and_exit_if_stop_file_exists
-
-            if (mod(e,diagnostics_print_interval)==0 .or. loop_ending) &
-              call print_diagnostics(plot_unit, e, cost_avg, cumulative_clock_time, nodes_per_layer)
-            if (mod(e,network_save_interval)==0 .or. loop_ending) call output(trainable_engine%to_inference_engine(), network_file)
-          end associate
-        end do
-
-        close(plot_unit)
-
-        report_network_performance: &
-        block
-          integer p
-#if defined _CRAYFTN || __GFORTRAN__
-          type(tensor_t), allocatable :: network_outputs(:)
-          network_outputs = trainable_engine%infer(inputs)
-#else
-          associate(network_outputs => trainable_engine%infer(inputs))
-#endif
-            print *," Inputs (normalized)          | Outputs                    | Desired outputs"
-            do p = 1, num_pairs
-              print "(6(G13.5,2x))", inputs(p)%values(), network_outputs(p)%values(), desired_outputs(p)%values()
-            end do
-#if defined _CRAYFTN || __GFORTRAN__
-#else
-          end associate
-#endif
-        end block report_network_performance
-
-      end block
-
-   end associate
-
-   call output(trainable_engine%to_inference_engine(), network_file)
-
-  end block
-
-contains
-
-  subroutine print_diagnostics(plot_file_unit, epoch, cost, clock, nodes)
-     integer, intent(in) :: plot_file_unit, epoch, nodes(:)
-     real, intent(in) :: cost, clock
-
-     write(unit=output_unit, fmt='(3(g13.5,2x))', advance='no') epoch, cost, clock
-     write(unit=output_unit, fmt=csv) nodes
-     write(unit=plot_file_unit, fmt='(3(g13.5,2x))', advance='no') epoch, cost, clock
-     write(unit=plot_file_unit, fmt=csv) nodes
-  end subroutine
-
-  subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
-    type(string_t), intent(in) :: file_name
-    type(file_t) json_file
-    json_file = inference_engine%to_json()
-    call json_file%write_lines(file_name)
-  end subroutine
-
-  pure function e(j,n) result(unit_vector)
-    integer, intent(in) :: j, n
-    integer k
-    real, allocatable :: unit_vector(:)
-    unit_vector = real([(merge(1,0,j==k),k=1,n)])
-  end function
-
-  function perturbed_identity_network(perturbation_magnitude, n) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
-    real, intent(in) :: perturbation_magnitude
-    integer, intent(in)  :: n(:)
-    integer j, k, l
-    real, allocatable :: identity(:,:,:), w_harvest(:,:,:), b_harvest(:,:)
-
-    associate(n_max => maxval(n), layers => size(n))
-      identity =  reshape( [( [(e(k,n_max), k=1,n_max)], l = 1, layers-1 )], [n_max, n_max, layers-1])
-
-      allocate(w_harvest, mold = identity)
-      allocate(b_harvest(size(identity,1), size(identity,3)))
-
-      call random_number(w_harvest)
-      call random_number(b_harvest)
-
-      associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
-
-        trainable_engine = trainable_engine_t( &
-          nodes = n, weights = w, biases = b, metadata = &
-            [string_t("Thompson microphysics procedures"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("sigmoid"), &
-             string_t("false")] &
-        )
-      end associate
-    end associate
-  end function
-
-  subroutine open_plot_file_for_appending(plot_file_name, plot_unit, previous_epoch, previous_clock)
-    character(len=*), intent(in) :: plot_file_name
-    integer, intent(out) :: plot_unit, previous_epoch
-    real, intent(out) :: previous_clock
-
-    type(file_t) plot_file
-    type(string_t), allocatable :: lines(:)
-    character(len=:), allocatable :: last_line
-    integer io_status
-    integer, parameter :: io_success = 0
-    logical preexisting_plot_file
-    real cost
-
-    inquire(file=plot_file_name, exist=preexisting_plot_file)
-    open(newunit=plot_unit,file="cost.plt",status="unknown",position="append")
-
-    associate(header => "        Epoch | Cost Function| System_Clock | Nodes per Layer")
-      if (.not. preexisting_plot_file) then
-        write(plot_unit,*) header
-        previous_epoch = 0
-        previous_clock = 0
-      else
-        plot_file = file_t(string_t(plot_file_name))
-        lines = plot_file%lines()
-        last_line = lines(size(lines))%string()
-        read(last_line,*, iostat=io_status) previous_epoch, cost, previous_clock
-        if ((io_status /= io_success .and. last_line == header) .or. len(trim(last_line))==0) then
-          previous_epoch = 0
-          previous_clock = 0
-        end if
-      end if
-    end associate
-
-  end subroutine
-
-end program
diff --git a/example/learn-multiplication.F90 b/example/learn-multiplication.F90
index 797335dd9..81d72e88d 100644
--- a/example/learn-multiplication.F90
+++ b/example/learn-multiplication.F90
@@ -21,7 +21,7 @@ elemental function y(x_tensor) result(a_tensor)
 program learn_multiplication
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use multiply_inputs, only : y
@@ -43,15 +43,15 @@ program learn_multiplication
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:), desired_outputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
 
     call random_init(image_distinct=.true., repeatable=.true.)
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.05)
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.05)
+    call output(trainable_network, string_t("initial-network.json"))
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
       block
         integer i, j
@@ -76,19 +76,18 @@ program learn_multiplication
           call random_number(random_numbers)
           call shuffle(input_output_pairs)
           mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+          call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
           print *,sum(cost)/size(cost)
         end do
       end block
 
       block
-        real, parameter :: tolerance = 1.E-06
         integer p
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
           print "(a,69x,a)","  Outputs", "| Desired outputs"
           do p = 1, num_pairs
@@ -102,14 +101,14 @@ program learn_multiplication
 
    end associate
 
-   call output(trainable_engine%to_inference_engine(), final_network_file)
+   call output(trainable_network, final_network_file)
 
   end block
 
 contains
 
   subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
+    class(inference_engine_t), intent(in) :: inference_engine
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
     json_file = inference_engine%to_json()
@@ -123,8 +122,8 @@ pure function e(j,n) result(unit_vector)
     unit_vector = real([(merge(1,0,j==k),k=1,n)])
   end function
 
-  function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, parameter :: n(*) = [8, 64, 64, 64, 6]
     integer, parameter :: n_max = maxval(n), layers = size(n)
@@ -141,10 +140,10 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_engine = trainable_engine_t( &
+      trainable_network = trainable_network_t( inference_engine_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
-      )
+      ))
 
     end associate
   end function
diff --git a/example/learn-power-series.F90 b/example/learn-power-series.F90
index c0490669e..e8a46e6dc 100644
--- a/example/learn-power-series.F90
+++ b/example/learn-power-series.F90
@@ -21,7 +21,7 @@ elemental function y(x_in) result(a)
 program learn_power_series
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use power_series, only : y
@@ -43,15 +43,15 @@ program learn_power_series
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:), desired_outputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
 
     call random_init(image_distinct=.true., repeatable=.true.)
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.05)
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.05)
+    call output(trainable_network, string_t("initial-network.json"))
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
       block
         integer i, j
@@ -78,19 +78,18 @@ program learn_power_series
           call random_number(random_numbers)
           call shuffle(input_output_pairs)
           mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+          call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
           print *,sum(cost)/size(cost)
         end do
       end block
 
       block
-        real, parameter :: tolerance = 1.E-06
         integer p
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
           print "(a,69x,a)","  Outputs", "| Desired outputs"
           do p = 1, num_pairs
@@ -104,14 +103,14 @@ program learn_power_series
 
    end associate
 
-   call output(trainable_engine%to_inference_engine(), final_network_file)
+   call output(trainable_network, final_network_file)
 
   end block
 
 contains
 
   subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
+    class(inference_engine_t), intent(in) :: inference_engine
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
     json_file = inference_engine%to_json()
@@ -125,8 +124,8 @@ pure function e(j,n) result(unit_vector)
     unit_vector = real([(merge(1,0,j==k),k=1,n)])
   end function
 
-  function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, parameter :: n(*) = [8, 196, 196, 196, 196, 6]
     integer, parameter :: n_max = maxval(n), layers = size(n)
@@ -143,10 +142,10 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_engine = trainable_engine_t( &
+      trainable_network = trainable_network_t( inference_engine_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
-      )
+      ))
 
     end associate
   end function
diff --git a/example/learn-saturated-mixing-ratio.F90 b/example/learn-saturated-mixing-ratio.F90
index 641f33f3e..bf49da58c 100644
--- a/example/learn-saturated-mixing-ratio.F90
+++ b/example/learn-saturated-mixing-ratio.F90
@@ -34,9 +34,9 @@ program train_saturated_mixture_ratio
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
     integer io_status, network_unit, plot_unit
-    integer, parameter :: io_success=0, diagnostics_print_interval = 100, network_save_interval = 1000
+    integer, parameter :: io_success=0, diagnostics_print_interval = 1000, network_save_interval = 10000
     integer, parameter :: nodes_per_layer(*) = [2, 4, 72, 2, 1]
-    real, parameter :: cost_tolerance = 1.E-06
+    real, parameter :: cost_tolerance = 1.E-08
 
     call random_init(image_distinct=.true., repeatable=.true.)
     open(newunit=network_unit, file=network_file%string(), form='formatted', status='old', iostat=io_status, action='read')
diff --git a/example/supporting-modules/mp_thompson.f90 b/example/supporting-modules/mp_thompson.f90
deleted file mode 100644
index 2e765471d..000000000
--- a/example/supporting-modules/mp_thompson.f90
+++ /dev/null
@@ -1,102 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
- 
-! MIT License
-! 
-! Copyright (c) 2017 National Center for Atmospheric Research
-! 
-! Permission is hereby granted, free of charge, to any person obtaining a copy
-! of this software and associated documentation files (the "Software"), to deal
-! in the Software without restriction, including without limitation the rights
-! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-! copies of the Software, and to permit persons to whom the Software is
-! furnished to do so, subject to the following conditions:
-! 
-! The above copyright notice and this permission notice shall be included in all
-! copies or substantial portions of the Software.
-! 
-! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-! SOFTWARE.
-
-      MODULE module_mp_thompson
-
-      ! Adapted from https://github.com/BerkeleyLab/icar
-
-      IMPLICIT NONE
-
-      CONTAINS
-
-!+---+-----------------------------------------------------------------+
-! THIS FUNCTION CALCULATES THE LIQUID SATURATION VAPOR MIXING RATIO AS
-! A FUNCTION OF TEMPERATURE AND PRESSURE
-!
-      REAL FUNCTION RSLF(P,T)
-
-      IMPLICIT NONE
-      REAL, INTENT(IN):: P, T
-      REAL:: ESL,X
-      REAL, PARAMETER:: C0= .611583699E03
-      REAL, PARAMETER:: C1= .444606896E02
-      REAL, PARAMETER:: C2= .143177157E01
-      REAL, PARAMETER:: C3= .264224321E-1
-      REAL, PARAMETER:: C4= .299291081E-3
-      REAL, PARAMETER:: C5= .203154182E-5
-      REAL, PARAMETER:: C6= .702620698E-8
-      REAL, PARAMETER:: C7= .379534310E-11
-      REAL, PARAMETER:: C8=-.321582393E-13
-
-      X=MAX(-80.,T-273.16)
-
-!      ESL=612.2*EXP(17.67*X/(T-29.65))
-      ESL=C0+X*(C1+X*(C2+X*(C3+X*(C4+X*(C5+X*(C6+X*(C7+X*C8)))))))
-      RSLF=.622*ESL/(P-ESL)
-
-!    ALTERNATIVE
-!  ; Source: Murphy and Koop, Review of the vapour pressure of ice and
-!             supercooled water for atmospheric applications, Q. J. R.
-!             Meteorol. Soc (2005), 131, pp. 1539-1565.
-!    ESL = EXP(54.842763 - 6763.22 / T - 4.210 * ALOG(T) + 0.000367 * T
-!        + TANH(0.0415 * (T - 218.8)) * (53.878 - 1331.22
-!        / T - 9.44523 * ALOG(T) + 0.014025 * T))
-
-      END FUNCTION RSLF
-!+---+-----------------------------------------------------------------+
-! THIS FUNCTION CALCULATES THE ICE SATURATION VAPOR MIXING RATIO AS A
-! FUNCTION OF TEMPERATURE AND PRESSURE
-!
-      REAL FUNCTION RSIF(P,T)
-
-      IMPLICIT NONE
-      REAL, INTENT(IN):: P, T
-      REAL:: ESI,X
-      REAL, PARAMETER:: C0= .609868993E03
-      REAL, PARAMETER:: C1= .499320233E02
-      REAL, PARAMETER:: C2= .184672631E01
-      REAL, PARAMETER:: C3= .402737184E-1
-      REAL, PARAMETER:: C4= .565392987E-3
-      REAL, PARAMETER:: C5= .521693933E-5
-      REAL, PARAMETER:: C6= .307839583E-7
-      REAL, PARAMETER:: C7= .105785160E-9
-      REAL, PARAMETER:: C8= .161444444E-12
-
-      X=MAX(-80.,T-273.16)
-      ESI=C0+X*(C1+X*(C2+X*(C3+X*(C4+X*(C5+X*(C6+X*(C7+X*C8)))))))
-      RSIF=.622*ESI/(P-ESI)
-
-!    ALTERNATIVE
-!  ; Source: Murphy and Koop, Review of the vapour pressure of ice and
-!             supercooled water for atmospheric applications, Q. J. R.
-!             Meteorol. Soc (2005), 131, pp. 1539-1565.
-!     ESI = EXP(9.550426 - 5723.265/T + 3.53068*ALOG(T) - 0.00728332*T)
-
-      END FUNCTION RSIF
-!+---+-----------------------------------------------------------------+
-
-!+---+-----------------------------------------------------------------+
-END MODULE module_mp_thompson
-!+---+-----------------------------------------------------------------+
diff --git a/example/supporting-modules/thompson_tensors_m.f90 b/example/supporting-modules/thompson_tensors_m.f90
deleted file mode 100644
index 548ab5d52..000000000
--- a/example/supporting-modules/thompson_tensors_m.f90
+++ /dev/null
@@ -1,33 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module thompson_tensors_m
-  !! This module supports the program in the file example/learn-microphysics-procedures.f90.
-  use module_mp_thompson, only : rslf, rsif
-  use inference_engine_m, only : tensor_t
-  use assert_m, only : assert
-  implicit none
- 
-  private
-  public :: T, p, y
-
-  real, parameter :: T_min = 236.352524, T_max = 307.610779
-  real, parameter :: p_min = 29671.1348, p_max = 98596.7578
-  integer, parameter :: resolution = 10
-  integer i
-  real, parameter :: T(*) = [(real(i)/real(resolution), i=0,resolution)]
-  real, parameter :: p(*) = [(real(i)/real(resolution), i=0,resolution)]
-
-contains
-
-  elemental impure function y(x_in) result(a)
-    type(tensor_t), intent(in) :: x_in
-    type(tensor_t) a
-    associate(x => x_in%values())
-      call assert(lbound(x,1)==1 .and. ubound(x,1)==2,"y(x) :: sufficient input")
-      associate(temperature => T_min + (T_max - T_min)*x(1), pressure => p_min + (p_max - p_min)*x(2) )
-        a = tensor_t([rslf(pressure, temperature), rsif(pressure, temperature)])
-      end associate
-    end associate
-  end function
- 
-end module
diff --git a/example/train-and-write.F90 b/example/train-and-write.F90
index 04df61a55..7105afde0 100644
--- a/example/train-and-write.F90
+++ b/example/train-and-write.F90
@@ -9,7 +9,7 @@ program train_and_write
   !! The initial condition corresponds to the desired network with all weights and biases perturbed by a random variable
   !! that is uniformly distributed on the range [0,0.1].
   use inference_engine_m, only : &
-    inference_engine_t, trainable_engine_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   implicit none
@@ -30,17 +30,17 @@ program train_and_write
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:), random_numbers(:)
 
     call random_init(image_distinct=.true., repeatable=.true.)
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.2)
-    call output(trainable_engine%to_inference_engine(), string_t("initial-network.json"))
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.2)
+    call output(trainable_network, string_t("initial-network.json"))
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
-      call assert(num_inputs == num_outputs,"trainable_engine_test_m(identity_mapping): # inputs == # outputs", &
+      call assert(num_inputs == num_outputs,"trainable_network_test_m(identity_mapping): # inputs == # outputs", &
         intrinsic_array_t([num_inputs, num_outputs]) &
       )
       block
@@ -64,7 +64,7 @@ program train_and_write
           call random_number(random_numbers)
           call shuffle(input_output_pairs)
           mini_batches = [(mini_batch_t(input_output_pairs(bins(b)%first():bins(b)%last())), b = 1, size(bins))]
-          call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+          call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
           print *,sum(cost)/size(cost)
         end do
       end block
@@ -74,14 +74,15 @@ program train_and_write
         integer p
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
-          print "(a,62x,a,53x,a)", " Output", "| Desired outputs", "| Errors"
-
+          print "(a,43x,a,35x,a)", " Output", "| Desired outputs", "| Errors"
+          
           do p = 1, num_pairs
-            print *,network_outputs(p)%values(),"|", inputs(p)%values(), "|",  network_outputs(p)%values() - inputs(p)%values()
+            print "(2(3(G11.5,', '), G11.5, a), 3(G11.5,', '), G11.5)", &
+              network_outputs(p)%values(),"| ", inputs(p)%values(), "| ",  network_outputs(p)%values() - inputs(p)%values()
           end do
 #if defined _CRAYFTN || __GFORTRAN__
 #else
@@ -91,14 +92,14 @@ program train_and_write
 
    end associate
 
-   call output(trainable_engine%to_inference_engine(), final_network_file)
+   call output(trainable_network, final_network_file)
 
   end block
 
 contains
 
   subroutine output(inference_engine, file_name)
-    type(inference_engine_t), intent(in) :: inference_engine
+    class(inference_engine_t), intent(in) :: inference_engine
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
     json_file = inference_engine%to_json()
@@ -111,8 +112,8 @@ pure function e(m,n) result(e_mn)
     e_mn = real(merge(1,0,m==n))
   end function
 
-  function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, parameter :: nodes_per_layer(*) = [4, 4, 4, 4]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
@@ -129,10 +130,10 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_engine = trainable_engine_t( &
+      trainable_network = trainable_network_t( inference_engine_t( &
         nodes = nodes_per_layer, weights = w, biases = b, metadata = &
-          [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
-      )
+          [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2024-10-13"), string_t("relu"), string_t("false")] &
+      ))
 
     end associate
 
diff --git a/src/inference_engine/tmp b/src/inference_engine/tmp
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/inference_engine/trainable_engine_m.F90 b/src/inference_engine/trainable_engine_m.F90
deleted file mode 100644
index b7acc6e83..000000000
--- a/src/inference_engine/trainable_engine_m.F90
+++ /dev/null
@@ -1,179 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-module trainable_engine_m
-  !! Define an abstraction that supports training a neural network
-  use activation_m, only : activation_t
-  use julienne_string_m, only : string_t
-  use inference_engine_m_, only : inference_engine_t
-  use kind_parameters_m, only : default_real
-  use metadata_m, only : metadata_t
-  use tensor_m, only :  tensor_t
-  use tensor_map_m, only :  tensor_map_t
-  use mini_batch_m, only : mini_batch_t
-  use training_configuration_m, only : training_configuration_t
-  use input_output_pair_m, only : input_output_pair_t
-  implicit none
-
-  private
-  public :: trainable_engine_t
-
-  type trainable_engine_t(k)
-    !! Encapsulate the information needed to perform training
-    integer, kind :: k = default_real
-    type(tensor_map_t(k)), private :: input_map_, output_map_ !! mappings to/from data ranges used during training
-    type(activation_t), private :: activation_
-    type(metadata_t) metadata_       !! metadata_ encapsulates strings for which default-kind suffices
-    real(k), allocatable :: w(:,:,:) !! weights
-    real(k), allocatable :: b(:,:)   !! biases
-    integer, allocatable :: n(:)     !! nodes per layer
-    real(k), allocatable, dimension(:,:) :: a
-    real(k), allocatable, dimension(:,:,:) :: dcdw, vdw, sdw, vdwc, sdwc
-    real(k), allocatable, dimension(:,:) :: z, delta, dcdb, vdb, sdb, vdbc, sdbc
-  contains
-    generic :: train                    => default_real_train
-    procedure, private, non_overridable :: default_real_train
-    generic :: predict => default_real_predict
-    generic :: infer =>   default_real_predict
-    procedure, private :: default_real_predict
-    generic   :: assert_consistent => default_real_assert_consistent
-    procedure, private ::             default_real_assert_consistent
-    generic :: num_layers => default_real_num_layers
-    procedure, private ::    default_real_num_layers
-    generic :: num_inputs => default_real_num_inputs
-    procedure, private, non_overridable :: default_real_num_inputs
-    generic :: num_outputs              => default_real_num_outputs
-    procedure, private ::     default_real_num_outputs
-    generic :: to_inference_engine => default_real_to_inference_engine
-    procedure, private ::             default_real_to_inference_engine
-    generic :: map_to_input_training_range => default_real_map_to_input_training_range
-    procedure, private ::                     default_real_map_to_input_training_range
-    generic :: map_from_input_training_range => default_real_map_from_input_training_range
-    procedure, private ::                       default_real_map_from_input_training_range
-    generic :: map_to_output_training_range => default_real_map_to_output_training_range
-    procedure, private ::                      default_real_map_to_output_training_range
-    generic :: map_from_output_training_range => default_real_map_from_output_training_range
-    procedure, private ::                        default_real_map_from_output_training_range
-    generic :: map_to_training_ranges => default_real_map_to_training_ranges
-    procedure, private ::                default_real_map_to_training_ranges
-  end type
-
-  integer, parameter :: input_layer = 0
-
-  interface trainable_engine_t
-#ifdef __INTEL_COMPILER
-     module function construct_trainable_engine_from_padded_arrays(nodes, weights, biases, metadata, input_map, output_map) &
-#else
-     module function construct_from_padded_arrays( nodes, weights, biases, metadata, input_map, output_map) &
-#endif
-      result(trainable_engine)
-      implicit none
-      integer, intent(in) :: nodes(input_layer:)
-      real, intent(in)  :: weights(:,:,:), biases(:,:)
-      type(string_t), intent(in) :: metadata(:)
-      type(tensor_map_t), intent(in), optional :: input_map, output_map
-      type(trainable_engine_t) trainable_engine
-    end function
-
-    module function construct_from_inference_engine(inference_engine) result(trainable_engine)
-      implicit none
-      type(inference_engine_t), intent(in) :: inference_engine
-      type(trainable_engine_t) trainable_engine
-    end function
-
-    module function perturbed_identity_network(training_configuration, perturbation_magnitude, metadata, input_map, output_map)&
-      result(trainable_engine)
-      implicit none
-      type(training_configuration_t), intent(in) :: training_configuration
-      type(string_t), intent(in) :: metadata(:)
-      real, intent(in) :: perturbation_magnitude
-      type(tensor_map_t) input_map, output_map
-      type(trainable_engine_t) trainable_engine
-    end function
-
-  end interface
-
-  interface
-
-    pure module subroutine default_real_assert_consistent(self)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-    end subroutine
-
-    pure module subroutine default_real_train(self, mini_batches_arr, cost, adam, learning_rate)
-      implicit none
-      class(trainable_engine_t), intent(inout) :: self
-      type(mini_batch_t), intent(in) :: mini_batches_arr(:)
-      real, intent(out), allocatable, optional :: cost(:)
-      logical, intent(in) :: adam
-      real, intent(in) :: learning_rate
-    end subroutine
-
-    elemental module function default_real_predict(self, inputs) result(outputs)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(tensor_t), intent(in) :: inputs
-      type(tensor_t) outputs
-    end function
-    
-    elemental module function default_real_num_inputs(self) result(n_in)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      integer n_in
-    end function
-
-    elemental module function default_real_num_outputs(self) result(n_out)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      integer n_out
-    end function
-
-    elemental module function default_real_num_layers(self) result(n_layers)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      integer n_layers
-    end function
-
-    module function default_real_to_inference_engine(self) result(inference_engine)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(inference_engine_t) :: inference_engine
-    end function
-
-    elemental module function default_real_map_to_input_training_range(self, tensor) result(normalized_tensor)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(tensor_t), intent(in) :: tensor
-      type(tensor_t) normalized_tensor
-    end function
-
-    elemental module function default_real_map_from_input_training_range(self, tensor) result(unnormalized_tensor)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(tensor_t), intent(in) :: tensor
-      type(tensor_t) unnormalized_tensor
-    end function
-
-    elemental module function default_real_map_to_output_training_range(self, tensor) result(normalized_tensor)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(tensor_t), intent(in) :: tensor
-      type(tensor_t) normalized_tensor
-    end function
-
-    elemental module function default_real_map_from_output_training_range(self, tensor) result(unnormalized_tensor)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(tensor_t), intent(in) :: tensor
-      type(tensor_t) unnormalized_tensor
-    end function
-
-    elemental module function default_real_map_to_training_ranges(self, input_output_pair) result(normalized_input_output_pair)
-      implicit none
-      class(trainable_engine_t), intent(in) :: self
-      type(input_output_pair_t), intent(in) :: input_output_pair
-      type(input_output_pair_t) normalized_input_output_pair
-    end function
-
-  end interface
-
-end module trainable_engine_m
diff --git a/src/inference_engine/trainable_engine_s.F90 b/src/inference_engine/trainable_engine_s.F90
deleted file mode 100644
index f72096fe6..000000000
--- a/src/inference_engine/trainable_engine_s.F90
+++ /dev/null
@@ -1,416 +0,0 @@
-! Copyright (c), The Regents of the University of California
-! Terms of use are as specified in LICENSE.txt
-
-#include "language-support.F90"
-
-submodule(trainable_engine_m) trainable_engine_s
-  use assert_m, only : assert
-  use intrinsic_array_m, only : intrinsic_array_t
-  use tensor_m, only : tensor_t
-#ifdef _CRAYFTN
-  use input_output_pair_m, only : input_output_pair_t
-#endif
-  implicit none
-
-  integer, parameter :: input_layer = 0
-
-contains
-
-  module procedure default_real_num_inputs
-    n_in = self%n(input_layer)
-  end procedure
-
-  module procedure default_real_num_layers
-    n_layers = size(self%n,1)
-  end procedure
-
-  module procedure default_real_num_outputs
-    n_out = self%n(ubound(self%n,1))
-  end procedure
-
-  module procedure construct_from_inference_engine
-
-#ifndef _CRAYFTN
-    associate(exchange => inference_engine%to_exchange())
-#else
-    use inference_engine_m_, only: exchange_t
-    type(exchange_t) exchange
-    exchange = inference_engine%to_exchange()
-#endif
-      trainable_engine%input_map_ = exchange%input_map_
-      trainable_engine%output_map_ = exchange%output_map_
-      trainable_engine%metadata_ = exchange%metadata_
-      trainable_engine%w = exchange%weights_
-      trainable_engine%b = exchange%biases_
-      trainable_engine%n = exchange%nodes_
-      trainable_engine%activation_ = exchange%activation_
-#ifndef _CRAYFTN
-    end associate
-#endif
-
-  end procedure
-
-  module procedure default_real_assert_consistent
-
-    associate( &
-      fully_allocated=>[allocated(self%w),allocated(self%b),allocated(self%n)] &
-    )
-      call assert(all(fully_allocated),"trainable_engine_s(assert_consistent): fully_allocated",intrinsic_array_t(fully_allocated))
-    end associate
-
-    associate(max_width => maxval(self%n), component_dims => [size(self%b,1), size(self%w,1), size(self%w,2)])
-      call assert(all(component_dims == max_width), "trainable_engine_s(assert_consistent): conformable arrays", &
-        intrinsic_array_t([max_width,component_dims]))
-    end associate
-
-    call assert(lbound(self%n,1)==input_layer, "trainable_engine_s(assert_consistent): n base subsscript", lbound(self%n,1))
-
-  end procedure
-
-  module procedure default_real_predict
-
-    real, allocatable :: a(:,:)
-    integer l
-
-    call self%assert_consistent
-
-    associate(w => self%w, b => self%b, n => self%n, output_layer => ubound(self%n,1))
-
-      allocate(a(maxval(n), input_layer:output_layer)) ! Activations
-
-#ifndef _CRAYFTN
-      associate(normalized_inputs => self%input_map_%map_to_training_range(inputs))
-#else
-      block
-        type(tensor_t) normalized_inputs
-        normalized_inputs = self%input_map_%map_to_training_range(inputs)
-#endif
-        a(1:n(input_layer),input_layer) = normalized_inputs%values()
-#ifndef _CRAYFTN
-      end associate
-#else
-      end block
-#endif
-
-      feed_forward: &
-      do l = 1,output_layer
-        associate(z=>matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
-          a(1:n(l),l) = self%activation_%evaluate(z)
-        end associate
-      end do feed_forward
- 
-      associate(normalized_outputs => tensor_t(a(1:n(output_layer), output_layer)))
-        outputs = self%output_map_%map_from_training_range(normalized_outputs)
-      end associate
-
-    end associate
-
-  end procedure
-
-  module procedure default_real_train
-    integer l, batch, mini_batch_size, pair
-    type(tensor_t), allocatable :: inputs(:), expected_outputs(:)
-
-    call self%assert_consistent
-
-    if (.not. allocated(self%dcdw)) allocate(self%dcdw, mold=self%w) ! Gradient of cost function with respect to weights
-    if (.not. allocated(self%vdw)) allocate(self%vdw, mold=self%w) 
-    if (.not. allocated(self%sdw)) allocate(self%sdw, mold=self%w) 
-    if (.not. allocated(self%vdwc)) allocate(self%vdwc,  mold=self%w) 
-    if (.not. allocated(self%sdwc)) allocate(self%sdwc,  mold=self%w) 
-
-    if (.not. allocated(self%dcdb)) allocate(self%dcdb,  mold=self%b) ! Gradient of cost function with respect with biases
-    if (.not. allocated(self%vdb)) allocate(self%vdb,   mold=self%b) 
-    if (.not. allocated(self%sdb)) allocate(self%sdb,   mold=self%b) 
-    if (.not. allocated(self%vdbc)) allocate(self%vdbc,  mold=self%b) 
-    if (.not. allocated(self%sdbc)) allocate(self%sdbc,  mold=self%b) 
-
-    associate(output_layer => ubound(self%n,1))
-      
-#if F2023_LOCALITY || F2018_LOCALITY
-      if (.not. allocated(self%z)) allocate(self%z,  mold=self%b) ! z-values: Sum z_j^l = w_jk^{l} a_k^{l-1} + b_j^l
-      if (.not. allocated(self%delta)) allocate(self%delta, mold=self%b)
-      if (.not. allocated(self%a)) allocate(self%a(maxval(self%n), input_layer:output_layer)) ! Activations
-#endif
-
-      associate( &
-        a => self%a, dcdw => self%dcdw, vdw => self%vdw, sdw => self%sdw, vdwc => self%vdwc, sdwc => self%sdwc, &
-        z => self%z, delta => self%delta, dcdb => self%dcdb, vdb => self%vdb, sdb => self%sdb, vdbc => self%vdbc, sdbc=> self%sdbc &
-      )
-        vdw = 0.; sdw = 1.; vdb = 0.; sdb = 1.
-
-        associate(w => self%w, b => self%b, n => self%n, num_mini_batches => size(mini_batches_arr))
-
-          if (present(cost)) allocate(cost(num_mini_batches))
-        
-          iterate_across_batches: &
-          do batch = 1, num_mini_batches
-
-            dcdw = 0.; dcdb = 0.
-
-#ifndef _CRAYFTN
-            associate(input_output_pairs => mini_batches_arr(batch)%input_output_pairs())
-#else
-            block
-              type(input_output_pair_t), allocatable :: input_output_pairs(:)
-              input_output_pairs = mini_batches_arr(batch)%input_output_pairs()
-#endif  
-              inputs = input_output_pairs%inputs()
-              expected_outputs = input_output_pairs%expected_outputs()
-              mini_batch_size = size(input_output_pairs)
-#ifndef _CRAYFTN
-            end associate
-#else
-            end block
-#endif  
-            block
-              real, allocatable :: pair_cost(:)
-              if (present(cost)) allocate(pair_cost(mini_batch_size))
-
-#if F2023_LOCALITY
-              iterate_through_batch: &
-              do concurrent (pair = 1:mini_batch_size) local(a,z,delta) reduce(+: dcdb, dcdw)
-
-#elif F2018_LOCALITY
-
-              reduce_gradients: &
-              block
-                real reduce_dcdb(size(dcdb,1),size(dcdb,2),mini_batch_size)
-                real reduce_dcdw(size(dcdw,1),size(dcdw,2),size(dcdw,3),mini_batch_size)
-                reduce_dcdb = 0.
-                reduce_dcdw = 0.
-
-              iterate_through_batch: &
-              do concurrent (pair = 1:mini_batch_size) local(a,z,delta)
-
-#else
-
-              reduce_gradients: &
-              block
-                real reduce_dcdb(size(dcdb,1),size(dcdb,2),mini_batch_size)
-                real reduce_dcdw(size(dcdw,1),size(dcdw,2),size(dcdw,3),mini_batch_size)
-                reduce_dcdb = 0.
-                reduce_dcdw = 0.
-              
-              iterate_through_batch: &
-              do concurrent (pair = 1:mini_batch_size)
-
-                iteration: &
-                block
-
-                real a(maxval(self%n), input_layer:output_layer) ! Activations
-                real z(size(b,1),size(b,2)), delta(size(b,1),size(b,2))
-#endif
-
-                a(1:self%num_inputs(), input_layer) = inputs(pair)%values()
-
-                feed_forward: &
-                do l = 1,output_layer
-                  z(1:n(l),l) = matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l)
-                  a(1:n(l),l) = self%activation_%evaluate(z(1:n(l),l))
-                end do feed_forward
-
-                associate(y => expected_outputs(pair)%values())
-                  if (present(cost)) pair_cost(pair) = sum((y(1:n(output_layer))-a(1:n(output_layer),output_layer))**2)
-              
-                  delta(1:n(output_layer),output_layer) = (a(1:n(output_layer),output_layer) - y(1:n(output_layer))) &
-                                                         * self%activation_%differentiate(z(1:n(output_layer),output_layer))
-                end associate
-                
-                associate(n_hidden => self%num_layers()-2)
-                  back_propagate_error: &
-                  do l = n_hidden,1,-1
-                    delta(1:n(l),l) = matmul(transpose(w(1:n(l+1),1:n(l),l+1)), delta(1:n(l+1),l+1)) &
-                                     * self%activation_%differentiate(z(1:n(l),l))
-                  end do back_propagate_error
-                end associate
-
-                block
-                  integer j
-                  sum_gradients: &
-                  do l = 1,output_layer
-#if F2023_LOCALITY
-                    dcdb(1:n(l),l) = dcdb(1:n(l),l) + delta(1:n(l),l)
-                    do concurrent(j = 1:n(l)) reduce(+: dcdw)
-                      dcdw(j,1:n(l-1),l) = dcdw(j,1:n(l-1),l) + a(1:n(l-1),l-1)*delta(j,l)
-                    end do
-#else
-                    reduce_dcdb(1:n(l),l,pair) = reduce_dcdb(1:n(l),l,pair) + delta(1:n(l),l)
-                    do j = 1,n(l)
-                      reduce_dcdw(j,1:n(l-1),l,pair) = reduce_dcdw(j,1:n(l-1),l,pair) + a(1:n(l-1),l-1)*delta(j,l)
-                    end do
-#endif
-                  end do sum_gradients
-                end block
-    
-#if F2023_LOCALITY
-                end do iterate_through_batch
-#elif F2018_LOCALITY
-
-                end do iterate_through_batch
-                dcdb = sum(reduce_dcdb,dim=3)
-                dcdw = sum(reduce_dcdw,dim=4)
-
-                end block reduce_gradients
-#else
-                end block iteration
-                end do iterate_through_batch
-                dcdb = sum(reduce_dcdb,dim=3)
-                dcdw = sum(reduce_dcdw,dim=4)
-  
-                end block reduce_gradients
-#endif
-
-              if (present(cost)) cost(batch) = sum(pair_cost)/(2*mini_batch_size)
-            end block
-          
-            if (adam) then
-              block
-                ! Adam parameters  
-                real, parameter :: beta(*) = [.9, .999]
-                real, parameter :: obeta(*) = [1.- beta(1), 1.- beta(2)]
-                real, parameter :: epsilon = 1.E-08
-
-                associate(alpha => learning_rate)
-                  adam_adjust_weights_and_biases: &
-                  do concurrent(l = 1:output_layer)
-                    dcdw(1:n(l),1:n(l-1),l) = dcdw(1:n(l),1:n(l-1),l)/(mini_batch_size)
-                    vdw(1:n(l),1:n(l-1),l)  = beta(1)*vdw(1:n(l),1:n(l-1),l) + obeta(1)*dcdw(1:n(l),1:n(l-1),l)
-                    sdw (1:n(l),1:n(l-1),l) = beta(2)*sdw(1:n(l),1:n(l-1),l) + obeta(2)*(dcdw(1:n(l),1:n(l-1),l)**2)
-                    vdwc(1:n(l),1:n(l-1),l) = vdw(1:n(l),1:n(l-1),l)/(1.- beta(1)**num_mini_batches)
-                    sdwc(1:n(l),1:n(l-1),l) = sdw(1:n(l),1:n(l-1),l)/(1.- beta(2)**num_mini_batches)
-                    w(1:n(l),1:n(l-1),l) = w(1:n(l),1:n(l-1),l) &
-                      - alpha*vdwc(1:n(l),1:n(l-1),l)/(sqrt(sdwc(1:n(l),1:n(l-1),l))+epsilon) ! Adjust weights
-
-                    dcdb(1:n(l),l) = dcdb(1:n(l),l)/mini_batch_size
-                    vdb(1:n(l),l) = beta(1)*vdb(1:n(l),l) + obeta(1)*dcdb(1:n(l),l)
-                    sdb(1:n(l),l) = beta(2)*sdb(1:n(l),l) + obeta(2)*(dcdb(1:n(l),l)**2)
-                    vdbc(1:n(l),l) = vdb(1:n(l),l)/(1. - beta(1)**num_mini_batches)
-                    sdbc(1:n(l),l) = sdb(1:n(l),l)/(1. - beta(2)**num_mini_batches)
-                    b(1:n(l),l) = b(1:n(l),l) - alpha*vdbc(1:n(l),l)/(sqrt(sdbc(1:n(l),l))+epsilon) ! Adjust weights
-                  end do adam_adjust_weights_and_biases
-                end associate
-              end block
-            else
-              associate(eta => learning_rate)
-                adjust_weights_and_biases: &
-                do concurrent(l = 1:output_layer)
-                  dcdb(1:n(l),l) = dcdb(1:n(l),l)/mini_batch_size
-                  b(1:n(l),l) = b(1:n(l),l) - eta*dcdb(1:n(l),l) ! Adjust biases
-                  dcdw(1:n(l),1:n(l-1),l) = dcdw(1:n(l),1:n(l-1),l)/mini_batch_size
-                  w(1:n(l),1:n(l-1),l) = w(1:n(l),1:n(l-1),l) - eta*dcdw(1:n(l),1:n(l-1),l) ! Adjust weights
-                end do adjust_weights_and_biases
-              end associate
-            end if
-          end do iterate_across_batches
-        end associate
-      end associate
-    end associate
-  end procedure
-
-#ifdef __INTEL_COMPILER
-  module procedure construct_trainable_engine_from_padded_arrays
-#else
-  module procedure construct_from_padded_arrays
-#endif
-
-    trainable_engine%metadata_ = metadata_t(metadata(1),metadata(2),metadata(3),metadata(4),metadata(5))
-    trainable_engine%n = nodes
-    trainable_engine%w = weights
-    trainable_engine%b = biases
-    trainable_engine%activation_ = activation_t(metadata(4)%string())
-
-    block 
-      integer i
-
-      if (present(input_map)) then
-         trainable_engine%input_map_ = input_map
-      else
-        associate(num_inputs => nodes(lbound(nodes,1)))
-          trainable_engine%input_map_ = tensor_map_t("inputs", minima=[(0., i=1,num_inputs)], maxima=[(1., i=1,num_inputs)])
-        end associate
-      end if
-
-      if (present(output_map)) then
-         trainable_engine%output_map_ = output_map
-      else
-        associate(num_outputs => nodes(ubound(nodes,1)))
-          trainable_engine%output_map_ = tensor_map_t("outputs", minima=[(0., i=1,num_outputs)], maxima=[(1., i=1,num_outputs)])
-        end associate
-      end if
-    end block
-
-    call trainable_engine%assert_consistent
-  end procedure
-
-  module procedure default_real_to_inference_engine
-    inference_engine = inference_engine_t(self%metadata_%strings(), self%w, self%b, self%n, self%input_map_, self%output_map_)
-  end procedure
-
-  module procedure perturbed_identity_network
-
-    integer k, l
-    real, allocatable :: identity(:,:,:), w_harvest(:,:,:), b_harvest(:,:)
-
-    associate(n=>training_configuration%nodes_per_layer())
-      associate(n_max => maxval(n), layers => size(n))
-
-        identity = reshape( [( [(e(k,n_max), k=1,n_max)], l = 1, layers-1 )], [n_max, n_max, layers-1])
-        allocate(w_harvest, mold = identity)
-        allocate(b_harvest(size(identity,1), size(identity,3)))
-        call random_number(w_harvest)
-        call random_number(b_harvest)
-
-        associate( &
-          w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, &
-          b => perturbation_magnitude*(b_harvest-0.5)/0.5 &
-        )
-          trainable_engine = trainable_engine_t( &
-            nodes = n, weights = w, biases = b, metadata = metadata, input_map = input_map, output_map = output_map &
-          )
-        end associate
-      end associate
-    end associate
-
-  contains
-
-    pure function e(j,n) result(unit_vector)
-      integer, intent(in) :: j, n
-      integer k
-      real, allocatable :: unit_vector(:)
-      unit_vector = real([(merge(1,0,j==k),k=1,n)])
-    end function
-
-  end procedure
-
-  module procedure default_real_map_to_training_ranges
-    associate( &
-      inputs => input_output_pair%inputs(), &
-      expected_outputs => input_output_pair%expected_outputs() &
-    )
-      associate( &
-         normalized_inputs => self%input_map_%map_to_training_range(inputs), &
-         normalized_outputs => self%output_map_%map_to_training_range(expected_outputs) &
-      )
-        normalized_input_output_pair = input_output_pair_t(normalized_inputs, normalized_outputs)
-      end associate
-    end associate
-  end procedure
-
-  module procedure default_real_map_to_input_training_range
-    normalized_tensor = self%input_map_%map_to_training_range(tensor)
-  end procedure
-
-  module procedure default_real_map_from_input_training_range
-    unnormalized_tensor = self%input_map_%map_from_training_range(tensor)
-  end procedure
-  
-  module procedure default_real_map_to_output_training_range
-    normalized_tensor = self%output_map_%map_to_training_range(tensor)
-  end procedure
-
-  module procedure default_real_map_from_output_training_range
-    unnormalized_tensor = self%output_map_%map_from_training_range(tensor)
-  end procedure
-
-end submodule trainable_engine_s
diff --git a/src/inference_engine/trainable_network_m.f90 b/src/inference_engine/trainable_network_m.f90
index 3338f0f69..86380eb0f 100644
--- a/src/inference_engine/trainable_network_m.f90
+++ b/src/inference_engine/trainable_network_m.f90
@@ -1,7 +1,11 @@
 module trainable_network_m
   use inference_engine_m_, only : inference_engine_t, workspace_t
-  use mini_batch_m, only : mini_batch_t
+  use input_output_pair_m, only : input_output_pair_t
+  use julienne_m, only : string_t
   use kind_parameters_m, only : default_real
+  use mini_batch_m, only : mini_batch_t
+  use training_configuration_m, only : training_configuration_t
+  use tensor_map_m, only : tensor_map_t
   implicit none
 
   private
@@ -13,7 +17,9 @@ module trainable_network_m
     type(workspace_t), private :: workspace_
   contains
     generic :: train           => default_real_train                   
-    procedure, non_overridable :: default_real_train
+    procedure, private, non_overridable :: default_real_train
+    generic ::   map_to_training_ranges => default_real_map_to_training_ranges
+    procedure, private, non_overridable :: default_real_map_to_training_ranges
   end type
 
   interface trainable_network_t 
@@ -23,7 +29,17 @@ pure module function default_real_network(inference_engine) result(trainable_net
       type(inference_engine_t), intent(in) :: inference_engine
       type(trainable_network_t) trainable_network
     end function 
- 
+
+    module function perturbed_identity_network(training_configuration, perturbation_magnitude, metadata, input_map, output_map) &
+      result(trainable_network)
+      implicit none
+      type(training_configuration_t), intent(in) :: training_configuration
+      type(string_t), intent(in) :: metadata(:)
+      real, intent(in) :: perturbation_magnitude
+      type(tensor_map_t) input_map, output_map
+      type(trainable_network_t) trainable_network
+    end function
+
   end interface
 
   interface
@@ -37,6 +53,13 @@ pure module subroutine default_real_train(self, mini_batches_arr, cost, adam, le
       real, intent(in) :: learning_rate
     end subroutine
 
+    elemental module function default_real_map_to_training_ranges(self, input_output_pair) result(normalized_input_output_pair)
+      implicit none
+      class(trainable_network_t), intent(in) :: self
+      type(input_output_pair_t), intent(in) :: input_output_pair
+      type(input_output_pair_t) normalized_input_output_pair
+    end function
+
   end interface
 
 end module trainable_network_m
diff --git a/src/inference_engine/trainable_network_s.f90 b/src/inference_engine/trainable_network_s.f90
index d6e1d83a6..a4e3caa6c 100644
--- a/src/inference_engine/trainable_network_s.f90
+++ b/src/inference_engine/trainable_network_s.f90
@@ -9,7 +9,53 @@
   end procedure
 
   module procedure default_real_train
-    call self%inference_engine_t%default_real_learn(mini_batches_arr, cost, adam, learning_rate, self%workspace_)
+    call self%learn(mini_batches_arr, cost, adam, learning_rate, self%workspace_)
   end procedure
 
+  module procedure default_real_map_to_training_ranges
+    associate(inputs => input_output_pair%inputs(), expected_outputs => input_output_pair%expected_outputs())
+      associate( &
+         normalized_inputs  => self%input_map_%map_to_training_range(inputs), &
+         normalized_outputs => self%output_map_%map_to_training_range(expected_outputs) &
+      )
+        normalized_input_output_pair = input_output_pair_t(normalized_inputs, normalized_outputs)
+      end associate
+    end associate
+  end procedure
+
+  module procedure perturbed_identity_network
+
+    integer k, l
+    real, allocatable :: identity(:,:,:), w_harvest(:,:,:), b_harvest(:,:)
+
+    associate(n=>training_configuration%nodes_per_layer())
+      associate(n_max => maxval(n), layers => size(n))
+
+        identity = reshape( [( [(e(k,n_max), k=1,n_max)], l = 1, layers-1 )], [n_max, n_max, layers-1])
+        allocate(w_harvest, mold = identity)
+        allocate(b_harvest(size(identity,1), size(identity,3)))
+        call random_number(w_harvest)
+        call random_number(b_harvest)
+
+        associate( &
+          w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, &
+          b => perturbation_magnitude*(b_harvest-0.5)/0.5 &
+        )
+          trainable_network = trainable_network_t( &
+            inference_engine_t(nodes=n, weights=w, biases=b, metadata=metadata, input_map=input_map, output_map=output_map) &
+          )
+        end associate
+      end associate
+    end associate
+
+  contains
+
+    pure function e(j,n) result(unit_vector)
+      integer, intent(in) :: j, n
+      integer k
+      real, allocatable :: unit_vector(:)
+      unit_vector = real([(merge(1,0,j==k),k=1,n)])
+    end function
+
+  end procedure
 end submodule trainable_network_s
diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90
index 5b6820d1e..45a803ab7 100644
--- a/src/inference_engine_m.f90
+++ b/src/inference_engine_m.f90
@@ -14,7 +14,6 @@ module inference_engine_m
  use tensor_m, only : tensor_t
  use tensor_map_m, only : tensor_map_t
  use trainable_network_m, only : trainable_network_t
- use trainable_engine_m, only : trainable_engine_t
  use training_configuration_m, only : training_configuration_t
  use ubounds_m, only : ubounds_t
  implicit none
diff --git a/test/main.F90 b/test/main.F90
index 1b7d8d4ec..a21444d24 100644
--- a/test/main.F90
+++ b/test/main.F90
@@ -3,7 +3,7 @@
 program main
   use inference_engine_test_m, only : inference_engine_test_t
   use asymmetric_engine_test_m, only : asymmetric_engine_test_t
-  use trainable_engine_test_m, only : trainable_engine_test_t
+  use trainable_network_test_m, only : trainable_network_test_t
   use metadata_test_m, only : metadata_test_t
   use hyperparameters_test_m, only : hyperparameters_test_t
   use network_configuration_test_m, only : network_configuration_test_t
@@ -15,7 +15,7 @@ program main
 
   type(inference_engine_test_t) inference_engine_test
   type(asymmetric_engine_test_t) asymmetric_engine_test
-  type(trainable_engine_test_t) trainable_engine_test
+  type(trainable_network_test_t) trainable_network_test
   type(hyperparameters_test_t) hyperparameters_test
   type(metadata_test_t) metadata_test
   type(network_configuration_test_t) network_configuration_test
@@ -49,7 +49,7 @@ program main
   call tensor_test%report(passes, tests)
   call asymmetric_engine_test%report(passes, tests)
   call inference_engine_test%report(passes, tests)
-  call trainable_engine_test%report(passes, tests)
+  call trainable_network_test%report(passes, tests)
   call cpu_time(t_finish)
 
   print *
diff --git a/test/trainable_engine_test_m.F90 b/test/trainable_network_test_m.F90
similarity index 86%
rename from test/trainable_engine_test_m.F90
rename to test/trainable_network_test_m.F90
index 929d40ea6..92518d74d 100644
--- a/test/trainable_engine_test_m.F90
+++ b/test/trainable_network_test_m.F90
@@ -1,6 +1,6 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-module trainable_engine_test_m
+module trainable_network_test_m
   !! Define inference tests and procedures required for reporting results
 
   ! External dependencies
@@ -12,11 +12,11 @@ module trainable_engine_test_m
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : trainable_engine_t, tensor_t, input_output_pair_t, mini_batch_t, shuffle
+  use inference_engine_m, only : trainable_network_t, inference_engine_t, tensor_t, input_output_pair_t, mini_batch_t, shuffle
   implicit none
 
   private
-  public :: trainable_engine_test_t
+  public :: trainable_network_test_t
 
   type, extends(vector_function_strategy_t) :: and_gate_test_function_t
   contains
@@ -38,7 +38,7 @@ module trainable_engine_test_m
     procedure, nopass :: vector_function => xor_gate_with_random_weights
   end type
     
-  type, extends(test_t) :: trainable_engine_test_t
+  type, extends(test_t) :: trainable_network_test_t
   contains
     procedure, nopass :: subject
     procedure, nopass :: results
@@ -60,7 +60,7 @@ function map_i(inputs) result(expected_outputs)
 
   pure function subject() result(specimen)
     character(len=:), allocatable :: specimen
-    specimen = "A trainable_engine_t" 
+    specimen = "A trainable_network_t object"
   end function
 
   function results() result(test_results)
@@ -163,7 +163,7 @@ subroutine print_truth_table(gate_name, gate_function_ptr, test_inputs, actual_o
     integer i
 
     call assert( size(test_inputs) == size(actual_outputs), &
-      "trainable_engine_test_m(print_truth_table): size(test_inputs) == size(actual_outputs)")
+      "trainable_network_test_m(print_truth_table): size(test_inputs) == size(actual_outputs)")
 
     print *,"_______" // gate_name // "_______"
 
@@ -173,8 +173,8 @@ subroutine print_truth_table(gate_name, gate_function_ptr, test_inputs, actual_o
     end do
   end subroutine
 
-  function two_zeroed_hidden_layers() result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function two_zeroed_hidden_layers() result(trainable_network)
+    type(trainable_network_t) trainable_network
     integer, parameter :: inputs = 2, outputs = 1, hidden = 3 ! number of neurons in input, output, and hidden layers
     integer, parameter :: neurons(*) = [inputs, hidden, hidden, outputs] ! neurons per layer
     integer, parameter :: max_neurons = maxval(neurons), layers=size(neurons) ! max layer width, number of layers
@@ -183,14 +183,14 @@ function two_zeroed_hidden_layers() result(trainable_engine)
     w = 0.
     b = 0.
 
-    trainable_engine = trainable_engine_t( &
-      nodes = neurons, weights = w, biases = b, metadata = &
-      [string_t("2-hide|3-wide"), string_t("Damian Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
-    )   
+    trainable_network = trainable_network_t( inference_engine_t( &
+      nodes = neurons, weights = w, biases = b &
+     ,metadata = [string_t("2-hide|3-wide"), string_t("Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
+    ))
   end function
 
-  function two_random_hidden_layers() result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function two_random_hidden_layers() result(trainable_network)
+    type(trainable_network_t) trainable_network
     integer, parameter :: inputs = 2, outputs = 1, hidden = 3 ! number of neurons in input, output, and hidden layers
     integer, parameter :: neurons(*) = [inputs, hidden, hidden, outputs] ! neurons per layer
     integer, parameter :: max_neurons = maxval(neurons), layers=size(neurons) ! max layer width, number of layers
@@ -201,10 +201,10 @@ function two_random_hidden_layers() result(trainable_engine)
     b = 2*b
     w = 2*w
 
-    trainable_engine = trainable_engine_t( &
-      nodes = neurons, weights = w, biases = b, metadata = &
-      [string_t("2-hide|3-wide"), string_t("Damian Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
-    )   
+    trainable_network = trainable_network_t( inference_engine_t( &
+      nodes = neurons, weights = w, biases = b &
+      ,metadata = [string_t("2-hide|3-wide"), string_t("Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
+    ))
   end function
 
   function and_gate_with_skewed_training_data() result(test_passes)
@@ -212,7 +212,7 @@ function and_gate_with_skewed_training_data() result(test_passes)
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(tensor_t), allocatable, dimension(:,:) :: training_inputs, training_outputs
     type(tensor_t), allocatable, dimension(:) :: tmp, tmp2, test_inputs, expected_test_outputs, actual_outputs
-    type(trainable_engine_t) trainable_engine
+    type(trainable_network_t) trainable_network
     real, parameter :: tolerance = 1.E-02
     real, allocatable :: harvest(:,:,:)
     integer, parameter :: num_inputs=2, mini_batch_size = 1, num_iterations=20000
@@ -231,13 +231,13 @@ function and_gate_with_skewed_training_data() result(test_passes)
     training_outputs = reshape(tmp2, [mini_batch_size, num_iterations])
 
     mini_batches = [(mini_batch_t(input_output_pair_t(training_inputs(:,iter), training_outputs(:,iter))), iter=1, num_iterations)]        
-    trainable_engine = two_zeroed_hidden_layers()
+    trainable_network = two_zeroed_hidden_layers()
 
-    call trainable_engine%train(mini_batches, adam=.false., learning_rate=1.5)
+    call trainable_network%train(mini_batches, adam=.false., learning_rate=1.5)
 
     test_inputs = [tensor_t([true,true]), tensor_t([false,true]), tensor_t([true,false]), tensor_t([false,false])]
     expected_test_outputs = [(and(test_inputs(i)), i=1, size(test_inputs))]
-    actual_outputs = trainable_engine%infer(test_inputs)
+    actual_outputs = trainable_network%infer(test_inputs)
     test_passes = [(abs(actual_outputs(i)%values() - expected_test_outputs(i)%values()) < tolerance, i=1, size(actual_outputs))]
 
   contains
@@ -255,7 +255,7 @@ function not_and_gate_with_skewed_training_data() result(test_passes)
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(tensor_t), allocatable :: training_inputs(:,:), tmp(:), test_inputs(:)
     type(tensor_t), allocatable :: training_outputs(:,:), expected_test_outputs(:), tmp2(:)
-    type(trainable_engine_t) trainable_engine
+    type(trainable_network_t) trainable_network
     type(tensor_t), allocatable :: actual_outputs(:)
     real, parameter :: tolerance = 1.E-02
     real, allocatable :: harvest(:,:,:)
@@ -275,13 +275,13 @@ function not_and_gate_with_skewed_training_data() result(test_passes)
     training_outputs = reshape(tmp2, [mini_batch_size, num_iterations])
 
     mini_batches = [(mini_batch_t(input_output_pair_t(training_inputs(:,iter), training_outputs(:,iter))), iter=1, num_iterations)]        
-    trainable_engine = two_zeroed_hidden_layers()
+    trainable_network = two_zeroed_hidden_layers()
 
-    call trainable_engine%train(mini_batches, adam=.false., learning_rate=1.5)
+    call trainable_network%train(mini_batches, adam=.false., learning_rate=1.5)
 
     test_inputs = [tensor_t([true,true]), tensor_t([false,true]), tensor_t([true,false]), tensor_t([false,false])]
     expected_test_outputs = [(not_and(test_inputs(i)), i=1, size(test_inputs))]
-    actual_outputs = trainable_engine%infer(test_inputs)
+    actual_outputs = trainable_network%infer(test_inputs)
     test_passes = [(abs(actual_outputs(i)%values() - expected_test_outputs(i)%values()) < tolerance, i=1, size(actual_outputs))]
 
   contains
@@ -299,7 +299,7 @@ function or_gate_with_random_weights() result(test_passes)
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(tensor_t), allocatable :: training_inputs(:,:), test_inputs(:), actual_outputs(:)
     type(tensor_t), allocatable :: training_outputs(:,:), expected_test_outputs(:)
-    type(trainable_engine_t) trainable_engine
+    type(trainable_network_t) trainable_network
     real, parameter :: tolerance = 1.E-02
     real, allocatable :: harvest(:,:,:)
     integer, parameter :: num_inputs=2, mini_batch_size = 1, num_iterations=50000
@@ -320,13 +320,13 @@ function or_gate_with_random_weights() result(test_passes)
       mini_batches(iter) = mini_batch_t(input_output_pair_t(training_inputs(:,iter), training_outputs(:,iter)))
     end do
 
-    trainable_engine = two_random_hidden_layers()
+    trainable_network = two_random_hidden_layers()
 
-    call trainable_engine%train(mini_batches, adam=.false., learning_rate=1.5)
+    call trainable_network%train(mini_batches, adam=.false., learning_rate=1.5)
 
     test_inputs = [tensor_t([true,true]), tensor_t([false,true]), tensor_t([true,false]), tensor_t([false,false])]
     expected_test_outputs = [(or(test_inputs(i)), i=1, size(test_inputs))]
-    actual_outputs = trainable_engine%infer(test_inputs)
+    actual_outputs = trainable_network%infer(test_inputs)
     test_passes = [(abs(actual_outputs(i)%values() - expected_test_outputs(i)%values()) < tolerance, i=1, size(actual_outputs))]
 
   contains
@@ -344,7 +344,7 @@ function xor_gate_with_random_weights() result(test_passes)
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(tensor_t), allocatable, dimension(:,:) :: training_inputs, training_outputs 
     type(tensor_t), allocatable, dimension(:) :: actual_outputs, test_inputs, expected_test_outputs
-    type(trainable_engine_t) trainable_engine
+    type(trainable_network_t) trainable_network
     real, parameter :: tolerance = 1.E-02
     real, allocatable :: harvest(:,:,:)
 #ifdef __flang__
@@ -375,16 +375,16 @@ function xor_gate_with_random_weights() result(test_passes)
       mini_batches(iter) = mini_batch_t(input_output_pair_t(training_inputs(:,iter), training_outputs(:,iter)))
     end do
 
-    trainable_engine = two_random_hidden_layers()
+    trainable_network = two_random_hidden_layers()
 
-    call trainable_engine%train(mini_batches, adam=.true., learning_rate=1.5)
+    call trainable_network%train(mini_batches, adam=.true., learning_rate=1.5)
 
     test_inputs = [tensor_t([true,true]), tensor_t([false,true]), tensor_t([true,false]), tensor_t([false,false])]
     block
       integer i
 
       expected_test_outputs = [(local_xor(test_inputs(i)), i=1, size(test_inputs))]
-      actual_outputs = trainable_engine%infer(test_inputs)
+      actual_outputs = trainable_network%infer(test_inputs)
       test_passes = [(abs(actual_outputs(i)%values() - expected_test_outputs(i)%values()) < tolerance, i=1, size(actual_outputs))]
     end block
 
@@ -400,8 +400,8 @@ pure function local_xor(inputs) result(expected_outputs)
 
   end function
 
-  function perturbed_identity_network(perturbation_magnitude) result(trainable_engine)
-    type(trainable_engine_t) trainable_engine
+  function perturbed_identity_network(perturbation_magnitude) result(trainable_network)
+    type(trainable_network_t) trainable_network
     real, intent(in) :: perturbation_magnitude
     integer, parameter :: nodes_per_layer(*) = [2, 2, 2, 2]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
@@ -420,12 +420,12 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_eng
     call random_number(harvest)
     harvest = perturbation_magnitude*harvest
 
-    trainable_engine = trainable_engine_t( &
+    trainable_network = trainable_network_t( inference_engine_t( &
       nodes = nodes_per_layer, &
       weights = identity + harvest , & 
       biases = reshape([real:: [0,0], [0,0], [0,0]], [max_n, layers-1]), &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")] &
-    )
+    ))
   end function
 
   function preserves_identity_mapping() result(test_passes)
@@ -433,17 +433,17 @@ function preserves_identity_mapping() result(test_passes)
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:)
     integer, parameter :: num_pairs = 100, num_epochs = 100, n_bins = 3
     integer i, bin, epoch
 
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.)
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.)
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
-      call assert(num_inputs == num_outputs,"trainable_engine_test_m(identity_mapping): # inputs == # outputs", &
+      call assert(num_inputs == num_outputs,"trainable_network_test_m(identity_mapping): # inputs == # outputs", &
         intrinsic_array_t([num_inputs, num_outputs]) &
       )
 #ifdef _CRAYFTN
@@ -461,16 +461,16 @@ function preserves_identity_mapping() result(test_passes)
 
       do epoch = 1,num_epochs
         mini_batches = [(mini_batch_t(input_output_pairs(bins(bin)%first():bins(bin)%last())), bin = 1, size(bins))]
-        call trainable_engine%train(mini_batches, cost, adam=.false., learning_rate=1.5)
+        call trainable_network%train(mini_batches, cost, adam=.false., learning_rate=1.5)
       end do
 
       block
         real, parameter :: tolerance = 1.E-06
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
           test_passes = maxval(abs([(network_outputs(i)%values() - inputs(i)%values(), i=1,num_pairs)])) < tolerance
 #if defined _CRAYFTN || __GFORTRAN__
@@ -492,7 +492,7 @@ function perturbed_identity_converges() result(test_passes)
     type(mini_batch_t), allocatable :: mini_batches(:)
     type(input_output_pair_t), allocatable :: input_output_pairs(:)
     type(tensor_t), allocatable :: inputs(:)
-    type(trainable_engine_t)  trainable_engine
+    type(trainable_network_t)  trainable_network
     type(bin_t), allocatable :: bins(:)
     real, allocatable :: cost(:)
     integer, parameter :: num_pairs = 6
@@ -500,11 +500,11 @@ function perturbed_identity_converges() result(test_passes)
     integer, parameter :: num_bins = 5 
     integer i, bin, epoch
 
-    trainable_engine = perturbed_identity_network(perturbation_magnitude=0.1)
+    trainable_network = perturbed_identity_network(perturbation_magnitude=0.1)
 
-    associate(num_inputs => trainable_engine%num_inputs(), num_outputs => trainable_engine%num_outputs())
+    associate(num_inputs => trainable_network%num_inputs(), num_outputs => trainable_network%num_outputs())
 
-      call assert(num_inputs == num_outputs,"trainable_engine_test_m(identity_mapping): # inputs == # outputs", &
+      call assert(num_inputs == num_outputs,"trainable_network_test_m(identity_mapping): # inputs == # outputs", &
         intrinsic_array_t([num_inputs, num_outputs]) &
       )
 #ifdef _CRAYFTN
@@ -523,16 +523,16 @@ function perturbed_identity_converges() result(test_passes)
       do epoch = 1,num_epochs
         call shuffle(input_output_pairs)
         mini_batches = [(mini_batch_t(input_output_pairs(bins(bin)%first():bins(bin)%last())), bin = 1, size(bins))]
-        call trainable_engine%train(mini_batches, cost, adam=.true., learning_rate=1.5)
+        call trainable_network%train(mini_batches, cost, adam=.true., learning_rate=1.5)
       end do
 
       block
         real, parameter :: tolerance = 1.E-06
 #if defined _CRAYFTN || __GFORTRAN__
         type(tensor_t), allocatable :: network_outputs(:)
-        network_outputs = trainable_engine%infer(inputs)
+        network_outputs = trainable_network%infer(inputs)
 #else
-        associate(network_outputs => trainable_engine%infer(inputs))
+        associate(network_outputs => trainable_network%infer(inputs))
 #endif
           test_passes = maxval(abs([(network_outputs(i)%values() - inputs(i)%values(), i=1,num_pairs)])) < tolerance
 #if defined _CRAYFTN || __GFORTRAN__
@@ -545,4 +545,4 @@ function perturbed_identity_converges() result(test_passes)
 
   end function
 
-end module trainable_engine_test_m
+end module trainable_network_test_m

From e89c7b4c6c7e6d278f4b17a52dd306bad84c2b8d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 01:12:32 -0700
Subject: [PATCH 087/105] fix(train-cloud-micro):let write_lines() open file

---
 demo/app/train-cloud-microphysics.F90 | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/demo/app/train-cloud-microphysics.F90 b/demo/app/train-cloud-microphysics.F90
index e96038f5d..9a8c5efcc 100644
--- a/demo/app/train-cloud-microphysics.F90
+++ b/demo/app/train-cloud-microphysics.F90
@@ -428,15 +428,9 @@ subroutine read_train_write(training_configuration, args, plot_file)
                       print *, epoch, average_cost
                       write(plot_file%plot_unit,*) epoch, average_cost
 
-                      block 
-                        integer net_unit
-
-                        open(newunit=net_unit, file=network_file, form='formatted', status='unknown', iostat=io_status, action='write')
-                        associate(json_file => trainable_network%to_json())
-                          call json_file%write_lines(string_t(network_file))
-                        end associate
-                        close(net_unit)
-                      end block
+                      associate(json_file => trainable_network%to_json())
+                        call json_file%write_lines(string_t(network_file))
+                      end associate
 
                     end if image_1_maybe_writes
 

From b23524df5a4b28bc2ef42345fee91f9c4372dc2c Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 02:14:25 -0700
Subject: [PATCH 088/105] chore(inference_engine): -> neural_network/fiats

This commit
1. Renames the project from inference-engine to fiats.
2. Renames inference_engine_t to neural_network_t.
---
 demo/app/infer-aerosol.f90                    |   8 +-
 demo/app/tensor-statistics.f90                |   2 +-
 demo/app/train-cloud-microphysics.F90         |  10 +-
 demo/fpm.toml                                 |  10 +-
 demo/include                                  |   1 +
 demo/setup.sh                                 |  44 +++----
 example/concurrent-inferences.f90             |  26 ++---
 example/learn-addition.F90                    |  14 +--
 example/learn-exponentiation.F90              |  13 +--
 example/learn-multiplication.F90              |  13 +--
 example/learn-power-series.F90                |  13 +--
 example/learn-saturated-mixing-ratio.F90      |  13 +--
 example/print-training-configuration.F90      |   2 +-
 example/read-query-infer.f90                  |  20 ++--
 .../saturated_mixing_ratio_m.f90              |   2 +-
 example/train-and-write.F90                   |  12 +-
 example/write-read-infer.F90                  |  36 +++---
 fpm.toml                                      |   4 +-
 setup.sh                                      |  14 +--
 .../activation_m.f90                          |   0
 .../activation_s.f90                          |   0
 .../double_precision_file_m.f90               |   0
 .../double_precision_file_s.f90               |   0
 .../double_precision_string_m.f90             |   0
 .../double_precision_string_s.f90             |   0
 .../hyperparameters_m.f90                     |   0
 .../hyperparameters_s.f90                     |   0
 .../input_output_pair_m.f90                   |   0
 .../input_output_pair_s.f90                   |   0
 .../kind_parameters_m.f90                     |   0
 src/{inference_engine => fiats}/layer_m.f90   |  14 +--
 src/{inference_engine => fiats}/layer_s.f90   |  16 +--
 .../metadata_m.f90                            |   0
 .../metadata_s.f90                            |   0
 .../mini_batch_m.f90                          |   0
 .../mini_batch_s.f90                          |   0
 .../network_configuration_m.f90               |   0
 .../network_configuration_s.F90               |   0
 .../neural_network_m.f90}                     | 108 +++++++++---------
 .../neural_network_s.F90}                     |  98 ++++++++--------
 src/{inference_engine => fiats}/neuron_m.f90  |   0
 src/{inference_engine => fiats}/neuron_s.f90  |   0
 src/{inference_engine => fiats}/tensor_m.f90  |   0
 .../tensor_map_m.f90                          |   0
 .../tensor_map_s.f90                          |   0
 src/{inference_engine => fiats}/tensor_s.f90  |   0
 .../trainable_network_m.f90                   |   8 +-
 .../trainable_network_s.f90                   |   6 +-
 .../training_configuration_m.f90              |   0
 .../training_configuration_s.F90              |   0
 src/{inference_engine => fiats}/ubounds_m.f90 |   0
 .../unmapped_network_s.f90                    |  32 +++---
 .../workspace_s.f90                           |  58 +++++-----
 src/{inference_engine_m.f90 => fiats_m.f90}   |   6 +-
 src/inference_engine/tmp                      |   0
 test/asymmetric_engine_test_m.F90             |  12 +-
 test/hyperparameters_test_m.F90               |   2 +-
 test/main.F90                                 |   6 +-
 test/metadata_test_m.F90                      |   2 +-
 test/network_configuration_test_m.F90         |   2 +-
 ...e_test_m.F90 => neural_network_test_m.F90} |  88 +++++++-------
 test/tensor_map_test_m.F90                    |   2 +-
 test/tensor_test_m.f90                        |   2 +-
 test/trainable_network_test_m.F90             |   8 +-
 test/training_configuration_test_m.F90        |   2 +-
 65 files changed, 362 insertions(+), 367 deletions(-)
 create mode 120000 demo/include
 rename src/{inference_engine => fiats}/activation_m.f90 (100%)
 rename src/{inference_engine => fiats}/activation_s.f90 (100%)
 rename src/{inference_engine => fiats}/double_precision_file_m.f90 (100%)
 rename src/{inference_engine => fiats}/double_precision_file_s.f90 (100%)
 rename src/{inference_engine => fiats}/double_precision_string_m.f90 (100%)
 rename src/{inference_engine => fiats}/double_precision_string_s.f90 (100%)
 rename src/{inference_engine => fiats}/hyperparameters_m.f90 (100%)
 rename src/{inference_engine => fiats}/hyperparameters_s.f90 (100%)
 rename src/{inference_engine => fiats}/input_output_pair_m.f90 (100%)
 rename src/{inference_engine => fiats}/input_output_pair_s.f90 (100%)
 rename src/{inference_engine => fiats}/kind_parameters_m.f90 (100%)
 rename src/{inference_engine => fiats}/layer_m.f90 (89%)
 rename src/{inference_engine => fiats}/layer_s.f90 (93%)
 rename src/{inference_engine => fiats}/metadata_m.f90 (100%)
 rename src/{inference_engine => fiats}/metadata_s.f90 (100%)
 rename src/{inference_engine => fiats}/mini_batch_m.f90 (100%)
 rename src/{inference_engine => fiats}/mini_batch_s.f90 (100%)
 rename src/{inference_engine => fiats}/network_configuration_m.f90 (100%)
 rename src/{inference_engine => fiats}/network_configuration_s.F90 (100%)
 rename src/{inference_engine/inference_engine_m_.f90 => fiats/neural_network_m.f90} (81%)
 rename src/{inference_engine/inference_engine_s.F90 => fiats/neural_network_s.F90} (91%)
 rename src/{inference_engine => fiats}/neuron_m.f90 (100%)
 rename src/{inference_engine => fiats}/neuron_s.f90 (100%)
 rename src/{inference_engine => fiats}/tensor_m.f90 (100%)
 rename src/{inference_engine => fiats}/tensor_map_m.f90 (100%)
 rename src/{inference_engine => fiats}/tensor_map_s.f90 (100%)
 rename src/{inference_engine => fiats}/tensor_s.f90 (100%)
 rename src/{inference_engine => fiats}/trainable_network_m.f90 (88%)
 rename src/{inference_engine => fiats}/trainable_network_s.f90 (88%)
 rename src/{inference_engine => fiats}/training_configuration_m.f90 (100%)
 rename src/{inference_engine => fiats}/training_configuration_s.F90 (100%)
 rename src/{inference_engine => fiats}/ubounds_m.f90 (100%)
 rename src/{inference_engine => fiats}/unmapped_network_s.f90 (66%)
 rename src/{inference_engine => fiats}/workspace_s.f90 (52%)
 rename src/{inference_engine_m.f90 => fiats_m.f90} (88%)
 delete mode 100644 src/inference_engine/tmp
 rename test/{inference_engine_test_m.F90 => neural_network_test_m.F90} (82%)

diff --git a/demo/app/infer-aerosol.f90 b/demo/app/infer-aerosol.f90
index d2211f2e9..d052f8481 100644
--- a/demo/app/infer-aerosol.f90
+++ b/demo/app/infer-aerosol.f90
@@ -7,7 +7,7 @@ program infer_aerosol
   use iso_fortran_env, only : int64, real64
 
   ! External dependencies:
-  use inference_engine_m, only : unmapped_network_t, tensor_t, double_precision, double_precision_file_t
+  use fiats_m, only : unmapped_network_t, tensor_t, double_precision, double_precision_file_t
   use julienne_m, only : string_t, command_line_t
   use omp_lib
 
@@ -49,7 +49,7 @@ subroutine read_stats_and_perform_inference(path)
     double precision cube_root
     double precision, allocatable, dimension(:,:) :: aerosol_data, input_components, output_components
     type(tensor_statistics_t) input_stats, output_stats
-    type(unmapped_network_t(double_precision)) inference_engine
+    type(unmapped_network_t(double_precision)) neural_network
     integer i, j
 
     input_stats  = read_tensor_statistics(path // "meanxp.txt", path // "stdxp.txt", num_inputs)  !for pre-processing normalization
@@ -76,7 +76,7 @@ subroutine read_stats_and_perform_inference(path)
     !$omp end parallel do   
       
     print *, "Reading the neural network from " // network_file_name
-    inference_engine = unmapped_network_t(double_precision_file_t(path // network_file_name))
+    neural_network = unmapped_network_t(double_precision_file_t(path // network_file_name))
 
     time_inference: &
     block
@@ -103,7 +103,7 @@ subroutine read_stats_and_perform_inference(path)
       !$ start_time = omp_get_wtime()
       !$omp parallel do shared(inputs,outputs,icc)
       do i = 1,icc
-         outputs(i) = inference_engine%infer(inputs(i))
+         outputs(i) = neural_network%infer(inputs(i))
       end do
       !$omp end parallel do   
       !$    end_time = omp_get_wtime()       
diff --git a/demo/app/tensor-statistics.f90 b/demo/app/tensor-statistics.f90
index 3a216205a..8c5cf2d06 100644
--- a/demo/app/tensor-statistics.f90
+++ b/demo/app/tensor-statistics.f90
@@ -9,7 +9,7 @@ program tensor_statistics
   ! External dependencies:
   use julienne_m, only : command_line_t, file_t, string_t
   use assert_m, only : assert, intrinsic_array_t
-  use inference_engine_m, only : ubounds_t
+  use fiats_m, only : ubounds_t
   use ieee_arithmetic, only : ieee_is_nan
   use iso_fortran_env, only : int64, real64
     
diff --git a/demo/app/train-cloud-microphysics.F90 b/demo/app/train-cloud-microphysics.F90
index 9a8c5efcc..1187b42e7 100644
--- a/demo/app/train-cloud-microphysics.F90
+++ b/demo/app/train-cloud-microphysics.F90
@@ -12,10 +12,10 @@ program train_on_flat_distribution
   !! External dependencies:
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
-  use inference_engine_m, only : &
-    inference_engine_t, mini_batch_t, input_output_pair_t, tensor_t, trainable_network_t, tensor_map_t, &
-    training_configuration_t, shuffle
-
+  use fiats_m, only : &
+    neural_network_t, mini_batch_t, input_output_pair_t, tensor_t, trainable_network_t, tensor_map_t, training_configuration_t, &
+    shuffle
+    
   !! Internal dependencies:
   use phase_space_bin_m, only : phase_space_bin_t
   use NetCDF_file_m, only: NetCDF_file_t
@@ -319,7 +319,7 @@ subroutine read_train_write(training_configuration, args, plot_file)
             read_or_initialize_engine: &
             if (io_status==0) then
               print *,"Reading network from file " // network_file
-              trainable_network = trainable_network_t(inference_engine_t(file_t(string_t(network_file))))
+              trainable_network = trainable_network_t(neural_network_t(file_t(string_t(network_file))))
               close(network_unit)
             else
               close(network_unit)
diff --git a/demo/fpm.toml b/demo/fpm.toml
index eefd579dd..9bed6f611 100644
--- a/demo/fpm.toml
+++ b/demo/fpm.toml
@@ -1,10 +1,10 @@
-name       = "Inference-Engine-Demonstration-Applications"
-license    = "(Please see inference-engine/LICENSE.txt.)"
-author     = "(Please see inference-engine/fpm.toml.)"
-maintainer = "(Please see inference-engine/fpm.toml.)"
+name       = "Fiats-Demonstration-Applications"
+license    = "(Please see fiats/LICENSE.txt.)"
+author     = "(Please see fiats/fpm.toml.)"
+maintainer = "(Please see fiats/fpm.toml.)"
 
 [dependencies]
 assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.7.0"}
 julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.2.2"}
-inference-engine = {path = "../"}
+fiats = {path = "../"}
 netcdf-interfaces = {git = "https://github.com/LKedward/netcdf-interfaces.git", rev = "d2bbb71ac52b4e346b62572b1ca1620134481096"}
diff --git a/demo/include b/demo/include
new file mode 120000
index 000000000..f5030fe88
--- /dev/null
+++ b/demo/include
@@ -0,0 +1 @@
+../include
\ No newline at end of file
diff --git a/demo/setup.sh b/demo/setup.sh
index 5d848cfe8..46ce5b05d 100755
--- a/demo/setup.sh
+++ b/demo/setup.sh
@@ -4,7 +4,7 @@ set -e # exit on error
 
 usage()
 {
-  echo "Inference Engine Setup Script"
+  echo "Fiats Demonstration Applications Setup Script"
   echo ""
   echo "USAGE:"
   echo "./setup.sh [--help|-h] | [-p|--prefix=PREFIX]"
@@ -112,25 +112,25 @@ else
   PKG_CONFIG_PATH=`realpath "$PKG_CONFIG_PATH"`
 fi
 
-INFERENCE_ENGINE_VERSION=$(grep version ../fpm.toml | grep -o '".*"' - | sed 's/"//g')
+FIATS_VERSION=$(grep version ../fpm.toml | grep -o '".*"' - | sed 's/"//g')
 
-INFERENCE_ENGINE_PC="$PKG_CONFIG_PATH/inference-engine.pc"
-echo "INFERENCE_ENGINE_FPM_CC=\"$FPM_CC\""                  >  $INFERENCE_ENGINE_PC
-echo "INFERENCE_ENGINE_FPM_FC=\"$FPM_FC\""                  >> $INFERENCE_ENGINE_PC
+FIATS_PC="$PKG_CONFIG_PATH/fiats.pc"
+echo "FIATS_FPM_CC=\"$FPM_CC\""                   >  $FIATS_PC
+echo "FIATS_FPM_FC=\"$FPM_FC\""                   >> $FIATS_PC
 if [[ ! -z ${FPM_RUNNER:-} ]];  then
-  echo "INFERENCE_ENGINE_FPM_RUNNER=\"$FPM_RUNNER\""          >> $INFERENCE_ENGINE_PC
+  echo "FIATS_FPM_RUNNER=\"$FPM_RUNNER\""         >> $FIATS_PC
 fi
-echo "INFERENCE_ENGINE_FPM_LD_FLAG=\"$FPM_LD_FLAG\""        >> $INFERENCE_ENGINE_PC
-echo "INFERENCE_ENGINE_FPM_FLAG=\"$FPM_FLAG\""              >> $INFERENCE_ENGINE_PC
-echo "Name: inference-engine"                               >> $INFERENCE_ENGINE_PC
-echo "Description: Inference Engine"                        >> $INFERENCE_ENGINE_PC
-echo "URL: https://github.com/berkeleylab/inference-engine" >> $INFERENCE_ENGINE_PC
-echo "Version: $INFERENCE_ENGINE_VERSION"                   >> $INFERENCE_ENGINE_PC
+echo "FIATS_FPM_LD_FLAG=\"$FPM_LD_FLAG\""         >> $FIATS_PC
+echo "FIATS_FPM_FLAG=\"$FPM_FLAG\""               >> $FIATS_PC
+echo "Name: fiats"                                >> $FIATS_PC
+echo "Description: Fiats"                         >> $FIATS_PC
+echo "URL: https://github.com/berkeleylab/fiats " >> $FIATS_PC
+echo "Version: $FIATS_VERSION"                    >> $FIATS_PC
 
 if [ $CI = true ]; then
   echo "---------------"
-  echo "cat \$INFERENCE_ENGINE_PC"
-  cat $INFERENCE_ENGINE_PC
+  echo "cat \$FIATS_PC"
+  cat $FIATS_PC
   echo "---------------"
 fi
 
@@ -139,14 +139,14 @@ cp src/run-fpm.sh-header build/run-fpm.sh
 RUN_FPM_SH="`realpath ./build/run-fpm.sh`"
 echo "`which fpm` \$fpm_arguments \\" >>  $RUN_FPM_SH
 echo "--profile release \\" >> $RUN_FPM_SH
-echo "--c-compiler \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_CC`\" \\"     >> $RUN_FPM_SH
-echo "--compiler \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_FC`\" \\"       >> $RUN_FPM_SH
+echo "--c-compiler \"`pkg-config fiats --variable=FIATS_FPM_CC`\" \\"     >> $RUN_FPM_SH
+echo "--compiler \"`pkg-config fiats --variable=FIATS_FPM_FC`\" \\"       >> $RUN_FPM_SH
 if [[ ! -z ${FPM_RUNNER:-} ]];  then
-  echo "--runner \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_RUNNER`\" \\"     >> $RUN_FPM_SH
+  echo "--runner \"`pkg-config fiats --variable=FIATS_FPM_RUNNER`\" \\"   >> $RUN_FPM_SH
 fi
-echo "--flag \"-cpp `pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_FLAG`\" \\"    >> $RUN_FPM_SH
-echo "--link-flag \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_LD_FLAG`\" \\" >> $RUN_FPM_SH
-echo "\$program_arguments" >> $RUN_FPM_SH
+echo "--flag \"-cpp `pkg-config fiats --variable=FIATS_FPM_FLAG`\" \\"    >> $RUN_FPM_SH
+echo "--link-flag \"`pkg-config fiats --variable=FIATS_FPM_LD_FLAG`\" \\" >> $RUN_FPM_SH
+echo "\$program_arguments"                                                           >> $RUN_FPM_SH
 chmod u+x $RUN_FPM_SH
 if [ $CI = true ]; then
   echo "---------------"
@@ -158,10 +158,10 @@ fi
 $RUN_FPM_SH build
 
 echo ""
-echo "____________________ The inference-engine demo apps build succeeded! _______________________"
+echo "____________________ The fiats demo apps build succeeded! _______________________"
 echo ""
 echo "Run the following command to see a list of available apps:"
 echo ""
 echo "./build/run-fpm.sh run"
 echo ""
-echo "Append a space followe by an app's name to see basic app usage information."
+echo "Append a space followed by an app's name to see basic app usage information."
diff --git a/example/concurrent-inferences.f90 b/example/concurrent-inferences.f90
index 5036e67b7..9a50ae249 100644
--- a/example/concurrent-inferences.f90
+++ b/example/concurrent-inferences.f90
@@ -3,7 +3,7 @@
 program concurrent_inferences
   !! This program demonstrates how to read a neural network from a JSON file
   !! and use the network to perform concurrent inferences.
-  use inference_engine_m, only : inference_engine_t, tensor_t, double_precision, double_precision_string_t, double_precision_file_t
+  use fiats_m, only : neural_network_t, tensor_t, double_precision, double_precision_string_t, double_precision_file_t
   use julienne_m, only : string_t, command_line_t, file_t
   use assert_m, only : assert
   use iso_fortran_env, only : int64, real64
@@ -27,17 +27,17 @@ program concurrent_inferences
     block
       integer(int64) t_start, t_finish, clock_rate
 
-      type(inference_engine_t) inference_engine
+      type(neural_network_t) neural_network
       type(tensor_t), allocatable :: inputs(:,:,:), outputs(:,:,:)
       real, allocatable :: input_components(:,:,:,:)
 
-      print *, "Constructing a new inference_engine_t object from the file " // network_file_name%string()
-      inference_engine = inference_engine_t(file_t(network_file_name))
+      print *, "Constructing a new neural_network_t object from the file " // network_file_name%string()
+      neural_network = neural_network_t(file_t(network_file_name))
 
       print *,"Defining an array of tensor_t input objects with random normalized components"
       allocate(outputs(lat,lon,lev))
       allocate( inputs(lat,lon,lev))
-      allocate(input_components(lat,lon,lev,inference_engine%num_inputs()))
+      allocate(input_components(lat,lon,lev,neural_network%num_inputs()))
       call random_number(input_components)
 
       do concurrent(i=1:lat, j=1:lon, k=1:lev)
@@ -47,7 +47,7 @@ program concurrent_inferences
       print *,"Performing concurrent inference"
       call system_clock(t_start, clock_rate)
       do concurrent(i=1:lat, j=1:lon, k=1:lev)
-        outputs(i,j,k) = inference_engine%infer(inputs(i,j,k))
+        outputs(i,j,k) = neural_network%infer(inputs(i,j,k))
       end do
       call system_clock(t_finish)
       print *,"Concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
@@ -57,7 +57,7 @@ program concurrent_inferences
       do k=1,lev
         do j=1,lon
           do i=1,lat
-            outputs(i,j,k) = inference_engine%infer(inputs(i,j,k))
+            outputs(i,j,k) = neural_network%infer(inputs(i,j,k))
           end do
         end do
       end do
@@ -66,7 +66,7 @@ program concurrent_inferences
 
       print *,"Performing elemental inferences"
       call system_clock(t_start, clock_rate)
-      outputs = inference_engine%infer(inputs)  ! implicit (re)allocation of outputs array only if shape(inputs) /= shape(outputs)
+      outputs = neural_network%infer(inputs)  ! implicit (re)allocation of outputs array only if shape(inputs) /= shape(outputs)
       call system_clock(t_finish)
       print *,"Elemental inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
 
@@ -76,17 +76,17 @@ program concurrent_inferences
     block
       integer(int64) t_start, t_finish, clock_rate
 
-      type(inference_engine_t(double_precision)) inference_engine
+      type(neural_network_t(double_precision)) neural_network
       type(tensor_t(double_precision)), allocatable :: inputs(:,:,:), outputs(:,:,:)
       double precision, allocatable :: input_components(:,:,:,:)
 
-      print *, "Constructing a new inference_engine_t object from the file " // network_file_name%string()
-      inference_engine = inference_engine_t(double_precision_file_t(network_file_name))
+      print *, "Constructing a new neural_network_t object from the file " // network_file_name%string()
+      neural_network = neural_network_t(double_precision_file_t(network_file_name))
 
       print *,"Defining an array of tensor_t input objects with random normalized components"
       allocate(outputs(lat,lon,lev))
       allocate( inputs(lat,lon,lev))
-      allocate(input_components(lat,lon,lev,inference_engine%num_inputs()))
+      allocate(input_components(lat,lon,lev,neural_network%num_inputs()))
       call random_number(input_components)
 
       do concurrent(i=1:lat, j=1:lon, k=1:lev)
@@ -96,7 +96,7 @@ program concurrent_inferences
       print *,"Performing double-precision concurrent inference"
       call system_clock(t_start, clock_rate)
       do concurrent(i=1:lat, j=1:lon, k=1:lev)
-        outputs(i,j,k) = inference_engine%infer(inputs(i,j,k))           
+        outputs(i,j,k) = neural_network%infer(inputs(i,j,k))           
       end do
       call system_clock(t_finish)
       print *,"Double-precision concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64)
diff --git a/example/learn-addition.F90 b/example/learn-addition.F90
index b6174650f..a5ca64ffa 100644
--- a/example/learn-addition.F90
+++ b/example/learn-addition.F90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module addition_m
   !! Define a function that produces the desired network output for a given network input
-  use inference_engine_m, only : tensor_t
+  use fiats_m, only : tensor_t
   use assert_m, only : assert
   implicit none
 
@@ -20,8 +20,8 @@ elemental function y(x_tensor) result(a_tensor)
 
 program learn_addition
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+  use fiats_m, only : &
+    neural_network_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use addition_m, only : y
@@ -107,11 +107,11 @@ program learn_addition
 
 contains
 
-  subroutine output(inference_engine, file_name)
-    class(inference_engine_t), intent(in) :: inference_engine
+  subroutine output(neural_network, file_name)
+    class(neural_network_t), intent(in) :: neural_network
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
-    json_file = inference_engine%to_json()
+    json_file = neural_network%to_json()
     call json_file%write_lines(file_name)
   end subroutine
 
@@ -140,7 +140,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_net
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_network = trainable_network_t( inference_engine_t( &
+      trainable_network = trainable_network_t( neural_network_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       ))
diff --git a/example/learn-exponentiation.F90 b/example/learn-exponentiation.F90
index a09b9359d..cfb549b85 100644
--- a/example/learn-exponentiation.F90
+++ b/example/learn-exponentiation.F90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module exponentiation_m
   !! Define a function that produces the desired network output for a given network input
-  use inference_engine_m, only : tensor_t
+  use fiats_m, only : tensor_t
   use assert_m, only : assert
   implicit none
 
@@ -20,8 +20,7 @@ elemental function y(x_tensor) result(a_tensor)
 
 program learn_exponentiation
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+  use fiats_m, only : neural_network_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use exponentiation_m, only : y
@@ -107,11 +106,11 @@ program learn_exponentiation
 
 contains
 
-  subroutine output(inference_engine, file_name)
-    class(inference_engine_t), intent(in) :: inference_engine
+  subroutine output(neural_network, file_name)
+    class(neural_network_t), intent(in) :: neural_network
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
-    json_file = inference_engine%to_json()
+    json_file = neural_network%to_json()
     call json_file%write_lines(file_name)
   end subroutine
 
@@ -140,7 +139,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_net
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_network = trainable_network_t( inference_engine_t( &
+      trainable_network = trainable_network_t( neural_network_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       ))
diff --git a/example/learn-multiplication.F90 b/example/learn-multiplication.F90
index 81d72e88d..116c961c0 100644
--- a/example/learn-multiplication.F90
+++ b/example/learn-multiplication.F90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module multiply_inputs
   !! Define a function that produces the desired network output for a given network input
-  use inference_engine_m, only : tensor_t
+  use fiats_m, only : tensor_t
   use assert_m, only : assert
   implicit none
 
@@ -20,8 +20,7 @@ elemental function y(x_tensor) result(a_tensor)
 
 program learn_multiplication
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+  use fiats_m, only : neural_network_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use multiply_inputs, only : y
@@ -107,11 +106,11 @@ program learn_multiplication
 
 contains
 
-  subroutine output(inference_engine, file_name)
-    class(inference_engine_t), intent(in) :: inference_engine
+  subroutine output(neural_network, file_name)
+    class(neural_network_t), intent(in) :: neural_network
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
-    json_file = inference_engine%to_json()
+    json_file = neural_network%to_json()
     call json_file%write_lines(file_name)
   end subroutine
 
@@ -140,7 +139,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_net
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_network = trainable_network_t( inference_engine_t( &
+      trainable_network = trainable_network_t( neural_network_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       ))
diff --git a/example/learn-power-series.F90 b/example/learn-power-series.F90
index e8a46e6dc..3cbcc2719 100644
--- a/example/learn-power-series.F90
+++ b/example/learn-power-series.F90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 module power_series
   !! Define a function that produces the desired network output for a given network input
-  use inference_engine_m, only : tensor_t
+  use fiats_m, only : tensor_t
   use assert_m, only : assert
   implicit none
 
@@ -20,8 +20,7 @@ elemental function y(x_in) result(a)
 
 program learn_power_series
   !! This trains a neural network to learn the following six polynomial functions of its eight inputs.
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+  use fiats_m, only : neural_network_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   use power_series, only : y
@@ -109,11 +108,11 @@ program learn_power_series
 
 contains
 
-  subroutine output(inference_engine, file_name)
-    class(inference_engine_t), intent(in) :: inference_engine
+  subroutine output(neural_network, file_name)
+    class(neural_network_t), intent(in) :: neural_network
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
-    json_file = inference_engine%to_json()
+    json_file = neural_network%to_json()
     call json_file%write_lines(file_name)
   end subroutine
 
@@ -142,7 +141,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_net
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_network = trainable_network_t( inference_engine_t( &
+      trainable_network = trainable_network_t( neural_network_t( &
         nodes = n, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2023-09-23"), string_t("relu"), string_t("false")] &
       ))
diff --git a/example/learn-saturated-mixing-ratio.F90 b/example/learn-saturated-mixing-ratio.F90
index bf49da58c..29313e2c7 100644
--- a/example/learn-saturated-mixing-ratio.F90
+++ b/example/learn-saturated-mixing-ratio.F90
@@ -2,8 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 program train_saturated_mixture_ratio
   !! This program trains a neural network to learn the saturated mixing ratio function of ICAR.
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+  use fiats_m, only : neural_network_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t, csv
   use assert_m, only : assert, intrinsic_array_t
   use saturated_mixing_ratio_m, only : y, T, p
@@ -43,7 +42,7 @@ program train_saturated_mixture_ratio
 
     if (io_status == io_success) then
       print *,"Reading network from file " // network_file%string()
-      trainable_network = trainable_network_t(inference_engine_t(file_t(network_file)))
+      trainable_network = trainable_network_t(neural_network_t(file_t(network_file)))
       close(network_unit)
     else
       close(network_unit)
@@ -155,11 +154,11 @@ subroutine print_diagnostics(plot_file_unit, epoch, cost, clock, nodes)
      write(unit=plot_file_unit, fmt=csv) nodes
   end subroutine
 
-  subroutine output(inference_engine, file_name)
-    class(inference_engine_t), intent(in) :: inference_engine
+  subroutine output(neural_network, file_name)
+    class(neural_network_t), intent(in) :: neural_network
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
-    json_file = inference_engine%to_json()
+    json_file = neural_network%to_json()
     call json_file%write_lines(file_name)
   end subroutine
 
@@ -187,7 +186,7 @@ function perturbed_identity_network(perturbation_magnitude, n) result(trainable_
       call random_number(b_harvest)
 
       associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
-        trainable_network = trainable_network_t( inference_engine_t(nodes=n, weights=w, biases=b, &
+        trainable_network = trainable_network_t( neural_network_t(nodes=n, weights=w, biases=b, &
           metadata=[string_t("Saturated Mixing Ratio"),string_t("Rouson"),string_t("20241013"),string_t("relu"),string_t("false")] &
         ))
       end associate
diff --git a/example/print-training-configuration.F90 b/example/print-training-configuration.F90
index cf6f4996b..36ed0983f 100644
--- a/example/print-training-configuration.F90
+++ b/example/print-training-configuration.F90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 program print_training_configuration
   !! Demonstrate how to construct and print a training_configuration_t object
-  use inference_engine_m, only : training_configuration_t, hyperparameters_t, network_configuration_t
+  use fiats_m, only : training_configuration_t, hyperparameters_t, network_configuration_t
   use julienne_m, only : file_t
   implicit none
 #ifdef _CRAYFTN
diff --git a/example/read-query-infer.f90 b/example/read-query-infer.f90
index 86889192b..66bb7c7b0 100644
--- a/example/read-query-infer.f90
+++ b/example/read-query-infer.f90
@@ -4,7 +4,7 @@ program read_query_infer
   !! This program demonstrates how to read a neural network from a JSON file,
   !! query the network object for some of its properties, print those properties,
   !! and use the network to perform inference.  
-  use inference_engine_m, only : inference_engine_t, tensor_t
+  use fiats_m, only : neural_network_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
   implicit none
 
@@ -17,16 +17,16 @@ program read_query_infer
         'Usage: fpm run --example read-query -- --input-file "<file-name>"' 
     end if
 
-    print *, "Reading an inference_engine_t object from the same JSON file '"//file_name%string()//"'."
-    associate(inference_engine => inference_engine_t(file_t(file_name)))
+    print *, "Reading an neural_network_t object from the same JSON file '"//file_name%string()//"'."
+    associate(neural_network => neural_network_t(file_t(file_name)))
 
-      print *, "Querying the new inference_engine_t object for several properties:"
-      associate(activation_name => inference_engine%activation_function_name())
+      print *, "Querying the new neural_network_t object for several properties:"
+      associate(activation_name => neural_network%activation_function_name())
         print *, "Activation function: ", activation_name%string()
       end associate
-      print *, "Number of outputs:", inference_engine%num_outputs()
-      print *, "Number of inputs:", inference_engine%num_inputs()
-      print *, "Nodes per layer:", inference_engine%nodes_per_layer()
+      print *, "Number of outputs:", neural_network%num_outputs()
+      print *, "Number of inputs:", neural_network%num_inputs()
+      print *, "Nodes per layer:", neural_network%nodes_per_layer()
       print *, "Performing inference:"
 
       block
@@ -41,13 +41,13 @@ program read_query_infer
         do i = 1, num_tests
           call random_number(harvest)
           associate(inputs => tensor_t(tensor_min + (tensor_max-tensor_min)*harvest))
-            associate(outputs => inference_engine%infer(inputs))
+            associate(outputs => neural_network%infer(inputs))
               print '(2(2g12.5,a,2x))', inputs%values(), "|",  outputs%values()
             end associate
           end associate
         end do
 
       end block
-    end associate ! associate(inference_engine => ...)
+    end associate ! associate(neural_network => ...)
   end associate ! associate(file_name => ...)
 end program
diff --git a/example/supporting-modules/saturated_mixing_ratio_m.f90 b/example/supporting-modules/saturated_mixing_ratio_m.f90
index e888f652d..88067358d 100644
--- a/example/supporting-modules/saturated_mixing_ratio_m.f90
+++ b/example/supporting-modules/saturated_mixing_ratio_m.f90
@@ -27,7 +27,7 @@ module saturated_mixing_ratio_m
   !! The saturated_mixing_ratio function in this module resulted from refactoring the sat_mr function
   !! in the Intermediate Complexity Atmospheric Research (ICAR) model file src/physics/mp_simple.f90.
   !! ICAR is distributed under the above MIT license.  See https://github.com/ncar/icar.
-  use inference_engine_m, only : tensor_t
+  use fiats_m, only : tensor_t
   use assert_m, only : assert
   implicit none
  
diff --git a/example/train-and-write.F90 b/example/train-and-write.F90
index 7105afde0..3594b1007 100644
--- a/example/train-and-write.F90
+++ b/example/train-and-write.F90
@@ -8,8 +8,8 @@ program train_and_write
   !! the desired network therefore contains weights corresponding to identity matrices and biases that vanish everywhere.
   !! The initial condition corresponds to the desired network with all weights and biases perturbed by a random variable
   !! that is uniformly distributed on the range [0,0.1].
-  use inference_engine_m, only : &
-    inference_engine_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
+  use fiats_m, only : &
+    neural_network_t, trainable_network_t, mini_batch_t, tensor_t, input_output_pair_t, shuffle
   use julienne_m, only : string_t, file_t, command_line_t, bin_t
   use assert_m, only : assert, intrinsic_array_t
   implicit none
@@ -98,11 +98,11 @@ program train_and_write
 
 contains
 
-  subroutine output(inference_engine, file_name)
-    class(inference_engine_t), intent(in) :: inference_engine
+  subroutine output(neural_network, file_name)
+    class(neural_network_t), intent(in) :: neural_network
     type(string_t), intent(in) :: file_name
     type(file_t) json_file
-    json_file = inference_engine%to_json()
+    json_file = neural_network%to_json()
     call json_file%write_lines(file_name)
   end subroutine
 
@@ -130,7 +130,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_net
 
     associate(w => identity + perturbation_magnitude*(w_harvest-0.5)/0.5, b => perturbation_magnitude*(b_harvest-0.5)/0.5)
 
-      trainable_network = trainable_network_t( inference_engine_t( &
+      trainable_network = trainable_network_t( neural_network_t( &
         nodes = nodes_per_layer, weights = w, biases = b, metadata = &
           [string_t("Perturbed Identity"), string_t("Damian Rouson"), string_t("2024-10-13"), string_t("relu"), string_t("false")] &
       ))
diff --git a/example/write-read-infer.F90 b/example/write-read-infer.F90
index 8786d4b24..1f0cbed31 100644
--- a/example/write-read-infer.F90
+++ b/example/write-read-infer.F90
@@ -7,7 +7,7 @@ program write_read_infer
   !! perform inference.  The network performs an identity mapping from any
   !! non-negative inputs to the corresponding outputs using a RELU activation
   !! function.
-  use inference_engine_m, only : inference_engine_t, tensor_t
+  use fiats_m, only : neural_network_t, tensor_t
   use julienne_m, only : string_t, command_line_t, file_t
   implicit none
 
@@ -25,21 +25,21 @@ program write_read_infer
 
 contains
 
-  function identity_network() result(inference_engine)
-    type(inference_engine_t) inference_engine
+  function identity_network() result(neural_network)
+    type(neural_network_t) neural_network
     integer, parameter :: nodes_per_layer(*) = [2, 2, 2]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
 #ifdef _CRAYFTN
     real, allocatable :: weights(:,:,:)
     weights = reshape([real :: [1,0, 0,1], [1,0, 0,1]], [max_n, max_n, layers-1])
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")], &
       weights = weights, &
       biases = reshape([real:: [0,0], [0,0]], [max_n, layers-1]), &
       nodes = nodes_per_layer &
     )
 #else
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [string_t("Identity"), string_t("Damian Rouson"), string_t("2023-09-18"), string_t("relu"), string_t("false")], &
       weights = reshape([real :: [1,0, 0,1], [1,0, 0,1]], [max_n, max_n, layers-1]), &
       biases = reshape([real :: [0,0], [0,0]], [max_n, layers-1]), &
@@ -53,35 +53,35 @@ subroutine write_read_query_infer(output_file_name)
     type(string_t) activation_name
     integer i, j
     integer, parameter :: num_neurons = 3, num_hidden_layers = 2
-    type(inference_engine_t) network, inference_engine
+    type(neural_network_t) network, neural_network
     type(file_t) json_output_file, json_input_file
     type(tensor_t) inputs, outputs 
 
-    print *, "Constructing an inference_engine_t neural-network object from scratch."
+    print *, "Constructing an neural_network_t neural-network object from scratch."
     network = identity_network()
 
-    print *, "Converting an inference_engine_t object to a file_t object."
+    print *, "Converting an neural_network_t object to a file_t object."
     json_output_file = network%to_json()
 
-    print *, "Writing an inference_engine_t object to the file '"//output_file_name%string()//"' in JSON format."
+    print *, "Writing an neural_network_t object to the file '"//output_file_name%string()//"' in JSON format."
     call json_output_file%write_lines(output_file_name)
 
-    print *, "Reading an inference_engine_t object from the same JSON file '"//output_file_name%string()//"'."
+    print *, "Reading an neural_network_t object from the same JSON file '"//output_file_name%string()//"'."
     json_input_file = file_t(output_file_name)
 
-    print *, "Constructing a new inference_engine_t object from the parameters read."
-    inference_engine = inference_engine_t(json_input_file)
+    print *, "Constructing a new neural_network_t object from the parameters read."
+    neural_network = neural_network_t(json_input_file)
 
-    print *, "Querying the new inference_engine_t object for several properties:"
-    print *, "Number of outputs:", inference_engine%num_outputs()
-    print *, "Number of inputs:", inference_engine%num_inputs()
-    print *, "Nodes per layer:", inference_engine%nodes_per_layer()
-    activation_name = inference_engine%activation_function_name()
+    print *, "Querying the new neural_network_t object for several properties:"
+    print *, "Number of outputs:", neural_network%num_outputs()
+    print *, "Number of inputs:", neural_network%num_inputs()
+    print *, "Nodes per layer:", neural_network%nodes_per_layer()
+    activation_name = neural_network%activation_function_name()
     print *, "Activation function: ", activation_name%string()
     print *, "Performing inference:"
     inputs = tensor_t([2.,3.])
     print *, "Inputs: ", inputs%values()
-    outputs = inference_engine%infer(inputs)
+    outputs = neural_network%infer(inputs)
     print *, "Actual outputs: ", outputs%values()
     print *, "Correct outputs:  ", inputs%values()
   end subroutine write_read_query_infer
diff --git a/fpm.toml b/fpm.toml
index 92ba67a53..fbf885f9c 100644
--- a/fpm.toml
+++ b/fpm.toml
@@ -1,4 +1,4 @@
-name = "inference-engine"
+name = "fiats"
 version = "0.13.1"
 license = "see LICENSE.txt"
 author = "Damian Rouson, Tan Nguyen, Jordan Welsman, David Torres, Brad Richardson, Katherine Rasmussen, Federica Villani, Dan Bonachea"
@@ -6,6 +6,4 @@ maintainer = "rouson@lbl.gov"
 
 [dependencies]
 assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.7.0"}
-
-[dev-dependencies]
 julienne = {git = "https://github.com/berkeleylab/julienne", tag = "1.3.1"}
diff --git a/setup.sh b/setup.sh
index 5908ce704..455007afa 100755
--- a/setup.sh
+++ b/setup.sh
@@ -4,13 +4,13 @@ set -e # exit on error
 
 usage()
 {
-  echo "Inference Engine Setup Script"
+  echo "Fiats Setup Script"
   echo ""
   echo "USAGE:"
   echo "./setup.sh [--help|-h]"
   echo ""
   echo " --help             Display this help text"
-  echo " --prefix=PREFIX    Install any binaries needed to build inference-engine in 'PREFIX/bin'"
+  echo " --prefix=PREFIX    Install any binaries needed to build fiats in 'PREFIX/bin'"
   echo "                    Default prefix='\$HOME/.local/bin'"
   echo ""
 }
@@ -48,10 +48,10 @@ install_fpm_from_source()
     echo "Please install curl and then rerun ./setup.sh"
     exit 1
   fi
-  mkdir temp-dir-to-build-fpm-for-inference-engine-installation
-  curl -L -o temp-dir-to-build-fpm-for-inference-engine-installation/fpm.F90 https://github.com/fortran-lang/fpm/releases/download/current/fpm.F90
-  gfortran -o $PREFIX/bin/fpm -Jtemp-dir-to-build-fpm-for-inference-engine-installation temp-dir-to-build-fpm-for-inference-engine-installation/fpm.F90
-  rm -rf temp-dir-to-build-fpm-for-inference-engine-installation
+  mkdir temp-dir-to-build-fpm-for-fiats-installation
+  curl -L -o temp-dir-to-build-fpm-for-fiats-installation/fpm.F90 https://github.com/fortran-lang/fpm/releases/download/current/fpm.F90
+  gfortran -o $PREFIX/bin/fpm -Jtemp-dir-to-build-fpm-for-fiats-installation temp-dir-to-build-fpm-for-fiats-installation/fpm.F90
+  rm -rf temp-dir-to-build-fpm-for-fiats-installation
   if command -v fpm > /dev/null ; then
     echo "fpm installed"
   else
@@ -80,7 +80,7 @@ FPM_CC=${CC:-"gcc-14"}
 fpm test --profile release --flag "-fopenmp"
 
 echo ""
-echo "____________________ Inference-Engine has been set up! _______________________"
+echo "____________________ Fiats has been set up! _______________________"
 echo ""
 echo "Enter the command below to the see names of example use cases that you can run:"
 echo ""
diff --git a/src/inference_engine/activation_m.f90 b/src/fiats/activation_m.f90
similarity index 100%
rename from src/inference_engine/activation_m.f90
rename to src/fiats/activation_m.f90
diff --git a/src/inference_engine/activation_s.f90 b/src/fiats/activation_s.f90
similarity index 100%
rename from src/inference_engine/activation_s.f90
rename to src/fiats/activation_s.f90
diff --git a/src/inference_engine/double_precision_file_m.f90 b/src/fiats/double_precision_file_m.f90
similarity index 100%
rename from src/inference_engine/double_precision_file_m.f90
rename to src/fiats/double_precision_file_m.f90
diff --git a/src/inference_engine/double_precision_file_s.f90 b/src/fiats/double_precision_file_s.f90
similarity index 100%
rename from src/inference_engine/double_precision_file_s.f90
rename to src/fiats/double_precision_file_s.f90
diff --git a/src/inference_engine/double_precision_string_m.f90 b/src/fiats/double_precision_string_m.f90
similarity index 100%
rename from src/inference_engine/double_precision_string_m.f90
rename to src/fiats/double_precision_string_m.f90
diff --git a/src/inference_engine/double_precision_string_s.f90 b/src/fiats/double_precision_string_s.f90
similarity index 100%
rename from src/inference_engine/double_precision_string_s.f90
rename to src/fiats/double_precision_string_s.f90
diff --git a/src/inference_engine/hyperparameters_m.f90 b/src/fiats/hyperparameters_m.f90
similarity index 100%
rename from src/inference_engine/hyperparameters_m.f90
rename to src/fiats/hyperparameters_m.f90
diff --git a/src/inference_engine/hyperparameters_s.f90 b/src/fiats/hyperparameters_s.f90
similarity index 100%
rename from src/inference_engine/hyperparameters_s.f90
rename to src/fiats/hyperparameters_s.f90
diff --git a/src/inference_engine/input_output_pair_m.f90 b/src/fiats/input_output_pair_m.f90
similarity index 100%
rename from src/inference_engine/input_output_pair_m.f90
rename to src/fiats/input_output_pair_m.f90
diff --git a/src/inference_engine/input_output_pair_s.f90 b/src/fiats/input_output_pair_s.f90
similarity index 100%
rename from src/inference_engine/input_output_pair_s.f90
rename to src/fiats/input_output_pair_s.f90
diff --git a/src/inference_engine/kind_parameters_m.f90 b/src/fiats/kind_parameters_m.f90
similarity index 100%
rename from src/inference_engine/kind_parameters_m.f90
rename to src/fiats/kind_parameters_m.f90
diff --git a/src/inference_engine/layer_m.f90 b/src/fiats/layer_m.f90
similarity index 89%
rename from src/inference_engine/layer_m.f90
rename to src/fiats/layer_m.f90
index cc418e8d6..390f3bfa6 100644
--- a/src/inference_engine/layer_m.f90
+++ b/src/fiats/layer_m.f90
@@ -4,7 +4,7 @@ module layer_m
   use double_precision_string_m, only : double_precision_string_t
   use kind_parameters_m, only : default_real, double_precision
   use julienne_string_m, only : string_t
-  use inference_engine_m_, only : inference_engine_t
+  use neural_network_m, only : neural_network_t
   use metadata_m, only : metadata_t
   use neuron_m, only : neuron_t
   use tensor_map_m, only : tensor_map_t
@@ -19,8 +19,8 @@ module layer_m
     type(neuron_t(k)),             private :: neuron !! linked list of this layer's neurons 
     type(layer_t(k)), allocatable, private :: next !! next layer
   contains
-    generic :: inference_engine => default_real_inference_engine, double_precision_inference_engine
-    procedure, private ::          default_real_inference_engine, double_precision_inference_engine
+    generic :: neural_network => default_real_neural_network, double_precision_neural_network
+    procedure, private ::          default_real_neural_network, double_precision_neural_network
     generic :: count_layers => default_real_count_layers, double_precision_count_layers
     procedure, private ::      default_real_count_layers, double_precision_count_layers
     generic :: count_neurons => default_real_count_neurons, double_precision_count_neurons
@@ -57,22 +57,22 @@ recursive module function double_precision_construct_layer(layer_lines, start) r
 
   interface
 
-    module function default_real_inference_engine(hidden_layers, metadata, output_layer, input_map, output_map) result(inference_engine_)
+    module function default_real_neural_network(hidden_layers, metadata, output_layer, input_map, output_map) result(neural_network_)
       implicit none
       class(layer_t), intent(in), target :: hidden_layers
       type(layer_t), intent(in), target :: output_layer
       type(string_t), intent(in) :: metadata(:)
       type(tensor_map_t), intent(in) :: input_map, output_map
-      type(inference_engine_t) inference_engine_
+      type(neural_network_t) neural_network_
     end function
 
-    module function double_precision_inference_engine(hidden_layers, metadata, output_layer, input_map, output_map) result(inference_engine_)
+    module function double_precision_neural_network(hidden_layers, metadata, output_layer, input_map, output_map) result(neural_network_)
       implicit none
       class(layer_t(double_precision)), intent(in), target :: hidden_layers
       type(layer_t(double_precision)), intent(in), target :: output_layer
       type(metadata_t), intent(in) :: metadata
       type(tensor_map_t(double_precision)), intent(in) :: input_map, output_map
-      type(inference_engine_t(double_precision)) inference_engine_
+      type(neural_network_t(double_precision)) neural_network_
     end function
 
     module function default_real_count_layers(layer) result(num_layers)
diff --git a/src/inference_engine/layer_s.f90 b/src/fiats/layer_s.f90
similarity index 93%
rename from src/inference_engine/layer_s.f90
rename to src/fiats/layer_s.f90
index 231514e2f..589740b36 100644
--- a/src/inference_engine/layer_s.f90
+++ b/src/fiats/layer_s.f90
@@ -68,7 +68,7 @@
 
   end procedure
 
-  module procedure default_real_inference_engine
+  module procedure default_real_neural_network
 
     associate( &
       num_inputs => hidden_layers%count_inputs(), &
@@ -77,7 +77,7 @@
       num_hidden_layers =>  hidden_layers%count_layers(), &
       num_output_layers => output_layer%count_layers() &
     )   
-      call assert(num_output_layers==1, "inference_engine_s(default_real_inference_engine): 1 output layer", num_output_layers)
+      call assert(num_output_layers==1, "neural_network_s(default_real_neural_network): 1 output layer", num_output_layers)
 
       associate(nodes => [num_inputs, neurons_per_hidden_layer, num_outputs])
         associate(n_max => maxval(nodes))
@@ -132,15 +132,15 @@
 
             end do loop_over_output_neurons
 
-            inference_engine_ = inference_engine_t(metadata, weights, biases, nodes, input_map, output_map)
+            neural_network_ = neural_network_t(metadata, weights, biases, nodes, input_map, output_map)
           end block
         end associate
       end associate
     end associate
     
-  end procedure default_real_inference_engine
+  end procedure default_real_neural_network
 
-  module procedure double_precision_inference_engine
+  module procedure double_precision_neural_network
 
     associate( &
       num_inputs => hidden_layers%count_inputs(), &
@@ -149,7 +149,7 @@
       num_hidden_layers =>  hidden_layers%count_layers(), &
       num_output_layers => output_layer%count_layers() &
     )   
-      call assert(num_output_layers==1, "inference_engine_s(double_precision_inference_engine): 1 output layer", num_output_layers)
+      call assert(num_output_layers==1, "neural_network_s(double_precision_neural_network): 1 output layer", num_output_layers)
 
       associate(nodes => [num_inputs, neurons_per_hidden_layer, num_outputs])
         associate(n_max => maxval(nodes))
@@ -204,13 +204,13 @@
 
             end do loop_over_output_neurons
 
-            inference_engine_ = inference_engine_t(metadata, weights, biases, nodes, input_map, output_map)
+            neural_network_ = neural_network_t(metadata, weights, biases, nodes, input_map, output_map)
           end block
         end associate
       end associate
     end associate
     
-  end procedure double_precision_inference_engine
+  end procedure double_precision_neural_network
 
   module procedure default_real_count_layers
 
diff --git a/src/inference_engine/metadata_m.f90 b/src/fiats/metadata_m.f90
similarity index 100%
rename from src/inference_engine/metadata_m.f90
rename to src/fiats/metadata_m.f90
diff --git a/src/inference_engine/metadata_s.f90 b/src/fiats/metadata_s.f90
similarity index 100%
rename from src/inference_engine/metadata_s.f90
rename to src/fiats/metadata_s.f90
diff --git a/src/inference_engine/mini_batch_m.f90 b/src/fiats/mini_batch_m.f90
similarity index 100%
rename from src/inference_engine/mini_batch_m.f90
rename to src/fiats/mini_batch_m.f90
diff --git a/src/inference_engine/mini_batch_s.f90 b/src/fiats/mini_batch_s.f90
similarity index 100%
rename from src/inference_engine/mini_batch_s.f90
rename to src/fiats/mini_batch_s.f90
diff --git a/src/inference_engine/network_configuration_m.f90 b/src/fiats/network_configuration_m.f90
similarity index 100%
rename from src/inference_engine/network_configuration_m.f90
rename to src/fiats/network_configuration_m.f90
diff --git a/src/inference_engine/network_configuration_s.F90 b/src/fiats/network_configuration_s.F90
similarity index 100%
rename from src/inference_engine/network_configuration_s.F90
rename to src/fiats/network_configuration_s.F90
diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/fiats/neural_network_m.f90
similarity index 81%
rename from src/inference_engine/inference_engine_m_.f90
rename to src/fiats/neural_network_m.f90
index 40121b4cf..eb72c1524 100644
--- a/src/inference_engine/inference_engine_m_.f90
+++ b/src/fiats/neural_network_m.f90
@@ -1,7 +1,7 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-module inference_engine_m_
-  !! Define an abstraction that supports inference operationsn on a neural network
+module neural_network_m
+  !! Define an abstraction that supports inference operations on a neural network
   use activation_m, only : activation_t
   use double_precision_file_m, only : double_precision_file_t
   use kind_parameters_m, only : default_real, double_precision
@@ -13,13 +13,13 @@ module inference_engine_m_
   implicit none
 
   private
-  public :: inference_engine_t
+  public :: neural_network_t
   public :: unmapped_network_t
   public :: exchange_t
   public :: workspace_t
 
-  type inference_engine_t(k)
-    !! Encapsulate the minimal information needed to perform inference
+  type neural_network_t(k)
+    !! Encapsulate the information needed to perform inference
     integer, kind :: k = default_real 
     type(tensor_map_t(k)), private :: input_map_, output_map_
     type(metadata_t), private :: metadata_
@@ -73,9 +73,9 @@ module inference_engine_m_
 
   interface workspace_t
 
-    pure module function default_real_workspace(inference_engine) result(workspace)
+    pure module function default_real_workspace(neural_network) result(workspace)
       implicit none
-      type(inference_engine_t), intent(in) :: inference_engine
+      type(neural_network_t), intent(in) :: neural_network
       type(workspace_t) workspace
     end function
 
@@ -83,10 +83,10 @@ pure module function default_real_workspace(inference_engine) result(workspace)
 
   interface
 
-    module subroutine default_real_allocate(self, inference_engine)
+    module subroutine default_real_allocate(self, neural_network)
       implicit none
       class(workspace_t), intent(inout) :: self
-      type(inference_engine_t), intent(in) :: inference_engine
+      type(neural_network_t), intent(in) :: neural_network
     end subroutine
 
     pure module function default_real_allocated(self) result(all_allocated)
@@ -110,50 +110,50 @@ pure module function default_real_allocated(self) result(all_allocated)
 
     module function default_real_to_exchange(self) result(exchange)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       type(exchange_t) exchange
     end function
 
     module function double_precision_to_exchange(self) result(exchange)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       type(exchange_t(double_precision)) exchange
     end function
 
   end interface
 
-  interface inference_engine_t
+  interface neural_network_t
 
     module function default_real_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
-      result(inference_engine)
+      result(neural_network)
       implicit none
       type(string_t), intent(in) :: metadata(:)
       real, intent(in) :: weights(:,:,:), biases(:,:)
       integer, intent(in) :: nodes(0:)
       type(tensor_map_t), intent(in), optional :: input_map, output_map
-      type(inference_engine_t) inference_engine
+      type(neural_network_t) neural_network
     end function
 
     module function double_precision_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
-      result(inference_engine)
+      result(neural_network)
       implicit none
       type(metadata_t), intent(in) :: metadata
       double precision, intent(in) :: weights(:,:,:), biases(:,:)
       integer, intent(in) :: nodes(0:)
       type(tensor_map_t(double_precision)), intent(in), optional :: input_map, output_map
-      type(inference_engine_t(double_precision)) inference_engine
+      type(neural_network_t(double_precision)) neural_network
     end function
 
-    impure elemental module function default_real_from_json(file_) result(inference_engine)
+    impure elemental module function default_real_from_json(file_) result(neural_network)
       implicit none
       type(file_t), intent(in) :: file_
-      type(inference_engine_t) inference_engine
+      type(neural_network_t) neural_network
     end function
 
-    impure elemental module function double_precision_from_json(file) result(inference_engine)
+    impure elemental module function double_precision_from_json(file) result(neural_network)
       implicit none
       type(double_precision_file_t), intent(in) :: file
-      type(inference_engine_t(double_precision)) inference_engine
+      type(neural_network_t(double_precision)) neural_network
     end function
 
   end interface
@@ -161,26 +161,26 @@ impure elemental module function double_precision_from_json(file) result(inferen
 
 
 
-  interface ! inference_engine_t type-bound procedures
+  interface ! neural_network_t type-bound procedures
 
     elemental module function default_real_approximately_equal(lhs, rhs) result(lhs_eq_rhs)
       !! The result is true if lhs and rhs are the same to within a tolerance
       implicit none
-      class(inference_engine_t), intent(in) :: lhs, rhs
+      class(neural_network_t), intent(in) :: lhs, rhs
       logical lhs_eq_rhs
     end function
 
     elemental module function double_precision_approximately_equal(lhs, rhs) result(lhs_eq_rhs)
       !! The result is true if lhs and rhs are the same to within a tolerance
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: lhs, rhs
+      class(neural_network_t(double_precision)), intent(in) :: lhs, rhs
       logical lhs_eq_rhs
     end function
 
     elemental module function default_real_map_to_input_range(self, tensor) result(normalized_tensor)
       !! The result contains the input tensor values normalized to fall on the range used during training
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       type(tensor_t), intent(in) :: tensor
       type(tensor_t) normalized_tensor
     end function
@@ -188,7 +188,7 @@ elemental module function default_real_map_to_input_range(self, tensor) result(n
     elemental module function double_precision_map_to_input_range(self, tensor) result(normalized_tensor)
       !! The result contains the input tensor values normalized to fall on the range used during training
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       type(tensor_t(double_precision)), intent(in) :: tensor
       type(tensor_t(double_precision)) normalized_tensor
     end function
@@ -196,7 +196,7 @@ elemental module function double_precision_map_to_input_range(self, tensor) resu
     elemental module function default_real_map_from_output_range(self, normalized_tensor) result(tensor)
       !! The result contains the output tensor values unmapped via the inverse of the mapping used in training
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       type(tensor_t), intent(in) :: normalized_tensor
       type(tensor_t) tensor
     end function
@@ -204,124 +204,124 @@ elemental module function default_real_map_from_output_range(self, normalized_te
     elemental module function double_precision_map_from_output_range(self, normalized_tensor) result(tensor)
       !! The result contains the output tensor values unmapped via the inverse of the mapping used in training
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       type(tensor_t(double_precision)), intent(in) :: normalized_tensor
       type(tensor_t(double_precision)) tensor
     end function
 
     impure elemental module function default_real_to_json(self) result(json_file)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       type(file_t) json_file
     end function
 
     impure elemental module function double_precision_to_json(self) result(json_file)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       type(file_t) json_file
     end function
 
-    elemental module subroutine default_real_assert_conformable_with(self, inference_engine)
+    elemental module subroutine default_real_assert_conformable_with(self, neural_network)
       implicit none
-      class(inference_engine_t), intent(in) :: self
-      type(inference_engine_t), intent(in) :: inference_engine
+      class(neural_network_t), intent(in) :: self
+      type(neural_network_t), intent(in) :: neural_network
     end subroutine
 
-    elemental module subroutine double_precision_assert_conformable_with(self, inference_engine)
+    elemental module subroutine double_precision_assert_conformable_with(self, neural_network)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
-      type(inference_engine_t(double_precision)), intent(in) :: inference_engine
+      class(neural_network_t(double_precision)), intent(in) :: self
+      type(neural_network_t(double_precision)), intent(in) :: neural_network
     end subroutine
 
     elemental module function default_real_infer(self, inputs) result(outputs)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       type(tensor_t), intent(in) :: inputs
       type(tensor_t) outputs
     end function
 
     elemental module function double_precision_infer(self, inputs) result(outputs)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       type(tensor_t(double_precision)), intent(in) :: inputs
       type(tensor_t(double_precision)) outputs
     end function
 
     elemental module function default_real_num_outputs(self) result(output_count)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       integer output_count
     end function
 
     elemental module function double_precision_num_outputs(self) result(output_count)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       integer output_count
     end function
 
     elemental module function default_real_num_hidden_layers(self) result(hidden_layer_count)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       integer hidden_layer_count
     end function
 
     elemental module function double_precision_num_hidden_layers(self) result(hidden_layer_count)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       integer hidden_layer_count
     end function
 
     elemental module function default_real_num_inputs(self) result(input_count)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       integer input_count
     end function
 
     elemental module function double_precision_num_inputs(self) result(input_count)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       integer input_count
     end function
 
     pure module function default_real_nodes_per_layer(self) result(node_count)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       integer, allocatable :: node_count(:)
     end function
 
     pure module function double_precision_nodes_per_layer(self) result(node_count)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       integer, allocatable :: node_count(:)
     end function
 
     elemental module function default_real_activation_name(self) result(activation_name)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       type(string_t) activation_name
     end function
 
     elemental module function double_precision_activation_name(self) result(activation_name)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       type(string_t) activation_name
     end function
 
     pure module function default_real_skip(self) result(use_skip_connections)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
       logical use_skip_connections
     end function
 
     pure module function double_precision_skip(self) result(use_skip_connections)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
       logical use_skip_connections
     end function
 
     pure module subroutine default_real_learn(self, mini_batches_arr, cost, adam, learning_rate, workspace)
       implicit none
-      class(inference_engine_t), intent(inout) :: self
+      class(neural_network_t), intent(inout) :: self
       type(mini_batch_t), intent(in) :: mini_batches_arr(:)
       real, intent(out), allocatable, optional :: cost(:)
       logical, intent(in) :: adam
@@ -331,12 +331,12 @@ pure module subroutine default_real_learn(self, mini_batches_arr, cost, adam, le
 
     pure module subroutine default_real_consistency(self)
       implicit none
-      class(inference_engine_t), intent(in) :: self
+      class(neural_network_t), intent(in) :: self
     end subroutine
 
     pure module subroutine double_precision_consistency(self)
       implicit none
-      class(inference_engine_t(double_precision)), intent(in) :: self
+      class(neural_network_t(double_precision)), intent(in) :: self
     end subroutine
 
   end interface
@@ -344,7 +344,7 @@ pure module subroutine double_precision_consistency(self)
   type unmapped_network_t(k)
     integer, kind :: k = default_real
     private
-    type(inference_engine_t(k)) inference_engine_
+    type(neural_network_t(k)) neural_network_
   contains
     generic :: infer                    => default_real_infer_unmapped, double_precision_infer_unmapped
     procedure, private, non_overridable :: default_real_infer_unmapped, double_precision_infer_unmapped
@@ -378,4 +378,4 @@ elemental module function double_precision_infer_unmapped(self, inputs) result(o
 
   end interface
 
-end module inference_engine_m_
+end module neural_network_m
diff --git a/src/inference_engine/inference_engine_s.F90 b/src/fiats/neural_network_s.F90
similarity index 91%
rename from src/inference_engine/inference_engine_s.F90
rename to src/fiats/neural_network_s.F90
index ac31642f6..c652f6f2c 100644
--- a/src/inference_engine/inference_engine_s.F90
+++ b/src/fiats/neural_network_s.F90
@@ -1,6 +1,6 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-submodule(inference_engine_m_) inference_engine_s
+submodule(neural_network_m) neural_network_s
   use assert_m, only : assert, intrinsic_array_t
   use double_precision_string_m, only : double_precision_string_t
   use kind_parameters_m, only : double_precision
@@ -152,17 +152,17 @@
     associate( &
       all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
-      call assert(all(all_allocated),"inference_engine_s(default_real_consistency): fully_allocated", &
+      call assert(all(all_allocated),"neural_network_s(default_real_consistency): fully_allocated", &
         intrinsic_array_t(all_allocated))
     end associate
 
     associate(max_width=>maxval(self%nodes_), component_dims=>[size(self%biases_,1), size(self%weights_,1), size(self%weights_,2)])
-      call assert(all(component_dims == max_width), "inference_engine_s(default_real_consistency): conformable arrays", &
+      call assert(all(component_dims == max_width), "neural_network_s(default_real_consistency): conformable arrays", &
         intrinsic_array_t([max_width,component_dims]))
     end associate
 
     associate(input_subscript => lbound(self%nodes_,1))
-      call assert(input_subscript == input_layer, "inference_engine_s(default_real_consistency): n base subsscript", &
+      call assert(input_subscript == input_layer, "neural_network_s(default_real_consistency): n base subsscript", &
         input_subscript)
     end associate
 
@@ -173,17 +173,17 @@
     associate( &
       all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_)]&
     )   
-      call assert(all(all_allocated),"inference_engine_s(default_real_consistency): fully_allocated", &
+      call assert(all(all_allocated),"neural_network_s(default_real_consistency): fully_allocated", &
         intrinsic_array_t(all_allocated))
     end associate
 
     associate(max_width=>maxval(self%nodes_), component_dims=>[size(self%biases_,1), size(self%weights_,1), size(self%weights_,2)])
-      call assert(all(component_dims == max_width), "inference_engine_s(default_real_consistency): conformable arrays", &
+      call assert(all(component_dims == max_width), "neural_network_s(default_real_consistency): conformable arrays", &
         intrinsic_array_t([max_width,component_dims]))
     end associate
 
     associate(input_subscript => lbound(self%nodes_,1))
-      call assert(input_subscript == input_layer, "inference_engine_s(default_real_consistency): n base subsscript", &
+      call assert(input_subscript == input_layer, "neural_network_s(default_real_consistency): n base subsscript", &
         input_subscript)
     end associate
 
@@ -191,77 +191,77 @@
 
   module procedure default_real_construct_from_components
 
-    inference_engine%metadata_ = metadata_t(metadata(1),metadata(2),metadata(3),metadata(4),metadata(5))
-    inference_engine%weights_ = weights
-    inference_engine%biases_ = biases
-    inference_engine%nodes_ = nodes
+    neural_network%metadata_ = metadata_t(metadata(1),metadata(2),metadata(3),metadata(4),metadata(5))
+    neural_network%weights_ = weights
+    neural_network%biases_ = biases
+    neural_network%nodes_ = nodes
 
     block
       integer i
 
       if (present(input_map)) then
-        inference_engine%input_map_ = input_map
+        neural_network%input_map_ = input_map
       else
         associate(num_inputs => nodes(lbound(nodes,1)))
           associate(default_minima => [(0., i=1,num_inputs)], default_maxima => [(1., i=1,num_inputs)])
-            inference_engine%input_map_ = tensor_map_t("inputs", default_minima, default_maxima)
+            neural_network%input_map_ = tensor_map_t("inputs", default_minima, default_maxima)
           end associate
         end associate
       end if
 
       if (present(output_map)) then
-        inference_engine%output_map_ = output_map
+        neural_network%output_map_ = output_map
       else
         associate(num_outputs => nodes(ubound(nodes,1)))
           associate(default_minima => [(0., i=1,num_outputs)], default_maxima => [(1., i=1,num_outputs)])
-            inference_engine%output_map_ = tensor_map_t("outputs", default_minima, default_maxima)
+            neural_network%output_map_ = tensor_map_t("outputs", default_minima, default_maxima)
           end associate
         end associate
       end if
     end block
 
-    inference_engine%activation_ = activation_t(metadata(4)%string())
+    neural_network%activation_ = activation_t(metadata(4)%string())
 
-    call inference_engine%assert_consistency()
+    call neural_network%assert_consistency()
 
   end procedure default_real_construct_from_components
 
   module procedure double_precision_construct_from_components
 
-    inference_engine%metadata_ = metadata
-    inference_engine%weights_ = weights
-    inference_engine%biases_ = biases
-    inference_engine%nodes_ = nodes
+    neural_network%metadata_ = metadata
+    neural_network%weights_ = weights
+    neural_network%biases_ = biases
+    neural_network%nodes_ = nodes
 
     block
       integer i
 
       if (present(input_map)) then
-        inference_engine%input_map_ = input_map
+        neural_network%input_map_ = input_map
       else
         associate(num_inputs => nodes(lbound(nodes,1)))
           associate(default_intercept => [(0D0, i=1,num_inputs)], default_slope => [(1D0, i=1,num_inputs)])
-            inference_engine%input_map_ = tensor_map_t("inputs", default_intercept, default_slope)
+            neural_network%input_map_ = tensor_map_t("inputs", default_intercept, default_slope)
           end associate
         end associate
       end if
 
       if (present(output_map)) then
-        inference_engine%output_map_ = output_map
+        neural_network%output_map_ = output_map
       else
         associate(num_outputs => nodes(ubound(nodes,1)))
           associate(default_intercept => [(0D0, i=1,num_outputs)], default_slope => [(1D0, i=1,num_outputs)])
-            inference_engine%output_map_ = tensor_map_t("outputs", default_intercept, default_slope)
+            neural_network%output_map_ = tensor_map_t("outputs", default_intercept, default_slope)
           end associate
         end associate
       end if
     end block
 
     associate(function_name => metadata%activation_name())
-      inference_engine%activation_ = activation_t(function_name%string())
+      neural_network%activation_ = activation_t(function_name%string())
     end associate
 
-    call inference_engine%assert_consistency()
+    call neural_network%assert_consistency()
 
   end procedure double_precision_construct_from_components
 
@@ -373,7 +373,7 @@
             lines(line) = string_t('         ]')
             line = line + 1
             lines(line) = string_t('}')
-            call assert(line == json_lines, "inference_engine_t%to_json: all lines defined", intrinsic_array_t([json_lines, line]))
+            call assert(line == json_lines, "neural_network_t%to_json: all lines defined", intrinsic_array_t([json_lines, line]))
           end associate
           json_file = file_t(lines)
         end block
@@ -489,7 +489,7 @@
             lines(line) = string_t('         ]')
             line = line + 1
             lines(line) = string_t('}')
-            call assert(line == json_lines, "inference_engine_t%to_json: all lines defined", intrinsic_array_t([json_lines, line]))
+            call assert(line == json_lines, "neural_network_t%to_json: all lines defined", intrinsic_array_t([json_lines, line]))
           end associate
           json_file = file_t(lines)
         end block
@@ -506,7 +506,7 @@
     type(layer_t) hidden_layers, output_layer
 
     lines = file_%lines()
-    call assert(adjustl(lines(1)%string())=="{", "inference_engine_s(default_real_from_json): expected outermost object '{'")
+    call assert(adjustl(lines(1)%string())=="{", "neural_network_s(default_real_from_json): expected outermost object '{'")
  
     check_git_tag: &
     block 
@@ -515,7 +515,7 @@
       tag = lines(2)%get_json_value("acceptable_engine_tag", mold="")
       call assert( &
         tag == acceptable_engine_tag &
-        ,"inference_engine_s(default_real_from_json): acceptable_engine_tag" &
+        ,"neural_network_s(default_real_from_json): acceptable_engine_tag" &
         ,tag //"(expected " //acceptable_engine_tag // ")" &
       )
     end block check_git_tag
@@ -587,15 +587,15 @@
     associate(proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")))
       associate(metadata => metadata_t(lines(l : l + size(proto_meta%to_json()) - 1)))
         associate(metadata_strings => metadata%strings())
-          inference_engine = hidden_layers%inference_engine(metadata_strings, output_layer, input_map, output_map)
+          neural_network = hidden_layers%neural_network(metadata_strings, output_layer, input_map, output_map)
           associate(function_name => metadata%activation_name())
-            inference_engine%activation_ = activation_t(function_name%string())
+            neural_network%activation_ = activation_t(function_name%string())
           end associate
         end associate
       end associate
     end associate read_metadata
 
-    call inference_engine%assert_consistency()
+    call neural_network%assert_consistency()
 
   end procedure default_real_from_json
 
@@ -608,7 +608,7 @@
     type(layer_t(double_precision)) hidden_layers, output_layer
 
     lines = file%double_precision_lines()
-    call assert(adjustl(lines(1)%string())=="{", "inference_engine_s(double_precision_from_json): expected outermost object '{'")
+    call assert(adjustl(lines(1)%string())=="{", "neural_network_s(double_precision_from_json): expected outermost object '{'")
 
     check_git_tag: &
     block
@@ -617,7 +617,7 @@
       tag = lines(2)%get_json_value("acceptable_engine_tag", mold="")
       call assert( &
         tag == acceptable_engine_tag &
-        ,"inference_engine_s(double_precision_from_json): acceptable_engine_tag" &
+        ,"neural_network_s(double_precision_from_json): acceptable_engine_tag" &
         ,tag //"(expected " //acceptable_engine_tag // ")" &
       )
     end block check_git_tag
@@ -688,14 +688,14 @@
     read_metadata: &
     associate(proto_meta => metadata_t(string_t(""),string_t(""),string_t(""),string_t(""),string_t("")))
       associate(metadata => metadata_t(lines(l : l + size(proto_meta%to_json()) - 1)))
-        inference_engine = hidden_layers%inference_engine(metadata, output_layer, input_map, output_map)
+        neural_network = hidden_layers%neural_network(metadata, output_layer, input_map, output_map)
         associate(function_name => metadata%activation_name())
-          inference_engine%activation_ = activation_t(function_name%string())
+          neural_network%activation_ = activation_t(function_name%string())
         end associate
       end associate
     end associate read_metadata
 
-    call inference_engine%assert_consistency()
+    call neural_network%assert_consistency()
 
   end procedure double_precision_from_json
 
@@ -704,14 +704,14 @@
     call self%assert_consistency()
 
     associate(equal_shapes => [ &
-      shape(self%weights_) == shape(inference_engine%weights_), &
-      shape(self%biases_) == shape(inference_engine%biases_), &
-      shape(self%nodes_) == shape(inference_engine%nodes_)  &
+      shape(self%weights_) == shape(neural_network%weights_), &
+      shape(self%biases_) == shape(neural_network%biases_), &
+      shape(self%nodes_) == shape(neural_network%nodes_)  &
      ])
       call assert(all(equal_shapes), "assert_conformable_with: all(equal_shapes)", intrinsic_array_t(equal_shapes))
     end associate
 
-    call assert(self%activation_ == inference_engine%activation_, "assert_conformable_with: activation_")
+    call assert(self%activation_ == neural_network%activation_, "assert_conformable_with: activation_")
     
   end procedure
 
@@ -720,14 +720,14 @@
     call self%assert_consistency()
 
     associate(equal_shapes => [ &
-      shape(self%weights_) == shape(inference_engine%weights_), &
-      shape(self%biases_) == shape(inference_engine%biases_), &
-      shape(self%nodes_) == shape(inference_engine%nodes_)  &
+      shape(self%weights_) == shape(neural_network%weights_), &
+      shape(self%biases_) == shape(neural_network%biases_), &
+      shape(self%nodes_) == shape(neural_network%nodes_)  &
      ])
       call assert(all(equal_shapes), "assert_conformable_with: all(equal_shapes)", intrinsic_array_t(equal_shapes))
     end associate
 
-    call assert(self%activation_ == inference_engine%activation_, "assert_conformable_with: activation_")
+    call assert(self%activation_ == neural_network%activation_, "assert_conformable_with: activation_")
     
   end procedure
 
@@ -888,7 +888,7 @@
     type(tensor_t), allocatable :: inputs(:), expected_outputs(:)
 
     call self%assert_consistency()
-    call assert(workspace%fully_allocated(), "inference_engine_s(default_real_learn): workspace%fully_allocated()")
+    call assert(workspace%fully_allocated(), "neural_network_s(default_real_learn): workspace%fully_allocated()")
 
     associate(output_layer => ubound(self%nodes_,1))
 
@@ -1072,4 +1072,4 @@
     end associate
   end procedure default_real_learn
 
-end submodule inference_engine_s
+end submodule neural_network_s
diff --git a/src/inference_engine/neuron_m.f90 b/src/fiats/neuron_m.f90
similarity index 100%
rename from src/inference_engine/neuron_m.f90
rename to src/fiats/neuron_m.f90
diff --git a/src/inference_engine/neuron_s.f90 b/src/fiats/neuron_s.f90
similarity index 100%
rename from src/inference_engine/neuron_s.f90
rename to src/fiats/neuron_s.f90
diff --git a/src/inference_engine/tensor_m.f90 b/src/fiats/tensor_m.f90
similarity index 100%
rename from src/inference_engine/tensor_m.f90
rename to src/fiats/tensor_m.f90
diff --git a/src/inference_engine/tensor_map_m.f90 b/src/fiats/tensor_map_m.f90
similarity index 100%
rename from src/inference_engine/tensor_map_m.f90
rename to src/fiats/tensor_map_m.f90
diff --git a/src/inference_engine/tensor_map_s.f90 b/src/fiats/tensor_map_s.f90
similarity index 100%
rename from src/inference_engine/tensor_map_s.f90
rename to src/fiats/tensor_map_s.f90
diff --git a/src/inference_engine/tensor_s.f90 b/src/fiats/tensor_s.f90
similarity index 100%
rename from src/inference_engine/tensor_s.f90
rename to src/fiats/tensor_s.f90
diff --git a/src/inference_engine/trainable_network_m.f90 b/src/fiats/trainable_network_m.f90
similarity index 88%
rename from src/inference_engine/trainable_network_m.f90
rename to src/fiats/trainable_network_m.f90
index 86380eb0f..0cb2cfc50 100644
--- a/src/inference_engine/trainable_network_m.f90
+++ b/src/fiats/trainable_network_m.f90
@@ -1,5 +1,5 @@
 module trainable_network_m
-  use inference_engine_m_, only : inference_engine_t, workspace_t
+  use neural_network_m, only : neural_network_t, workspace_t
   use input_output_pair_m, only : input_output_pair_t
   use julienne_m, only : string_t
   use kind_parameters_m, only : default_real
@@ -11,7 +11,7 @@ module trainable_network_m
   private
   public :: trainable_network_t 
 
-  type, extends(inference_engine_t) ::  trainable_network_t(k)
+  type, extends(neural_network_t) ::  trainable_network_t(k)
     integer, kind :: k = default_real 
     private
     type(workspace_t), private :: workspace_
@@ -24,9 +24,9 @@ module trainable_network_m
 
   interface trainable_network_t 
 
-    pure module function default_real_network(inference_engine) result(trainable_network)
+    pure module function default_real_network(neural_network) result(trainable_network)
       implicit none
-      type(inference_engine_t), intent(in) :: inference_engine
+      type(neural_network_t), intent(in) :: neural_network
       type(trainable_network_t) trainable_network
     end function 
 
diff --git a/src/inference_engine/trainable_network_s.f90 b/src/fiats/trainable_network_s.f90
similarity index 88%
rename from src/inference_engine/trainable_network_s.f90
rename to src/fiats/trainable_network_s.f90
index a4e3caa6c..12c025661 100644
--- a/src/inference_engine/trainable_network_s.f90
+++ b/src/fiats/trainable_network_s.f90
@@ -4,8 +4,8 @@
 contains
 
   module procedure default_real_network
-    trainable_network%inference_engine_t = inference_engine
-    trainable_network%workspace_ = workspace_t(inference_engine)
+    trainable_network%neural_network_t = neural_network
+    trainable_network%workspace_ = workspace_t(neural_network)
   end procedure
 
   module procedure default_real_train
@@ -42,7 +42,7 @@
           b => perturbation_magnitude*(b_harvest-0.5)/0.5 &
         )
           trainable_network = trainable_network_t( &
-            inference_engine_t(nodes=n, weights=w, biases=b, metadata=metadata, input_map=input_map, output_map=output_map) &
+            neural_network_t(nodes=n, weights=w, biases=b, metadata=metadata, input_map=input_map, output_map=output_map) &
           )
         end associate
       end associate
diff --git a/src/inference_engine/training_configuration_m.f90 b/src/fiats/training_configuration_m.f90
similarity index 100%
rename from src/inference_engine/training_configuration_m.f90
rename to src/fiats/training_configuration_m.f90
diff --git a/src/inference_engine/training_configuration_s.F90 b/src/fiats/training_configuration_s.F90
similarity index 100%
rename from src/inference_engine/training_configuration_s.F90
rename to src/fiats/training_configuration_s.F90
diff --git a/src/inference_engine/ubounds_m.f90 b/src/fiats/ubounds_m.f90
similarity index 100%
rename from src/inference_engine/ubounds_m.f90
rename to src/fiats/ubounds_m.f90
diff --git a/src/inference_engine/unmapped_network_s.f90 b/src/fiats/unmapped_network_s.f90
similarity index 66%
rename from src/inference_engine/unmapped_network_s.f90
rename to src/fiats/unmapped_network_s.f90
index 9ced289e5..4a2cf6e43 100644
--- a/src/inference_engine/unmapped_network_s.f90
+++ b/src/fiats/unmapped_network_s.f90
@@ -1,6 +1,6 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-submodule(inference_engine_m_) unmapped_network_s
+submodule(neural_network_m) unmapped_network_s
   implicit none
 
   integer, parameter :: input_layer = 0 
@@ -8,7 +8,7 @@
 contains
 
   module procedure double_precision_unmapped_from_json
-    unmapped_network%inference_engine_ = double_precision_from_json(file)
+    unmapped_network%neural_network_ = double_precision_from_json(file)
   end procedure
 
   module procedure default_real_infer_unmapped
@@ -16,15 +16,15 @@
     real, allocatable :: a(:,:)
     integer l
 
-    associate(inference_engine => self%inference_engine_)
+    associate(neural_network => self%neural_network_)
 
-      call inference_engine%assert_consistency()
+      call neural_network%assert_consistency()
 
       associate( &
-        w => inference_engine%weights_ &
-       ,b => inference_engine%biases_ &
-       ,n => inference_engine%nodes_ &
-       ,output_layer => ubound(inference_engine%nodes_,1) &
+        w => neural_network%weights_ &
+       ,b => neural_network%biases_ &
+       ,n => neural_network%nodes_ &
+       ,output_layer => ubound(neural_network%nodes_,1) &
       )
         allocate(a(maxval(n), input_layer:output_layer))
 
@@ -34,7 +34,7 @@
         do l = input_layer+1, output_layer
           associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
             if (l .lt. output_layer) then
-               a(1:n(l),l) = inference_engine%activation_%evaluate(z)
+               a(1:n(l),l) = neural_network%activation_%evaluate(z)
             else
                a(1:n(l),l) = z(1:n(l))
             end if
@@ -53,15 +53,15 @@
     double precision, allocatable :: a(:,:)
     integer l
 
-    associate(inference_engine => self%inference_engine_)
+    associate(neural_network => self%neural_network_)
 
-      call inference_engine%assert_consistency()
+      call neural_network%assert_consistency()
 
       associate( &
-        w => inference_engine%weights_ &
-       ,b => inference_engine%biases_ &
-       ,n => inference_engine%nodes_ &
-       ,output_layer => ubound(inference_engine%nodes_,1) &
+        w => neural_network%weights_ &
+       ,b => neural_network%biases_ &
+       ,n => neural_network%nodes_ &
+       ,output_layer => ubound(neural_network%nodes_,1) &
       )
 
         allocate(a(maxval(n), input_layer:output_layer))
@@ -72,7 +72,7 @@
         do l = input_layer+1, output_layer
           associate(z => matmul(w(1:n(l),1:n(l-1),l), a(1:n(l-1),l-1)) + b(1:n(l),l))
             if (l .lt. output_layer) then
-               a(1:n(l),l) = inference_engine%activation_%evaluate(z)
+               a(1:n(l),l) = neural_network%activation_%evaluate(z)
             else
                a(1:n(l),l) = z(1:n(l))
             end if
diff --git a/src/inference_engine/workspace_s.f90 b/src/fiats/workspace_s.f90
similarity index 52%
rename from src/inference_engine/workspace_s.f90
rename to src/fiats/workspace_s.f90
index f3d80139c..f3e1209d6 100644
--- a/src/inference_engine/workspace_s.f90
+++ b/src/fiats/workspace_s.f90
@@ -1,4 +1,4 @@
-submodule(inference_engine_m_) workspace_s
+submodule(neural_network_m) workspace_s
   use assert_m, only : assert
   implicit none
 
@@ -8,26 +8,26 @@
 
   module procedure default_real_workspace
 
-    allocate(workspace%dcdw, mold=inference_engine%weights_) ! Gradient of cost function with respect to weights
-    allocate(workspace%vdw , mold=inference_engine%weights_)
-    allocate(workspace%sdw , mold=inference_engine%weights_)
-    allocate(workspace%vdwc, mold=inference_engine%weights_)
-    allocate(workspace%sdwc, mold=inference_engine%weights_)
+    allocate(workspace%dcdw, mold=neural_network%weights_) ! Gradient of cost function with respect to weights
+    allocate(workspace%vdw , mold=neural_network%weights_)
+    allocate(workspace%sdw , mold=neural_network%weights_)
+    allocate(workspace%vdwc, mold=neural_network%weights_)
+    allocate(workspace%sdwc, mold=neural_network%weights_)
 
-    allocate(workspace%dcdb, mold=inference_engine%biases_ ) ! Gradient of cost function with respect with biases
-    allocate(workspace%vdb , mold=inference_engine%biases_ )
-    allocate(workspace%sdb , mold=inference_engine%biases_ )
-    allocate(workspace%vdbc, mold=inference_engine%biases_ )
-    allocate(workspace%sdbc, mold=inference_engine%biases_ )
+    allocate(workspace%dcdb, mold=neural_network%biases_ ) ! Gradient of cost function with respect with biases
+    allocate(workspace%vdb , mold=neural_network%biases_ )
+    allocate(workspace%sdb , mold=neural_network%biases_ )
+    allocate(workspace%vdbc, mold=neural_network%biases_ )
+    allocate(workspace%sdbc, mold=neural_network%biases_ )
 
     ! TODO: #if ! (F2023_LOCALITY || F2018_LOCALITY)
     !          then don't allocate a, z, and delta
 
-    allocate(workspace%z    , mold=inference_engine%biases_)
-    allocate(workspace%delta, mold=inference_engine%biases_)
+    allocate(workspace%z    , mold=neural_network%biases_)
+    allocate(workspace%delta, mold=neural_network%biases_)
 
-    associate(output_layer => ubound(inference_engine%nodes_,1))
-      allocate(workspace%a(maxval(inference_engine%nodes_), input_layer:output_layer)) ! Activations
+    associate(output_layer => ubound(neural_network%nodes_,1))
+      allocate(workspace%a(maxval(neural_network%nodes_), input_layer:output_layer)) ! Activations
     end associate
 
     call assert(workspace%fully_allocated(), "workspace_s(defalt_real_workspace): workspace allocated")
@@ -49,26 +49,26 @@
 
   module procedure default_real_allocate
 
-    call allocate_if_necessary(self%dcdw,  mold=inference_engine%weights_) ! Gradient of cost function with respect to weights
-    call allocate_if_necessary(self%vdw ,  mold=inference_engine%weights_) 
-    call allocate_if_necessary(self%sdw ,  mold=inference_engine%weights_) 
-    call allocate_if_necessary(self%vdwc,  mold=inference_engine%weights_) 
-    call allocate_if_necessary(self%sdwc,  mold=inference_engine%weights_) 
+    call allocate_if_necessary(self%dcdw,  mold=neural_network%weights_) ! Gradient of cost function with respect to weights
+    call allocate_if_necessary(self%vdw ,  mold=neural_network%weights_) 
+    call allocate_if_necessary(self%sdw ,  mold=neural_network%weights_) 
+    call allocate_if_necessary(self%vdwc,  mold=neural_network%weights_) 
+    call allocate_if_necessary(self%sdwc,  mold=neural_network%weights_) 
 
-    call allocate_if_necessary(self%dcdb , mold=inference_engine%biases_) ! Gradient of cost function with respect with biases
-    call allocate_if_necessary(self%vdb  , mold=inference_engine%biases_) 
-    call allocate_if_necessary(self%sdb  , mold=inference_engine%biases_) 
-    call allocate_if_necessary(self%vdbc , mold=inference_engine%biases_) 
-    call allocate_if_necessary(self%sdbc , mold=inference_engine%biases_) 
+    call allocate_if_necessary(self%dcdb , mold=neural_network%biases_) ! Gradient of cost function with respect with biases
+    call allocate_if_necessary(self%vdb  , mold=neural_network%biases_) 
+    call allocate_if_necessary(self%sdb  , mold=neural_network%biases_) 
+    call allocate_if_necessary(self%vdbc , mold=neural_network%biases_) 
+    call allocate_if_necessary(self%sdbc , mold=neural_network%biases_) 
 
     ! TODO: #if ! (F2023_LOCALITY || F2018_LOCALITY)
     !          then don't allocate a, z, and delta
 
-    call allocate_if_necessary(self%z    , mold=inference_engine%biases_) 
-    call allocate_if_necessary(self%delta, mold=inference_engine%biases_) 
+    call allocate_if_necessary(self%z    , mold=neural_network%biases_) 
+    call allocate_if_necessary(self%delta, mold=neural_network%biases_) 
 
-    associate(output_layer => ubound(inference_engine%nodes_,1))
-      allocate(self%a(maxval(inference_engine%nodes_), input_layer:output_layer)) ! Activations
+    associate(output_layer => ubound(neural_network%nodes_,1))
+      allocate(self%a(maxval(neural_network%nodes_), input_layer:output_layer)) ! Activations
     end associate
 
   contains
diff --git a/src/inference_engine_m.f90 b/src/fiats_m.f90
similarity index 88%
rename from src/inference_engine_m.f90
rename to src/fiats_m.f90
index 45a803ab7..4d09e40c7 100644
--- a/src/inference_engine_m.f90
+++ b/src/fiats_m.f90
@@ -1,15 +1,15 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-module inference_engine_m
+module fiats_m
  !! Specify the user-facing modules, derived types, and type parameters
  use double_precision_file_m, only : double_precision_file_t
  use double_precision_string_m, only : double_precision_string_t
  use hyperparameters_m, only : hyperparameters_t
  use input_output_pair_m, only : input_output_pair_t, shuffle, write_to_stdout
- use inference_engine_m_, only : inference_engine_t, unmapped_network_t
  use kind_parameters_m, only : default_real, double_precision
  use metadata_m, only : metadata_t
  use mini_batch_m, only : mini_batch_t
+ use neural_network_m, only : neural_network_t, unmapped_network_t
  use network_configuration_m, only : network_configuration_t
  use tensor_m, only : tensor_t
  use tensor_map_m, only : tensor_map_t
@@ -17,4 +17,4 @@ module inference_engine_m
  use training_configuration_m, only : training_configuration_t
  use ubounds_m, only : ubounds_t
  implicit none
-end module
+end module fiats_m
diff --git a/src/inference_engine/tmp b/src/inference_engine/tmp
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/asymmetric_engine_test_m.F90 b/test/asymmetric_engine_test_m.F90
index 522bff898..fe69fa759 100644
--- a/test/asymmetric_engine_test_m.F90
+++ b/test/asymmetric_engine_test_m.F90
@@ -9,7 +9,7 @@ module asymmetric_engine_test_m
     test_t, test_result_t, vector_test_description_t, test_description_substring, string_t, vector_function_strategy_t
 
   ! Internal dependencies
-  use inference_engine_m, only : inference_engine_t, tensor_t
+  use fiats_m, only : neural_network_t, tensor_t
 
   implicit none
 
@@ -31,7 +31,7 @@ module asymmetric_engine_test_m
 
   pure function subject() result(specimen)
     character(len=:), allocatable :: specimen
-    specimen = "An inference_engine_t object encoding an asymmetric XOR-AND-the-2nd-input network"
+    specimen = "An neural_network_t object encoding an asymmetric XOR-AND-the-2nd-input network"
   end function
 
   function results() result(test_results)
@@ -60,8 +60,8 @@ function results() result(test_results)
     end associate
   end function
 
-  function xor_and_2nd_input_network() result(inference_engine)
-    type(inference_engine_t) inference_engine
+  function xor_and_2nd_input_network() result(neural_network)
+    type(neural_network_t) neural_network
     real, allocatable :: biases(:,:),  weights(:,:,:)
     type(string_t), allocatable :: metadata(:)
     integer, parameter :: n(0:*) = [2,4,4,1]
@@ -107,14 +107,14 @@ function xor_and_2nd_input_network() result(inference_engine)
       biases(1:n(l),l) = [-1]
     call assert(j == n(l), "l=3; j=1: j == n(l)")
 
-    inference_engine = inference_engine_t(metadata, weights, biases, nodes = n)
+    neural_network = neural_network_t(metadata, weights, biases, nodes = n)
 
   end function
 
   function xor_and_2nd_input_truth_table() result(test_passes)
     logical, allocatable :: test_passes(:)
 
-    type(inference_engine_t) asymmetric
+    type(neural_network_t) asymmetric
 
     asymmetric = xor_and_2nd_input_network()
 
diff --git a/test/hyperparameters_test_m.F90 b/test/hyperparameters_test_m.F90
index 2313c1c91..07d1140ab 100644
--- a/test/hyperparameters_test_m.F90
+++ b/test/hyperparameters_test_m.F90
@@ -4,7 +4,7 @@ module hyperparameters_test_m
   !! Test hyperparameters_t object I/O and construction
 
   ! External dependencies
-  use inference_engine_m, only : hyperparameters_t
+  use fiats_m, only : hyperparameters_t
   use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i
diff --git a/test/main.F90 b/test/main.F90
index a21444d24..a280ae011 100644
--- a/test/main.F90
+++ b/test/main.F90
@@ -1,7 +1,7 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
 program main
-  use inference_engine_test_m, only : inference_engine_test_t
+  use neural_network_test_m, only : neural_network_test_t
   use asymmetric_engine_test_m, only : asymmetric_engine_test_t
   use trainable_network_test_m, only : trainable_network_test_t
   use metadata_test_m, only : metadata_test_t
@@ -13,7 +13,7 @@ program main
   use julienne_m, only : command_line_t
   implicit none
 
-  type(inference_engine_test_t) inference_engine_test
+  type(neural_network_test_t) neural_network_test
   type(asymmetric_engine_test_t) asymmetric_engine_test
   type(trainable_network_test_t) trainable_network_test
   type(hyperparameters_test_t) hyperparameters_test
@@ -48,7 +48,7 @@ program main
   call tensor_map_test%report(passes, tests)
   call tensor_test%report(passes, tests)
   call asymmetric_engine_test%report(passes, tests)
-  call inference_engine_test%report(passes, tests)
+  call neural_network_test%report(passes, tests)
   call trainable_network_test%report(passes, tests)
   call cpu_time(t_finish)
 
diff --git a/test/metadata_test_m.F90 b/test/metadata_test_m.F90
index b87b499b3..5c3448a70 100644
--- a/test/metadata_test_m.F90
+++ b/test/metadata_test_m.F90
@@ -4,7 +4,7 @@ module metadata_test_m
   !! Test metadata_t object I/O and construction
 
   ! External dependencies
-  use inference_engine_m, only : metadata_t
+  use fiats_m, only : metadata_t
   use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i
diff --git a/test/network_configuration_test_m.F90 b/test/network_configuration_test_m.F90
index 97e4e8a64..5ba09fc97 100644
--- a/test/network_configuration_test_m.F90
+++ b/test/network_configuration_test_m.F90
@@ -4,7 +4,7 @@ module network_configuration_test_m
   !! Test network_configuration_t object I/O and construction
 
   ! External dependencies
-  use inference_engine_m, only : network_configuration_t
+  use fiats_m, only : network_configuration_t
   use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t, file_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i
diff --git a/test/inference_engine_test_m.F90 b/test/neural_network_test_m.F90
similarity index 82%
rename from test/inference_engine_test_m.F90
rename to test/neural_network_test_m.F90
index 2d7d3071c..0207ca7d1 100644
--- a/test/inference_engine_test_m.F90
+++ b/test/neural_network_test_m.F90
@@ -1,6 +1,6 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-module inference_engine_test_m
+module neural_network_test_m
   !! Define inference tests and procedures required for reporting results
 
   ! External dependencies
@@ -12,14 +12,14 @@ module inference_engine_test_m
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : inference_engine_t, tensor_t, metadata_t
+  use fiats_m, only : neural_network_t, tensor_t, metadata_t
 
   implicit none
 
   private
-  public :: inference_engine_test_t
+  public :: neural_network_test_t
 
-  type, extends(test_t) :: inference_engine_test_t
+  type, extends(test_t) :: neural_network_test_t
   contains
     procedure, nopass :: subject
     procedure, nopass :: results
@@ -29,7 +29,7 @@ module inference_engine_test_m
 
   pure function subject() result(specimen)
     character(len=:), allocatable :: specimen
-    specimen = "An inference_engine_t that encodes an XOR gate" 
+    specimen = "An neural_network_t that encodes an XOR gate" 
   end function
 
   function results() result(test_results)
@@ -74,12 +74,12 @@ function results() result(test_results)
     test_results = test_descriptions%run()
   end function
 
-  function single_hidden_layer_xor_network() result(inference_engine)
-    type(inference_engine_t) inference_engine
+  function single_hidden_layer_xor_network() result(neural_network)
+    type(neural_network_t) neural_network
     integer, parameter :: nodes_per_layer(*) = [2, 3, 1]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
 
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [string_t("XOR"), string_t("Damian Rouson"), string_t("2023-07-02"), string_t("step"), string_t("false")], &
       weights = reshape([real:: [1,1,0, 0,1,1, 0,0,0], [1,0,0, -2,0,0, 1,0,0]], [max_n, max_n, layers-1]), &
       biases = reshape([[0.,-1.99,0.], [0., 0., 0.]], [max_n, layers-1]), &
@@ -87,12 +87,12 @@ function single_hidden_layer_xor_network() result(inference_engine)
     )
   end function
 
-  function multi_layer_xor_network() result(inference_engine)
-    type(inference_engine_t) inference_engine
+  function multi_layer_xor_network() result(neural_network)
+    type(neural_network_t) neural_network
     integer, parameter :: nodes_per_layer(*) = [2, 3, 3, 1]
     integer, parameter :: max_n = maxval(nodes_per_layer), layers = size(nodes_per_layer)
 
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [string_t("XOR"), string_t("Damian Rouson"), string_t("2023-07-02"), string_t("step"), string_t("false")], &
       weights = reshape([real:: [1,1,0, 0,1,1, 1,0,0, 1,0,0, 0,1,0, 0,0,1], [1,0,0, -2,0,0, 1,0,0]], &
         [max_n, max_n, layers-1]), &
@@ -101,13 +101,13 @@ function multi_layer_xor_network() result(inference_engine)
     )
   end function
 
-  function decrement_split_combine_increment() result(inference_engine)
+  function decrement_split_combine_increment() result(neural_network)
     !! Define a network that produces outputs identical to the 2 inputs for any input greater than or equal to 1
     !! based on the following algorithm:
     !! 1. A 1st hidden layer that forwards input 1 unmolested and decrements input 2 by 1,
     !! 2. A 2nd hidden layer that forwards input 1 unmolested and splits input 2 into two halves,
     !! 3. An output layer that recombines those two halves and increments the result by 1.
-    type(inference_engine_t) inference_engine
+    type(neural_network_t) neural_network
     integer, parameter :: inputs = 2, hidden(*) = [2,3], outputs = 2 ! number of neurons in input, output, and hidden layers
     integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n) ! max layer width, number of layers
@@ -116,7 +116,7 @@ function decrement_split_combine_increment() result(inference_engine)
       w(*,*,*) = reshape( [1.,0.,0., 0.,1.,0., 0.,0.,0., 1.,0.,0., 0.,.5,.5, 0.,0.,0., 1.,0.,0., 0.,1.,0., 0.,1.,1.], w_shape), &
       b(*,*) = reshape( [0.,-1.,0., 0.,0.,0., 0.,1.,0.], b_shape)
 
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [ &
         string_t("Decrement/Split/Combine/Increment => Identity") &
        ,string_t("Damian Rouson") &
@@ -127,9 +127,9 @@ function decrement_split_combine_increment() result(inference_engine)
     )
   end function
 
-  function double_precision_network() result(inference_engine)
+  function double_precision_network() result(neural_network)
     !! The result is a double-precision version of the this network defined in the decrement_split_combine_increment function
-    type(inference_engine_t(double_precision)) inference_engine
+    type(neural_network_t(double_precision)) neural_network
     integer, parameter :: inputs = 2, hidden(*) = [2,3], outputs = 2 ! number of neurons in input, output, and hidden layers
     integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n) ! max layer width, number of layers
@@ -139,15 +139,15 @@ function double_precision_network() result(inference_engine)
         [double precision :: 1.,0.,0., 0.,1.,0., 0.,0.,0., 1.,0.,0., 0.,.5,.5, 0.,0.,0., 1.,0.,0., 0.,1.,0., 0.,1.,1.], w_shape &
       ), b(*,*) = reshape( [double precision :: 0.,-1.,0., 0.,0.,0., 0.,1.,0.], b_shape)
 
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = metadata_t( &
         string_t("Double-Precision"), string_t("Damian Rouson"), string_t("2024-09-02"), string_t("relu"), string_t("false") &
      ), weights = w, biases = b, nodes = n &
     )
   end function
 
-  function varying_width() result(inference_engine)
-    type(inference_engine_t) inference_engine
+  function varying_width() result(neural_network)
+    type(neural_network_t) neural_network
     integer, parameter :: inputs = 2, hidden(*) = [2,3], outputs = 2 ! number of neurons in input, output, and hidden layers
     integer, parameter :: n(*) = [inputs, hidden(1), hidden(2), outputs] ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n)   ! max layer width, number of layers
@@ -158,14 +158,14 @@ function varying_width() result(inference_engine)
     w = reshape( [(i, i=1,product(w_shape))], w_shape)
     b = reshape( [(maxval(w) + i, i=1,product(b_shape))], b_shape)
 
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [string_t("random"), string_t("Damian Rouson"), string_t("2024-07-03"), string_t("sigmoid"), string_t("false")], &
       weights = w, biases = b, nodes = n &
     )
   end function
 
-  function distinct_parameters() result(inference_engine)
-    type(inference_engine_t) inference_engine
+  function distinct_parameters() result(neural_network)
+    type(neural_network_t) neural_network
     integer, parameter :: inputs = 2, hidden = 3, outputs = 1 ! number of neurons in input, output, and hidden layers
     integer, parameter :: n(*) = [inputs, hidden, hidden, outputs]    ! nodes per layer
     integer, parameter :: n_max = maxval(n), layers=size(n)   ! max layer width, number of layers
@@ -176,7 +176,7 @@ function distinct_parameters() result(inference_engine)
     w = reshape( [(i, i=1,product(w_shape))], w_shape)
     b = reshape( [(maxval(w) + i, i=1,product(b_shape))], b_shape)
 
-    inference_engine = inference_engine_t( &
+    neural_network = neural_network_t( &
       metadata = [string_t("random"), string_t("Damian Rouson"), string_t("2023-07-15"), string_t("sigmoid"), string_t("false")], &
       weights = w, biases = b, nodes = n &
     )   
@@ -184,54 +184,54 @@ function distinct_parameters() result(inference_engine)
 
   function multi_hidden_layer_net_to_from_json() result(test_passes)
     logical test_passes
-    type(inference_engine_t) inference_engine, from_json
+    type(neural_network_t) neural_network, from_json
     type(file_t) json_file
 
-    inference_engine = distinct_parameters()
-    json_file = inference_engine%to_json()
-    from_json = inference_engine_t(json_file)
-    test_passes = inference_engine == from_json 
+    neural_network = distinct_parameters()
+    json_file = neural_network%to_json()
+    from_json = neural_network_t(json_file)
+    test_passes = neural_network == from_json 
   end function
 
   function varying_width_net_to_from_json() result(test_passes)
     logical test_passes
 
-    associate(inference_engine => varying_width())
-      associate(from_json => inference_engine_t( inference_engine%to_json() ))
-        test_passes = inference_engine == from_json 
+    associate(neural_network => varying_width())
+      associate(from_json => neural_network_t( neural_network%to_json() ))
+        test_passes = neural_network == from_json 
       end associate
     end associate
   end function
 
   function infer_with_varying_width_net() result(test_passes)
     logical test_passes
-    type(inference_engine_t) inference_engine
+    type(neural_network_t) neural_network
     type(tensor_t) inputs, outputs
     real, parameter :: tolerance = 1.E-08
 
-    inference_engine = decrement_split_combine_increment()
+    neural_network = decrement_split_combine_increment()
     inputs = tensor_t([1.1, 2.7])
-    outputs = inference_engine%infer(inputs)
+    outputs = neural_network%infer(inputs)
     test_passes = all(abs(inputs%values() - outputs%values()) < tolerance)
   end function
 
   function double_precision_inference() result(test_passes)
     logical test_passes
-    type(inference_engine_t(double_precision)) inference_engine
+    type(neural_network_t(double_precision)) neural_network
     type(tensor_t(double_precision)) inputs, outputs
     real, parameter :: tolerance = 1.D-08
 
-    inference_engine = double_precision_network()
+    neural_network = double_precision_network()
     inputs = tensor_t([1.1D0, 2.7D0])
-    outputs = inference_engine%infer(inputs)
+    outputs = neural_network%infer(inputs)
     test_passes = all(abs(inputs%values() - outputs%values()) < tolerance)
   end function
 
   function elemental_infer_with_1_hidden_layer_xor_net() result(test_passes)
     logical test_passes
-    type(inference_engine_t) inference_engine
+    type(neural_network_t) neural_network
 
-    inference_engine = single_hidden_layer_xor_network()
+    neural_network = single_hidden_layer_xor_network()
 
     block
       type(tensor_t), allocatable :: truth_table(:)
@@ -239,7 +239,7 @@ function elemental_infer_with_1_hidden_layer_xor_net() result(test_passes)
       integer i
 
       associate(array_of_inputs => [tensor_t([true,true]), tensor_t([true,false]), tensor_t([false,true]), tensor_t([false,false])])
-        truth_table = inference_engine%infer(array_of_inputs)
+        truth_table = neural_network%infer(array_of_inputs)
       end associate
       test_passes = all( &
         abs(truth_table(1)%values() - false) < tolerance .and. abs(truth_table(2)%values() - true) < tolerance .and. &
@@ -250,9 +250,9 @@ function elemental_infer_with_1_hidden_layer_xor_net() result(test_passes)
 
   function elemental_infer_with_2_hidden_layer_xor_net() result(test_passes)
     logical test_passes
-    type(inference_engine_t) inference_engine
+    type(neural_network_t) neural_network
 
-    inference_engine = multi_layer_xor_network()
+    neural_network = multi_layer_xor_network()
 
     block
       type(tensor_t), allocatable :: truth_table(:)
@@ -260,7 +260,7 @@ function elemental_infer_with_2_hidden_layer_xor_net() result(test_passes)
       integer i
 
       associate(array_of_inputs => [tensor_t([true,true]), tensor_t([true,false]), tensor_t([false,true]), tensor_t([false,false])])
-        truth_table = inference_engine%infer(array_of_inputs)
+        truth_table = neural_network%infer(array_of_inputs)
       end associate
       test_passes = all( &
         abs(truth_table(1)%values() - false) < tolerance .and. abs(truth_table(2)%values() - true) < tolerance .and. &
@@ -269,4 +269,4 @@ function elemental_infer_with_2_hidden_layer_xor_net() result(test_passes)
     end block
   end function
 
-end module inference_engine_test_m
+end module neural_network_test_m
diff --git a/test/tensor_map_test_m.F90 b/test/tensor_map_test_m.F90
index b99c31d3b..cdc961411 100644
--- a/test/tensor_map_test_m.F90
+++ b/test/tensor_map_test_m.F90
@@ -6,7 +6,7 @@ module tensor_map_test_m
   ! External dependencies
   use assert_m, only : assert
   use julienne_m, only : string_t, test_t, test_result_t, test_description_t, test_description_substring, file_t
-  use inference_engine_m, only : tensor_map_t, tensor_t
+  use fiats_m, only : tensor_map_t, tensor_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i
 #endif
diff --git a/test/tensor_test_m.f90 b/test/tensor_test_m.f90
index f7e321504..7d4ac7c21 100644
--- a/test/tensor_test_m.f90
+++ b/test/tensor_test_m.f90
@@ -11,7 +11,7 @@ module tensor_test_m
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : tensor_t
+  use fiats_m, only : tensor_t
 
   implicit none
 
diff --git a/test/trainable_network_test_m.F90 b/test/trainable_network_test_m.F90
index 92518d74d..f6c88d910 100644
--- a/test/trainable_network_test_m.F90
+++ b/test/trainable_network_test_m.F90
@@ -12,7 +12,7 @@ module trainable_network_test_m
 #endif
 
   ! Internal dependencies
-  use inference_engine_m, only : trainable_network_t, inference_engine_t, tensor_t, input_output_pair_t, mini_batch_t, shuffle
+  use fiats_m, only : trainable_network_t, neural_network_t, tensor_t, input_output_pair_t, mini_batch_t, shuffle
   implicit none
 
   private
@@ -183,7 +183,7 @@ function two_zeroed_hidden_layers() result(trainable_network)
     w = 0.
     b = 0.
 
-    trainable_network = trainable_network_t( inference_engine_t( &
+    trainable_network = trainable_network_t( neural_network_t( &
       nodes = neurons, weights = w, biases = b &
      ,metadata = [string_t("2-hide|3-wide"), string_t("Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
     ))
@@ -201,7 +201,7 @@ function two_random_hidden_layers() result(trainable_network)
     b = 2*b
     w = 2*w
 
-    trainable_network = trainable_network_t( inference_engine_t( &
+    trainable_network = trainable_network_t( neural_network_t( &
       nodes = neurons, weights = w, biases = b &
       ,metadata = [string_t("2-hide|3-wide"), string_t("Rouson"), string_t("2023-06-30"), string_t("sigmoid"), string_t("false")] &
     ))
@@ -420,7 +420,7 @@ function perturbed_identity_network(perturbation_magnitude) result(trainable_net
     call random_number(harvest)
     harvest = perturbation_magnitude*harvest
 
-    trainable_network = trainable_network_t( inference_engine_t( &
+    trainable_network = trainable_network_t( neural_network_t( &
       nodes = nodes_per_layer, &
       weights = identity + harvest , & 
       biases = reshape([real:: [0,0], [0,0], [0,0]], [max_n, layers-1]), &
diff --git a/test/training_configuration_test_m.F90 b/test/training_configuration_test_m.F90
index 13ed5ba2d..a09dc0c09 100644
--- a/test/training_configuration_test_m.F90
+++ b/test/training_configuration_test_m.F90
@@ -4,7 +4,7 @@ module training_configuration_test_m
   !! Test training_configuration_t object I/O and construction
 
   ! External dependencies
-  use inference_engine_m, only : training_configuration_t, hyperparameters_t, network_configuration_t
+  use fiats_m, only : training_configuration_t, hyperparameters_t, network_configuration_t
   use julienne_m, only : test_t, test_result_t, test_description_t, test_description_substring, string_t, file_t
 #ifdef __GFORTRAN__
   use julienne_m, only : test_function_i

From 2fbb16184cb5457f68e71cf97b5cd29b91340f02 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 02:40:06 -0700
Subject: [PATCH 089/105] doc(ford): update project name & summary

---
 README.md | 72 ++++++++++++++++++++++++++++++-------------------------
 ford.md   | 10 ++++----
 2 files changed, 44 insertions(+), 38 deletions(-)

diff --git a/README.md b/README.md
index f6c6656f2..aba5ca419 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,28 @@
-
 ```ascii
-  _        __                                                     _            
- (_)      / _|                                                   (_)           
-  _ _ __ | |_ ___ _ __ ___ _ __   ___ ___         ___ _ __   __ _ _ _ __   ___ 
- | | '_ \|  _/ _ \ '__/ _ \ '_ \ / __/ _ \  __   / _ \ '_ \ / _` | | '_ \ / _ \
- | | | | | ||  __/ | |  __/ | | | (_|  __/ |__| |  __/ | | | (_| | | | | |  __/
- |_|_| |_|_| \___|_|  \___|_| |_|\___\___|       \___|_| |_|\__, |_|_| |_|\___|
-                                                             __/ |             
-                                                            |___/              
-```
-Inference-Engine
-================
+___________.__        __          
+\_   _____/|__|____ _/  |_  ______
+ |    __)  |  \__  \\   __\/  ___/
+ |     \   |  |/ __ \|  |  \___ \ 
+ \___  /   |__(____  /__| /____  >
+     \/            \/          \/ 
+```
+Fiats: Functional inference and training for surrogates
+=======================================================
 
 [Overview](#overview) | [Getting Started](#getting-started) | [Documentation](#documentation)
 
 Overview
 --------
-Inference-Engine supports research in the training and deployment of neural-network surrogate models for computational science.
-Inference-Engine also provides a platform for exploring and advancing the native parallel programming features of Fortran 2023 in the context of deep learning.
-The language features of interest facilitate loop-level parallelism via the `do concurrent` construct and Single-Program, Multiple Data (SMPD) parallelism via "multi-image" (e.g., multithreaded or multiprocess) execution.
-Toward these ends,
+Fiats supports research on the training and deployment of neural-network surrogate models for computational science.
+Fiats also provides a platform for exploring and advancing the native parallel programming features of Fortran 2023 in the context of deep learning.
+Toward these ends, the design of Fiats centers around functional programming patterns that facilitate concurrent execution:
+The language features of interest facilitate loop-level parallelism via the `do concurrent` construct and Single-Program, Multiple Data (SMPD) parallelism via "multi-image" (e.g., multithreaded or multiprocess) execution:
 
-* Most Inference-Engine procedures are `pure` and thus satisfy a language requirement for invocation inside `do concurrent`,
+* Most Fiats procedures are `pure` and thus satisfy a language requirement for invocation inside `do concurrent`,
 * The network training procedure uses `do concurrent` to expose automatic parallelization opportunities to compilers, and
 * Exploiting multi-image execution to speedup training is under investigation.
 
-To broaden support for the native parallel features, Inference-Engine's contributors also write compiler tests, bug reports, and patches; develop a parallel runtime library ([Caffeine]); participate in the language standardization process; and provide example inference and training code for exercising and evaluating compilers' automatic parallelization capabilities on processors and accelerators, including Graphics Processing Units (GPUs).
+To broaden support for the native parallel features, the Fiats contributors also write compiler tests, bug reports, and patches; develop a parallel runtime library ([Caffeine]); participate in the language standardization process; and provide example inference and training code for exercising and evaluating compilers' automatic parallelization capabilities on processors and accelerators, including Graphics Processing Units (GPUs).
 
 Available optimizers:
 * Stochastic gradient descent and
@@ -51,7 +48,7 @@ Getting Started
 The [example] subdirectory contains demonstrations of several relatively simple use cases.
 We recommend reviewing the examples to see how to handle basic tasks such as configuring a network training run or reading a neural network and using it to perform inference.
 
-The [demo] subdirectory contains demonstration applications that depend on Inference-Engine but build separately due to requiring additional prerequisites such as NetCDF and HDF5.
+The [demo] subdirectory contains demonstration applications that depend on Fiats but build separately due to requiring additional prerequisites such as NetCDF and HDF5.
 The demonstration applications
  - Train a cloud microphysics model surrogate for the Intermediate Complexity Atmospheric Research ([ICAR]) package,
  - Perform inference using a pretrained model for aerosol dynamics in the Energy Exascale Earth System ([E3SM]) package, and
@@ -61,20 +58,18 @@ The demonstration applications
 Because this repository supports programming language research, the code exercises new language features in novel ways.
 We recommend using any compiler's latest release or even building open-source compilers from source.
 The [handy-dandy] repository contains scripts capturing steps for building the [LLVM] compiler suite.
-The remainder of this section contains commands for building Inference-Engine with a recent Fortran compiler and the Fortran Package Manager ([`fpm`]) in your `PATH`.
+The remainder of this section contains commands for building Fiats with a recent Fortran compiler and the Fortran Package Manager ([`fpm`]) in your `PATH`.
 
-#### GNU (`gfortran`) 13 or higher required
-```
-fpm test --compiler gfortran --profile release
-```
 
-#### NAG (`nagfor`)
+
+#### LLVM (`flang-new`): supported
+With LLVM `flang` 20 installed and in your `PATH`, build and test Fiats with the installed `flang-new` symlink:
 ```
-fpm test --compiler nagfor --flag -fpp --profile release
+fpm test --compiler flang-new --flag "-O3"
 ```
 
-#### LLVM (`flang-new`)
-Building with `flang-new` requires passing flags to enable the compiler's experimental support for assumed-rank entities:
+With LLVM flang 19, enable the compiler's experimental support for assumed-rank entities:
+
 ```
 fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3"
 ```
@@ -89,9 +84,20 @@ fpm run \
   -- --network model.json
 
 ```
-where `model.json` must be a neural network in the [JSON] format used by Inference-Engine and the companion [nexport] package.
+where `model.json` must be a neural network in the [JSON] format used by Fiats and the companion [nexport] package.
 Automatic parallelization for training is under development.
 
+#### NAG (`nagfor`):
+```
+fpm test --compiler nagfor --flag -fpp --profile release
+```
+
+#### GNU (`gfortran`): 
+
+```
+fpm test --compiler gfortran --profile release
+```
+
 #### Intel (`ifx`)
 ```
 fpm test --compiler ifx --profile release --flag -O3
@@ -119,7 +125,7 @@ fpm test --compiler crayftn.sh
 ```
 
 ### Configuring a training run
-Inference-Engine imports hyperparameters and network configurations to and from JSON files.
+Fiats imports hyperparameters and network configurations to and from JSON files.
 To see the expected file format, run the [print-training-configuration] example as follows:
 ```
 % fpm run --example print-training-configuration --compiler gfortran
@@ -141,7 +147,7 @@ Project is up to date
      }
  }
 ```
-Inference-Engine's JSON file format is fragile: splitting or combining lines breaks the file reader.
+The Fiats JSON file format is fragile: splitting or combining lines breaks the file reader.
 Files with added or removed white space or reordered whole objects ("hyperparameters" or "network configuration") should work.
 A future release will leverage the [rojff] JSON interface to allow for more flexible file formatting.
 
@@ -163,7 +169,7 @@ Before halting, the program will print a table of expected and predicted saturat
 The program also writes the neural network initial condition to `initial-network.json` and the final (trained) network to the file specified in the above command: `sat-mix-rat.json`.
 
 ### Performing inference
-Users with a PyTorch model may use [nexport] to export the model to JSON files that Inference-Engine can read.
+Users with a PyTorch model may use [nexport] to export the model to JSON files that Fiats can read.
 Examples of performing inference using a neural-network JSON file are in [example/concurrent-inferences].
 
 Documentation
@@ -182,7 +188,7 @@ Please see our [GitHub Pages site] for HTML documentation generated by [`ford`]
 [`ford`]: https://github.com/Fortran-FOSS-Programmers/ford
 [`fpm`]: https://github.com/fortran-lang/fpm
 [Getting Started]: #getting-started
-[GitHub Pages site]: https://berkeleylab.github.io/inference-engine/ 
+[GitHub Pages site]: https://berkeleylab.github.io/fiats/ 
 [handy-dandy]: https://github.com/rouson/handy-dandy/blob/main/src
 [ICAR]: https://github.com/BerkeleyLab/icar/tree/neural-net
 [JSON]: https://www.json.org/json-en.html
diff --git a/ford.md b/ford.md
index 2e8c5c03e..35de3c8ef 100644
--- a/ford.md
+++ b/ford.md
@@ -1,5 +1,5 @@
-project: Inference-Engine
-summary: A deep learning library targeting high-performance computing (HPC) applications with performance-critical inference and training needs.
+project: Fiats 
+summary: Functional inference and training of surrogate models for computational science.
 src_dir: src/
 src_dir: example
 exclude_dir: doc
@@ -17,12 +17,12 @@ coloured_edges: true
 sort: permission-alpha
 extra_mods: iso_fortran_env:https://gcc.gnu.org/onlinedocs/gfortran/ISO_005fFORTRAN_005fENV.html
             iso_c_binding:https://gcc.gnu.org/onlinedocs/gfortran/ISO_005fC_005fBINDING.html#ISO_005fC_005fBINDING
-project_github: https://github.com/berkeleylab/inference-engine
+project_github: https://github.com/berkeleylab/fiats
 author: Berkeley Lab
 print_creation_date: true
 creation_date: %Y-%m-%d %H:%M %z
-project_github: https://github.com/berkeleylab/inference-engine
-project_download: https://github.com/berkeleylab/inference-engine/releases
+project_github: https://github.com/berkeleylab/fiats
+project_download: https://github.com/berkeleylab/fiats/releases
 github: https://github.com/berkeleylab
 predocmark_alt: >
 predocmark: <

From bada395389762737fe9ae1d0d974d49cbf69a763 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 21:21:55 -0700
Subject: [PATCH 090/105] chore: rename asymmetric_{engine,network}_test

---
 ...engine_test_m.F90 => asymmetric_network_test_m.F90} | 10 +++++-----
 test/main.F90                                          |  6 +++---
 2 files changed, 8 insertions(+), 8 deletions(-)
 rename test/{asymmetric_engine_test_m.F90 => asymmetric_network_test_m.F90} (95%)

diff --git a/test/asymmetric_engine_test_m.F90 b/test/asymmetric_network_test_m.F90
similarity index 95%
rename from test/asymmetric_engine_test_m.F90
rename to test/asymmetric_network_test_m.F90
index fe69fa759..f25dbfdda 100644
--- a/test/asymmetric_engine_test_m.F90
+++ b/test/asymmetric_network_test_m.F90
@@ -1,7 +1,7 @@
 ! Copyright (c), The Regents of the University of California
 ! Terms of use are as specified in LICENSE.txt
-module asymmetric_engine_test_m
-  !! Define asymmetric tests and procedures required for reporting results
+module asymmetric_network_test_m
+  !! Define tests in which the desired output depends asymmetrically on the inputs 
 
   ! External dependencies
   use assert_m, only : assert
@@ -14,9 +14,9 @@ module asymmetric_engine_test_m
   implicit none
 
   private
-  public :: asymmetric_engine_test_t
+  public :: asymmetric_network_test_t
 
-  type, extends(test_t) :: asymmetric_engine_test_t
+  type, extends(test_t) :: asymmetric_network_test_t
   contains
     procedure, nopass :: subject
     procedure, nopass :: results
@@ -144,4 +144,4 @@ function xor_and_2nd_input_truth_table() result(test_passes)
 
   end function
 
-end module asymmetric_engine_test_m
+end module asymmetric_network_test_m
diff --git a/test/main.F90 b/test/main.F90
index a280ae011..40026b524 100644
--- a/test/main.F90
+++ b/test/main.F90
@@ -2,7 +2,7 @@
 ! Terms of use are as specified in LICENSE.txt
 program main
   use neural_network_test_m, only : neural_network_test_t
-  use asymmetric_engine_test_m, only : asymmetric_engine_test_t
+  use asymmetric_network_test_m, only : asymmetric_network_test_t
   use trainable_network_test_m, only : trainable_network_test_t
   use metadata_test_m, only : metadata_test_t
   use hyperparameters_test_m, only : hyperparameters_test_t
@@ -14,7 +14,7 @@ program main
   implicit none
 
   type(neural_network_test_t) neural_network_test
-  type(asymmetric_engine_test_t) asymmetric_engine_test
+  type(asymmetric_network_test_t) asymmetric_network_test
   type(trainable_network_test_t) trainable_network_test
   type(hyperparameters_test_t) hyperparameters_test
   type(metadata_test_t) metadata_test
@@ -47,7 +47,7 @@ program main
   call training_configuration_test%report(passes, tests)
   call tensor_map_test%report(passes, tests)
   call tensor_test%report(passes, tests)
-  call asymmetric_engine_test%report(passes, tests)
+  call asymmetric_network_test%report(passes, tests)
   call neural_network_test%report(passes, tests)
   call trainable_network_test%report(passes, tests)
   call cpu_time(t_finish)

From be7960ef1cc59afe40b2be20dafef886180dc7ee Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 22:02:08 -0700
Subject: [PATCH 091/105] doc(README): update compiler status

---
 README.md | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index aba5ca419..096add8c2 100644
--- a/README.md
+++ b/README.md
@@ -63,19 +63,18 @@ The remainder of this section contains commands for building Fiats with a recent
 
 
 #### LLVM (`flang-new`): supported
-With LLVM `flang` 20 installed and in your `PATH`, build and test Fiats with the installed `flang-new` symlink:
+With LLVM `flang` 20 installed in your `PATH`, build and test Fiats with the installed `flang-new` symlink in order for `fpm` to correctly identify the compiler:
 ```
 fpm test --compiler flang-new --flag "-O3"
 ```
-
-With LLVM flang 19, enable the compiler's experimental support for assumed-rank entities:
+With LLVM `flang` 19, enable the compiler's experimental support for assumed-rank entities:
 
 ```
 fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3"
 ```
 
 ##### _Experimental:_ Automatic parallelization of `do concurrent` on CPUs
-With the `amd_trunk_dev` branch of the [ROCm fork] fork of LLVM, automatic parallelization currently works for inference, e.g.
+With the `amd_trunk_dev` branch of the [ROCm fork] fork of LLVM, automatically parallelize inference calculations inside `do concurrent` constructs:
 ```
 fpm run \
   --example concurrent-inferences \
@@ -85,7 +84,8 @@ fpm run \
 
 ```
 where `model.json` must be a neural network in the [JSON] format used by Fiats and the companion [nexport] package.
-Automatic parallelization for training is under development.
+
+Automatic parallelization for training neural networks is under development.
 
 #### NAG (`nagfor`):
 ```
@@ -93,12 +93,15 @@ fpm test --compiler nagfor --flag -fpp --profile release
 ```
 
 #### GNU (`gfortran`): 
-
+Compiler bugs related to parameterized derived types currently prevent `gfortran` from building Fiats versions 0.15.0 or later.
+Test and build earlier versions of Fiats build with the following command:
 ```
 fpm test --compiler gfortran --profile release
 ```
 
 #### Intel (`ifx`)
+Compiler bugs related to parameterized derived types currently prevent `gfortran` from building Fiats versions 0.15.0 or later.
+Test and build earlier versions of Fiats build with the following command:
 ```
 fpm test --compiler ifx --profile release --flag -O3
 ```
@@ -106,7 +109,7 @@ fpm test --compiler ifx --profile release --flag -O3
 ##### _Experimental:_ Automatic offloading of `do concurrent` to GPUs
 This capability is under development with the goal to facilitate automatic GPU offloading via the following command:
 ```
-fpm test --compiler ifx --profile releae --flag "-fopenmp-target-do-concurrent -qopenmp -fopenmp-targets=spir64 -O3"
+fpm test --compiler ifx --profile release --flag "-fopenmp-target-do-concurrent -qopenmp -fopenmp-targets=spir64 -O3"
 ```
 
 #### HPE (`crayftn.sh`) -- under development
@@ -164,7 +167,7 @@ The following is representative output after 3000 epochs:
          2000    0.61259E-04     9.8345      2,4,72,2,1
          3000    0.45270E-04     14.864      2,4,72,2,1
 ```
-The example program halts execution after reaching a cost-function threshold (which requires millions of epochws) or a maximum number of iterations or if the program detects a file named `stop` in the source-tree root directory.
+The example program halts execution after reaching a cost-function threshold (which requires millions of epochs) or a maximum number of iterations or if the program detects a file named `stop` in the source-tree root directory.
 Before halting, the program will print a table of expected and predicted saturated mixing ratio values across a range of input pressures and temperatures, wherein two the inputs have each been mapped to the unit interval [0,1].
 The program also writes the neural network initial condition to `initial-network.json` and the final (trained) network to the file specified in the above command: `sat-mix-rat.json`.
 
@@ -174,7 +177,7 @@ Examples of performing inference using a neural-network JSON file are in [exampl
 
 Documentation
 -------------
-Please see our [GitHub Pages site] for HTML documentation generated by [`ford`] or generate documentaiton locally by installing `ford` and executing `ford ford.md`.
+Please see our [GitHub Pages site] for HTML documentation generated by [`ford`] or generate documentation locally by installing `ford` and executing `ford ford.md`.
 
 
 [Building and testing]: #building-and-testing

From 4b09cdc9f86d38ccab37982bc6ce8dd186234295 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 22:10:58 -0700
Subject: [PATCH 092/105] refac(json): rename acceptable_engine_tag

This commit renames "acceptable_engine_tag" to
"minimum_acceptable_tag" in the network JSON file format.
---
 fpm.toml                       |  2 +-
 src/fiats/neural_network_s.F90 | 26 +++++++++++++-------------
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/fpm.toml b/fpm.toml
index fbf885f9c..815ffa443 100644
--- a/fpm.toml
+++ b/fpm.toml
@@ -1,5 +1,5 @@
 name = "fiats"
-version = "0.13.1"
+version = "0.15.0"
 license = "see LICENSE.txt"
 author = "Damian Rouson, Tan Nguyen, Jordan Welsman, David Torres, Brad Richardson, Katherine Rasmussen, Federica Villani, Dan Bonachea"
 maintainer = "rouson@lbl.gov"
diff --git a/src/fiats/neural_network_s.F90 b/src/fiats/neural_network_s.F90
index c652f6f2c..f1ee235ca 100644
--- a/src/fiats/neural_network_s.F90
+++ b/src/fiats/neural_network_s.F90
@@ -8,7 +8,7 @@
   use neuron_m, only : neuron_t
   implicit none
 
-  character(len=*), parameter :: acceptable_engine_tag = "0.13.0" ! git tag capable of reading the current json file format
+  character(len=*), parameter :: minimum_acceptable_tag = "0.15.0" ! git tag capable of reading the current json file format
   integer, parameter :: input_layer = 0 
 
 contains
@@ -303,7 +303,7 @@
                
           associate( json_lines => &
             brace + &                                                          ! { 
-              file_version_lines + &                                           !   "acceptable_engine_tag": ...
+              file_version_lines + &                                           !   "minimum_acceptable_tag": ...
               metadata_lines + &                                               !   "metadata": ...
               tensor_map_lines + &                                             !   "inputs_tensor_map": ...
               tensor_map_lines + &                                             !   "outputs_tensor_map": ...
@@ -319,7 +319,7 @@
           )
             allocate(lines(json_lines))
             lines(brace) = string_t('{')
-            lines(brace+1:brace+file_version_lines)= string_t('    "acceptable_engine_tag": "')//acceptable_engine_tag//'",'
+            lines(brace+1:brace+file_version_lines)= string_t('    "minimum_acceptable_tag": "')//minimum_acceptable_tag//'",'
             associate(meta_start => brace + file_version_lines + 1)
               associate(meta_end => meta_start + metadata_lines - 1)
               lines(meta_start:meta_end) = self%metadata_%to_json()
@@ -419,7 +419,7 @@
                
           associate( json_lines => &
             brace + &                                                          ! { 
-              file_version_lines + &                                           !   "acceptable_engine_tag": ...
+              file_version_lines + &                                           !   "minimum_acceptable_tag": ...
               metadata_lines + &                                               !   "metadata": ...
               tensor_map_lines + &                                             !   "inputs_tensor_map": ...
               tensor_map_lines + &                                             !   "outputs_tensor_map": ...
@@ -435,7 +435,7 @@
           )
             allocate(lines(json_lines))
             lines(brace) = string_t('{')
-            lines(brace+1:brace+file_version_lines)= string_t('    "acceptable_engine_tag": "')//acceptable_engine_tag//'",'
+            lines(brace+1:brace+file_version_lines)= string_t('    "minimum_acceptable_tag": "')//minimum_acceptable_tag//'",'
             associate(meta_start => brace + file_version_lines + 1)
               associate(meta_end => meta_start + metadata_lines - 1)
               lines(meta_start:meta_end) = self%metadata_%to_json()
@@ -512,11 +512,11 @@
     block 
       character(len=:), allocatable :: tag
 
-      tag = lines(2)%get_json_value("acceptable_engine_tag", mold="")
+      tag = lines(2)%get_json_value("minimum_acceptable_tag", mold="")
       call assert( &
-        tag == acceptable_engine_tag &
-        ,"neural_network_s(default_real_from_json): acceptable_engine_tag" &
-        ,tag //"(expected " //acceptable_engine_tag // ")" &
+        tag == minimum_acceptable_tag &
+        ,"neural_network_s(default_real_from_json): minimum_acceptable_tag" &
+        ,tag //"(expected " //minimum_acceptable_tag // ")" &
       )
     end block check_git_tag
       
@@ -614,11 +614,11 @@
     block
       character(len=:), allocatable :: tag
 
-      tag = lines(2)%get_json_value("acceptable_engine_tag", mold="")
+      tag = lines(2)%get_json_value("minimum_acceptable_tag", mold="")
       call assert( &
-        tag == acceptable_engine_tag &
-        ,"neural_network_s(double_precision_from_json): acceptable_engine_tag" &
-        ,tag //"(expected " //acceptable_engine_tag // ")" &
+        tag == minimum_acceptable_tag &
+        ,"neural_network_s(double_precision_from_json): minimum_acceptable_tag" &
+        ,tag //"(expected " //minimum_acceptable_tag // ")" &
       )
     end block check_git_tag
 

From d69324dfc862378d4e7a88a662f3149d029f2636 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 22:17:31 -0700
Subject: [PATCH 093/105] chore: rm unused exhcnage_t type

---
 src/fiats/neural_network_m.f90 | 28 ----------------------------
 src/fiats/neural_network_s.F90 | 24 ------------------------
 2 files changed, 52 deletions(-)

diff --git a/src/fiats/neural_network_m.f90 b/src/fiats/neural_network_m.f90
index eb72c1524..06e5737e0 100644
--- a/src/fiats/neural_network_m.f90
+++ b/src/fiats/neural_network_m.f90
@@ -15,7 +15,6 @@ module neural_network_m
   private
   public :: neural_network_t
   public :: unmapped_network_t
-  public :: exchange_t
   public :: workspace_t
 
   type neural_network_t(k)
@@ -39,7 +38,6 @@ module neural_network_m
     generic :: assert_conformable_with  => default_real_assert_conformable_with, double_precision_assert_conformable_with
     generic :: skip                     => default_real_skip,                    double_precision_skip
     generic :: activation_function_name => default_real_activation_name,         double_precision_activation_name
-    generic :: to_exchange              => default_real_to_exchange,             double_precision_to_exchange
     generic :: learn                    => default_real_learn
     generic :: assert_consistency       => default_real_consistency,             double_precision_consistency
     procedure, private, non_overridable :: default_real_consistency,             double_precision_consistency
@@ -56,7 +54,6 @@ module neural_network_m
     procedure, private, non_overridable :: default_real_assert_conformable_with, double_precision_assert_conformable_with
     procedure, private, non_overridable :: default_real_skip,                    double_precision_skip
     procedure, private, non_overridable :: default_real_activation_name,         double_precision_activation_name
-    procedure, private, non_overridable :: default_real_to_exchange,             double_precision_to_exchange
   end type
 
   type workspace_t(k)
@@ -97,31 +94,6 @@ pure module function default_real_allocated(self) result(all_allocated)
 
   end interface
 
-  type exchange_t(k)
-    integer, kind :: k = default_real
-    type(tensor_map_t(k)) input_map_, output_map_
-    type(metadata_t) metadata_
-    real(k), allocatable :: weights_(:,:,:), biases_(:,:)
-    integer, allocatable :: nodes_(:)
-    type(activation_t) activation_
-  end type
-
-  interface
-
-    module function default_real_to_exchange(self) result(exchange)
-      implicit none
-      class(neural_network_t), intent(in) :: self
-      type(exchange_t) exchange
-    end function
-
-    module function double_precision_to_exchange(self) result(exchange)
-      implicit none
-      class(neural_network_t(double_precision)), intent(in) :: self
-      type(exchange_t(double_precision)) exchange
-    end function
-
-  end interface
-
   interface neural_network_t
 
     module function default_real_construct_from_components(metadata, weights, biases, nodes, input_map, output_map) &
diff --git a/src/fiats/neural_network_s.F90 b/src/fiats/neural_network_s.F90
index f1ee235ca..6f3334c80 100644
--- a/src/fiats/neural_network_s.F90
+++ b/src/fiats/neural_network_s.F90
@@ -29,30 +29,6 @@
     tensor = self%output_map_%map_from_training_range(normalized_tensor)
   end procedure
 
-  module procedure default_real_to_exchange
-    exchange%input_map_ = self%input_map_
-    exchange%output_map_ = self%output_map_
-    associate(strings => self%metadata_%strings())
-      exchange%metadata_ = metadata_t(strings(1),strings(2),strings(3),strings(4),strings(5))
-    end associate
-    exchange%weights_ = self%weights_
-    exchange%biases_ = self%biases_
-    exchange%nodes_ = self%nodes_
-    exchange%activation_ = self%activation_
-  end procedure
-
-  module procedure double_precision_to_exchange
-    exchange%input_map_ = self%input_map_
-    exchange%output_map_ = self%output_map_
-    associate(strings => self%metadata_%strings())
-      exchange%metadata_ = metadata_t(strings(1),strings(2),strings(3),strings(4),strings(5))
-    end associate
-    exchange%weights_ = self%weights_
-    exchange%biases_ = self%biases_
-    exchange%nodes_ = self%nodes_
-    exchange%activation_ = self%activation_
-  end procedure
-
   module procedure default_real_infer
 
     real, allocatable :: a(:,:)

From b8473d67151b731b3d2a8b79d5fb4c2e7f16ec0d Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Mon, 14 Oct 2024 22:42:30 -0700
Subject: [PATCH 094/105] doc(README.md): update

---
 README.md | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index 096add8c2..a02840c94 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ ___________.__        __
 ```
 Fiats: Functional inference and training for surrogates
 =======================================================
+Alternatively, _Fortran inference and training for science_.
 
 [Overview](#overview) | [Getting Started](#getting-started) | [Documentation](#documentation)
 
@@ -15,11 +16,11 @@ Overview
 --------
 Fiats supports research on the training and deployment of neural-network surrogate models for computational science.
 Fiats also provides a platform for exploring and advancing the native parallel programming features of Fortran 2023 in the context of deep learning.
-Toward these ends, the design of Fiats centers around functional programming patterns that facilitate concurrent execution:
-The language features of interest facilitate loop-level parallelism via the `do concurrent` construct and Single-Program, Multiple Data (SMPD) parallelism via "multi-image" (e.g., multithreaded or multiprocess) execution:
+The design of Fiats centers around functional programming patterns that facilitate concurrency, including loop-level parallelism via the `do concurrent` construct and Single-Program, Multiple Data (SMPD) parallelism via "multi-image" (e.g., multithreaded or multiprocess) execution.
+Towards these ends,
 
 * Most Fiats procedures are `pure` and thus satisfy a language requirement for invocation inside `do concurrent`,
-* The network training procedure uses `do concurrent` to expose automatic parallelization opportunities to compilers, and
+* The network training procedure use `do concurrent` to expose automatic parallelization opportunities to compilers, and
 * Exploiting multi-image execution to speedup training is under investigation.
 
 To broaden support for the native parallel features, the Fiats contributors also write compiler tests, bug reports, and patches; develop a parallel runtime library ([Caffeine]); participate in the language standardization process; and provide example inference and training code for exercising and evaluating compilers' automatic parallelization capabilities on processors and accelerators, including Graphics Processing Units (GPUs).
@@ -48,7 +49,7 @@ Getting Started
 The [example] subdirectory contains demonstrations of several relatively simple use cases.
 We recommend reviewing the examples to see how to handle basic tasks such as configuring a network training run or reading a neural network and using it to perform inference.
 
-The [demo] subdirectory contains demonstration applications that depend on Fiats but build separately due to requiring additional prerequisites such as NetCDF and HDF5.
+The [demo] subdirectory contains demonstration applications that depend on Fiats but build separately due to requiring additional prerequisites such as [NetCDF] and [HDF5].
 The demonstration applications
  - Train a cloud microphysics model surrogate for the Intermediate Complexity Atmospheric Research ([ICAR]) package,
  - Perform inference using a pretrained model for aerosol dynamics in the Energy Exascale Earth System ([E3SM]) package, and
@@ -58,11 +59,9 @@ The demonstration applications
 Because this repository supports programming language research, the code exercises new language features in novel ways.
 We recommend using any compiler's latest release or even building open-source compilers from source.
 The [handy-dandy] repository contains scripts capturing steps for building the [LLVM] compiler suite.
-The remainder of this section contains commands for building Fiats with a recent Fortran compiler and the Fortran Package Manager ([`fpm`]) in your `PATH`.
+The remainder of this section contains commands for building Fiats with a recent Fortran compiler and the Fortran Package Manager ([`fpm`]).
 
-
-
-#### LLVM (`flang-new`): supported
+#### LLVM (`flang-new`)
 With LLVM `flang` 20 installed in your `PATH`, build and test Fiats with the installed `flang-new` symlink in order for `fpm` to correctly identify the compiler:
 ```
 fpm test --compiler flang-new --flag "-O3"
@@ -87,12 +86,12 @@ where `model.json` must be a neural network in the [JSON] format used by Fiats a
 
 Automatic parallelization for training neural networks is under development.
 
-#### NAG (`nagfor`):
+#### NAG (`nagfor`)
 ```
 fpm test --compiler nagfor --flag -fpp --profile release
 ```
 
-#### GNU (`gfortran`): 
+#### GNU (`gfortran`)
 Compiler bugs related to parameterized derived types currently prevent `gfortran` from building Fiats versions 0.15.0 or later.
 Test and build earlier versions of Fiats build with the following command:
 ```
@@ -112,7 +111,7 @@ This capability is under development with the goal to facilitate automatic GPU o
 fpm test --compiler ifx --profile release --flag "-fopenmp-target-do-concurrent -qopenmp -fopenmp-targets=spir64 -O3"
 ```
 
-#### HPE (`crayftn.sh`) -- under development
+#### HPE (`crayftn.sh`)
 Support for the Cray Compiler Environment (CCE) Fortran compiler is under development.
 Building with the CCE `ftn` compiler wrapper requires an additional trivial wrapper
 shell script. For example, create a file `crayftn.sh` with the following contents and
@@ -193,8 +192,10 @@ Please see our [GitHub Pages site] for HTML documentation generated by [`ford`]
 [Getting Started]: #getting-started
 [GitHub Pages site]: https://berkeleylab.github.io/fiats/ 
 [handy-dandy]: https://github.com/rouson/handy-dandy/blob/main/src
+[HDF5]: https://www.hdfgroup.org/solutions/hdf5/
 [ICAR]: https://github.com/BerkeleyLab/icar/tree/neural-net
 [JSON]: https://www.json.org/json-en.html
+[NetCDF]: https://www.unidata.ucar.edu/software/netcdf/
 [LLVM]: https://github.com/llvm/llvm-project
 [nexport]: https://go.lbl.gov/nexport
 [Overview]: #overview

From 6f03491a3aa427a4b710112c521e174fe4dc4efe Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 17:34:01 -0700
Subject: [PATCH 095/105] chore: white space edits

---
 src/fiats/activation_m.f90     |  8 ++++----
 src/fiats/layer_m.f90          | 26 +++++++++++++-------------
 src/fiats/neural_network_m.f90 |  3 ---
 3 files changed, 17 insertions(+), 20 deletions(-)

diff --git a/src/fiats/activation_m.f90 b/src/fiats/activation_m.f90
index 966f6beea..9450a069c 100644
--- a/src/fiats/activation_m.f90
+++ b/src/fiats/activation_m.f90
@@ -20,10 +20,10 @@ module activation_m
     procedure, non_overridable :: function_name
     generic :: operator(==) => equals
     procedure, private      :: equals
-    generic :: evaluate        => default_real_evaluate     , double_precision_evaluate
-    procedure, non_overridable :: default_real_evaluate     , double_precision_evaluate
-    generic :: differentiate   => default_real_differentiate, double_precision_differentiate
-    procedure, non_overridable :: default_real_differentiate, double_precision_differentiate
+    generic :: evaluate                 => default_real_evaluate     , double_precision_evaluate
+    procedure, non_overridable, private :: default_real_evaluate     , double_precision_evaluate
+    generic :: differentiate            => default_real_differentiate, double_precision_differentiate
+    procedure, non_overridable, private :: default_real_differentiate, double_precision_differentiate
   end type
 
   interface activation_t
diff --git a/src/fiats/layer_m.f90 b/src/fiats/layer_m.f90
index 390f3bfa6..e877d397a 100644
--- a/src/fiats/layer_m.f90
+++ b/src/fiats/layer_m.f90
@@ -19,20 +19,20 @@ module layer_m
     type(neuron_t(k)),             private :: neuron !! linked list of this layer's neurons 
     type(layer_t(k)), allocatable, private :: next !! next layer
   contains
-    generic :: neural_network => default_real_neural_network, double_precision_neural_network
-    procedure, private ::          default_real_neural_network, double_precision_neural_network
-    generic :: count_layers => default_real_count_layers, double_precision_count_layers
-    procedure, private ::      default_real_count_layers, double_precision_count_layers
-    generic :: count_neurons => default_real_count_neurons, double_precision_count_neurons
-    procedure, private ::       default_real_count_neurons, double_precision_count_neurons
-    generic :: count_inputs => default_real_count_inputs, double_precision_count_inputs
-    procedure, private ::      default_real_count_inputs, double_precision_count_inputs
+    generic :: neural_network    => default_real_neural_network   , double_precision_neural_network
+    procedure, private           :: default_real_neural_network   , double_precision_neural_network
+    generic :: count_layers      => default_real_count_layers     , double_precision_count_layers
+    procedure, private           :: default_real_count_layers     , double_precision_count_layers
+    generic :: count_neurons     => default_real_count_neurons    , double_precision_count_neurons
+    procedure, private           :: default_real_count_neurons    , double_precision_count_neurons
+    generic :: count_inputs      => default_real_count_inputs     , double_precision_count_inputs
+    procedure, private           :: default_real_count_inputs     , double_precision_count_inputs
     generic :: neurons_per_layer => default_real_neurons_per_layer, double_precision_neurons_per_layer
-    procedure, private ::           default_real_neurons_per_layer, double_precision_neurons_per_layer
-    generic :: next_allocated => default_real_next_allocated, double_precision_next_allocated
-    procedure, private ::        default_real_next_allocated, double_precision_next_allocated
-    generic :: next_pointer => default_real_next_pointer, double_precision_next_pointer 
-    procedure, private ::      default_real_next_pointer, double_precision_next_pointer
+    procedure, private           :: default_real_neurons_per_layer, double_precision_neurons_per_layer
+    generic :: next_allocated    => default_real_next_allocated   , double_precision_next_allocated
+    procedure, private           :: default_real_next_allocated   , double_precision_next_allocated
+    generic :: next_pointer      => default_real_next_pointer     , double_precision_next_pointer 
+    procedure, private           :: default_real_next_pointer     , double_precision_next_pointer
   end type
 
   interface layer_t
diff --git a/src/fiats/neural_network_m.f90 b/src/fiats/neural_network_m.f90
index 06e5737e0..50bdd7654 100644
--- a/src/fiats/neural_network_m.f90
+++ b/src/fiats/neural_network_m.f90
@@ -130,9 +130,6 @@ impure elemental module function double_precision_from_json(file) result(neural_
 
   end interface
 
-
-
-
   interface ! neural_network_t type-bound procedures
 
     elemental module function default_real_approximately_equal(lhs, rhs) result(lhs_eq_rhs)

From 31ecd597c14e0a14084ee02f028f07884f6e3a84 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 17:36:07 -0700
Subject: [PATCH 096/105] chore(training_configuration): rm unused function

---
 src/fiats/training_configuration_m.f90 | 14 --------
 src/fiats/training_configuration_s.F90 | 50 --------------------------
 2 files changed, 64 deletions(-)

diff --git a/src/fiats/training_configuration_m.f90 b/src/fiats/training_configuration_m.f90
index eb20a30f0..be693220d 100644
--- a/src/fiats/training_configuration_m.f90
+++ b/src/fiats/training_configuration_m.f90
@@ -28,8 +28,6 @@ module training_configuration_m
     procedure, private ::        default_real_optimizer_name, double_precision_optimizer_name
     generic :: learning_rate => default_real_learning_rate, double_precision_learning_rate
     procedure, private ::       default_real_learning_rate, double_precision_learning_rate
-    generic :: differentiable_activation => default_real_differentiable_activation, double_precision_differentiable_activation
-    procedure, private ::                   default_real_differentiable_activation, double_precision_differentiable_activation
     generic :: nodes_per_layer => default_real_nodes_per_layer, double_precision_nodes_per_layer
     procedure, private ::         default_real_nodes_per_layer, double_precision_nodes_per_layer
     generic :: skip_connections => default_real_skip_connections, double_precision_skip_connections
@@ -128,18 +126,6 @@ elemental module function double_precision_learning_rate(self) result(rate)
       double precision rate
     end function
 
-    module function default_real_differentiable_activation(self) result(activation)
-      implicit none
-      class(training_configuration_t), intent(in) :: self
-      type(activation_t) activation
-    end function
-
-    module function double_precision_differentiable_activation(self) result(activation)
-      implicit none
-      class(training_configuration_t(double_precision)), intent(in) :: self
-      type(activation_t) activation
-    end function
- 
     pure module function default_real_nodes_per_layer(self) result(nodes)
       implicit none
       class(training_configuration_t), intent(in) :: self
diff --git a/src/fiats/training_configuration_s.F90 b/src/fiats/training_configuration_s.F90
index 3b982a202..bdb57932e 100644
--- a/src/fiats/training_configuration_s.F90
+++ b/src/fiats/training_configuration_s.F90
@@ -181,54 +181,4 @@
     using_skip = self%network_configuration_%skip_connections()
   end procedure
 
-  module procedure default_real_differentiable_activation
-#if defined __INTEL_COMPILER || _CRAYFTN
-    type(string_t) :: activation_name
-    activation_name = self%network_configuration_%activation_name()
-#else
-    associate(activation_name => self%network_configuration_%activation_name())
-#endif
-      select case(activation_name%string())
-        case ("gelu")
-          activation = activation_t(gelu)
-        case ("relu")
-          activation = activation_t(relu)
-        case ("sigmoid")
-          activation = activation_t(sigmoid)
-        case ("swish")
-          activation = activation_t(swish)
-        case default
-          error stop 'activation_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
-      end select
-#if defined __INTEL_COMPILER || _CRAYFTN
-#else
-    end associate
-#endif
-  end procedure
-
-  module procedure double_precision_differentiable_activation
-#if defined __INTEL_COMPILER || _CRAYFTN
-    type(string_t) :: activation_name
-    activation_name = self%network_configuration_%activation_name()
-#else
-    associate(activation_name => self%network_configuration_%activation_name())
-#endif
-      select case(activation_name%string())
-        case ("gelu")
-          activation = activation_t(gelu)
-        case ("relu")
-          activation = activation_t(relu)
-        case ("sigmoid")
-          activation = activation_t(sigmoid)
-        case ("swish")
-          activation = activation_t(swish)
-        case default
-          error stop 'activation_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
-      end select
-#if defined __INTEL_COMPILER || _CRAYFTN
-#else
-    end associate
-#endif
-  end procedure
-
 end submodule training_configuration_s

From c574fd030c74af5aa263edc1977f09f67f1fc885 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 12:59:01 -0700
Subject: [PATCH 097/105] doc(UML): update class diagram

---
 doc/uml_diagrams/inference_engine.md | 59 +++++++++++++---------------
 1 file changed, 27 insertions(+), 32 deletions(-)

diff --git a/doc/uml_diagrams/inference_engine.md b/doc/uml_diagrams/inference_engine.md
index 6d5b7bd39..9e4880892 100644
--- a/doc/uml_diagrams/inference_engine.md
+++ b/doc/uml_diagrams/inference_engine.md
@@ -3,39 +3,38 @@ inference engine
 ```mermaid
 classDiagram
 
-class activation_strategy_t
-
-activation_strategy_t <|-- differentiable_activation_strategy_t
-
-class expected_outputs_t
-
-class inference_engine_t
-
-inference_engine_t o-- string_t
-
-input_output_pair_t o-- inputs_t
-input_output_pair_t o-- expected_outputs_t
+class neural_network_t
+class trainable_network_t
+class tensor_map_t
+class activation_t
+class metadata_t
+class mini_batch_t
+class tensor_t
+
+neural_network_t <|-- trainable_network_t
+neural_network_t o-- metadata_t
+neural_network_t o-- tensor_map_t
+neural_network_t o-- activation_t
+
+
+class neuron_t
+class layer_t
+class hyperparameters_t
+class network_configuration_t
+class file_t
+class double_precision_file_t
+
+mini_batch_t o--"1..*" input_output_pair_t
+input_output_pair_t o-- "2" tensor_t
+file_t <|-- double_precision_file_t
 
 layer_t o-- neuron_t
 layer_t o--"0..*" layer_t
-
-mini_batch_t o--"0..*" input_output_pair_t
-
-class netCDF_file_t
-
 neuron_t o--"0..*" neuron_t
 
-class outputs_t
-
-
-differentiable_activation_strategy_t <|--sigmoid_t
-<<abstract>> differentiable_activation_strategy_t
-activation_strategy_t <|-- step_t
-
-differentiable_activation_strategy_t <|-- swish_t
-
-class trainable_engine_t{
-    -metadata_:string
+class trainable_network_t{
+    -neural_network_t : neural_network_t
+    -metadata_t : metadata
     -w : real
     -b : real
     -n : integer
@@ -45,8 +44,4 @@ class trainable_engine_t{
     +infer()
     +num_layers()
     +num_inputs()
-    +to_inference_engine()
-    differentiable_activation_strategy_t()
-
 }
-trainable_engine_t o--"0..*" string_t

From 7f606a4562375b8cd0108862d80e2eae9338f332 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 14:50:43 -0700
Subject: [PATCH 098/105] doc(uml): add components & type-bound procedures

---
 doc/uml_diagrams/inference_engine.md | 38 ++++++++++++++++++++--------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/doc/uml_diagrams/inference_engine.md b/doc/uml_diagrams/inference_engine.md
index 9e4880892..31f202f7e 100644
--- a/doc/uml_diagrams/inference_engine.md
+++ b/doc/uml_diagrams/inference_engine.md
@@ -10,12 +10,13 @@ class activation_t
 class metadata_t
 class mini_batch_t
 class tensor_t
+class workspace_t
 
 neural_network_t <|-- trainable_network_t
 neural_network_t o-- metadata_t
 neural_network_t o-- tensor_map_t
 neural_network_t o-- activation_t
-
+trainable_network_t o-- workspace_t
 
 class neuron_t
 class layer_t
@@ -32,16 +33,33 @@ layer_t o-- neuron_t
 layer_t o--"0..*" layer_t
 neuron_t o--"0..*" neuron_t
 
+
+class neural_network_t{
+    -input_map_ : tensor_map_t
+    -output_map_ : tensor_map_t
+    -activation_ : activation_t
+    -metadata_ : metadata_t
+    -weights_ : real
+    -bisases_ : real
+    -nodes_ : integer
++operator(==)
++infer()
++to_json()
++map_to_input_range()
++map_from_output_range()
++num_hidden_layers()
++num_inputs()
++num_outputs()
++nodes_per_layer()       
++assert_conformable_with ()
++skip()                  
++activation_function_name()
++learn()                 
++assert_consistency() 
+}
 class trainable_network_t{
     -neural_network_t : neural_network_t
-    -metadata_t : metadata
-    -w : real
-    -b : real
-    -n : integer
-
-    +assert_consistent()
+    -workspace_ : workspace_t
     +train()
-    +infer()
-    +num_layers()
-    +num_inputs()
+    +map_to_training_ranges()
 }

From 5f163341588c136d2b805913556870f58361e3d3 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 18:06:34 -0700
Subject: [PATCH 099/105] doc(README/uml): add link to UML diagram

---
 README.md                                           | 13 +++++++++----
 .../inference_engine.md => uml/class-diagram.md}    |  0
 2 files changed, 9 insertions(+), 4 deletions(-)
 rename doc/{uml_diagrams/inference_engine.md => uml/class-diagram.md} (100%)

diff --git a/README.md b/README.md
index a02840c94..a98a7d8fa 100644
--- a/README.md
+++ b/README.md
@@ -176,15 +176,19 @@ Examples of performing inference using a neural-network JSON file are in [exampl
 
 Documentation
 -------------
-Please see our [GitHub Pages site] for HTML documentation generated by [`ford`] or generate documentation locally by installing `ford` and executing `ford ford.md`.
+### HTML
+Please see our [GitHub Pages site] for Hypertext Markup Languge (HTML) documentation generated by [`ford`] or generate documentation locally by installing `ford` and executing `ford ford.md`.
 
+### UML
+Please see the `doc/uml` subdirectory for Unified Modeling Language (UML) diagrams such as a comprehensive Fiats [class diagram] with human-readable [Mermaid] source that renders graphically when opened by browsing to the document on GitHub.
 
 [Building and testing]: #building-and-testing
 [Caffeine]: https://go.lbl.gov/caffeine
+[class diagram]: ./doc/uml/class-diagram.md
+[Documentation]: #documentation
+[demo]: demo
 [E3SM]: https://e3sm.org
 [example]: example
-[demo]: demo
-[Documentation]: #documentation
 [example/print-training-configuration.F90]: example/print-training-configuration.F90
 [example/concurrent-inferences]: example/concurrent-inferences.f90
 [`ford`]: https://github.com/Fortran-FOSS-Programmers/ford
@@ -195,8 +199,9 @@ Please see our [GitHub Pages site] for HTML documentation generated by [`ford`]
 [HDF5]: https://www.hdfgroup.org/solutions/hdf5/
 [ICAR]: https://github.com/BerkeleyLab/icar/tree/neural-net
 [JSON]: https://www.json.org/json-en.html
-[NetCDF]: https://www.unidata.ucar.edu/software/netcdf/
 [LLVM]: https://github.com/llvm/llvm-project
+[Mermaid]: https://mermaid.js.org
+[NetCDF]: https://www.unidata.ucar.edu/software/netcdf/
 [nexport]: https://go.lbl.gov/nexport
 [Overview]: #overview
 [ROCm fork]: https://github.com/ROCm/llvm-project
diff --git a/doc/uml_diagrams/inference_engine.md b/doc/uml/class-diagram.md
similarity index 100%
rename from doc/uml_diagrams/inference_engine.md
rename to doc/uml/class-diagram.md

From 5292c9ba91e2d53ba6d9505d2d77fa79b2a50e7c Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 17:47:16 -0700
Subject: [PATCH 100/105] doc(uml): add classes & relationships

---
 doc/uml/class-diagram.md | 194 +++++++++++++++++++++++++++++----------
 1 file changed, 148 insertions(+), 46 deletions(-)

diff --git a/doc/uml/class-diagram.md b/doc/uml/class-diagram.md
index 31f202f7e..2402c09ba 100644
--- a/doc/uml/class-diagram.md
+++ b/doc/uml/class-diagram.md
@@ -1,65 +1,167 @@
-inference engine
----
+Fiats Class Diagram
+-------------------
 ```mermaid
 classDiagram
 
-class neural_network_t
-class trainable_network_t
-class tensor_map_t
 class activation_t
+class double_precision_string_t
 class metadata_t
 class mini_batch_t
+class neural_network_t
+class string_t
 class tensor_t
+class tensor_map_t
+class trainable_network_t
 class workspace_t
 
-neural_network_t <|-- trainable_network_t
-neural_network_t o-- metadata_t
-neural_network_t o-- tensor_map_t
-neural_network_t o-- activation_t
-trainable_network_t o-- workspace_t
+neural_network_t    <|-- trainable_network_t
+neural_network_t     o-- metadata_t
+neural_network_t     o-- tensor_map_t
+neural_network_t     o-- activation_t
+trainable_network_t  o-- workspace_t
+string_t            <|-- double_precision_string_t
 
-class neuron_t
-class layer_t
+class double_precision_file_t
+class file_t
 class hyperparameters_t
+class layer_t
 class network_configuration_t
-class file_t
-class double_precision_file_t
-
-mini_batch_t o--"1..*" input_output_pair_t
-input_output_pair_t o-- "2" tensor_t
-file_t <|-- double_precision_file_t
+class neuron_t
 
-layer_t o-- neuron_t
-layer_t o--"0..*" layer_t
-neuron_t o--"0..*" neuron_t
+mini_batch_t         o--"1..*" input_output_pair_t
+input_output_pair_t  o-- "2"   tensor_t
+file_t              <|--       double_precision_file_t
 
+layer_t                  o-- neuron_t
+layer_t                  o--"0..*" layer_t
+neuron_t                 o--"0..*" neuron_t
+training_configuration_t o-- hyperparameters_t
+training_configuration_t o-- network_configuration_t
 
 class neural_network_t{
-    -input_map_ : tensor_map_t
-    -output_map_ : tensor_map_t
-    -activation_ : activation_t
-    -metadata_ : metadata_t
-    -weights_ : real
-    -bisases_ : real
-    -nodes_ : integer
-+operator(==)
-+infer()
-+to_json()
-+map_to_input_range()
-+map_from_output_range()
-+num_hidden_layers()
-+num_inputs()
-+num_outputs()
-+nodes_per_layer()       
-+assert_conformable_with ()
-+skip()                  
-+activation_function_name()
-+learn()                 
-+assert_consistency() 
+    - input_map_ : tensor_map_t
+    - output_map_ : tensor_map_t
+    - activation_ : activation_t
+    - metadata_ : metadata_t
+    - weights_ : real
+    - bisases_ : real
+    - nodes_ : integer
+    + operator(==) logical
+    + infer(tensor_t) tensor_t
+    + to_json() file_t
+    + map_to_input_range(tensor_t) tensor_t
+    + map_from_output_range(tensor_t) tensor_t
+    + num_hidden_layers() integer
+    + num_inputs() integer
+    + num_outputs() integer
+    + nodes_per_layer() integer
+    + assert_conformable_with()
+    + skip() logical
+    + activation_function_name() string_t
+    + learn(mini_batch_t, real, logical, real, workspace_t)
+    + assert_consistency()
 }
 class trainable_network_t{
-    -neural_network_t : neural_network_t
-    -workspace_ : workspace_t
-    +train()
-    +map_to_training_ranges()
+    - workspace_ : workspace_t
+    + train(mini_batch_t, real, logical, real)
+    + map_to_training_ranges() : input_output_pair_t
+}
+class tensor_map_t{
+  - layer_ : character
+  - intercept_ : real
+  - slope_ : real
+  + to_json() : file_t
+  + operator(==) logical
+}
+class tensor_t{
+  - values_ : real
+  + values() real
+  - num_components() integer
+}
+class metadata_t{
+  - modelName_ : string_t
+  - modelAuthor_ : string_t
+  - compilationDate_ : string_t
+  - activationFunction_: string_t
+  - usingSkipConnections_ : string_t
+  + strings() string_t
+  + to_json() file_t
+  + activation_name() string_t
+  + operator(==) logical
+}
+class mini_batch_t{
+  - input_output_pairs_ : input_output_pair_t
+  + input_output_pairs() input_output_pair_t
+}
+class activation_t{
+  - selection_ : integer
+  + function_name() string_t
+  + evaluate() real
+  + differentiate() real
+}
+class input_output_pair_t{
+  - inputs_ : tensor_t
+  - expected_outputs_ : tensor_t
+  + inputs() tensor_t
+  + expected_outputs() tensor_t
+  + shuffle()
+  + write_to_stdout()
+}
+class file_t{
+  - lines_ : string_t
+  + lines() : string_t
+  + write_lines(string_t)
+}
+class double_precision_file_t{
+  + double_precision_lines() double_precision_string_t
+}
+class hyperparameters_t{
+  - mini_batches_ : integer
+  - learning_rate_ : real
+  - optimizer_ : character
+  + to_json() file_t
+  + mini_batches() : integer
+  + learning_rate() : real
+  + optimizer_name() : character
+}
+class network_configuration_t{
+  - skip_connections_ : logical
+  - nodes_per_layer_: integer
+  - activation_name_ : character
+  + to_json() file_t
+  + operator(==) logical
+  + activation_name() string_t
+  + nodes_per_layer() integer
+  + skip_connections() logical
+}
+class layer_t{
+  - neuron_ : neuron_t
+  - next_ : layer_t
+  + neural_network() : neural_network_t
+  + count_layers() integer
+  + count_neurons() integer
+  + count_inputs() integer
+  + neurons_per_layer() integer
+  + next_allocated() logical
+  + next_pointer() layer_t
+}
+class neuron_t{
+  - weights_ : real
+  - bias_ : next
+  + to_json() file_t
+  + weights() real
+  + bias() real
+  + next_allocated() logical
+  + next_pointer() neuron_t
+  + num_inputs() integer
+}
+class training_configuration_t{
+  - hyperparameters_ : hyperparameters_t
+  - network_configuration : nework_configuration_t
+  + operator(==) logical
+  + to_json() file_t
+  + mini_batches() integer
+  + optimizer_name() string_t
+  + nodes_per_layer() integer
+  + skip_connections() logical
 }

From 48f22680918777a922cb1f9f50739efc61b415cd Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Tue, 15 Oct 2024 20:03:20 -0700
Subject: [PATCH 101/105] Revert "chore(training_configuration): rm unused
 function"

This reverts commit 31ecd597c14e0a14084ee02f028f07884f6e3a84.
---
 src/fiats/training_configuration_m.f90 | 14 ++++++++
 src/fiats/training_configuration_s.F90 | 50 ++++++++++++++++++++++++++
 2 files changed, 64 insertions(+)

diff --git a/src/fiats/training_configuration_m.f90 b/src/fiats/training_configuration_m.f90
index be693220d..eb20a30f0 100644
--- a/src/fiats/training_configuration_m.f90
+++ b/src/fiats/training_configuration_m.f90
@@ -28,6 +28,8 @@ module training_configuration_m
     procedure, private ::        default_real_optimizer_name, double_precision_optimizer_name
     generic :: learning_rate => default_real_learning_rate, double_precision_learning_rate
     procedure, private ::       default_real_learning_rate, double_precision_learning_rate
+    generic :: differentiable_activation => default_real_differentiable_activation, double_precision_differentiable_activation
+    procedure, private ::                   default_real_differentiable_activation, double_precision_differentiable_activation
     generic :: nodes_per_layer => default_real_nodes_per_layer, double_precision_nodes_per_layer
     procedure, private ::         default_real_nodes_per_layer, double_precision_nodes_per_layer
     generic :: skip_connections => default_real_skip_connections, double_precision_skip_connections
@@ -126,6 +128,18 @@ elemental module function double_precision_learning_rate(self) result(rate)
       double precision rate
     end function
 
+    module function default_real_differentiable_activation(self) result(activation)
+      implicit none
+      class(training_configuration_t), intent(in) :: self
+      type(activation_t) activation
+    end function
+
+    module function double_precision_differentiable_activation(self) result(activation)
+      implicit none
+      class(training_configuration_t(double_precision)), intent(in) :: self
+      type(activation_t) activation
+    end function
+ 
     pure module function default_real_nodes_per_layer(self) result(nodes)
       implicit none
       class(training_configuration_t), intent(in) :: self
diff --git a/src/fiats/training_configuration_s.F90 b/src/fiats/training_configuration_s.F90
index bdb57932e..3b982a202 100644
--- a/src/fiats/training_configuration_s.F90
+++ b/src/fiats/training_configuration_s.F90
@@ -181,4 +181,54 @@
     using_skip = self%network_configuration_%skip_connections()
   end procedure
 
+  module procedure default_real_differentiable_activation
+#if defined __INTEL_COMPILER || _CRAYFTN
+    type(string_t) :: activation_name
+    activation_name = self%network_configuration_%activation_name()
+#else
+    associate(activation_name => self%network_configuration_%activation_name())
+#endif
+      select case(activation_name%string())
+        case ("gelu")
+          activation = activation_t(gelu)
+        case ("relu")
+          activation = activation_t(relu)
+        case ("sigmoid")
+          activation = activation_t(sigmoid)
+        case ("swish")
+          activation = activation_t(swish)
+        case default
+          error stop 'activation_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
+      end select
+#if defined __INTEL_COMPILER || _CRAYFTN
+#else
+    end associate
+#endif
+  end procedure
+
+  module procedure double_precision_differentiable_activation
+#if defined __INTEL_COMPILER || _CRAYFTN
+    type(string_t) :: activation_name
+    activation_name = self%network_configuration_%activation_name()
+#else
+    associate(activation_name => self%network_configuration_%activation_name())
+#endif
+      select case(activation_name%string())
+        case ("gelu")
+          activation = activation_t(gelu)
+        case ("relu")
+          activation = activation_t(relu)
+        case ("sigmoid")
+          activation = activation_t(sigmoid)
+        case ("swish")
+          activation = activation_t(swish)
+        case default
+          error stop 'activation_factory_s(factory): unrecognized activation name "' // activation_name%string() // '"' 
+      end select
+#if defined __INTEL_COMPILER || _CRAYFTN
+#else
+    end associate
+#endif
+  end procedure
+
 end submodule training_configuration_s

From 289dc4a86f201538f265b54cae41a440f122dbc6 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 16 Oct 2024 15:40:20 -0700
Subject: [PATCH 102/105] doc(README): edit compiler status

---
 README.md | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index a98a7d8fa..a644424ff 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,8 @@ We recommend using any compiler's latest release or even building open-source co
 The [handy-dandy] repository contains scripts capturing steps for building the [LLVM] compiler suite.
 The remainder of this section contains commands for building Fiats with a recent Fortran compiler and the Fortran Package Manager ([`fpm`]).
 
-#### LLVM (`flang-new`)
+#### Supported Compilers
+##### LLVM (`flang-new`)
 With LLVM `flang` 20 installed in your `PATH`, build and test Fiats with the installed `flang-new` symlink in order for `fpm` to correctly identify the compiler:
 ```
 fpm test --compiler flang-new --flag "-O3"
@@ -72,7 +73,7 @@ With LLVM `flang` 19, enable the compiler's experimental support for assumed-ran
 fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3"
 ```
 
-##### _Experimental:_ Automatic parallelization of `do concurrent` on CPUs
+###### _Experimental:_ Automatic parallelization of `do concurrent` on CPUs
 With the `amd_trunk_dev` branch of the [ROCm fork] fork of LLVM, automatically parallelize inference calculations inside `do concurrent` constructs:
 ```
 fpm run \
@@ -86,19 +87,24 @@ where `model.json` must be a neural network in the [JSON] format used by Fiats a
 
 Automatic parallelization for training neural networks is under development.
 
-#### NAG (`nagfor`)
+#### Partially Supported Compilers
+
+Fiats release 0.14.0 and earlier support the use of the NAG, GNU, and Intel Fortran compilers.
+We are corresponding with these compilers' developers about addressing the compiler issues preventing building newer Fiats releases.
+
+##### NAG (`nagfor`)
 ```
 fpm test --compiler nagfor --flag -fpp --profile release
 ```
 
-#### GNU (`gfortran`)
+##### GNU (`gfortran`)
 Compiler bugs related to parameterized derived types currently prevent `gfortran` from building Fiats versions 0.15.0 or later.
 Test and build earlier versions of Fiats build with the following command:
 ```
 fpm test --compiler gfortran --profile release
 ```
 
-#### Intel (`ifx`)
+##### Intel (`ifx`)
 Compiler bugs related to parameterized derived types currently prevent `gfortran` from building Fiats versions 0.15.0 or later.
 Test and build earlier versions of Fiats build with the following command:
 ```
@@ -111,11 +117,12 @@ This capability is under development with the goal to facilitate automatic GPU o
 fpm test --compiler ifx --profile release --flag "-fopenmp-target-do-concurrent -qopenmp -fopenmp-targets=spir64 -O3"
 ```
 
-#### HPE (`crayftn.sh`)
-Support for the Cray Compiler Environment (CCE) Fortran compiler is under development.
-Building with the CCE `ftn` compiler wrapper requires an additional trivial wrapper
-shell script. For example, create a file `crayftn.sh` with the following contents and
-place this file's location in your `PATH`:
+#### Under Development
+We are corresponding with the developers of the compiler(s) below developers about addressing the compiler issues preventing building newer Fiats releases.
+
+#### HPE Cray Compiler Environment (CCE) (`crayftn.sh`)
+Building with the CCE `ftn` compiler wrapper requires an additional trivial wrapper.
+For example, create a file `crayftn.sh` with the following contents and place this file's location in your `PATH`:
 ```
 #!/bin/bash
 

From dcc407ef5e489458efd0d78a586bed8e6c9e9aaa Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 16 Oct 2024 15:53:25 -0700
Subject: [PATCH 103/105] doc(README): fix typo in branch name

Co-authored-by: Katherine Rasmussen <krasmussen@lbl.gov>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a644424ff..16625640a 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3"
 ```
 
 ###### _Experimental:_ Automatic parallelization of `do concurrent` on CPUs
-With the `amd_trunk_dev` branch of the [ROCm fork] fork of LLVM, automatically parallelize inference calculations inside `do concurrent` constructs:
+With the `amd-trunk-dev` branch of the [ROCm fork] of LLVM, automatically parallelize inference calculations inside `do concurrent` constructs:
 ```
 fpm run \
   --example concurrent-inferences \

From 55c377b1bc2756b231d8a0ab223b6166fef20ef3 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 16 Oct 2024 16:55:45 -0700
Subject: [PATCH 104/105] doc(README): specify compiler issue

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 16625640a..c620ebd1d 100644
--- a/README.md
+++ b/README.md
@@ -105,7 +105,7 @@ fpm test --compiler gfortran --profile release
 ```
 
 ##### Intel (`ifx`)
-Compiler bugs related to parameterized derived types currently prevent `gfortran` from building Fiats versions 0.15.0 or later.
+Compiler bugs related to generic name resolution currently prevent `ifx` from building Fiats versions 0.15.0 or later.
 Test and build earlier versions of Fiats build with the following command:
 ```
 fpm test --compiler ifx --profile release --flag -O3

From 53c8550f2ec66cc81b6911152d25acce77a273f5 Mon Sep 17 00:00:00 2001
From: Damian Rouson <rouson@lbl.gov>
Date: Wed, 16 Oct 2024 16:58:03 -0700
Subject: [PATCH 105/105] doc(README): fix CCE compiler status text

Co-authored-by: Katherine Rasmussen <krasmussen@lbl.gov>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index c620ebd1d..2e07f20bf 100644
--- a/README.md
+++ b/README.md
@@ -118,7 +118,7 @@ fpm test --compiler ifx --profile release --flag "-fopenmp-target-do-concurrent
 ```
 
 #### Under Development
-We are corresponding with the developers of the compiler(s) below developers about addressing the compiler issues preventing building newer Fiats releases.
+We are corresponding with the developers of the compiler(s) below about addressing the compiler issues preventing building Fiats.
 
 #### HPE Cray Compiler Environment (CCE) (`crayftn.sh`)
 Building with the CCE `ftn` compiler wrapper requires an additional trivial wrapper.
