Repository: astro-informatics/harmonic (language: python)

Issue:
Normalizing flows model



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index d3ee0ef9..a07048b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ docs/assets/static_notebooks/.ipynb_checkpoints/
 docs/assets/static_notebooks/.temp.*
 dist/
 harmonic.egg-info/
+harmonic/examples/data/NUTS
diff --git a/README.rst b/README.rst
index 841f7225..f4da42ce 100644
--- a/README.rst
+++ b/README.rst
@@ -89,7 +89,7 @@ Comprehensive  `documentation for harmonic <https://astro-informatics.github.io/
 Contributors
 ============
 
-`Jason D. McEwen <http://www.jasonmcewen.org/>`_, `Christopher G. R. Wallis <https://scholar.google.co.uk/citations?user=Igl7nakAAAAJ&hl=en>`_, `Matthew A. Price <https://cosmomatt.github.io/>`_, `Matthew M. Docherty <https://mdochertyastro.com/>`_, `Alessio Spurio Mancini <https://www.alessiospuriomancini.com/>`_
+`Jason D. McEwen <http://www.jasonmcewen.org/>`_, `Christopher G. R. Wallis <https://scholar.google.co.uk/citations?user=Igl7nakAAAAJ&hl=en>`_, `Matthew A. Price <https://cosmomatt.github.io/>`_, `Matthew M. Docherty <https://mdochertyastro.com/>`_, `Alessio Spurio Mancini <https://www.alessiospuriomancini.com/>`_, `Alicja Polanska <https://alicjaap.github.io/>`_.
 
 
 Attribution
@@ -133,7 +133,8 @@ the non-commercial use condition (see `LICENSE_EXT.txt <https://github.com/astro
 
      harmonic
      Copyright (C) 2021 Jason D. McEwen, Christopher G. R. Wallis, 
-     Matthew A. Price, Matthew M. Docherty, Alessio Spurio Mancini & contributors
+     Matthew A. Price, Matthew M. Docherty, Alessio Spurio Mancini, 
+     Alicja Polanska & contributors
 
      This program is released under the GPL-3 license (see LICENSE.txt), 
      subject to a non-commercial use condition (see LICENSE_EXT.txt).
diff --git a/build_harmonic.sh b/build_harmonic.sh
index b0ffc172..ecdcd775 100755
--- a/build_harmonic.sh
+++ b/build_harmonic.sh
@@ -3,6 +3,11 @@
 # Install core and extra requirements
 
 echo -ne 'Building Dependencies... \r'
+
+# Install jax and TFP on jax substrates (on M1 mac)
+# conda install -q -c conda-forge jax==0.4.1 -y
+# conda install -q -c conda-forge flax==0.6.1 chex==0.1.6 -y
+
 pip install -q -r requirements/requirements-core.txt
 echo -ne 'Building Dependencies... #####               (25%)\r'
 pip install -q -r requirements/requirements-examples.txt
@@ -13,9 +18,11 @@ pip install -q -r requirements/requirements-docs.txt
 echo -ne 'Building Dependencies... ####################(100%)\r'
 echo -ne '\n'
 
+# pip install -Uq tfp-nightly[jax]==0.20.0.dev20230801 > /dev/null
+
 # Install specific converter for building tutorial documentation
 
-conda install pandoc=1.19.2.1 -y
+# conda install pandoc=1.19.2.1 -y
 
 # Build Harmonic
 
diff --git a/examples/gaussian_diagcov.py b/examples/gaussian_diagcov.py
index 303cb33d..ec3360a0 100644
--- a/examples/gaussian_diagcov.py
+++ b/examples/gaussian_diagcov.py
@@ -1,13 +1,14 @@
 import numpy as np
 import sys
+
 sys.path.append(".")
 import harmonic as hm
 import emcee
 import scipy.special as sp
-import time 
+import time
 import matplotlib.pyplot as plt
 import utils
-import gc 
+import gc
 
 
 def ln_analytic_evidence(ndim, cov):
@@ -25,7 +26,7 @@ def ln_analytic_evidence(ndim, cov):
 
     """
 
-    ln_norm_lik = -0.5*ndim*np.log(2*np.pi)-0.5*np.log(np.linalg.det(cov))   
+    ln_norm_lik = -0.5 * ndim * np.log(2 * np.pi) - 0.5 * np.log(np.linalg.det(cov))
     return -ln_norm_lik
 
 
@@ -42,10 +43,10 @@ def ln_posterior(x, inv_cov):
 
     """
 
-    return -np.dot(x,np.dot(inv_cov,x))/2.0   
+    return -np.dot(x, np.dot(inv_cov, x)) / 2.0
 
 
-def init_cov(ndim): 
+def init_cov(ndim):
     """Initialise random diagonal covariance matrix.
 
     Args:
@@ -58,16 +59,22 @@ def init_cov(ndim):
 
     """
 
-    cov = np.zeros((ndim,ndim))
+    cov = np.zeros((ndim, ndim))
     diag_cov = np.ones(ndim)
     np.fill_diagonal(cov, diag_cov)
-    
+
     return cov
 
 
-def run_example(ndim=2, nchains=100, samples_per_chain=1000, 
-                nburn=500, chain_iterations=1, 
-                plot_corner=False, plot_surface=False):
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    chain_iterations=1,
+    plot_corner=False,
+    plot_surface=False,
+):
     """
     Run nD Gaussian example with generalised covariance matrix.
 
@@ -96,22 +103,23 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     # Initialise covariance matrix.
     # ==========================================================================
     cov = init_cov(ndim)
-    inv_cov = np.linalg.inv(cov)  
-    hm.logs.debug_log('Covariance matrix diagonal entries = \n{}'
-        .format(np.diagonal(cov)))
+    inv_cov = np.linalg.inv(cov)
+    hm.logs.debug_log(
+        "Covariance matrix diagonal entries = \n{}".format(np.diagonal(cov))
+    )
 
     # ==========================================================================
     # Compute analytic log-evidence for comparison
     # ==========================================================================
     ln_rho = -ln_analytic_evidence(ndim, cov)
-    hm.logs.info_log('Ln Inverse Analytic evidence = {}'.format(ln_rho))
+    hm.logs.info_log("Ln Inverse Analytic evidence = {}".format(ln_rho))
 
     # ==========================================================================
     # Set up hyper-parameters for AI model
     # ==========================================================================
 
-    max_r_prob = np.sqrt(ndim-1)
-    domains_sphere = [max_r_prob*np.array([1E0,2E1])]
+    max_r_prob = np.sqrt(ndim - 1)
+    domains_sphere = [max_r_prob * np.array([1e0, 2e1])]
     hyper_parameters_sphere = [None]
 
     # ==========================================================================
@@ -122,26 +130,29 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     clock = time.process_time()
 
     # ======================================================================
-    # Recover a set of MCMC samples from the posterior 
+    # Recover a set of MCMC samples from the posterior
     # ======================================================================
 
     burn_iterations = 50
     pos = np.random.rand(ndim * nchains).reshape((nchains, ndim))
-    rstate = np.random.get_state() # Set random state to be repeatable.
+    rstate = np.random.get_state()  # Set random state to be repeatable.
 
     for burn_iteration in range(burn_iterations):
-        hm.logs.info_log('Run burn sampling for burning subiteration {}...'.format(
-                burn_iteration+1))
+        hm.logs.info_log(
+            "Run burn sampling for burning subiteration {}...".format(
+                burn_iteration + 1
+            )
+        )
         # Clear memory
         if burn_iteration > 0:
             del sampler, prob
             gc.collect()
         # Run the emcee sampler from previous endpoint
-        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, \
-                                    args=[inv_cov])
+        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=[inv_cov])
 
-        (pos, prob, rstate) = sampler.run_mcmc(pos, nburn/burn_iterations, \
-                                      rstate0=rstate) 
+        (pos, prob, rstate) = sampler.run_mcmc(
+            pos, nburn / burn_iterations, rstate0=rstate
+        )
 
     # ======================================================================
     # Configure chains
@@ -151,34 +162,34 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     lnprob = []
 
     # ======================================================================
-    # Train hyper-spherical model 
+    # Train hyper-spherical model
     # ======================================================================
     model = hm.model.HyperSphere(ndim, domains_sphere)
     model.set_R(max_r_prob)
     model.fitted = True
-    
+
     # ======================================================================
     # Compute ln evidence by iteratively adding chains
     # ======================================================================
     # Instantiate the evidence class
-    hm.logs.info_log('Compute evidence...')
+    hm.logs.info_log("Compute evidence...")
     cal_ev = hm.Evidence(nchains, model)
 
     for chain_iteration in range(chain_iterations):
-        hm.logs.info_log('Run sampling for chain subiteration {}...'.format(
-                chain_iteration+1))
+        hm.logs.info_log(
+            "Run sampling for chain subiteration {}...".format(chain_iteration + 1)
+        )
 
         # Clear memory
         del chains, samples, lnprob, sampler, prob
         gc.collect()
         # Run the emcee sampler from previous endpoint
-        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, \
-                                    args=[inv_cov])
-        (pos, prob, rstate) = sampler.run_mcmc(pos, \
-                             (samples_per_chain-nburn)/chain_iterations, \
-                              rstate0=rstate) 
-        samples = np.ascontiguousarray(sampler.chain[:,:,:])
-        lnprob = np.ascontiguousarray(sampler.lnprobability[:,:])
+        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=[inv_cov])
+        (pos, prob, rstate) = sampler.run_mcmc(
+            pos, (samples_per_chain - nburn) / chain_iterations, rstate0=rstate
+        )
+        samples = np.ascontiguousarray(sampler.chain[:, :, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, :])
         # Create a new chains class and add the new chains
         chains = hm.Chains(ndim)
         chains.add_chains_3d(samples, lnprob)
@@ -191,99 +202,121 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     # ======================================================================
     # Display logarithmic inverse evidence computation results.
     # ======================================================================
-    hm.logs.info_log('Ln Inv Evidence: analytic = {}, estimate = {}'
-        .format(ln_rho, cal_ev.ln_evidence_inv))
-    hm.logs.info_log('Ln Inv Evidence: 100 * |analytic - estimate| / |analytic| = {}%'
-        .format(100.0 * np.abs( (cal_ev.ln_evidence_inv - ln_rho) \
-                                                             / ln_rho ))) 
+    hm.logs.info_log(
+        "Ln Inv Evidence: analytic = {}, estimate = {}".format(
+            ln_rho, cal_ev.ln_evidence_inv
+        )
+    )
+    hm.logs.info_log(
+        "Ln Inv Evidence: 100 * |analytic - estimate| / |analytic| = {}%".format(
+            100.0 * np.abs((cal_ev.ln_evidence_inv - ln_rho) / ln_rho)
+        )
+    )
     # ======================================================================
     # Display inverse evidence computation results.
     # ======================================================================
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('Inv Evidence: analytic = {}, estimate = {}'
-        .format(np.exp(ln_rho), cal_ev.evidence_inv))
-    hm.logs.debug_log('Inv Evidence: std = {}, std / estimate = {}'
-        .format(np.sqrt(cal_ev.evidence_inv_var), \
-                np.sqrt(cal_ev.evidence_inv_var)/cal_ev.evidence_inv))
-    hm.logs.info_log("Inv Evidence: 100 * |analytic - estimate| / estimate = {}%"
-        .format(100.0 * np.abs( np.exp(ln_rho) - cal_ev.evidence_inv ) \
-                                               / cal_ev.evidence_inv ) )
-
-    #===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log(
+        "Inv Evidence: analytic = {}, estimate = {}".format(
+            np.exp(ln_rho), cal_ev.evidence_inv
+        )
+    )
+    hm.logs.debug_log(
+        "Inv Evidence: std = {}, std / estimate = {}".format(
+            np.sqrt(cal_ev.evidence_inv_var),
+            np.sqrt(cal_ev.evidence_inv_var) / cal_ev.evidence_inv,
+        )
+    )
+    hm.logs.info_log(
+        "Inv Evidence: 100 * |analytic - estimate| / estimate = {}%".format(
+            100.0 * np.abs(np.exp(ln_rho) - cal_ev.evidence_inv) / cal_ev.evidence_inv
+        )
+    )
+
+    # ===========================================================================
     # Display more technical details
-    #===========================================================================
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('Technical Details')
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-        .format(cal_ev.lnargmax, cal_ev.lnargmin))
-    hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-        .format(cal_ev.lnprobmax, cal_ev.lnprobmin))
-    hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-        .format(cal_ev.lnpredictmax, cal_ev.lnpredictmin))
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('shift = {}, shift setting = {}'
-        .format(cal_ev.shift_value, cal_ev.shift))
-    hm.logs.debug_log('running sum total = {}'
-        .format(sum(cal_ev.running_sum)))
-    hm.logs.debug_log('running sum = \n{}'
-        .format(cal_ev.running_sum))
-    hm.logs.debug_log('nsamples per chain = \n{}'
-        .format(cal_ev.nsamples_per_chain))
-    hm.logs.debug_log('nsamples eff per chain = \n{}'
-        .format(cal_ev.nsamples_eff_per_chain))
-    hm.logs.debug_log('===============================')
-    
+    # ===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("Technical Details")
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log(
+        "lnargmax = {}, lnargmin = {}".format(cal_ev.lnargmax, cal_ev.lnargmin)
+    )
+    hm.logs.debug_log(
+        "lnprobmax = {}, lnprobmin = {}".format(cal_ev.lnprobmax, cal_ev.lnprobmin)
+    )
+    hm.logs.debug_log(
+        "lnpredictmax = {}, lnpredictmin = {}".format(
+            cal_ev.lnpredictmax, cal_ev.lnpredictmin
+        )
+    )
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log(
+        "shift = {}, shift setting = {}".format(cal_ev.shift_value, cal_ev.shift)
+    )
+    hm.logs.debug_log("running sum total = {}".format(sum(cal_ev.running_sum)))
+    hm.logs.debug_log("running sum = \n{}".format(cal_ev.running_sum))
+    hm.logs.debug_log("nsamples per chain = \n{}".format(cal_ev.nsamples_per_chain))
+    hm.logs.debug_log(
+        "nsamples eff per chain = \n{}".format(cal_ev.nsamples_eff_per_chain)
+    )
+    hm.logs.debug_log("===============================")
+
     # ======================================================================
     # Create corner/triangle plot.
     # ======================================================================
     created_plots = False
     if plot_corner and i_realisation == 0:
-        
         utils.plot_corner(samples.reshape((-1, ndim)))
 
         if savefigs:
-            plt.savefig('examples/plots/nD_gaussian_corner.png',
-                        bbox_inches='tight')
+            plt.savefig("examples/plots/nD_gaussian_corner.png", bbox_inches="tight")
 
         plt.show(block=False)
         created_plots = True
 
     clock = time.process_time() - clock
-    hm.logs.info_log('Execution_time = {}s'.format(clock))
+    hm.logs.info_log("Execution_time = {}s".format(clock))
 
     if created_plots:
         input("\nPress Enter to continue...")
-    
+
     return samples
 
-if __name__ == '__main__':
 
+if __name__ == "__main__":
     # Setup logging config.
     hm.logs.setup_logging()
 
     # Define parameters.
     ndim = 32
-    nchains = 2*ndim
+    nchains = 2 * ndim
     samples_per_chain = 10000
     nburn = 7000
     chain_iterations = 200
     np.random.seed(2)
 
-    hm.logs.info_log('nD Gaussian example')
-
-    hm.logs.debug_log('-- Selected Parameters --')
+    hm.logs.info_log("nD Gaussian example")
 
-    hm.logs.debug_log('Dimensionality = {}'.format(ndim))
-    hm.logs.debug_log('Number of chains = {}'.format(nchains))
-    hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-    hm.logs.debug_log('Burn in = {}'.format(nburn))
-    hm.logs.debug_log('Number of additional chain iterations = {}'.format(chain_iterations))
-    
-    hm.logs.debug_log('-------------------------')
+    hm.logs.debug_log("-- Selected Parameters --")
 
-    # Run example.
-    run_example(ndim, nchains, samples_per_chain, nburn, chain_iterations,
-                plot_corner=False, plot_surface=False)
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+    hm.logs.debug_log(
+        "Number of additional chain iterations = {}".format(chain_iterations)
+    )
 
+    hm.logs.debug_log("-------------------------")
 
+    # Run example.
+    run_example(
+        ndim,
+        nchains,
+        samples_per_chain,
+        nburn,
+        chain_iterations,
+        plot_corner=False,
+        plot_surface=False,
+    )
diff --git a/examples/gaussian_nondiagcov.py b/examples/gaussian_nondiagcov.py
index e1c8d029..b6b5abba 100644
--- a/examples/gaussian_nondiagcov.py
+++ b/examples/gaussian_nondiagcov.py
@@ -1,12 +1,14 @@
 import numpy as np
 import sys
 import emcee
-import time 
+import time
 import matplotlib.pyplot as plt
 from functools import partial
 from matplotlib import cm
+
 sys.path.append(".")
 import harmonic as hm
+
 sys.path.append("examples")
 import utils
 
@@ -25,8 +27,8 @@ def ln_analytic_evidence(ndim, cov):
         double: Analytic evidence.
 
     """
-    
-    ln_norm_lik = 0.5*ndim*np.log(2*np.pi) + 0.5*np.log(np.linalg.det(cov))
+
+    ln_norm_lik = 0.5 * ndim * np.log(2 * np.pi) + 0.5 * np.log(np.linalg.det(cov))
     return ln_norm_lik
 
 
@@ -44,8 +46,8 @@ def ln_posterior(x, inv_cov):
         double: Value of Gaussian at specified point.
 
     """
-    
-    return -np.dot(x,np.dot(inv_cov,x))/2.0
+
+    return -np.dot(x, np.dot(inv_cov, x)) / 2.0
 
 
 def init_cov(ndim):
@@ -60,20 +62,26 @@ def init_cov(ndim):
         cov: Covariance matrix of shape (ndim,ndim).
 
     """
-    
-    cov = np.zeros((ndim,ndim))
-    diag_cov = np.ones(ndim) + np.random.randn(ndim)*0.1
+
+    cov = np.zeros((ndim, ndim))
+    diag_cov = np.ones(ndim) + np.random.randn(ndim) * 0.1
     np.fill_diagonal(cov, diag_cov)
-    
-    for i in range(ndim-1):
-        cov[i,i+1] = (-1)**i * 0.5*np.sqrt(cov[i,i]*cov[i+1,i+1])
-        cov[i+1,i] = cov[i,i+1]
-    
+
+    for i in range(ndim - 1):
+        cov[i, i + 1] = (-1) ** i * 0.5 * np.sqrt(cov[i, i] * cov[i + 1, i + 1])
+        cov[i + 1, i] = cov[i, i + 1]
+
     return cov
 
 
-def run_example(ndim=2, nchains=100, samples_per_chain=1000, 
-                nburn=500, plot_corner=False, plot_surface=False):
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_surface=False,
+):
     """Run Gaussian example with non-diagonal covariance matrix.
 
     Args:
@@ -96,60 +104,60 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
 
     # Initialise covariance matrix.
     cov = init_cov(ndim)
-    inv_cov = np.linalg.inv(cov)    
+    inv_cov = np.linalg.inv(cov)
 
     # Start timer.
     clock = time.process_time()
-    
+
     # Run multiple realisations.
     n_realisations = 100
-    evidence_inv_summary = np.zeros((n_realisations,3))
+    evidence_inv_summary = np.zeros((n_realisations, 3))
     for i_realisation in range(n_realisations):
-        
         if n_realisations > 0:
-            hm.logs.info_log('Realisation = {}/{}'
-                .format(i_realisation+1, n_realisations))
+            hm.logs.info_log(
+                "Realisation = {}/{}".format(i_realisation + 1, n_realisations)
+            )
 
         # Set up and run sampler.
-        hm.logs.info_log('Run sampling...')
+        hm.logs.info_log("Run sampling...")
 
         pos = np.random.rand(ndim * nchains).reshape((nchains, ndim))
 
-        sampler = emcee.EnsembleSampler(nchains, ndim, \
-                                        ln_posterior, args=[inv_cov])
-        rstate = np.random.get_state() # Set random state to repeatable 
-                                       # across calls.
-        (pos, prob, state) = sampler.run_mcmc(pos, samples_per_chain,  
-                                              rstate0=rstate) 
-        samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])
-        lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])
-            
+        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=[inv_cov])
+        rstate = np.random.get_state()  # Set random state to repeatable
+        # across calls.
+        (pos, prob, state) = sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
         # Calculate evidence using harmonic....
-        
+
         # Set up chains.
         chains = hm.Chains(ndim)
         chains.add_chains_3d(samples, lnprob)
-        chains_train, chains_test = hm.utils.split_data(chains, \
-                                                    training_proportion=0.05)
-        
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=0.05
+        )
+
         # Fit model.
-        hm.logs.info_log('Fit model...')
+        hm.logs.info_log("Fit model...")
 
-        r_scale = np.sqrt(ndim-1)
-        hm.logs.debug_log('r scale = {}'.format(r_scale))
-        domains = [r_scale*np.array([1E-1,1E0])]
-        hm.logs.debug_log('Domain = {}'.format(domains))
+        r_scale = np.sqrt(ndim - 1)
+        hm.logs.debug_log("r scale = {}".format(r_scale))
+        domains = [r_scale * np.array([1e-1, 1e0])]
+        hm.logs.debug_log("Domain = {}".format(domains))
         model = hm.model.HyperSphere(ndim, domains)
-        fit_success, objective = model.fit(chains_train.samples, \
-                                           chains_train.ln_posterior)        
+        fit_success, objective = model.fit(
+            chains_train.samples, chains_train.ln_posterior
+        )
 
-        hm.logs.info_log('Fit success = {}'.format(fit_success))    
-        hm.logs.info_log('Objective = {}'.format(objective))    
+        hm.logs.info_log("Fit success = {}".format(fit_success))
+        hm.logs.info_log("Objective = {}".format(objective))
 
         # Use chains and model to compute inverse evidence.
-        hm.logs.info_log('Compute evidence...')
+        hm.logs.info_log("Compute evidence...")
 
-        ev = hm.Evidence(chains_test.nchains, model)    
+        ev = hm.Evidence(chains_test.nchains, model)
         # ev.set_mean_shift(0.0)
         ev.add_chains(chains_test)
         ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
@@ -161,188 +169,243 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
         # ======================================================================
         # Display evidence computation results.
         # ======================================================================
-        hm.logs.info_log('---------------------------------')
-        hm.logs.info_log('Evidence: analytic = {}, estimated = {}'
-            .format(np.exp(ln_evidence_analytic), np.exp(ln_evidence)))
-        hm.logs.info_log('Evidence: std = {}, std / estimate = {}'
-            .format(np.exp(ln_evidence_std), \
-                    np.exp(ln_evidence_std - ln_evidence)))
-        diff = np.log(np.abs(np.exp(ln_evidence_analytic) -np.exp(ln_evidence)))
-        hm.logs.info_log("Evidence: |analytic - estimate| / estimate = {}"
-            .format(np.exp(diff - ln_evidence)))
+        hm.logs.info_log("---------------------------------")
+        hm.logs.info_log(
+            "Evidence: analytic = {}, estimated = {}".format(
+                np.exp(ln_evidence_analytic), np.exp(ln_evidence)
+            )
+        )
+        hm.logs.info_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(np.exp(ln_evidence_analytic) - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |analytic - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
         # ======================================================================
         # Display inverse evidence computation results.
         # ======================================================================
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('Inv Evidence: analytic = {}, estimate = {}'
-            .format(np.exp(-ln_evidence_analytic), ev.evidence_inv))
-        hm.logs.debug_log('Inv Evidence: std = {}, std / estimate = {}'
-            .format(np.sqrt(ev.evidence_inv_var), \
-                    np.sqrt(ev.evidence_inv_var)/ev.evidence_inv))
-        hm.logs.debug_log('Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}'
-            .format(ev.kurtosis, np.sqrt(2.0/(ev.n_eff-1))))     
-        hm.logs.debug_log('Inv Evidence: sqrt( var(var) ) / var = {}'
-            .format(np.sqrt(ev.evidence_inv_var_var)/ev.evidence_inv_var))        
-        hm.logs.info_log('Inv Evidence: |analytic - estimate| / estimate = {}'
-            .format(np.abs(np.exp(-ln_evidence_analytic) \
-                                  - ev.evidence_inv)/ev.evidence_inv))   
-
-        #===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "Inv Evidence: analytic = {}, estimate = {}".format(
+                np.exp(-ln_evidence_analytic), ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) ) / var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |analytic - estimate| / estimate = {}".format(
+                np.abs(np.exp(-ln_evidence_analytic) - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
         # Display more technical details
-        #===========================================================================
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('Technical Details')
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-            .format(ev.lnargmax, ev.lnargmin))
-        hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-            .format(ev.lnprobmax, ev.lnprobmin))
-        hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-            .format(ev.lnpredictmax, ev.lnpredictmin))
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('shift = {}, shift setting = {}'
-            .format(ev.shift_value, ev.shift))
-        hm.logs.debug_log('running sum total = {}'
-            .format(sum(ev.running_sum)))
-        hm.logs.debug_log('running sum = \n{}'
-            .format(ev.running_sum))
-        hm.logs.debug_log('nsamples per chain = \n{}'
-            .format(ev.nsamples_per_chain))
-        hm.logs.debug_log('nsamples eff per chain = \n{}'
-            .format(ev.nsamples_eff_per_chain))
-        hm.logs.debug_log('===============================')
-    
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
         # ======================================================================
         # Create corner/triangle plot.
         # ======================================================================
         if plot_corner and i_realisation == 0:
-            
             utils.plot_corner(samples.reshape((-1, ndim)))
             if savefigs:
-                plt.savefig('examples/plots/gaussian_nondiagcov_corner.png',
-                            bbox_inches='tight')
-            
+                plt.savefig(
+                    "examples/plots/gaussian_nondiagcov_corner.png", bbox_inches="tight"
+                )
+
             utils.plot_getdist(samples.reshape((-1, ndim)))
             if savefigs:
-                plt.savefig('examples/plots/gaussian_nondiagcov_getdist.png',
-                            bbox_inches='tight')
-                    
-            plt.show()        
-            
+                plt.savefig(
+                    "examples/plots/gaussian_nondiagcov_getdist.png",
+                    bbox_inches="tight",
+                )
+
+            plt.show()
+
         # ======================================================================
-        # In 2D case, plot surface/image and samples.  
+        # In 2D case, plot surface/image and samples.
         # ======================================================================
         if plot_surface and ndim == 2 and i_realisation == 0:
-                    
             # ==================================================================
-            # Define plot parameters.  
+            # Define plot parameters.
             # ==================================================================
             nx = 50
             xmin = -3.0
             xmax = 3.0
 
             # ==================================================================
-            # 2D surface plot of posterior. 
+            # 2D surface plot of posterior.
             # ==================================================================
             ln_posterior_func = partial(ln_posterior, inv_cov=inv_cov)
-            ln_posterior_grid, x_grid, y_grid = \
-                utils.eval_func_on_grid(ln_posterior_func, 
-                                        xmin=xmin, xmax=xmax, 
-                                        ymin=xmin, ymax=xmax, 
-                                        nx=nx, ny=nx)
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=xmin,
+                xmax=xmax,
+                ymin=xmin,
+                ymax=xmax,
+                nx=nx,
+                ny=nx,
+            )
             i_chain = 0
-            ax = utils.plot_surface(np.exp(ln_posterior_grid), x_grid, y_grid, 
-                                    samples[i_chain,:,:].reshape((-1, ndim)), 
-                                    np.exp(lnprob[i_chain,:].reshape((-1, 1))),
-                                    contour_z_offset=-0.5, alpha=0.3)
-               
-            ax.set_zlabel(r'$\mathcal{L}$') 
+            ax = utils.plot_surface(
+                np.exp(ln_posterior_grid),
+                x_grid,
+                y_grid,
+                samples[i_chain, :, :].reshape((-1, ndim)),
+                np.exp(lnprob[i_chain, :].reshape((-1, 1))),
+                contour_z_offset=-0.5,
+                alpha=0.3,
+            )
+
+            ax.set_zlabel(r"$\mathcal{L}$")
 
             # Save.
             if savefigs:
-                plt.savefig('examples/plots/gaussian_nondiagcov_posterior_surface.png'\
-                    , bbox_inches='tight')
+                plt.savefig(
+                    "examples/plots/gaussian_nondiagcov_posterior_surface.png",
+                    bbox_inches="tight",
+                )
 
             plt.show(block=False)
 
             # ==================================================================
             # Image of posterior samples overlayed with contour plot.
-            # ================================================================== 
+            # ==================================================================
             # Plot posterior image.
-            ax = utils.plot_image(np.exp(ln_posterior_grid), x_grid, y_grid, 
-                                  samples[i_chain].reshape((-1, ndim)),
-                                  colorbar_label='$\mathcal{L}$',
-                                  plot_contour=True, markersize=1.0)
+            ax = utils.plot_image(
+                np.exp(ln_posterior_grid),
+                x_grid,
+                y_grid,
+                samples[i_chain].reshape((-1, ndim)),
+                colorbar_label="$\mathcal{L}$",
+                plot_contour=True,
+                markersize=1.0,
+            )
             # Save.
             if savefigs:
                 plt.savefig(
-                    'examples/plots/gaussian_nondiagcov_posterior_image.png'
-                    , bbox_inches='tight')
+                    "examples/plots/gaussian_nondiagcov_posterior_image.png",
+                    bbox_inches="tight",
+                )
+
+            plt.show(block=False)
 
-            plt.show(block=False) 
-        
             # ==================================================================
-            # Learnt model of the posterior 
-            # ================================================================== 
+            # Learnt model of the posterior
+            # ==================================================================
             # Evaluate ln_posterior and model over grid.
-            x = np.linspace(xmin, xmax, nx); y = np.linspace(xmin, xmax, nx)
-            x, y = np.meshgrid(x, y)     
-            ln_model_grid = np.zeros((nx,nx))      
+            x = np.linspace(xmin, xmax, nx)
+            y = np.linspace(xmin, xmax, nx)
+            x, y = np.meshgrid(x, y)
+            ln_model_grid = np.zeros((nx, nx))
             for i in range(nx):
                 for j in range(nx):
-                    ln_model_grid[i,j] =model.predict(np.array([x[i,j],y[i,j]]))
+                    ln_model_grid[i, j] = model.predict(np.array([x[i, j], y[i, j]]))
 
             i_chain = 0
-            ax = utils.plot_surface(np.exp(ln_model_grid), x_grid, y_grid, 
-                                    contour_z_offset=-0.075)              
-            ax.set_zlabel(r'$\mathcal{L}$') 
+            ax = utils.plot_surface(
+                np.exp(ln_model_grid), x_grid, y_grid, contour_z_offset=-0.075
+            )
+            ax.set_zlabel(r"$\mathcal{L}$")
 
             # Save.
             if savefigs:
-                plt.savefig('examples/plots/gaussian_nondiagcov_surface.png'
-                    , bbox_inches='tight')
+                plt.savefig(
+                    "examples/plots/gaussian_nondiagcov_surface.png",
+                    bbox_inches="tight",
+                )
 
             plt.show(block=False)
 
             # ==================================================================
             # Projection of posteior onto x1,x2 plane with contours.
-            # ================================================================== 
+            # ==================================================================
             # Plot posterior image.
-            ax = utils.plot_image(np.exp(ln_model_grid), x_grid, y_grid, 
-                                  colorbar_label='$\mathcal{L}$',
-                                  plot_contour=True)
+            ax = utils.plot_image(
+                np.exp(ln_model_grid),
+                x_grid,
+                y_grid,
+                colorbar_label="$\mathcal{L}$",
+                plot_contour=True,
+            )
             # Save.
             if savefigs:
-                plt.savefig('examples/plots/gaussian_nondiagcov_image.png', 
-                            bbox_inches='tight')
-
-            plt.show(block=False) 
-            # ================================================================== 
-        
-        evidence_inv_summary[i_realisation,0] = ev.evidence_inv
-        evidence_inv_summary[i_realisation,1] = ev.evidence_inv_var
-        evidence_inv_summary[i_realisation,2] = ev.evidence_inv_var_var
-        
+                plt.savefig(
+                    "examples/plots/gaussian_nondiagcov_image.png", bbox_inches="tight"
+                )
+
+            plt.show(block=False)
+            # ==================================================================
+
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
+
     clock = time.process_time() - clock
-    hm.logs.info_log('Execution_time = {}s'.format(clock))
+    hm.logs.info_log("Execution_time = {}s".format(clock))
 
     if n_realisations > 1:
-        np.savetxt("examples/data/gaussian_nondiagcov_evidence_inv" +
-                   "_realisations.dat",
-                   evidence_inv_summary)
+        np.savetxt(
+            "examples/data/gaussian_nondiagcov_evidence_inv" + "_realisations.dat",
+            evidence_inv_summary,
+        )
         evidence_inv_analytic_summary = np.zeros(1)
         evidence_inv_analytic_summary[0] = np.exp(-ln_evidence_analytic)
-        np.savetxt("examples/data/gaussian_nondiagcov_evidence_inv" +
-                   "_analytic.dat",
-                   evidence_inv_analytic_summary)
-    
-    created_plots = True 
+        np.savetxt(
+            "examples/data/gaussian_nondiagcov_evidence_inv" + "_analytic.dat",
+            evidence_inv_analytic_summary,
+        )
+
+    created_plots = True
     if created_plots:
         input("\nPress Enter to continue...")
-        
 
-if __name__ == '__main__':
 
+if __name__ == "__main__":
     # Setup logging config.
     hm.logs.setup_logging()
 
@@ -350,20 +413,21 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     ndim = 2
     nchains = 100
     samples_per_chain = 5000
-    nburn = 500     
+    nburn = 500
     np.random.seed(10)
 
-    hm.logs.info_log('Non-diagonal Covariance Gaussian example')
+    hm.logs.info_log("Non-diagonal Covariance Gaussian example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
 
-    hm.logs.debug_log('-- Selected Parameters --')
+    hm.logs.debug_log("-------------------------")
 
-    hm.logs.debug_log('Dimensionality = {}'.format(ndim))
-    hm.logs.debug_log('Number of chains = {}'.format(nchains))
-    hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-    hm.logs.debug_log('Burn in = {}'.format(nburn))
-    
-    hm.logs.debug_log('-------------------------')
-    
     # Run example.
-    run_example(ndim, nchains, samples_per_chain, nburn, 
-                plot_corner=True, plot_surface=True)
+    run_example(
+        ndim, nchains, samples_per_chain, nburn, plot_corner=True, plot_surface=True
+    )
diff --git a/examples/gaussian_nondiagcov_nvp.py b/examples/gaussian_nondiagcov_nvp.py
new file mode 100644
index 00000000..d272e42a
--- /dev/null
+++ b/examples/gaussian_nondiagcov_nvp.py
@@ -0,0 +1,339 @@
+import numpy as np
+import sys
+import emcee
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+from matplotlib import cm
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+
+
+def ln_analytic_evidence(ndim, cov):
+    """Compute analytic evidence for nD Gaussian.
+
+    Args:
+
+        ndim: Dimension of Gaussian.
+
+        cov: Covariance matrix.
+
+    Returns:
+
+        double: Analytic evidence.
+
+    """
+
+    ln_norm_lik = 0.5 * ndim * np.log(2 * np.pi) + 0.5 * np.log(np.linalg.det(cov))
+    return ln_norm_lik
+
+
+def ln_posterior(x, inv_cov):
+    """Compute log_e of posterior.
+
+    Args:
+
+        x: Position at which to evaluate posterior.
+
+        inv_cov: Inverse covariance matrix.
+
+    Returns:
+
+        double: Value of Gaussian at specified point.
+
+    """
+
+    return -np.dot(x, np.dot(inv_cov, x)) / 2.0
+
+
+def init_cov(ndim):
+    """Initialise random non-diagonal covariance matrix.
+
+    Args:
+
+        ndim: Dimension of Gaussian.
+
+    Returns:
+
+        cov: Covariance matrix of shape (ndim,ndim).
+
+    """
+
+    cov = np.zeros((ndim, ndim))
+    diag_cov = np.ones(ndim) + np.random.randn(ndim) * 0.1
+    np.fill_diagonal(cov, diag_cov)
+
+    for i in range(ndim - 1):
+        cov[i, i + 1] = (-1) ** i * 0.5 * np.sqrt(cov[i, i] * cov[i + 1, i + 1])
+        cov[i + 1, i] = cov[i, i + 1]
+
+    return cov
+
+
+def run_example(
+    ndim=2, nchains=100, samples_per_chain=1000, nburn=500, plot_corner=False
+):
+    """Run Gaussian example with non-diagonal covariance matrix.
+
+    Args:
+
+        ndim: Dimension.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+
+    """
+
+    savefigs = True
+
+    # Initialise covariance matrix.
+    cov = init_cov(ndim)
+    inv_cov = np.linalg.inv(cov)
+    training_proportion = 0.5
+    epochs_num = 5
+    temperature = 0.9
+    standardize = True
+    verbose = True
+
+    # Start timer.
+    clock = time.process_time()
+
+    # Run multiple realisations.
+    n_realisations = 1
+    evidence_inv_summary = np.zeros((n_realisations, 3))
+    for i_realisation in range(n_realisations):
+        if n_realisations > 0:
+            hm.logs.info_log(
+                "Realisation = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+
+        # Set up and run sampler.
+        hm.logs.info_log("Run sampling...")
+
+        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim))
+
+        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=[inv_cov])
+        rstate = np.random.get_state()  # Set random state to repeatable
+        # across calls.
+        (pos, prob, state) = sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # Calculate evidence using harmonic....
+
+        # Set up chains.
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        model = model_nf.RealNVPModel(
+            ndim, standardize=standardize, temperature=temperature
+        )
+        model.fit(chains_train.samples, epochs=epochs_num, verbose=verbose)
+
+        # Use chains and model to compute inverse evidence.
+        hm.logs.info_log("Compute evidence...")
+
+        ev = hm.Evidence(chains_test.nchains, model)
+        # ev.set_mean_shift(0.0)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        if i_realisation == 0:
+            ln_evidence_analytic = ln_analytic_evidence(ndim, cov)
+
+        # ======================================================================
+        # Display evidence computation results.
+        # ======================================================================
+        hm.logs.info_log("---------------------------------")
+        hm.logs.info_log(
+            "Evidence: analytic = {}, estimated = {}".format(
+                np.exp(ln_evidence_analytic), np.exp(ln_evidence)
+            )
+        )
+        hm.logs.info_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(np.exp(ln_evidence_analytic) - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |analytic - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+        # ======================================================================
+        # Display inverse evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "Inv Evidence: analytic = {}, estimate = {}".format(
+                np.exp(-ln_evidence_analytic), ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) ) / var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |analytic - estimate| / estimate = {}".format(
+                np.abs(np.exp(-ln_evidence_analytic) - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # ======================================================================
+        # Create corner/triangle plot.
+        # ======================================================================
+        if plot_corner and i_realisation == 0:
+            utils.plot_corner(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_gaussian_nondiagcov_corner.png",
+                    bbox_inches="tight",
+                )
+
+            utils.plot_getdist(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_gaussian_nondiagcov_getdist.png",
+                    bbox_inches="tight",
+                )
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(chains_train.samples, samps_compressed)
+            plt.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_gaussian_nondiagcov_corner_all_{}D.png".format(
+                        ndim
+                    ),
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+
+            utils.plot_getdist(samps_compressed)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/gaussian_nondiagcov_flow_getdist_{}D.png".format(
+                        ndim
+                    ),
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+
+            plt.show()
+
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
+
+    clock = time.process_time() - clock
+    hm.logs.info_log("Execution_time = {}s".format(clock))
+
+    if n_realisations > 1:
+        np.savetxt(
+            "examples/data/nvp_gaussian_nondiagcov_evidence_inv"
+            + "_realisations_{}D.dat".format(ndim),
+            evidence_inv_summary,
+        )
+        evidence_inv_analytic_summary = np.zeros(1)
+        evidence_inv_analytic_summary[0] = np.exp(-ln_evidence_analytic)
+        np.savetxt(
+            "examples/data/nvp_gaussian_nondiagcov_evidence_inv"
+            + "_analytic_{}D.dat".format(ndim),
+            evidence_inv_analytic_summary,
+        )
+
+    created_plots = True
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 5
+    nchains = 100
+    samples_per_chain = 5000
+    nburn = 500
+    np.random.seed(10)
+
+    hm.logs.info_log("Non-diagonal Covariance Gaussian example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    run_example(ndim, nchains, samples_per_chain, nburn, plot_corner=True)
diff --git a/examples/gaussian_nondiagcov_splines.py b/examples/gaussian_nondiagcov_splines.py
new file mode 100644
index 00000000..3bdc63f7
--- /dev/null
+++ b/examples/gaussian_nondiagcov_splines.py
@@ -0,0 +1,349 @@
+import numpy as np
+import sys
+import emcee
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+from matplotlib import cm
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+from harmonic import model_nf
+import jax
+import jax.numpy as jnp
+
+
+def ln_analytic_evidence(ndim, cov):
+    """Compute analytic evidence for nD Gaussian.
+
+    Args:
+
+        ndim: Dimension of Gaussian.
+
+        cov: Covariance matrix.
+
+    Returns:
+
+        double: Analytic evidence.
+
+    """
+
+    ln_norm_lik = 0.5 * ndim * np.log(2 * np.pi) + 0.5 * np.log(np.linalg.det(cov))
+    return ln_norm_lik
+
+
+# @partial(jax.jit, static_argnums=(1,))
+def ln_posterior(x, inv_cov):
+    """Compute log_e of posterior.
+
+    Args:
+
+        x: Position at which to evaluate posterior.
+
+        inv_cov: Inverse covariance matrix.
+
+    Returns:
+
+        double: Value of Gaussian at specified point.
+
+    """
+
+    return -jnp.dot(x, jnp.dot(inv_cov, x)) / 2.0
+
+
+def init_cov(ndim):
+    """Initialise random non-diagonal covariance matrix.
+
+    Args:
+
+        ndim: Dimension of Gaussian.
+
+    Returns:
+
+        cov: Covariance matrix of shape (ndim,ndim).
+
+    """
+
+    cov = np.zeros((ndim, ndim))
+    diag_cov = np.ones(ndim) + np.random.randn(ndim) * 0.1
+    np.fill_diagonal(cov, diag_cov)
+
+    for i in range(ndim - 1):
+        cov[i, i + 1] = (-1) ** i * 0.5 * np.sqrt(cov[i, i] * cov[i + 1, i + 1])
+        cov[i + 1, i] = cov[i, i + 1]
+
+    return cov
+
+
+def run_example(ndim=2, nchains=100, samples_per_chain=1000, plot_corner=False):
+    """Run Gaussian example with non-diagonal covariance matrix.
+
+    Args:
+
+        ndim: Dimension.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        plot_corner: Plot marginalised distributions if true.
+
+    """
+
+    savefigs = True
+
+    # Initialise covariance matrix.
+    cov = init_cov(ndim)
+    inv_cov = jnp.linalg.inv(cov)
+    training_proportion = 0.5
+    epochs_num = 80
+    temperature = 0.8
+    standardize = True
+    verbose = True
+
+    # Spline params
+    n_layers = 13
+    n_bins = 8
+    hidden_size = [64, 64]
+    spline_range = (-10.0, 10.0)
+
+    # Start timer.
+    clock = time.process_time()
+
+    # Run multiple realisations.
+    n_realisations = 1
+    evidence_inv_summary = np.zeros((n_realisations, 3))
+    for i_realisation in range(n_realisations):
+        if n_realisations > 0:
+            hm.logs.info_log(
+                "Realisation = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+        # Define the number of dimensions and the mean of the Gaussian
+        num_samples = nchains * samples_per_chain
+        # Initialize a PRNG key (you can use any valid key)
+        key = jax.random.PRNGKey(0)
+        mean = jnp.zeros(ndim)
+
+        # Generate random samples from the 2D Gaussian distribution
+        samples = jax.random.multivariate_normal(key, mean, cov, shape=(num_samples,))
+        lnprob = jax.vmap(ln_posterior, in_axes=(0, None))(samples, jnp.array(inv_cov))
+        samples = jnp.reshape(samples, (nchains, -1, ndim))
+        lnprob = jnp.reshape(lnprob, (nchains, -1))
+
+        # Calculate evidence using harmonic....
+
+        # Set up chains.
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(
+            np.array(samples).astype("double"), np.array(lnprob).astype("double")
+        )
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        model = model_nf.RQSplineModel(
+            ndim,
+            n_layers=n_layers,
+            n_bins=n_bins,
+            hidden_size=hidden_size,
+            spline_range=spline_range,
+            standardize=standardize,
+            temperature=temperature,
+        )
+        model.fit(jnp.array(chains_train.samples), epochs=epochs_num, verbose=verbose)
+
+        # Use chains and model to compute inverse evidence.
+        hm.logs.info_log("Compute evidence...")
+
+        ev = hm.Evidence(chains_test.nchains, model)
+        # ev.set_mean_shift(0.0)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        if i_realisation == 0:
+            ln_evidence_analytic = ln_analytic_evidence(ndim, cov)
+
+        # ======================================================================
+        # Display evidence computation results.
+        # ======================================================================
+        hm.logs.info_log("---------------------------------")
+        hm.logs.info_log(
+            "Evidence: analytic = {}, estimated = {}".format(
+                np.exp(ln_evidence_analytic), np.exp(ln_evidence)
+            )
+        )
+        hm.logs.info_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(np.exp(ln_evidence_analytic) - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |analytic - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+        # ======================================================================
+        # Display inverse evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "Inv Evidence: analytic = {}, estimate = {}".format(
+                np.exp(-ln_evidence_analytic), ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) ) / var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |analytic - estimate| / estimate = {}".format(
+                np.abs(np.exp(-ln_evidence_analytic) - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # ======================================================================
+        # Create corner/triangle plot.
+        # ======================================================================
+        if plot_corner and i_realisation == 0:
+            utils.plot_corner(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_gaussian_nondiagcov_corner.png",
+                    bbox_inches="tight",
+                )
+
+            utils.plot_getdist(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_gaussian_nondiagcov_getdist.png",
+                    bbox_inches="tight",
+                )
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(chains_train.samples, samps_compressed)
+            plt.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_gaussian_nondiagcov_corner_all_{}D.png".format(
+                        ndim
+                    ),
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+
+            utils.plot_getdist(samps_compressed)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_gaussian_nondiagcov_flow_getdist_{}D.png".format(
+                        ndim
+                    ),
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+
+            plt.show()
+
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
+
+    clock = time.process_time() - clock
+    hm.logs.info_log("Execution_time = {}s".format(clock))
+
+    if n_realisations > 1:
+        np.savetxt(
+            "examples/data/splines_gaussian_nondiagcov_evidence_inv"
+            + "_realisations_{}D.dat".format(ndim),
+            evidence_inv_summary,
+        )
+        evidence_inv_analytic_summary = np.zeros(1)
+        evidence_inv_analytic_summary[0] = np.exp(-ln_evidence_analytic)
+        np.savetxt(
+            "examples/data/splines_gaussian_nondiagcov_evidence_inv"
+            + "_analytic_{}D.dat".format(ndim),
+            evidence_inv_analytic_summary,
+        )
+
+    created_plots = True
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 50
+    nchains = 200
+    samples_per_chain = 5000
+    np.random.seed(10)
+
+    hm.logs.info_log("Non-diagonal Covariance Gaussian example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    run_example(ndim, nchains, samples_per_chain, plot_corner=False)
diff --git a/examples/normal_gamma.py b/examples/normal_gamma.py
index fd733e79..b5d4c1d3 100644
--- a/examples/normal_gamma.py
+++ b/examples/normal_gamma.py
@@ -5,511 +5,575 @@
 import time
 import matplotlib.pyplot as plt
 from functools import partial
+
 sys.path.append(".")
 import harmonic as hm
+
 sys.path.append("examples")
 import utils
 
 
 def ln_likelihood(x_mean, x_std, x_n, mu, tau):
-	"""Compute log_e of likelihood.
+    """Compute log_e of likelihood.
 
-	Args:
+    Args:
 
-		x_mean: Mean of simulated data.
+            x_mean: Mean of simulated data.
 
-		x_std: Standard deviation of simulated data.
+            x_std: Standard deviation of simulated data.
 
-		x_n: Number of samples of simulated data.
+            x_n: Number of samples of simulated data.
 
-		mu: Mu value for which to evaluate prior.
+            mu: Mu value for which to evaluate prior.
 
-		tau: Tau value for which to evaluate prior.
+            tau: Tau value for which to evaluate prior.
 
-	Returns:
+    Returns:
 
-		double: Value of log_e likelihood at specified (mu, tau) point.
+            double: Value of log_e likelihood at specified (mu, tau) point.
 
-	"""
+    """
 
-	return -0.5 * x_n * tau * (x_std**2 + (x_mean-mu)**2) \
-		- 0.5 * x_n * np.log(2 * np.pi) + 0.5 * x_n * np.log(tau)
+    return (
+        -0.5 * x_n * tau * (x_std**2 + (x_mean - mu) ** 2)
+        - 0.5 * x_n * np.log(2 * np.pi)
+        + 0.5 * x_n * np.log(tau)
+    )
 
 
 def ln_prior(mu, tau, prior_params):
-	"""Compute log_e of prior.
+    """Compute log_e of prior.
 
-	Args:
+    Args:
 
-		mu: Mean value for which to evaluate prior.
+            mu: Mean value for which to evaluate prior.
 
-		tau: Precision value for which to evaluate prior.
+            tau: Precision value for which to evaluate prior.
 
-		prior_params: Tuple of prior parameters, including (mu_0, tau_0,
-			alpha_0, beta_0).
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
 
-	Returns:
+    Returns:
 
-		double: Value of log_e prior at specified (mu, tau) point.
+            double: Value of log_e prior at specified (mu, tau) point.
 
-	"""
+    """
 
-	if tau < 0:
-		return -np.inf
+    if tau < 0:
+        return -np.inf
 
-	mu_0, tau_0, alpha_0, beta_0 = prior_params
+    mu_0, tau_0, alpha_0, beta_0 = prior_params
 
-	ln_pr = alpha_0 * np.log(beta_0) + 0.5 * np.log(tau_0)
-	ln_pr += - sp.gammaln(alpha_0) - 0.5 * np.log(2*np.pi)
-	ln_pr += (alpha_0 - 0.5) * np.log(tau)
-	ln_pr += -beta_0 * tau
-	ln_pr += -0.5 * tau_0 * tau * (mu - mu_0)**2
+    ln_pr = alpha_0 * np.log(beta_0) + 0.5 * np.log(tau_0)
+    ln_pr += -sp.gammaln(alpha_0) - 0.5 * np.log(2 * np.pi)
+    ln_pr += (alpha_0 - 0.5) * np.log(tau)
+    ln_pr += -beta_0 * tau
+    ln_pr += -0.5 * tau_0 * tau * (mu - mu_0) ** 2
 
-	return ln_pr
+    return ln_pr
 
 
 def ln_posterior(theta, x_mean, x_std, x_n, prior_params):
-	"""Compute log_e of posterior.
+    """Compute log_e of posterior.
 
-	Args:
+    Args:
 
-		theta: Position (mu, tau) at which to evaluate posterior.
+            theta: Position (mu, tau) at which to evaluate posterior.
 
-		x_mean: Mean of simulated data.
+            x_mean: Mean of simulated data.
 
-		x_std: Standard deviation of simulated data.
+            x_std: Standard deviation of simulated data.
 
-		x_n: Number of samples of simulated data.
+            x_n: Number of samples of simulated data.
 
-		prior_params: Tuple of prior parameters, including (mu_0, tau_0,
-			alpha_0, beta_0).
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
 
-	Returns:
+    Returns:
 
-		double: Value of log_e posterior at specified (mu, tau) point.
+            double: Value of log_e posterior at specified (mu, tau) point.
 
-	"""
+    """
 
-	mu, tau = theta
+    mu, tau = theta
 
-	ln_pr = ln_prior(mu, tau, prior_params)
+    ln_pr = ln_prior(mu, tau, prior_params)
 
-	if not np.isfinite(ln_pr):
-		return -np.inf
+    if not np.isfinite(ln_pr):
+        return -np.inf
 
-	ln_L = ln_likelihood(x_mean, x_std, x_n, mu, tau)
+    ln_L = ln_likelihood(x_mean, x_std, x_n, mu, tau)
 
-	return  ln_L + ln_pr
+    return ln_L + ln_pr
 
 
 def ln_analytic_evidence(x_mean, x_std, x_n, prior_params):
-	"""Compute analytic evidence.
+    """Compute analytic evidence.
 
-	Args:
+    Args:
 
-		x_mean: Mean of simulated data.
+            x_mean: Mean of simulated data.
 
-		x_std: Standard deviation of simulated data.
+            x_std: Standard deviation of simulated data.
 
-		x_n: Number of samples of simulated data.
+            x_n: Number of samples of simulated data.
 
-		prior_params: Tuple of prior parameters, including (mu_0, tau_0,
-			alpha_0, beta_0).
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
 
-	Returns:
+    Returns:
 
-		double: Value of log_e evidence computed analytically.
+            double: Value of log_e evidence computed analytically.
 
-	"""
+    """
 
-	mu_0, tau_0, alpha_0, beta_0 = prior_params
+    mu_0, tau_0, alpha_0, beta_0 = prior_params
 
-	tau_n  = tau_0  + x_n
-	alpha_n = alpha_0 + x_n/2
-	beta_n  = beta_0 + 0.5 * x_n * x_std**2 \
-		+ tau_0 * x_n * (x_mean - mu_0)**2 / (2 * (tau_0 + x_n))
+    tau_n = tau_0 + x_n
+    alpha_n = alpha_0 + x_n / 2
+    beta_n = (
+        beta_0
+        + 0.5 * x_n * x_std**2
+        + tau_0 * x_n * (x_mean - mu_0) ** 2 / (2 * (tau_0 + x_n))
+    )
 
-	ln_z  = sp.gammaln(alpha_n) - sp.gammaln(alpha_0)
-	ln_z += alpha_0 * np.log(beta_0) - alpha_n * np.log(beta_n)
-	ln_z += 0.5 * np.log(tau_0) - 0.5 * np.log(tau_n)
-	ln_z -= 0.5 * x_n * np.log(2*np.pi)
+    ln_z = sp.gammaln(alpha_n) - sp.gammaln(alpha_0)
+    ln_z += alpha_0 * np.log(beta_0) - alpha_n * np.log(beta_n)
+    ln_z += 0.5 * np.log(tau_0) - 0.5 * np.log(tau_n)
+    ln_z -= 0.5 * x_n * np.log(2 * np.pi)
 
-	return ln_z
+    return ln_z
 
 
-def run_example(ndim=2, nchains=100, samples_per_chain=1000,
-				nburn=500, plot_corner=False, plot_surface=False,
-				plot_comparison=False):
-	"""Run Normal-Gamma example.
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_surface=False,
+    plot_comparison=False,
+):
+    """Run Normal-Gamma example.
 
-	Args:
+    Args:
 
-		ndim: Dimension.
+            ndim: Dimension.
 
-		nchains: Number of chains.
+            nchains: Number of chains.
 
-		samples_per_chain: Number of samples per chain.
+            samples_per_chain: Number of samples per chain.
 
-		nburn: Number of burn in samples for each chain.
+            nburn: Number of burn in samples for each chain.
 
-		plot_corner: Plot marginalised distributions if true.
+            plot_corner: Plot marginalised distributions if true.
 
-		plot_surface: Plot surface and samples if true.
+            plot_surface: Plot surface and samples if true.
 
-		plot_comparison: Plot accuracy for various tau priors if true.
+            plot_comparison: Plot accuracy for various tau priors if true.
 
-	"""
+    """
 
-	if ndim != 2:
-		raise ValueError("Only ndim=2 is supported (ndim={} specified)"
-			.format(ndim))
+    if ndim != 2:
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
 
-	#===========================================================================
-	# Configure Parameters.
-	#===========================================================================
-	"""
+    # ===========================================================================
+    # Configure Parameters.
+    # ===========================================================================
+    """
 	Configure machine learning parameters
 	"""
-	n_meas = 100
-	mu_in  = 0.0
-	tau_in = 1.0
-	tau_array = [1E-4, 1E-3, 1E-2, 1E-1, 1E0]
-
-	savefigs = True
-	created_plots = False
-
-	nfold = 3
-	training_proportion = 0.25
-	hyper_parameters_MGMM = [[1, 1E-8, 0.1, 6, 10],\
-			[2, 1E-8, 0.5, 6, 10]]#, [3,1E-8,2.0,10,10]]
-	hyper_parameters_sphere = [None]
-	domains_sphere = [np.array([1E-1,5E0])]
-	domains_MGMM = [np.array([1E-1,5E0])]
-
-	#===========================================================================
-	# Simulate data 
-	#===========================================================================
-	hm.logs.info_log('Simulate data...')
-	"""
+    n_meas = 100
+    mu_in = 0.0
+    tau_in = 1.0
+    tau_array = [1e-4, 1e-3, 1e-2, 1e-1, 1e0]
+
+    savefigs = True
+    created_plots = False
+
+    nfold = 3
+    training_proportion = 0.25
+    hyper_parameters_MGMM = [
+        [1, 1e-8, 0.1, 6, 10],
+        [2, 1e-8, 0.5, 6, 10],
+    ]  # , [3,1E-8,2.0,10,10]]
+    hyper_parameters_sphere = [None]
+    domains_sphere = [np.array([1e-1, 5e0])]
+    domains_MGMM = [np.array([1e-1, 5e0])]
+
+    # ===========================================================================
+    # Simulate data
+    # ===========================================================================
+    hm.logs.info_log("Simulate data...")
+    """
 	Construct simulations of data one might observe from a typical normal-gamma
 	model.
 	"""
-	x = np.random.normal(mu_in, np.sqrt(1/tau_in), (n_meas))
-	x_mean = np.mean(x)
-	x_std = np.std(x)
-	x_n = x.size
-	hm.logs.debug_log('x: mean = {}, std = {}, n = {}'.format(x_mean, x_std, x_n))
+    x = np.random.normal(mu_in, np.sqrt(1 / tau_in), (n_meas))
+    x_mean = np.mean(x)
+    x_std = np.std(x)
+    x_n = x.size
+    hm.logs.debug_log("x: mean = {}, std = {}, n = {}".format(x_mean, x_std, x_n))
 
-	summary = np.empty((len(tau_array), 4), dtype=float)
+    summary = np.empty((len(tau_array), 4), dtype=float)
 
-	# Start timer.
-	clock = time.process_time()
+    # Start timer.
+    clock = time.process_time()
 
-	#===========================================================================
-	# Loop over all values of Tau one wishes to consider
-	#===========================================================================
-	"""
+    # ===========================================================================
+    # Loop over all values of Tau one wishes to consider
+    # ===========================================================================
+    """
 	Run many realisations for each Tau value.
 	"""
-	for i_tau, tau_prior in enumerate(tau_array):
-		
-		hm.logs.info_log('Considering tau = {}...'.format(tau_prior))
+    for i_tau, tau_prior in enumerate(tau_array):
+        hm.logs.info_log("Considering tau = {}...".format(tau_prior))
 
-		prior_params = (0.0, tau_prior, 1E-3, 1E-3)
+        prior_params = (0.0, tau_prior, 1e-3, 1e-3)
 
-		#===================================================================
-		# Run Emcee to recover posterior samples 
-		#===================================================================
-		hm.logs.info_log('Run sampling...')
-		"""
+        # ===================================================================
+        # Run Emcee to recover posterior samples
+        # ===================================================================
+        hm.logs.info_log("Run sampling...")
+        """
 		Feed emcee the ln_posterior function, starting positions and recover 
 		chains.
 		"""
-		pos = [np.array([x_mean, 1.0/x_std**2]) \
-			   + x_std * np.random.randn(ndim) / np.sqrt(x_n) \
-			   for i in range(nchains)]
-		sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, \
-			args=(x_mean, x_std, x_n, prior_params))
-		rstate = np.random.get_state()
-		sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
-		samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])
-		lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])
-
-		#===================================================================
-		# Configure emcee chains for harmonic
-		#===================================================================
-		hm.logs.info_log('Configure chains...')
-		"""
+        pos = [
+            np.array([x_mean, 1.0 / x_std**2])
+            + x_std * np.random.randn(ndim) / np.sqrt(x_n)
+            for i in range(nchains)
+        ]
+        sampler = emcee.EnsembleSampler(
+            nchains, ndim, ln_posterior, args=(x_mean, x_std, x_n, prior_params)
+        )
+        rstate = np.random.get_state()
+        sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # ===================================================================
+        # Configure emcee chains for harmonic
+        # ===================================================================
+        hm.logs.info_log("Configure chains...")
+        """
 		Configure chains for the cross-validation stage.
 		"""
-		chains = hm.Chains(ndim)
-		chains.add_chains_3d(samples, lnprob)
-		chains_train, chains_test = hm.utils.split_data(chains, \
-			training_proportion=training_proportion)
-
-		#===================================================================
-		# Perform cross-validation
-		#===================================================================
-		hm.logs.info_log('Perform cross-validation...')
-		"""
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # ===================================================================
+        # Perform cross-validation
+        # ===================================================================
+        hm.logs.info_log("Perform cross-validation...")
+        """
 		There are several different machine learning models. 
 		Cross-validation allows the software to select the optimal model 
 		and the optimal model hyper-parameters for a given situation.
 		"""
-		validation_variances_MGMM = \
-			hm.utils.cross_validation(chains_train,
-				domains_MGMM, \
-				hyper_parameters_MGMM, \
-				nfold=nfold,
-				modelClass=hm.model.ModifiedGaussianMixtureModel, \
-				seed=0)
-		hm.logs.debug_log('validation_variances_MGMM = {}'
-			.format(validation_variances_MGMM))
-		best_hyper_param_MGMM_ind = np.argmin(validation_variances_MGMM)
-		best_hyper_param_MGMM = \
-			hyper_parameters_MGMM[best_hyper_param_MGMM_ind]
-
-		validation_variances_sphere = \
-			hm.utils.cross_validation(chains_train,
-				domains_sphere, \
-				hyper_parameters_sphere, nfold=nfold,
-				modelClass=hm.model.HyperSphere,
-				seed=0)
-		hm.logs.debug_log('validation_variances_sphere = {}'
-			.format(validation_variances_sphere))
-		best_hyper_param_sphere_ind = np.argmin(validation_variances_sphere)
-		best_hyper_param_sphere = \
-			hyper_parameters_sphere[best_hyper_param_sphere_ind]
-
-		#===================================================================
-		# Fit optimal model hyper-parameters
-		#===================================================================
-		hm.logs.info_log('Fit model...')
-		"""
+        validation_variances_MGMM = hm.utils.cross_validation(
+            chains_train,
+            domains_MGMM,
+            hyper_parameters_MGMM,
+            nfold=nfold,
+            modelClass=hm.model.ModifiedGaussianMixtureModel,
+            seed=0,
+        )
+        hm.logs.debug_log(
+            "validation_variances_MGMM = {}".format(validation_variances_MGMM)
+        )
+        best_hyper_param_MGMM_ind = np.argmin(validation_variances_MGMM)
+        best_hyper_param_MGMM = hyper_parameters_MGMM[best_hyper_param_MGMM_ind]
+
+        validation_variances_sphere = hm.utils.cross_validation(
+            chains_train,
+            domains_sphere,
+            hyper_parameters_sphere,
+            nfold=nfold,
+            modelClass=hm.model.HyperSphere,
+            seed=0,
+        )
+        hm.logs.debug_log(
+            "validation_variances_sphere = {}".format(validation_variances_sphere)
+        )
+        best_hyper_param_sphere_ind = np.argmin(validation_variances_sphere)
+        best_hyper_param_sphere = hyper_parameters_sphere[best_hyper_param_sphere_ind]
+
+        # ===================================================================
+        # Fit optimal model hyper-parameters
+        # ===================================================================
+        hm.logs.info_log("Fit model...")
+        """
 		Fit model by selecing the configuration of hyper-parameters which 
 		minimises the validation variances.
 		"""
-		best_var_MGMM = \
-			validation_variances_MGMM[best_hyper_param_MGMM_ind]
-		best_var_sphere = \
-			validation_variances_sphere[best_hyper_param_sphere_ind]
-		if best_var_MGMM < best_var_sphere:
-			hm.logs.debug_log('Using MGMM with hyper_parameters = {}'
-				.format(best_hyper_param_MGMM))
-			model = hm.model.ModifiedGaussianMixtureModel(ndim, \
-				domains_MGMM, hyper_parameters=best_hyper_param_MGMM)
-		else:
-			hm.logs.debug_log('Using HyperSphere')
-			model = hm.model.HyperSphere(ndim, domains_sphere, \
-				hyper_parameters=best_hyper_param_sphere)
-		fit_success = model.fit(chains_train.samples,
-								chains_train.ln_posterior)
-		hm.logs.debug_log('Fit success = {}'.format(fit_success))
-
-		#===================================================================
-		# Computing evidence using learnt model and emcee chains
-		#===================================================================
-		hm.logs.info_log('Compute evidence...')
-		"""
+        best_var_MGMM = validation_variances_MGMM[best_hyper_param_MGMM_ind]
+        best_var_sphere = validation_variances_sphere[best_hyper_param_sphere_ind]
+        if best_var_MGMM < best_var_sphere:
+            hm.logs.debug_log(
+                "Using MGMM with hyper_parameters = {}".format(best_hyper_param_MGMM)
+            )
+            model = hm.model.ModifiedGaussianMixtureModel(
+                ndim, domains_MGMM, hyper_parameters=best_hyper_param_MGMM
+            )
+        else:
+            hm.logs.debug_log("Using HyperSphere")
+            model = hm.model.HyperSphere(
+                ndim, domains_sphere, hyper_parameters=best_hyper_param_sphere
+            )
+        fit_success = model.fit(chains_train.samples, chains_train.ln_posterior)
+        hm.logs.debug_log("Fit success = {}".format(fit_success))
+
+        # ===================================================================
+        # Computing evidence using learnt model and emcee chains
+        # ===================================================================
+        hm.logs.info_log("Compute evidence...")
+        """
 		Instantiates the evidence class with a given model. Adds some chains 
 		and computes the log-space evidence (marginal likelihood).
 		"""
-		ev = hm.Evidence(chains_test.nchains, model)
-		ev.add_chains(chains_test)
-		ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
-
-		# Compute analytic evidence.
-		ln_evidence_analytic = ln_analytic_evidence(x_mean, \
-			x_std, x_n, prior_params)
-		evidence_analytic = np.exp(ln_evidence_analytic)
-
-		# Collate values.
-		summary[i_tau, 0] = tau_prior
-		summary[i_tau, 1] = ln_evidence_analytic
-		summary[i_tau, 2] = ln_evidence
-		summary[i_tau, 3] = ln_evidence_std
-
-		# ==================================================================
-		# Display evidence computation results.
-		# ==================================================================
-		hm.logs.info_log('Evidence: analytic = {}, estimated = {}'
-			.format(evidence_analytic, np.exp(ln_evidence)))
-		hm.logs.info_log('Evidence: std = {}, std / estimated = {}'
-			.format(np.exp(ln_evidence_std), \
-					np.exp(ln_evidence_std - ln_evidence)))
-		diff = np.log(np.abs(evidence_analytic - np.exp(ln_evidence)))
-		hm.logs.info_log('Evidence: |analytic - estimated| / estimated = {}'
-			.format(np.exp(diff - ln_evidence)))
-
-		# ==================================================================
-		# Display logarithmic evidence computation results.
-		# ==================================================================
-		hm.logs.debug_log('Ln Evidence: analytic = {}, estimated = {}'
-			.format(ln_evidence_analytic, ln_evidence))
-		diff = np.abs(ln_evidence_analytic - ln_evidence)
-		hm.logs.debug_log('Ln Evidence: |analytic - estimated| / estimated = {}'
-						  .format(diff/ln_evidence))
-
-		#===========================================================================
-		# Display more technical details
-		#===========================================================================
-		hm.logs.debug_log('---------------------------------')
-		hm.logs.debug_log('Technical Details')
-		hm.logs.debug_log('---------------------------------')
-		hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-			.format(ev.lnargmax, ev.lnargmin))
-		hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-			.format(ev.lnprobmax, ev.lnprobmin))
-		hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-			.format(ev.lnpredictmax, ev.lnpredictmin))
-		hm.logs.debug_log('---------------------------------')
-		hm.logs.debug_log('shift = {}, shift setting = {}'
-			.format(ev.shift_value, ev.shift))
-		hm.logs.debug_log('running sum total = {}'
-			.format(sum(ev.running_sum)))
-		hm.logs.debug_log('running sum = \n{}'
-			.format(ev.running_sum))
-		hm.logs.debug_log('nsamples per chain = \n{}'
-			.format(ev.nsamples_per_chain))
-		hm.logs.debug_log('nsamples eff per chain = \n{}'
-			.format(ev.nsamples_eff_per_chain))
-		hm.logs.debug_log('===============================')
-
-		# Create corner/triangle plot.
-		if plot_corner:
-
-			labels = [r'$\mu$', r'$\tau$']
-			utils.plot_corner(samples.reshape((-1, ndim)), labels)
-			if savefigs:
-				plt.savefig('examples/plots/normalgamma_corner_tau' +
-							str(tau_prior) +
-							'.pdf',
-							bbox_inches='tight')
-
-			labels = [r'\mu', r'\tau']
-			utils.plot_getdist(samples.reshape((-1, ndim)), labels)
-			if savefigs:
-				plt.savefig('examples/plots/normalgamma_getdist_tau' +
-							str(tau_prior) +
-							'.pdf',
-							bbox_inches='tight')
-
-			plt.show(block=False)
-			created_plots = True
-
-		if plot_surface:
-
-			# Evaluate posterior on grid.
-			ln_posterior_func = partial(ln_posterior,
-				x_mean=x_mean, x_std=x_std, x_n=x_n,
-				prior_params=prior_params)
-			ln_posterior_grid, x_grid, y_grid = \
-				utils.eval_func_on_grid(ln_posterior_func,
-										xmin=-0.6, xmax=0.6,
-										ymin=0.4, ymax=1.8,
-										nx=500, ny=500)
-
-			# Plot posterior image.
-			ax = utils.plot_image(np.exp(ln_posterior_grid),
-								  x_grid, y_grid,
-								  samples=None,
-								  #samples.reshape((-1,ndim)),
-								  colorbar_label=r'$\mathcal{L}$')
-			# ax.set_clim(vmin=0.0, vmax=0.003)
-			if savefigs:
-				plt.savefig('examples/plots/' +
-							'normalgamma_posterior_image' +
-							str(tau_prior) + '.png',
-							bbox_inches='tight')
-
-			# Evaluate model on grid.
-			model_grid, x_grid, y_grid = \
-				utils.eval_func_on_grid(model.predict,
-										xmin=-0.6, xmax=0.6,
-										ymin=0.4, ymax=1.8,
-										nx=500, ny=500)
-
-			# Plot model.
-			ax = utils.plot_image(model_grid, x_grid, y_grid,
-				colorbar_label=r'$\log \varphi$')
-			# ax.set_clim(vmin=-2.0, vmax=2.0)
-			if savefigs:
-				plt.savefig('examples/plots/' +
-							'normalgamma_model_image' +
-							str(tau_prior) +
-							'.png',
-							bbox_inches='tight')
-
-			# Plot exponential of model.
-			ax = utils.plot_image(np.exp(model_grid),
-								  x_grid, y_grid,
-								  colorbar_label=r'$\varphi$')
-			# ax.set_clim(vmin=0.0, vmax=10.0)
-			if savefigs:
-				plt.savefig('examples/plots/' +
-							'normalgamma_modelexp_image' +
-							str(tau_prior) +
-							'.png',
-							bbox_inches='tight')
-
-	# Display summary results.
-	hm.logs.info_log('tau_prior | ln_evidence_analytic | ln_evidence =')
-	hm.logs.info_log('{}'.format(summary[:,:-1]))
-
-	# Plot evidence values for different tau priors.
-	if plot_comparison:
-		created_plots = True
-
-		plt.rcParams.update({'font.size': 15})
-		fig, ax = plt.subplots()
-		ax.plot(np.array([1E-5, 1E1]), np.ones(2), 'r', linewidth=2)
-		ax.set_xlim([1E-5, 1E1])
-		ax.set_ylim([0.990, 1.010])
-		ax.set_xscale("log")
-		ax.set_xlabel(r"Prior size ($\tau_0$)")
-		ax.set_ylabel(r"Relative accuracy ($z_{\rm estimated}/z_{\rm analytic}$)")
-		ax.errorbar(tau_array, np.exp(summary[:,2])/np.exp(summary[:,1]),
-			yerr=np.exp(summary[:,3])/np.exp(summary[:,1]),
-			fmt='b.', capsize=4, capthick=2, elinewidth=2)
-		if savefigs:
-			plt.savefig('examples/plots/normalgamma_comparison.pdf',
-						bbox_inches='tight')
-		plt.show(block=False)
-
-	#===========================================================================
-	# End Timer.
-	clock = time.process_time() - clock
-	hm.logs.info_log('execution_time = {}s'.format(clock))
-
-
-	if created_plots:
-		input("\nPress Enter to continue...")
-
-
-if __name__ == '__main__':
-
-	# Setup logging config.
-	hm.logs.setup_logging()
-
-	# Define parameters.
-	ndim = 2 # Only 2 dimensional case supported.
-	nchains = 200
-	samples_per_chain = 1500
-	nburn = 500
-	np.random.seed(1)
-
-	hm.logs.info_log('Normal-Gamma example') 
-
-	hm.logs.debug_log('-- Selected Parameters --')
-
-	hm.logs.debug_log('Dimensionality = {}'.format(ndim))
-	hm.logs.debug_log('Number of chains = {}'.format(nchains))
-	hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-	hm.logs.debug_log('Burn in = {}'.format(nburn))
-	
-	hm.logs.debug_log('-------------------------')
-
-	# Run example.
-	samples = run_example(ndim, nchains, samples_per_chain, nburn,
-						  plot_corner=True, plot_surface=True,
-						  plot_comparison=True)
+        ev = hm.Evidence(chains_test.nchains, model)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        ln_evidence_analytic = ln_analytic_evidence(x_mean, x_std, x_n, prior_params)
+        evidence_analytic = np.exp(ln_evidence_analytic)
+
+        # Collate values.
+        summary[i_tau, 0] = tau_prior
+        summary[i_tau, 1] = ln_evidence_analytic
+        summary[i_tau, 2] = ln_evidence
+        summary[i_tau, 3] = ln_evidence_std
+
+        # ==================================================================
+        # Display evidence computation results.
+        # ==================================================================
+        hm.logs.info_log(
+            "Evidence: analytic = {}, estimated = {}".format(
+                evidence_analytic, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.info_log(
+            "Evidence: std = {}, std / estimated = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_analytic - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |analytic - estimated| / estimated = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+
+        # ==================================================================
+        # Display logarithmic evidence computation results.
+        # ==================================================================
+        hm.logs.debug_log(
+            "Ln Evidence: analytic = {}, estimated = {}".format(
+                ln_evidence_analytic, ln_evidence
+            )
+        )
+        diff = np.abs(ln_evidence_analytic - ln_evidence)
+        hm.logs.debug_log(
+            "Ln Evidence: |analytic - estimated| / estimated = {}".format(
+                diff / ln_evidence
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # Create corner/triangle plot.
+        if plot_corner:
+            labels = [r"$\mu$", r"$\tau$"]
+            utils.plot_corner(samples.reshape((-1, ndim)), labels)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/normalgamma_corner_tau" + str(tau_prior) + ".pdf",
+                    bbox_inches="tight",
+                )
+
+            labels = [r"\mu", r"\tau"]
+            utils.plot_getdist(samples.reshape((-1, ndim)), labels)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/normalgamma_getdist_tau" + str(tau_prior) + ".pdf",
+                    bbox_inches="tight",
+                )
+
+            plt.show(block=False)
+            created_plots = True
+
+        if plot_surface:
+            # Evaluate posterior on grid.
+            ln_posterior_func = partial(
+                ln_posterior,
+                x_mean=x_mean,
+                x_std=x_std,
+                x_n=x_n,
+                prior_params=prior_params,
+            )
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=-0.6,
+                xmax=0.6,
+                ymin=0.4,
+                ymax=1.8,
+                nx=500,
+                ny=500,
+            )
+
+            # Plot posterior image.
+            ax = utils.plot_image(
+                np.exp(ln_posterior_grid),
+                x_grid,
+                y_grid,
+                samples=None,
+                # samples.reshape((-1,ndim)),
+                colorbar_label=r"$\mathcal{L}$",
+            )
+            # ax.set_clim(vmin=0.0, vmax=0.003)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/"
+                    + "normalgamma_posterior_image"
+                    + str(tau_prior)
+                    + ".png",
+                    bbox_inches="tight",
+                )
+
+            # Evaluate model on grid.
+            model_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                model.predict, xmin=-0.6, xmax=0.6, ymin=0.4, ymax=1.8, nx=500, ny=500
+            )
+
+            # Plot model.
+            ax = utils.plot_image(
+                model_grid, x_grid, y_grid, colorbar_label=r"$\log \varphi$"
+            )
+            # ax.set_clim(vmin=-2.0, vmax=2.0)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/"
+                    + "normalgamma_model_image"
+                    + str(tau_prior)
+                    + ".png",
+                    bbox_inches="tight",
+                )
+
+            # Plot exponential of model.
+            ax = utils.plot_image(
+                np.exp(model_grid), x_grid, y_grid, colorbar_label=r"$\varphi$"
+            )
+            # ax.set_clim(vmin=0.0, vmax=10.0)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/"
+                    + "normalgamma_modelexp_image"
+                    + str(tau_prior)
+                    + ".png",
+                    bbox_inches="tight",
+                )
+
+    # Display summary results.
+    hm.logs.info_log("tau_prior | ln_evidence_analytic | ln_evidence =")
+    hm.logs.info_log("{}".format(summary[:, :-1]))
+
+    # Plot evidence values for different tau priors.
+    if plot_comparison:
+        created_plots = True
+
+        plt.rcParams.update({"font.size": 15})
+        fig, ax = plt.subplots()
+        ax.plot(np.array([1e-5, 1e1]), np.ones(2), "r", linewidth=2)
+        ax.set_xlim([1e-5, 1e1])
+        ax.set_ylim([0.990, 1.010])
+        ax.set_xscale("log")
+        ax.set_xlabel(r"Prior size ($\tau_0$)")
+        ax.set_ylabel(r"Relative accuracy ($z_{\rm estimated}/z_{\rm analytic}$)")
+        ax.errorbar(
+            tau_array,
+            np.exp(summary[:, 2]) / np.exp(summary[:, 1]),
+            yerr=np.exp(summary[:, 3]) / np.exp(summary[:, 1]),
+            fmt="b.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+        )
+        if savefigs:
+            plt.savefig(
+                "examples/plots/normalgamma_comparison.pdf", bbox_inches="tight"
+            )
+        plt.show(block=False)
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("execution_time = {}s".format(clock))
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 2  # Only 2 dimensional case supported.
+    nchains = 200
+    samples_per_chain = 1500
+    nburn = 500
+    np.random.seed(1)
+
+    hm.logs.info_log("Normal-Gamma example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(
+        ndim,
+        nchains,
+        samples_per_chain,
+        nburn,
+        plot_corner=True,
+        plot_surface=True,
+        plot_comparison=True,
+    )
diff --git a/examples/normal_gamma_nvp.py b/examples/normal_gamma_nvp.py
new file mode 100644
index 00000000..40277a53
--- /dev/null
+++ b/examples/normal_gamma_nvp.py
@@ -0,0 +1,534 @@
+import numpy as np
+import sys
+import emcee
+import scipy.special as sp
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+
+
+def ln_likelihood(x_mean, x_std, x_n, mu, tau):
+    """Compute log_e of likelihood.
+
+    Args:
+
+            x_mean: Mean of simulated data.
+
+            x_std: Standard deviation of simulated data.
+
+            x_n: Number of samples of simulated data.
+
+            mu: Mu value for which to evaluate prior.
+
+            tau: Tau value for which to evaluate prior.
+
+    Returns:
+
+            double: Value of log_e likelihood at specified (mu, tau) point.
+
+    """
+
+    return (
+        -0.5 * x_n * tau * (x_std**2 + (x_mean - mu) ** 2)
+        - 0.5 * x_n * np.log(2 * np.pi)
+        + 0.5 * x_n * np.log(tau)
+    )
+
+
+def ln_prior(mu, tau, prior_params):
+    """Compute log_e of prior.
+
+    Args:
+
+            mu: Mean value for which to evaluate prior.
+
+            tau: Precision value for which to evaluate prior.
+
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
+
+    Returns:
+
+            double: Value of log_e prior at specified (mu, tau) point.
+
+    """
+
+    if tau < 0:
+        return -np.inf
+
+    mu_0, tau_0, alpha_0, beta_0 = prior_params
+
+    ln_pr = alpha_0 * np.log(beta_0) + 0.5 * np.log(tau_0)
+    ln_pr += -sp.gammaln(alpha_0) - 0.5 * np.log(2 * np.pi)
+    ln_pr += (alpha_0 - 0.5) * np.log(tau)
+    ln_pr += -beta_0 * tau
+    ln_pr += -0.5 * tau_0 * tau * (mu - mu_0) ** 2
+
+    return ln_pr
+
+
+def ln_posterior(theta, x_mean, x_std, x_n, prior_params):
+    """Compute log_e of posterior.
+
+    Args:
+
+            theta: Position (mu, tau) at which to evaluate posterior.
+
+            x_mean: Mean of simulated data.
+
+            x_std: Standard deviation of simulated data.
+
+            x_n: Number of samples of simulated data.
+
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
+
+    Returns:
+
+            double: Value of log_e posterior at specified (mu, tau) point.
+
+    """
+
+    mu, tau = theta
+
+    ln_pr = ln_prior(mu, tau, prior_params)
+
+    if not np.isfinite(ln_pr):
+        return -np.inf
+
+    ln_L = ln_likelihood(x_mean, x_std, x_n, mu, tau)
+
+    return ln_L + ln_pr
+
+
+def ln_analytic_evidence(x_mean, x_std, x_n, prior_params):
+    """Compute analytic evidence.
+
+    Args:
+
+            x_mean: Mean of simulated data.
+
+            x_std: Standard deviation of simulated data.
+
+            x_n: Number of samples of simulated data.
+
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
+
+    Returns:
+
+            double: Value of log_e evidence computed analytically.
+
+    """
+
+    mu_0, tau_0, alpha_0, beta_0 = prior_params
+
+    tau_n = tau_0 + x_n
+    alpha_n = alpha_0 + x_n / 2
+    beta_n = (
+        beta_0
+        + 0.5 * x_n * x_std**2
+        + tau_0 * x_n * (x_mean - mu_0) ** 2 / (2 * (tau_0 + x_n))
+    )
+
+    ln_z = sp.gammaln(alpha_n) - sp.gammaln(alpha_0)
+    ln_z += alpha_0 * np.log(beta_0) - alpha_n * np.log(beta_n)
+    ln_z += 0.5 * np.log(tau_0) - 0.5 * np.log(tau_n)
+    ln_z -= 0.5 * x_n * np.log(2 * np.pi)
+
+    return ln_z
+
+
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_comparison=False,
+):
+    """Run Normal-Gamma example.
+
+    Args:
+
+            ndim: Dimension.
+
+            nchains: Number of chains.
+
+            samples_per_chain: Number of samples per chain.
+
+            nburn: Number of burn in samples for each chain.
+
+            plot_corner: Plot marginalised distributions if true.
+
+            plot_comparison: Plot accuracy for various tau priors if true.
+
+    """
+
+    if ndim != 2:
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
+
+    # ===========================================================================
+    # Configure Parameters.
+    # ===========================================================================
+    """
+	Configure machine learning parameters
+	"""
+    n_meas = 100
+    mu_in = 0.0
+    tau_in = 1.0
+    tau_array = [1e-4, 1e-3, 1e-2, 1e-1, 1e0]
+
+    savefigs = True
+    created_plots = False
+
+    training_proportion = 0.5
+    temperature = 0.9
+    epochs_num = 100
+    standardize = False
+
+    plot_comparison_2var = True
+    temperature_2 = 0.95
+
+    # ===========================================================================
+    # Simulate data
+    # ===========================================================================
+    hm.logs.info_log("Simulate data...")
+    """
+	Construct simulations of data one might observe from a typical normal-gamma
+	model.
+	"""
+    x = np.random.normal(mu_in, np.sqrt(1 / tau_in), (n_meas))
+    x_mean = np.mean(x)
+    x_std = np.std(x)
+    x_n = x.size
+    hm.logs.debug_log("x: mean = {}, std = {}, n = {}".format(x_mean, x_std, x_n))
+
+    summary = np.empty((len(tau_array), 4), dtype=float)
+    summary_2 = np.empty((len(tau_array), 4), dtype=float)
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Loop over all values of Tau one wishes to consider
+    # ===========================================================================
+    """
+	Run many realisations for each Tau value.
+	"""
+    for i_tau, tau_prior in enumerate(tau_array):
+        hm.logs.info_log("Considering tau = {}...".format(tau_prior))
+
+        prior_params = (0.0, tau_prior, 1e-3, 1e-3)
+
+        # ===================================================================
+        # Run Emcee to recover posterior samples
+        # ===================================================================
+        hm.logs.info_log("Run sampling...")
+        """
+		Feed emcee the ln_posterior function, starting positions and recover 
+		chains.
+		"""
+        pos = [
+            np.array([x_mean, 1.0 / x_std**2])
+            + x_std * np.random.randn(ndim) / np.sqrt(x_n)
+            for i in range(nchains)
+        ]
+        sampler = emcee.EnsembleSampler(
+            nchains, ndim, ln_posterior, args=(x_mean, x_std, x_n, prior_params)
+        )
+        rstate = np.random.get_state()
+        sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # ===================================================================
+        # Configure emcee chains for harmonic
+        # ===================================================================
+        hm.logs.info_log("Configure chains...")
+        """
+		Configure chains for the cross-validation stage.
+		"""
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        model = model_nf.RealNVPModel(
+            ndim, standardize=standardize, temperature=temperature
+        )
+        model.fit(chains_train.samples, epochs=epochs_num)
+
+        # ===================================================================
+        # Computing evidence using learnt model and emcee chains
+        # ===================================================================
+        hm.logs.info_log("Compute evidence...")
+        """
+		Instantiates the evidence class with a given model. Adds some chains 
+		and computes the log-space evidence (marginal likelihood).
+		"""
+        ev = hm.Evidence(chains_test.nchains, model)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        ln_evidence_analytic = ln_analytic_evidence(x_mean, x_std, x_n, prior_params)
+        evidence_analytic = np.exp(ln_evidence_analytic)
+
+        # Collate values.
+        summary[i_tau, 0] = tau_prior
+        summary[i_tau, 1] = ln_evidence_analytic
+        summary[i_tau, 2] = ln_evidence
+        summary[i_tau, 3] = ln_evidence_std
+
+        # compare two temperature parameters
+
+        if plot_comparison_2var:
+            model2 = model
+            model2.temperature = temperature_2
+            ev_2 = hm.Evidence(chains_test.nchains, model)
+            ev_2.add_chains(chains_test)
+            ln_evidence_2, ln_evidence_std_2 = ev_2.compute_ln_evidence()
+
+            summary_2[i_tau, 0] = tau_prior
+            summary_2[i_tau, 1] = ln_evidence_analytic
+            summary_2[i_tau, 2] = ln_evidence_2
+            summary_2[i_tau, 3] = ln_evidence_std_2
+
+        # ==================================================================
+        # Display evidence computation results.
+        # ==================================================================
+        hm.logs.info_log(
+            "Evidence: analytic = {}, estimated = {}".format(
+                evidence_analytic, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.info_log(
+            "Evidence: std = {}, std / estimated = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_analytic - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |analytic - estimated| / estimated = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+
+        # ==================================================================
+        # Display logarithmic evidence computation results.
+        # ==================================================================
+        hm.logs.debug_log(
+            "Ln Evidence: analytic = {}, estimated = {}".format(
+                ln_evidence_analytic, ln_evidence
+            )
+        )
+        diff = np.abs(ln_evidence_analytic - ln_evidence)
+        hm.logs.debug_log(
+            "Ln Evidence: |analytic - estimated| / estimated = {}".format(
+                diff / ln_evidence
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # Create corner/triangle plot.
+        if plot_corner:
+            labels = [r"$\mu$", r"$\tau$"]
+            utils.plot_corner(samples.reshape((-1, ndim)), labels)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_normalgamma_corner_tau"
+                    + str(tau_prior)
+                    + ".pdf",
+                    bbox_inches="tight",
+                )
+
+            labels = [r"\mu", r"\tau"]
+            utils.plot_getdist(samples.reshape((-1, ndim)), labels)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_normalgamma_getdist_tau"
+                    + str(tau_prior)
+                    + ".pdf",
+                    bbox_inches="tight",
+                )
+
+            # plt.show(block=False)
+
+            # =======================================================================
+            # Visualise distributions
+            # =======================================================================
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(
+                chains_train.samples, samps_compressed, labels, legend_fontsize=12.5
+            )
+
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_normalgamma_corner_all_"
+                    + str(temperature)
+                    + "tau"
+                    + str(tau_prior)
+                    + ".png",
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+
+            created_plots = True
+
+    # Display summary results.
+    hm.logs.info_log("tau_prior | ln_evidence_analytic | ln_evidence =")
+    hm.logs.info_log("{}".format(summary[:, :-1]))
+
+    # Plot evidence values for different tau priors.
+    if plot_comparison:
+        created_plots = True
+
+        plt.rcParams.update({"font.size": 15})
+        fig, ax = plt.subplots()
+        ax.plot(np.array([1e-5, 1e1]), np.ones(2), "r", linewidth=2)
+        ax.set_xlim([1e-5, 1e1])
+        ax.set_ylim([0.990, 1.010])
+        ax.set_xscale("log")
+        ax.set_xlabel(r"Prior size ($\tau_0$)")
+        ax.set_ylabel(r"Relative accuracy ($z_{\rm estimated}/z_{\rm analytic}$)")
+        ax.errorbar(
+            tau_array,
+            np.exp(summary[:, 2]) / np.exp(summary[:, 1]),
+            yerr=np.exp(summary[:, 3]) / np.exp(summary[:, 1]),
+            fmt="b.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+        )
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_normalgamma_comparison" + str(temperature) + ".pdf",
+                bbox_inches="tight",
+            )
+        plt.show(block=False)
+
+    if plot_comparison_2var:
+        created_plots = True
+
+        plt.rcParams.update({"font.size": 15})
+        fig, ax = plt.subplots()
+        ax.plot(np.array([1e-5, 1e1]), np.ones(2), "r", linewidth=2)
+        ax.set_xlim([1e-5, 1e1])
+        ax.set_ylim([0.990, 1.010])
+        ax.set_xscale("log")
+        ax.set_xlabel(r"Prior size ($\tau_0$)")
+        ax.set_ylabel(r"Relative accuracy ($z_{\rm estimated}/z_{\rm analytic}$)")
+        ax.errorbar(
+            np.array(tau_array) * 0.87,
+            np.exp(summary[:, 2]) / np.exp(summary[:, 1]),
+            yerr=np.exp(summary[:, 3]) / np.exp(summary[:, 1]),
+            fmt="b.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+            label="T=" + str(temperature),
+        )
+        ax.errorbar(
+            np.array(tau_array) * 1.13,
+            np.exp(summary_2[:, 2]) / np.exp(summary_2[:, 1]),
+            yerr=np.exp(summary_2[:, 3]) / np.exp(summary_2[:, 1]),
+            fmt="g.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+            label="T=" + str(temperature_2),
+        )
+        ax.legend(loc="lower right")
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_normalgamma_comparison_"
+                + str(temperature)
+                + "_"
+                + str(temperature_2)
+                + ".pdf",
+                bbox_inches="tight",
+                dpi=3000,
+            )
+        plt.show(block=False)
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("execution_time = {}s".format(clock))
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 2  # Only 2 dimensional case supported.
+    nchains = 200
+    samples_per_chain = 1500
+    nburn = 500
+    np.random.seed(1)
+
+    hm.logs.info_log("Normal-Gamma example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(
+        ndim, nchains, samples_per_chain, nburn, plot_corner=True, plot_comparison=True
+    )
diff --git a/examples/normal_gamma_splines.py b/examples/normal_gamma_splines.py
new file mode 100644
index 00000000..f5c5427f
--- /dev/null
+++ b/examples/normal_gamma_splines.py
@@ -0,0 +1,536 @@
+import numpy as np
+import sys
+import emcee
+import scipy.special as sp
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+
+
+def ln_likelihood(x_mean, x_std, x_n, mu, tau):
+    """Compute log_e of likelihood.
+
+    Args:
+
+            x_mean: Mean of simulated data.
+
+            x_std: Standard deviation of simulated data.
+
+            x_n: Number of samples of simulated data.
+
+            mu: Mu value for which to evaluate prior.
+
+            tau: Tau value for which to evaluate prior.
+
+    Returns:
+
+            double: Value of log_e likelihood at specified (mu, tau) point.
+
+    """
+
+    return (
+        -0.5 * x_n * tau * (x_std**2 + (x_mean - mu) ** 2)
+        - 0.5 * x_n * np.log(2 * np.pi)
+        + 0.5 * x_n * np.log(tau)
+    )
+
+
+def ln_prior(mu, tau, prior_params):
+    """Compute log_e of prior.
+
+    Args:
+
+            mu: Mean value for which to evaluate prior.
+
+            tau: Precision value for which to evaluate prior.
+
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
+
+    Returns:
+
+            double: Value of log_e prior at specified (mu, tau) point.
+
+    """
+
+    if tau < 0:
+        return -np.inf
+
+    mu_0, tau_0, alpha_0, beta_0 = prior_params
+
+    ln_pr = alpha_0 * np.log(beta_0) + 0.5 * np.log(tau_0)
+    ln_pr += -sp.gammaln(alpha_0) - 0.5 * np.log(2 * np.pi)
+    ln_pr += (alpha_0 - 0.5) * np.log(tau)
+    ln_pr += -beta_0 * tau
+    ln_pr += -0.5 * tau_0 * tau * (mu - mu_0) ** 2
+
+    return ln_pr
+
+
+def ln_posterior(theta, x_mean, x_std, x_n, prior_params):
+    """Compute log_e of posterior.
+
+    Args:
+
+            theta: Position (mu, tau) at which to evaluate posterior.
+
+            x_mean: Mean of simulated data.
+
+            x_std: Standard deviation of simulated data.
+
+            x_n: Number of samples of simulated data.
+
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
+
+    Returns:
+
+            double: Value of log_e posterior at specified (mu, tau) point.
+
+    """
+
+    mu, tau = theta
+
+    ln_pr = ln_prior(mu, tau, prior_params)
+
+    if not np.isfinite(ln_pr):
+        return -np.inf
+
+    ln_L = ln_likelihood(x_mean, x_std, x_n, mu, tau)
+
+    return ln_L + ln_pr
+
+
+def ln_analytic_evidence(x_mean, x_std, x_n, prior_params):
+    """Compute analytic evidence.
+
+    Args:
+
+            x_mean: Mean of simulated data.
+
+            x_std: Standard deviation of simulated data.
+
+            x_n: Number of samples of simulated data.
+
+            prior_params: Tuple of prior parameters, including (mu_0, tau_0,
+                    alpha_0, beta_0).
+
+    Returns:
+
+            double: Value of log_e evidence computed analytically.
+
+    """
+
+    mu_0, tau_0, alpha_0, beta_0 = prior_params
+
+    tau_n = tau_0 + x_n
+    alpha_n = alpha_0 + x_n / 2
+    beta_n = (
+        beta_0
+        + 0.5 * x_n * x_std**2
+        + tau_0 * x_n * (x_mean - mu_0) ** 2 / (2 * (tau_0 + x_n))
+    )
+
+    ln_z = sp.gammaln(alpha_n) - sp.gammaln(alpha_0)
+    ln_z += alpha_0 * np.log(beta_0) - alpha_n * np.log(beta_n)
+    ln_z += 0.5 * np.log(tau_0) - 0.5 * np.log(tau_n)
+    ln_z -= 0.5 * x_n * np.log(2 * np.pi)
+
+    return ln_z
+
+
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_comparison=False,
+):
+    """Run Normal-Gamma example.
+
+    Args:
+
+            ndim: Dimension.
+
+            nchains: Number of chains.
+
+            samples_per_chain: Number of samples per chain.
+
+            nburn: Number of burn in samples for each chain.
+
+            plot_corner: Plot marginalised distributions if true.
+
+            plot_comparison: Plot accuracy for various tau priors if true.
+
+    """
+
+    if ndim != 2:
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
+
+    # ===========================================================================
+    # Configure Parameters.
+    # ===========================================================================
+    """
+	Configure machine learning parameters
+	"""
+    n_meas = 100
+    mu_in = 0.0
+    tau_in = 1.0
+    tau_array = [1e-4, 1e-3, 1e-2, 1e-1, 1e0]
+
+    savefigs = True
+    created_plots = False
+
+    training_proportion = 0.5
+    temperature = 0.8
+    epochs_num = 10
+    standardize = False
+
+    plot_comparison_2var = False
+    temperature_2 = 0.95
+
+    # ===========================================================================
+    # Simulate data
+    # ===========================================================================
+    hm.logs.info_log("Simulate data...")
+    """
+	Construct simulations of data one might observe from a typical normal-gamma
+	model.
+	"""
+    x = np.random.normal(mu_in, np.sqrt(1 / tau_in), (n_meas))
+    x_mean = np.mean(x)
+    x_std = np.std(x)
+    x_n = x.size
+    hm.logs.debug_log("x: mean = {}, std = {}, n = {}".format(x_mean, x_std, x_n))
+
+    summary = np.empty((len(tau_array), 4), dtype=float)
+    summary_2 = np.empty((len(tau_array), 4), dtype=float)
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Loop over all values of Tau one wishes to consider
+    # ===========================================================================
+    """
+	Run many realisations for each Tau value.
+	"""
+    for i_tau, tau_prior in enumerate(tau_array):
+        hm.logs.info_log("Considering tau = {}...".format(tau_prior))
+
+        prior_params = (0.0, tau_prior, 1e-3, 1e-3)
+
+        # ===================================================================
+        # Run Emcee to recover posterior samples
+        # ===================================================================
+        hm.logs.info_log("Run sampling...")
+        """
+		Feed emcee the ln_posterior function, starting positions and recover 
+		chains.
+		"""
+        pos = [
+            np.array([x_mean, 1.0 / x_std**2])
+            + x_std * np.random.randn(ndim) / np.sqrt(x_n)
+            for i in range(nchains)
+        ]
+        sampler = emcee.EnsembleSampler(
+            nchains, ndim, ln_posterior, args=(x_mean, x_std, x_n, prior_params)
+        )
+        rstate = np.random.get_state()
+        sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # ===================================================================
+        # Configure emcee chains for harmonic
+        # ===================================================================
+        hm.logs.info_log("Configure chains...")
+        """
+		Configure chains for the cross-validation stage.
+		"""
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        model = model_nf.RQSplineModel(
+            ndim, standardize=standardize, temperature=temperature
+        )
+        model.fit(chains_train.samples, epochs=epochs_num)
+
+        # ===================================================================
+        # Computing evidence using learnt model and emcee chains
+        # ===================================================================
+        hm.logs.info_log("Compute evidence...")
+        """
+		Instantiates the evidence class with a given model. Adds some chains 
+		and computes the log-space evidence (marginal likelihood).
+		"""
+        ev = hm.Evidence(chains_test.nchains, model)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        ln_evidence_analytic = ln_analytic_evidence(x_mean, x_std, x_n, prior_params)
+        evidence_analytic = np.exp(ln_evidence_analytic)
+
+        # Collate values.
+        summary[i_tau, 0] = tau_prior
+        summary[i_tau, 1] = ln_evidence_analytic
+        summary[i_tau, 2] = ln_evidence
+        summary[i_tau, 3] = ln_evidence_std
+
+        # compare two temperature parameters
+
+        if plot_comparison_2var:
+            model2 = model
+            model2.temperature = temperature_2  # double check this doesn't modify model
+            ev_2 = hm.Evidence(chains_test.nchains, model)
+            ev_2.add_chains(chains_test)
+            ln_evidence_2, ln_evidence_std_2 = ev_2.compute_ln_evidence()
+
+            summary_2[i_tau, 0] = tau_prior
+            summary_2[i_tau, 1] = ln_evidence_analytic
+            summary_2[i_tau, 2] = ln_evidence_2
+            summary_2[i_tau, 3] = ln_evidence_std_2
+
+        # ==================================================================
+        # Display evidence computation results.
+        # ==================================================================
+        hm.logs.info_log(
+            "Evidence: analytic = {}, estimated = {}".format(
+                evidence_analytic, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.info_log(
+            "Evidence: std = {}, std / estimated = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_analytic - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |analytic - estimated| / estimated = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+
+        # ==================================================================
+        # Display logarithmic evidence computation results.
+        # ==================================================================
+        hm.logs.debug_log(
+            "Ln Evidence: analytic = {}, estimated = {}".format(
+                ln_evidence_analytic, ln_evidence
+            )
+        )
+        diff = np.abs(ln_evidence_analytic - ln_evidence)
+        hm.logs.debug_log(
+            "Ln Evidence: |analytic - estimated| / estimated = {}".format(
+                diff / ln_evidence
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # Create corner/triangle plot.
+        if plot_corner:
+            labels = [r"$\mu$", r"$\tau$"]
+            utils.plot_corner(samples.reshape((-1, ndim)), labels)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_normalgamma_corner_tau"
+                    + str(tau_prior)
+                    + ".pdf",
+                    bbox_inches="tight",
+                )
+
+            labels = [r"\mu", r"\tau"]
+            utils.plot_getdist(samples.reshape((-1, ndim)), labels)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_normalgamma_getdist_tau"
+                    + str(tau_prior)
+                    + ".pdf",
+                    bbox_inches="tight",
+                )
+
+            plt.show(block=False)
+
+            # =======================================================================
+            # Visualise distributions
+            # =======================================================================
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(
+                chains_train.samples, samps_compressed, labels, legend_fontsize=12.5
+            )
+
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/splines_normalgamma_corner_all_"
+                    + str(temperature)
+                    + "tau"
+                    + str(tau_prior)
+                    + ".png",
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+
+            created_plots = True
+
+    # Display summary results.
+    hm.logs.info_log("tau_prior | ln_evidence_analytic | ln_evidence =")
+    hm.logs.info_log("{}".format(summary[:, :-1]))
+
+    # Plot evidence values for different tau priors.
+    if plot_comparison:
+        created_plots = True
+
+        plt.rcParams.update({"font.size": 15})
+        fig, ax = plt.subplots()
+        ax.plot(np.array([1e-5, 1e1]), np.ones(2), "r", linewidth=2)
+        ax.set_xlim([1e-5, 1e1])
+        ax.set_ylim([0.990, 1.010])
+        ax.set_xscale("log")
+        ax.set_xlabel(r"Prior size ($\tau_0$)")
+        ax.set_ylabel(r"Relative accuracy ($z_{\rm estimated}/z_{\rm analytic}$)")
+        ax.errorbar(
+            tau_array,
+            np.exp(summary[:, 2]) / np.exp(summary[:, 1]),
+            yerr=np.exp(summary[:, 3]) / np.exp(summary[:, 1]),
+            fmt="b.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+        )
+        if savefigs:
+            plt.savefig(
+                "examples/plots/splines_normalgamma_comparison"
+                + str(temperature)
+                + ".pdf",
+                bbox_inches="tight",
+            )
+        plt.show(block=False)
+
+    if plot_comparison_2var:
+        created_plots = True
+
+        plt.rcParams.update({"font.size": 15})
+        fig, ax = plt.subplots()
+        ax.plot(np.array([1e-5, 1e1]), np.ones(2), "r", linewidth=2)
+        ax.set_xlim([1e-5, 1e1])
+        ax.set_ylim([0.990, 1.010])
+        ax.set_xscale("log")
+        ax.set_xlabel(r"Prior size ($\tau_0$)")
+        ax.set_ylabel(r"Relative accuracy ($z_{\rm estimated}/z_{\rm analytic}$)")
+        ax.errorbar(
+            np.array(tau_array) * 0.87,
+            np.exp(summary[:, 2]) / np.exp(summary[:, 1]),
+            yerr=np.exp(summary[:, 3]) / np.exp(summary[:, 1]),
+            fmt="b.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+            label="T=" + str(temperature),
+        )
+        ax.errorbar(
+            np.array(tau_array) * 1.13,
+            np.exp(summary_2[:, 2]) / np.exp(summary_2[:, 1]),
+            yerr=np.exp(summary_2[:, 3]) / np.exp(summary_2[:, 1]),
+            fmt="g.",
+            capsize=4,
+            capthick=2,
+            elinewidth=2,
+            label="T=" + str(temperature_2),
+        )
+        ax.legend(loc="lower right")
+        if savefigs:
+            plt.savefig(
+                "examples/plots/splines_normalgamma_comparison_"
+                + str(temperature)
+                + "_"
+                + str(temperature_2)
+                + ".pdf",
+                bbox_inches="tight",
+                dpi=3000,
+            )
+        plt.show(block=False)
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("execution_time = {}s".format(clock))
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 2  # Only 2 dimensional case supported.
+    nchains = 200
+    samples_per_chain = 1500
+    nburn = 500
+    np.random.seed(1)
+
+    hm.logs.info_log("Normal-Gamma example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(
+        ndim, nchains, samples_per_chain, nburn, plot_corner=True, plot_comparison=True
+    )
diff --git a/examples/pima_indian.py b/examples/pima_indian.py
index 1fbdb236..e537e42d 100644
--- a/examples/pima_indian.py
+++ b/examples/pima_indian.py
@@ -2,11 +2,13 @@
 import sys
 import emcee
 import scipy.special as sp
-import time 
+import time
 import matplotlib.pyplot as plt
 from functools import partial
+
 sys.path.append(".")
 import harmonic as hm
+
 sys.path.append("examples")
 import utils
 
@@ -30,11 +32,11 @@ def ln_likelihood(y, theta, x):
     """
 
     ln_p = compute_ln_p(theta, x)
-    ln_pp = np.log(1. - np.exp(ln_p))
-    return y.T.dot(ln_p) + (1-y).T.dot(ln_pp)
+    ln_pp = np.log(1.0 - np.exp(ln_p))
+    return y.T.dot(ln_p) + (1 - y).T.dot(ln_pp)
 
 
-def ln_prior(tau, theta): 
+def ln_prior(tau, theta):
     """Compute log_e of Pima Indian multivariate gaussian prior.
 
     Args:
@@ -50,10 +52,10 @@ def ln_prior(tau, theta):
     """
 
     d = len(theta)
-    return 0.5 * d * np.log(tau/(2.*np.pi)) - 0.5 * tau * theta.T.dot(theta)
+    return 0.5 * d * np.log(tau / (2.0 * np.pi)) - 0.5 * tau * theta.T.dot(theta)
 
 
-def ln_posterior(theta, tau, x, y): 
+def ln_posterior(theta, tau, x, y):
     """Compute log_e of Pima Indian multivariate gaussian prior
 
     Args:
@@ -94,12 +96,18 @@ def compute_ln_p(theta, x):
 
     """
 
-    return - np.log(1.0 + 1.0 / np.exp(x.dot(theta)))
+    return -np.log(1.0 + 1.0 / np.exp(x.dot(theta)))
 
 
-def run_example(model_1=True, tau=1.0,
-                nchains=100, samples_per_chain=1000,
-                nburn=500, plot_corner=False, plot_surface=False):
+def run_example(
+    model_1=True,
+    tau=1.0,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_surface=False,
+):
     """Run Pima Indians example.
 
     Args:
@@ -125,14 +133,14 @@ def run_example(model_1=True, tau=1.0,
         ndim = 5
     else:
         ndim = 6
-    hm.logs.debug_log('Dimensionality = {}'.format(ndim))
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
 
-    #===========================================================================
+    # ===========================================================================
     # Load Pima Indian data.
-    #===========================================================================
-    hm.logs.info_log('Loading data ...')
+    # ===========================================================================
+    hm.logs.info_log("Loading data ...")
 
-    data = np.loadtxt('examples/data/pima_indian.dat')
+    data = np.loadtxt("examples/data/pima_indian.dat")
 
     """
     Two primary models for comparison:
@@ -147,27 +155,27 @@ def run_example(model_1=True, tau=1.0,
     data[:,6] --> Diabetes pedigree function (DP)
     data[:,7] --> Age (AGE)
     """
-    x=np.zeros((len(data), ndim))
+    x = np.zeros((len(data), ndim))
 
     if model_1:
-        x[:,0] = 1.0
-        x[:,1] = data[:,1]
-        x[:,2] = data[:,2]
-        x[:,3] = data[:,5]
-        x[:,4] = data[:,6]
+        x[:, 0] = 1.0
+        x[:, 1] = data[:, 1]
+        x[:, 2] = data[:, 2]
+        x[:, 3] = data[:, 5]
+        x[:, 4] = data[:, 6]
 
     else:
-        x[:,0] = 1.0
-        x[:,1] = data[:,1]
-        x[:,2] = data[:,2]
-        x[:,3] = data[:,5]
-        x[:,4] = data[:,6]
-        x[:,5] = data[:,7] # --> model 2.
+        x[:, 0] = 1.0
+        x[:, 1] = data[:, 1]
+        x[:, 2] = data[:, 2]
+        x[:, 3] = data[:, 5]
+        x[:, 4] = data[:, 6]
+        x[:, 5] = data[:, 7]  # --> model 2.
 
     """
     y[:] = 1 if patient has diabetes, 0 if patient does not have diabetes.
     """
-    y = data[:,0]
+    y = data[:, 0]
 
     """
     Configure some general parameters.
@@ -179,135 +187,139 @@ def run_example(model_1=True, tau=1.0,
     """
     nfold = 3
     training_proportion = 0.25
-    hyper_parameters_MGMM = [[1, 1E-8, 0.1, 6, 10], [2, 1E-8, 0.5, 6, 10]]
+    hyper_parameters_MGMM = [[1, 1e-8, 0.1, 6, 10], [2, 1e-8, 0.5, 6, 10]]
     hyper_parameters_sphere = [None]
-    domains_sphere = [np.array([1E-2,1E0])]
-    domains_MGMM = [np.array([1E-2,1E0])]
+    domains_sphere = [np.array([1e-2, 1e0])]
+    domains_MGMM = [np.array([1e-2, 1e0])]
 
-    #===========================================================================
+    # ===========================================================================
     # Compute random positions to draw from for emcee sampler.
-    #===========================================================================
+    # ===========================================================================
     """
     Initial positions for each chain for each covariate \in [0,8).
     Simply drawn from directly from each covariate prior.
     """
     if model_1:
-        pos_0 = np.random.randn(nchains)*0.01
-        pos_1 = np.random.randn(nchains)*0.01
-        pos_2 = np.random.randn(nchains)*0.01
-        pos_3 = np.random.randn(nchains)*0.01
-        pos_4 = np.random.randn(nchains)*0.01
+        pos_0 = np.random.randn(nchains) * 0.01
+        pos_1 = np.random.randn(nchains) * 0.01
+        pos_2 = np.random.randn(nchains) * 0.01
+        pos_3 = np.random.randn(nchains) * 0.01
+        pos_4 = np.random.randn(nchains) * 0.01
         pos = np.c_[pos_0, pos_1, pos_2, pos_3, pos_4]
 
     else:
-        pos_0 = np.random.randn(nchains)*0.01
-        pos_1 = np.random.randn(nchains)*0.01
-        pos_2 = np.random.randn(nchains)*0.01
-        pos_3 = np.random.randn(nchains)*0.01
-        pos_4 = np.random.randn(nchains)*0.01
-        pos_5 = np.random.randn(nchains)*0.01 
+        pos_0 = np.random.randn(nchains) * 0.01
+        pos_1 = np.random.randn(nchains) * 0.01
+        pos_2 = np.random.randn(nchains) * 0.01
+        pos_3 = np.random.randn(nchains) * 0.01
+        pos_4 = np.random.randn(nchains) * 0.01
+        pos_5 = np.random.randn(nchains) * 0.01
         pos = np.c_[pos_0, pos_1, pos_2, pos_3, pos_4, pos_5]
 
     # Start Timer.
     clock = time.process_time()
 
-    #===========================================================================
-    # Run Emcee to recover posterior samples 
-    #===========================================================================
-    hm.logs.info_log('Run sampling...')
+    # ===========================================================================
+    # Run Emcee to recover posterior samples
+    # ===========================================================================
+    hm.logs.info_log("Run sampling...")
     """
     Feed emcee the ln_posterior function, starting positions and recover chains.
     """
-    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, \
-                                    args=(tau, x, y))
+    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=(tau, x, y))
     rstate = np.random.get_state()
     sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
-    samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])
-    lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])
+    samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+    lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
 
-    #===========================================================================
+    # ===========================================================================
     # Configure emcee chains for harmonic
-    #===========================================================================
-    hm.logs.info_log('Configure chains...')
+    # ===========================================================================
+    hm.logs.info_log("Configure chains...")
     """
     Configure chains for the cross-validation stage.
     """
     chains = hm.Chains(ndim)
     chains.add_chains_3d(samples, lnprob)
-    chains_train, chains_test = hm.utils.split_data(chains, \
-        training_proportion=training_proportion)
+    chains_train, chains_test = hm.utils.split_data(
+        chains, training_proportion=training_proportion
+    )
 
-    #===========================================================================
+    # ===========================================================================
     # Perform cross-validation
-    #===========================================================================
-    hm.logs.info_log('Perform cross-validation...')
+    # ===========================================================================
+    hm.logs.info_log("Perform cross-validation...")
     """
     There are several different machine learning models. Cross-validation
     allows the software to select the optimal model and the optimal model 
     hyper-parameters for a given situation.
     """
     # MGMM model
-    validation_variances_MGMM = \
-        hm.utils.cross_validation(chains_train, 
-            domains_MGMM, \
-            hyper_parameters_MGMM, \
-            nfold=nfold, 
-            modelClass=hm.model.ModifiedGaussianMixtureModel, \
-            seed=0)                
-    hm.logs.debug_log('Validation variances of MGMM = {}'
-        .format(validation_variances_MGMM))
+    validation_variances_MGMM = hm.utils.cross_validation(
+        chains_train,
+        domains_MGMM,
+        hyper_parameters_MGMM,
+        nfold=nfold,
+        modelClass=hm.model.ModifiedGaussianMixtureModel,
+        seed=0,
+    )
+    hm.logs.debug_log(
+        "Validation variances of MGMM = {}".format(validation_variances_MGMM)
+    )
     best_hyper_param_MGMM_ind = np.argmin(validation_variances_MGMM)
-    best_hyper_param_MGMM = \
-        hyper_parameters_MGMM[best_hyper_param_MGMM_ind]
+    best_hyper_param_MGMM = hyper_parameters_MGMM[best_hyper_param_MGMM_ind]
 
     # Hyper-spherical model
-    validation_variances_sphere = \
-        hm.utils.cross_validation(chains_train, 
-            domains_sphere, \
-            hyper_parameters_sphere, nfold=nfold, 
-            modelClass=hm.model.HyperSphere, 
-            seed=0)
-    hm.logs.debug_log('Validation variances of sphere = {}'
-        .format(validation_variances_sphere))
+    validation_variances_sphere = hm.utils.cross_validation(
+        chains_train,
+        domains_sphere,
+        hyper_parameters_sphere,
+        nfold=nfold,
+        modelClass=hm.model.HyperSphere,
+        seed=0,
+    )
+    hm.logs.debug_log(
+        "Validation variances of sphere = {}".format(validation_variances_sphere)
+    )
     best_hyper_param_sphere_ind = np.argmin(validation_variances_sphere)
-    best_hyper_param_sphere = \
-        hyper_parameters_sphere[best_hyper_param_sphere_ind]
+    best_hyper_param_sphere = hyper_parameters_sphere[best_hyper_param_sphere_ind]
 
-    #===========================================================================
+    # ===========================================================================
     # Select the optimal model from cross-validation results
-    #===========================================================================
-    hm.logs.info_log('Select optimal model...')
+    # ===========================================================================
+    hm.logs.info_log("Select optimal model...")
     """
     This simply uses the cross-validation results to choose the model which 
     has the smallest validation variance -- i.e. the best model for the job.
     """
-    best_var_MGMM = \
-        validation_variances_MGMM[best_hyper_param_MGMM_ind]
-    best_var_sphere = \
-        validation_variances_sphere[best_hyper_param_sphere_ind]
-    if best_var_MGMM < best_var_sphere:            
-        hm.logs.info_log('Using MGMM with hyper_parameters = {}'
-            .format(best_hyper_param_MGMM))                
-        model = hm.model.ModifiedGaussianMixtureModel(ndim, \
-            domains_MGMM, hyper_parameters=best_hyper_param_MGMM)
+    best_var_MGMM = validation_variances_MGMM[best_hyper_param_MGMM_ind]
+    best_var_sphere = validation_variances_sphere[best_hyper_param_sphere_ind]
+    if best_var_MGMM < best_var_sphere:
+        hm.logs.info_log(
+            "Using MGMM with hyper_parameters = {}".format(best_hyper_param_MGMM)
+        )
+        model = hm.model.ModifiedGaussianMixtureModel(
+            ndim, domains_MGMM, hyper_parameters=best_hyper_param_MGMM
+        )
     else:
-        hm.logs.info_log('Using HyperSphere')
-        model = hm.model.HyperSphere(ndim, domains_sphere, \
-                hyper_parameters=best_hyper_param_sphere)          
-
-    #===========================================================================
-    # Fit learnt model for container function 
-    #===========================================================================
+        hm.logs.info_log("Using HyperSphere")
+        model = hm.model.HyperSphere(
+            ndim, domains_sphere, hyper_parameters=best_hyper_param_sphere
+        )
+
+    # ===========================================================================
+    # Fit learnt model for container function
+    # ===========================================================================
     """
     Once the model is selected the model is fit to chain samples.
     """
     fit_success = model.fit(chains_train.samples, chains_train.ln_posterior)
-    hm.logs.debug_log('Fit success = {}'.format(fit_success)) 
+    hm.logs.debug_log("Fit success = {}".format(fit_success))
 
-    #===========================================================================
+    # ===========================================================================
     # Computing evidence using learnt model and emcee chains
-    #===========================================================================
-    hm.logs.info_log('Compute evidence...')
+    # ===========================================================================
+    hm.logs.info_log("Compute evidence...")
     """
     Instantiates the evidence class with a given model. Adds some chains and 
     computes the log-space evidence (marginal likelihood).
@@ -315,57 +327,56 @@ def run_example(model_1=True, tau=1.0,
     ev = hm.Evidence(chains_test.nchains, model)
     ev.add_chains(chains_test)
     ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
-    evidence_std_log_space = np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    evidence_std_log_space = (
+        np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    )
 
-    #===========================================================================
+    # ===========================================================================
     # End Timer.
     clock = time.process_time() - clock
-    hm.logs.info_log('Execution time = {}s'.format(clock))
-
-    #===========================================================================
-    # Display evidence results 
-    #===========================================================================
-    hm.logs.info_log('ln_evidence = {} +/- {}'.format(ln_evidence, evidence_std_log_space))
-    hm.logs.info_log('kurtosis = {}'.format(ev.kurtosis))
-    hm.logs.info_log('sqrt( 2/(n_eff-1) ) = {}'.format(np.sqrt(2.0/(ev.n_eff-1))))
+    hm.logs.info_log("Execution time = {}s".format(clock))
+
+    # ===========================================================================
+    # Display evidence results
+    # ===========================================================================
+    hm.logs.info_log(
+        "ln_evidence = {} +/- {}".format(ln_evidence, evidence_std_log_space)
+    )
+    hm.logs.info_log("kurtosis = {}".format(ev.kurtosis))
+    hm.logs.info_log("sqrt( 2/(n_eff-1) ) = {}".format(np.sqrt(2.0 / (ev.n_eff - 1))))
     check = np.exp(0.5 * ev.ln_evidence_inv_var_var - ev.ln_evidence_inv_var)
-    hm.logs.info_log('sqrt(evidence_inv_var_var) / evidence_inv_var = {}'.format(check))
+    hm.logs.info_log("sqrt(evidence_inv_var_var) / evidence_inv_var = {}".format(check))
 
-    #===========================================================================
+    # ===========================================================================
     # Display more technical details
-    #===========================================================================
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('Technical Details')
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-        .format(ev.lnargmax, ev.lnargmin))
-    hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-        .format(ev.lnprobmax, ev.lnprobmin))
-    hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-        .format(ev.lnpredictmax, ev.lnpredictmin))
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('shift = {}, shift setting = {}'
-        .format(ev.shift_value, ev.shift))
-    hm.logs.debug_log('running sum total = {}'
-        .format(sum(ev.running_sum)))
-    hm.logs.debug_log('running sum = \n{}'
-        .format(ev.running_sum))
-    hm.logs.debug_log('nsamples per chain = \n{}'
-        .format(ev.nsamples_per_chain))
-    hm.logs.debug_log('nsamples eff per chain = \n{}'
-        .format(ev.nsamples_eff_per_chain))
-    hm.logs.debug_log('===============================')
-
-
-if __name__ == '__main__':
-
+    # ===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("Technical Details")
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin))
+    hm.logs.debug_log(
+        "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+    )
+    hm.logs.debug_log(
+        "lnpredictmax = {}, lnpredictmin = {}".format(ev.lnpredictmax, ev.lnpredictmin)
+    )
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("shift = {}, shift setting = {}".format(ev.shift_value, ev.shift))
+    hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+    hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+    hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+    hm.logs.debug_log("nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain))
+    hm.logs.debug_log("===============================")
+
+
+if __name__ == "__main__":
     # Setup logging config.
     hm.logs.setup_logging()
 
     # Define problem parameters
     model_1 = False
     # Tau should be varied in [0.01, 1].
-    #tau = 1.0
+    # tau = 1.0
     tau = 0.01
 
     # Define parameters.
@@ -374,22 +385,29 @@ def run_example(model_1=True, tau=1.0,
     nburn = 1000
     np.random.seed(3)
 
-    hm.logs.info_log('Pima Indian example')
+    hm.logs.info_log("Pima Indian example")
 
     if model_1:
-        hm.logs.info_log('Using Model 1')
+        hm.logs.info_log("Using Model 1")
     else:
-        hm.logs.info_log('Using Model 2')
+        hm.logs.info_log("Using Model 2")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+    hm.logs.debug_log("Tau = {}".format(tau))
 
-    hm.logs.debug_log('-- Selected Parameters --')
+    hm.logs.debug_log("-------------------------")
 
-    hm.logs.debug_log('Number of chains = {}'.format(nchains))
-    hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-    hm.logs.debug_log('Burn in = {}'.format(nburn))
-    hm.logs.debug_log('Tau = {}'.format(tau))
-    
-    hm.logs.debug_log('-------------------------')
-    
     # Run example.
-    samples = run_example(model_1, tau, nchains, samples_per_chain, nburn, 
-                          plot_corner=False, plot_surface=False)
+    samples = run_example(
+        model_1,
+        tau,
+        nchains,
+        samples_per_chain,
+        nburn,
+        plot_corner=False,
+        plot_surface=False,
+    )
diff --git a/examples/pima_indian_nvp.py b/examples/pima_indian_nvp.py
new file mode 100644
index 00000000..8050c702
--- /dev/null
+++ b/examples/pima_indian_nvp.py
@@ -0,0 +1,385 @@
+import numpy as np
+import sys
+import emcee
+import scipy.special as sp
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+import flows
+
+
+def ln_likelihood(y, theta, x):
+    """Compute log_e of Pima Indian likelihood.
+
+    Args:
+
+        y: Vector of diabetes incidence (1=diabetes, 0=no diabetes).
+
+        theta: Vector of parameter variables associated with covariates x.
+
+        x: Vector of data covariates (e.g. NP, PGC, BP, TST, DMI etc.).
+
+    Returns:
+
+        double: Value of log_e likelihood at specified point in parameter
+            space.
+
+    """
+
+    ln_p = compute_ln_p(theta, x)
+    ln_pp = np.log(1.0 - np.exp(ln_p))
+    return y.T.dot(ln_p) + (1 - y).T.dot(ln_pp)
+
+
+def ln_prior(tau, theta):
+    """Compute log_e of Pima Indian multivariate gaussian prior.
+
+    Args:
+
+        tau: Characteristic width of posterior \in [0.01,1].
+
+        theta: Vector of parameter variables associated with covariates x.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+
+    d = len(theta)
+    return 0.5 * d * np.log(tau / (2.0 * np.pi)) - 0.5 * tau * theta.T.dot(theta)
+
+
+def ln_posterior(theta, tau, x, y):
+    """Compute log_e of Pima Indian multivariate gaussian prior
+
+    Args:
+
+        theta: Vector of parameter variables associated with covariates x.
+
+        tau: Characteristic width of posterior \in [0.01,1].
+
+        x: Vector of data covariates (e.g. NP, PGC, BP, TST, DMI etc).
+
+        y: Vector of incidence. 1=diabetes, 0=no diabetes.
+
+    Returns:
+
+        double: Value of log_e posterior at specified point in parameter
+            space.
+
+    """
+
+    ln_pr = ln_prior(tau, theta)
+    ln_L = ln_likelihood(y, theta, x)
+
+    return ln_pr + ln_L
+
+
+def compute_ln_p(theta, x):
+    """Computes log_e probability ln(p) to be used in likelihood function.
+
+    Args:
+
+        theta: Vector of parameter variables associated with covariates x.
+
+        x: Vector of data covariates (e.g. NP, PGC, BP, TST, DMI e.t.c.).
+
+    Returns:
+
+        double: Vector of the log-probabilities p to use in likelihood.
+
+    """
+
+    return -np.log(1.0 + 1.0 / np.exp(x.dot(theta)))
+
+
+def run_example(
+    model_1=True,
+    tau=1.0,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+):
+    """Run Pima Indians example.
+
+    Args:
+
+        model_1: Consider model 1 if true, otherwise model 2.
+
+        tau: Precision parameter.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+    """
+
+    # Set_dimension
+    if model_1:
+        ndim = 5
+    else:
+        ndim = 6
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+
+    # ===========================================================================
+    # Load Pima Indian data.
+    # ===========================================================================
+    hm.logs.info_log("Loading data ...")
+
+    data = np.loadtxt("examples/data/pima_indian.dat")
+
+    """
+    Two primary models for comparison:
+        Model 1: Uses rows const(1) + data(1,2,5,6) = 5 dimensional
+        Model 2: Uses rows const(1) + data(1,2,5,6,7) = 6 dimensional
+    data[:,0] --> Diabetes incidence. 
+    data[:,1] --> Number of pregnancies (NP)
+    data[:,2] --> Plasma glucose concentration (PGC)
+    data[:,3] --> Diastolic blood pressure (BP)
+    data[:,4] --> Tricept skin fold thickness (TST)
+    data[:,5] --> Body mass index (BMI)
+    data[:,6] --> Diabetes pedigree function (DP)
+    data[:,7] --> Age (AGE)
+    """
+    x = np.zeros((len(data), ndim))
+
+    if model_1:
+        x[:, 0] = 1.0
+        x[:, 1] = data[:, 1]
+        x[:, 2] = data[:, 2]
+        x[:, 3] = data[:, 5]
+        x[:, 4] = data[:, 6]
+
+    else:
+        x[:, 0] = 1.0
+        x[:, 1] = data[:, 1]
+        x[:, 2] = data[:, 2]
+        x[:, 3] = data[:, 5]
+        x[:, 4] = data[:, 6]
+        x[:, 5] = data[:, 7]  # --> model 2.
+
+    """
+    y[:] = 1 if patient has diabetes, 0 if patient does not have diabetes.
+    """
+    y = data[:, 0]
+
+    """
+    Configure some general parameters.
+    """
+    savefigs = True
+
+    """
+    Configure machine learning parameters.
+    """
+
+    training_proportion = 0.5
+    temperature = 0.9
+    epochs_num = 50
+    n_scaled = 6
+    n_unscaled = 2
+
+    # ===========================================================================
+    # Compute random positions to draw from for emcee sampler.
+    # ===========================================================================
+    """
+    Initial positions for each chain for each covariate \in [0,8).
+    Simply drawn from directly from each covariate prior.
+    """
+    if model_1:
+        pos_0 = np.random.randn(nchains) * 0.01
+        pos_1 = np.random.randn(nchains) * 0.01
+        pos_2 = np.random.randn(nchains) * 0.01
+        pos_3 = np.random.randn(nchains) * 0.01
+        pos_4 = np.random.randn(nchains) * 0.01
+        pos = np.c_[pos_0, pos_1, pos_2, pos_3, pos_4]
+
+    else:
+        pos_0 = np.random.randn(nchains) * 0.01
+        pos_1 = np.random.randn(nchains) * 0.01
+        pos_2 = np.random.randn(nchains) * 0.01
+        pos_3 = np.random.randn(nchains) * 0.01
+        pos_4 = np.random.randn(nchains) * 0.01
+        pos_5 = np.random.randn(nchains) * 0.01
+        pos = np.c_[pos_0, pos_1, pos_2, pos_3, pos_4, pos_5]
+
+    # Start Timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Run Emcee to recover posterior samples
+    # ===========================================================================
+    hm.logs.info_log("Run sampling...")
+    """
+    Feed emcee the ln_posterior function, starting positions and recover chains.
+    """
+    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=(tau, x, y))
+    rstate = np.random.get_state()
+    sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+    samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+    lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+    # ===========================================================================
+    # Configure emcee chains for harmonic
+    # ===========================================================================
+    hm.logs.info_log("Configure chains...")
+    """
+    Configure chains for the cross-validation stage.
+    """
+    chains = hm.Chains(ndim)
+    chains.add_chains_3d(samples, lnprob)
+    chains_train, chains_test = hm.utils.split_data(
+        chains, training_proportion=training_proportion
+    )
+
+    # =======================================================================
+    # Fit model
+    # =======================================================================
+    hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+    """
+    Fit model by selecing the configuration of hyper-parameters which 
+    minimises the validation variances.
+    """
+
+    model = model_nf.RealNVPModel(
+        ndim,
+        n_scaled_layers=n_scaled,
+        n_unscaled_layers=n_unscaled,
+        temperature=temperature,
+    )
+    model.fit(chains_train.samples, epochs=epochs_num)
+
+    # =======================================================================
+    # Visualise distributions
+    # =======================================================================
+
+    num_samp = chains_train.samples.shape[0]
+    # samps = np.array(model.sample(num_samp, temperature=1.))
+    samps_compressed = np.array(model.sample(num_samp))
+
+    labels = ["Bias", "NP", "PGC", "BMI", "DP", "AGE"]
+
+    if model_1:
+        model_lab = "model1"
+        labels = labels[:-1]
+    else:
+        model_lab = "model2"
+
+    utils.plot_getdist_compare(
+        chains_train.samples, samps_compressed, labels=labels, legend_fontsize=17
+    )
+
+    if savefigs:
+        plt.savefig(
+            "examples/plots/nvp_pima_indian_corner_all_{}_T{}_tau{}_".format(
+                n_scaled + n_unscaled, temperature, tau
+            )
+            + model_lab
+            + ".png",
+            bbox_inches="tight",
+            dpi=300,
+        )
+
+    # ===========================================================================
+    # Computing evidence using learnt model and emcee chains
+    # ===========================================================================
+    hm.logs.info_log("Compute evidence...")
+    """
+    Instantiates the evidence class with a given model. Adds some chains and 
+    computes the log-space evidence (marginal likelihood).
+    """
+    ev = hm.Evidence(chains_test.nchains, model)
+    ev.add_chains(chains_test)
+    ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+    evidence_std_log_space = (
+        np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    )
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("Execution time = {}s".format(clock))
+
+    # ===========================================================================
+    # Display evidence results
+    # ===========================================================================
+    hm.logs.info_log(
+        "ln_evidence = {} +/- {}".format(ln_evidence, evidence_std_log_space)
+    )
+    hm.logs.info_log("kurtosis = {}".format(ev.kurtosis))
+    hm.logs.info_log("sqrt( 2/(n_eff-1) ) = {}".format(np.sqrt(2.0 / (ev.n_eff - 1))))
+    check = np.exp(0.5 * ev.ln_evidence_inv_var_var - ev.ln_evidence_inv_var)
+    hm.logs.info_log("sqrt(evidence_inv_var_var) / evidence_inv_var = {}".format(check))
+
+    # ===========================================================================
+    # Display more technical details
+    # ===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("Technical Details")
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin))
+    hm.logs.debug_log(
+        "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+    )
+    hm.logs.debug_log(
+        "lnpredictmax = {}, lnpredictmin = {}".format(ev.lnpredictmax, ev.lnpredictmin)
+    )
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("shift = {}, shift setting = {}".format(ev.shift_value, ev.shift))
+    hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+    hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+    hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+    hm.logs.debug_log("nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain))
+    hm.logs.debug_log("===============================")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define problem parameters
+    model_1 = False
+    # Tau should be varied in [0.01, 1].
+    # tau = 1.0
+    tau = 0.01
+
+    # Define parameters.
+    nchains = 200
+    samples_per_chain = 5000
+    nburn = 1000
+    np.random.seed(3)
+
+    hm.logs.info_log("Pima Indian example")
+
+    if model_1:
+        hm.logs.info_log("Using Model 1")
+    else:
+        hm.logs.info_log("Using Model 2")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+    hm.logs.debug_log("Tau = {}".format(tau))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(
+        model_1, tau, nchains, samples_per_chain, nburn, plot_corner=True
+    )
diff --git a/examples/plot_realisations.py b/examples/plot_realisations.py
index 72707d3d..58f2c054 100644
--- a/examples/plot_realisations.py
+++ b/examples/plot_realisations.py
@@ -1,59 +1,75 @@
-import numpy as np 
+import numpy as np
 import matplotlib.pyplot as plt
 import argparse
 import os
 import sys
+
 sys.path.append("examples")
 import utils
 
 savefigs = True
 
 # Parse arguments.
-parser = argparse.ArgumentParser("Create violin plot of inverse evidences" +
-    "from many realisations")
-parser.add_argument('filename_realisations', metavar='filename_realisations', 
-                    type=str, 
-                    help='Name of file containing realisations')
-parser.add_argument('filename_analytic', metavar='filename_analytic', 
-                    type=str, 
-                    help='Name of file containing analytic inverse variance')
+parser = argparse.ArgumentParser(
+    "Create violin plot of inverse evidences" + "from many realisations"
+)
+parser.add_argument(
+    "filename_realisations",
+    metavar="filename_realisations",
+    type=str,
+    help="Name of file containing realisations",
+)
+parser.add_argument(
+    "filename_analytic",
+    metavar="filename_analytic",
+    type=str,
+    help="Name of file containing analytic inverse variance",
+)
 args = parser.parse_args()
 
 # Load data.
 evidence_inv_summary = np.loadtxt(args.filename_realisations)
-evidence_inv_realisations = evidence_inv_summary[:,0]
-evidence_inv_var_realisations = evidence_inv_summary[:,1]
-evidence_inv_var_var_realisations = evidence_inv_summary[:,2]
+evidence_inv_realisations = evidence_inv_summary[:, 0]
+evidence_inv_var_realisations = evidence_inv_summary[:, 1]
+evidence_inv_var_var_realisations = evidence_inv_summary[:, 2]
 
 evidence_inv_analytic = np.loadtxt(args.filename_analytic)
 
 # Plot inverse evidence.
-plt.rcParams.update({'font.size': 20})    
-ax = utils.plot_realisations(mc_estimates=evidence_inv_realisations,  
-                             std_estimated=np.sqrt(np.mean(evidence_inv_var_realisations)),
-                             analytic_val=evidence_inv_analytic,
-                             analytic_text=r'Truth')
-plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))                             
-ax.set_ylabel(r'Inverse evidence ($\rho$)')
+plt.rcParams.update({"font.size": 20})
+ax = utils.plot_realisations(
+    mc_estimates=evidence_inv_realisations,
+    std_estimated=np.sqrt(np.mean(evidence_inv_var_realisations)),
+    analytic_val=evidence_inv_analytic,
+    analytic_text=r"Truth",
+)
+plt.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ax.set_ylabel(r"Inverse evidence ($\rho$)")
 # ax.set_ylim([0.0, 0.01])
 
 filename_base = os.path.basename(args.filename_realisations)
 filename_base_noext = os.path.splitext(filename_base)[0]
 if savefigs:
-    plt.savefig('./examples/plots/' + filename_base_noext + '_evidence_inv.png',
-                bbox_inches='tight')      
-        
-# Plot variance of inverse evidence.        
-ax = utils.plot_realisations(mc_estimates=evidence_inv_var_realisations, 
-                             std_estimated=np.sqrt(np.mean(evidence_inv_var_var_realisations)))
-plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))        
-ax.set_ylabel(r'Inverse evidence variance ($\sigma^2$)')        
+    plt.savefig(
+        "./examples/plots/" + filename_base_noext + "_evidence_inv.png",
+        bbox_inches="tight",
+    )
+
+# Plot variance of inverse evidence.
+ax = utils.plot_realisations(
+    mc_estimates=evidence_inv_var_realisations,
+    std_estimated=np.sqrt(np.mean(evidence_inv_var_var_realisations)),
+)
+plt.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ax.set_ylabel(r"Inverse evidence variance ($\sigma^2$)")
 # ax.set_ylim([-0.1, 1])
-    
+
 if savefigs:
-    plt.savefig('./examples/plots/' + filename_base_noext + '_evidence_inv_var.png',
-                bbox_inches='tight')                  
-        
+    plt.savefig(
+        "./examples/plots/" + filename_base_noext + "_evidence_inv_var.png",
+        bbox_inches="tight",
+    )
+
 plt.show(block=False)
 
 input("\nPress Enter to continue...")
diff --git a/examples/radiata_pine.py b/examples/radiata_pine.py
index 9576b4ec..3073cd68 100644
--- a/examples/radiata_pine.py
+++ b/examples/radiata_pine.py
@@ -2,11 +2,13 @@
 import sys
 import emcee
 import scipy.special as sp
-import time 
+import time
 import matplotlib.pyplot as plt
 from functools import partial
+
 sys.path.append(".")
 import harmonic as hm
+
 sys.path.append("examples")
 import utils
 
@@ -35,14 +37,14 @@ def ln_likelihood(y, x, n, alpha, beta, tau):
 
     ln_like = 0.5 * n * np.log(tau)
     ln_like -= 0.5 * n * np.log(2.0 * np.pi)
-    
-    s = np.sum((y - alpha - beta * x)**2)
-    
+
+    s = np.sum((y - alpha - beta * x) ** 2)
+
     ln_like -= 0.5 * tau * s
-    
+
     return ln_like
 
-    
+
 def ln_prior_alpha(alpha, tau, mu_0, r_0):
     """Compute log_e of alpha / beta prior (Normal prior).
 
@@ -64,8 +66,8 @@ def ln_prior_alpha(alpha, tau, mu_0, r_0):
     ln_pr_alpha = 0.5 * np.log(tau)
     ln_pr_alpha += 0.5 * np.log(r_0)
     ln_pr_alpha -= 0.5 * np.log(2.0 * np.pi)
-    ln_pr_alpha -= 0.5 * tau * r_0 * (alpha - mu_0)**2
-    
+    ln_pr_alpha -= 0.5 * tau * r_0 * (alpha - mu_0) ** 2
+
     return ln_pr_alpha
 
 
@@ -89,7 +91,7 @@ def ln_prior_tau(tau, a_0, b_0):
 
     if tau < 0:
         return -np.inf
-    
+
     ln_pr_tau = a_0 * np.log(b_0)
     ln_pr_tau += (a_0 - 1.0) * np.log(tau)
     ln_pr_tau -= b_0 * tau
@@ -124,10 +126,10 @@ def ln_prior_separated(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
         double: Value of log_e prior at specified point in parameter space.
 
     """
-    ln_pr = ln_prior_alpha(alpha, tau, mu_0[0,0], r_0)
-    ln_pr += ln_prior_alpha(beta, tau, mu_0[1,0], s_0)
+    ln_pr = ln_prior_alpha(alpha, tau, mu_0[0, 0], r_0)
+    ln_pr += ln_prior_alpha(beta, tau, mu_0[1, 0], s_0)
     ln_pr += ln_prior_tau(tau, a_0, b_0)
-        
+
     return ln_pr
 
 
@@ -160,16 +162,17 @@ def ln_prior_combined(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
     if tau < 0:
         return -np.inf
 
-    ln_pr = a_0 * np.log(b_0)    
-    ln_pr += a_0 * np.log(tau)    
-    ln_pr -= b_0 * tau    
+    ln_pr = a_0 * np.log(b_0)
+    ln_pr += a_0 * np.log(tau)
+    ln_pr -= b_0 * tau
     ln_pr -= np.log(2.0 * np.pi)
     ln_pr -= sp.gammaln(a_0)
     ln_pr += 0.5 * np.log(r_0)
     ln_pr += 0.5 * np.log(s_0)
-    ln_pr -= 0.5 * tau \
-             * (r_0 * (alpha - mu_0[0,0])**2 + s_0 * (beta - mu_0[1,0])**2)
-    
+    ln_pr -= (
+        0.5 * tau * (r_0 * (alpha - mu_0[0, 0]) ** 2 + s_0 * (beta - mu_0[1, 0]) ** 2)
+    )
+
     return ln_pr
 
 
@@ -205,11 +208,11 @@ def ln_prior(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
     """
 
     return ln_prior_combined(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0)
-        
+
 
 def ln_posterior(theta, y, x, n, mu_0, r_0, s_0, a_0, b_0):
     """Compute log_e of posterior.
-    
+
     Args:
 
         theta: Position (alpha, beta, tau) at which to evaluate posterior.
@@ -236,18 +239,18 @@ def ln_posterior(theta, y, x, n, mu_0, r_0, s_0, a_0, b_0):
             tau) point.
 
     """
-    
+
     alpha, beta, tau = theta
 
     ln_pr = ln_prior(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0)
-    
+
     if not np.isfinite(ln_pr):
         return -np.inf
 
-    ln_L = ln_likelihood(y, x, n, alpha, beta, tau)    
-    
-    return  ln_L + ln_pr
-    
+    ln_L = ln_likelihood(y, x, n, alpha, beta, tau)
+
+    return ln_L + ln_pr
+
 
 def ln_evidence_analytic(x, y, n, mu_0, r_0, s_0, a_0, b_0):
     """Compute log_e of analytic evidence.
@@ -281,22 +284,26 @@ def ln_evidence_analytic(x, y, n, mu_0, r_0, s_0, a_0, b_0):
     M = X.T.dot(X) + Q_0
     nu_0 = np.linalg.inv(M).dot(X.T.dot(y) + Q_0.dot(mu_0))
 
-    quad_terms = y.T.dot(y) + mu_0.T.dot(Q_0).dot(mu_0) - \
-                        nu_0.T.dot(M).dot(nu_0)
+    quad_terms = y.T.dot(y) + mu_0.T.dot(Q_0).dot(mu_0) - nu_0.T.dot(M).dot(nu_0)
 
     ln_evidence = -0.5 * n * np.log(np.pi)
-    ln_evidence += a_0 * np.log(2.0*b_0)
-    ln_evidence += sp.gammaln(0.5*n + a_0) - sp.gammaln(a_0)
-    ln_evidence += 0.5 * np.log(np.linalg.det(Q_0)) - \
-               0.5 * np.log(np.linalg.det(M))
+    ln_evidence += a_0 * np.log(2.0 * b_0)
+    ln_evidence += sp.gammaln(0.5 * n + a_0) - sp.gammaln(a_0)
+    ln_evidence += 0.5 * np.log(np.linalg.det(Q_0)) - 0.5 * np.log(np.linalg.det(M))
 
     ln_evidence += -(0.5 * n + a_0) * np.log(quad_terms + 2.0 * b_0)
 
     return ln_evidence
 
-    
-def run_example(model_1=True, nchains=100, samples_per_chain=1000, 
-                nburn=500, plot_corner=False, plot_surface=False):
+
+def run_example(
+    model_1=True,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_surface=False,
+):
     """Run Radiata Pine example.
 
     Args:
@@ -314,50 +321,50 @@ def run_example(model_1=True, nchains=100, samples_per_chain=1000,
         plot_surface: Plot surface and samples if true.
 
     """
-       
-    ndim=3
-    hm.logs.debug_log('Dimensionality = {}'.format(ndim))
-         
-    # Set general parameters.    
+
+    ndim = 3
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+
+    # Set general parameters.
     savefigs = True
-    
+
     nfold = 3
     training_proportion = 0.25
     hyper_parameters_sphere = [None]
-    domains_sphere = [np.array([1E-1,5E0])]
-    
-    #===========================================================================
+    domains_sphere = [np.array([1e-1, 5e0])]
+
+    # ===========================================================================
     # Set-up Priors
-    #===========================================================================
+    # ===========================================================================
     # Define prior variables
-    mu_0 = np.array([[3000.0], [185.0]])    
+    mu_0 = np.array([[3000.0], [185.0]])
     r_0 = 0.06
     s_0 = 6.0
     a_0 = 3.0
     b_0 = 2.0 * 300**2
 
-    hm.logs.debug_log('r_0 = {}'.format(r_0))
-    hm.logs.debug_log('s_0 = {}'.format(s_0))
-    hm.logs.debug_log('a_0 = {}'.format(a_0))
-    hm.logs.debug_log('b_0 = {}'.format(b_0))
-    
-    #===========================================================================
+    hm.logs.debug_log("r_0 = {}".format(r_0))
+    hm.logs.debug_log("s_0 = {}".format(s_0))
+    hm.logs.debug_log("a_0 = {}".format(a_0))
+    hm.logs.debug_log("b_0 = {}".format(b_0))
+
+    # ===========================================================================
     # Load Radiata Pine data.
-    #===========================================================================
-    hm.logs.info_log('Loading data ...')
+    # ===========================================================================
+    hm.logs.info_log("Loading data ...")
 
     # Imports data file
-    data = np.loadtxt('examples/data/radiata_pine.dat')
-    id = data[:,0]
-    y = data[:,1]
-    x = data[:,2]
-    z = data[:,3]
+    data = np.loadtxt("examples/data/radiata_pine.dat")
+    id = data[:, 0]
+    y = data[:, 1]
+    x = data[:, 2]
+    z = data[:, 3]
     n = len(x)
 
     # Ensure column vectors
-    y = y.reshape(n,1)
-    x = x.reshape(n,1)
-    z = z.reshape(n,1)
+    y = y.reshape(n, 1)
+    x = x.reshape(n, 1)
+    z = z.reshape(n, 1)
 
     # Remove means from covariates.
     x = x - np.mean(x)
@@ -367,32 +374,35 @@ def run_example(model_1=True, nchains=100, samples_per_chain=1000,
     tau_prior_mean = a_0 / b_0
     tau_prior_std = np.sqrt(a_0) / b_0
 
-    #===========================================================================
+    # ===========================================================================
     # Compute random positions to draw from for emcee sampler.
-    #===========================================================================
+    # ===========================================================================
     """
     Initial positions for each chain for each covariate \in [0,8).
     Simply drawn from directly from each covariate prior.
     """
-    pos_alpha = mu_0[0,0] + \
-                1.0 / np.sqrt(tau_prior_mean * r_0) * np.random.randn(nchains)  
-    pos_beta = mu_0[1,0] + \
-               1.0 / np.sqrt(tau_prior_mean * s_0) * np.random.randn(nchains)              
-    pos_tau = tau_prior_mean + tau_prior_std * \
-                           (np.random.rand(nchains) - 0.5)  # avoid negative tau
+    pos_alpha = mu_0[0, 0] + 1.0 / np.sqrt(tau_prior_mean * r_0) * np.random.randn(
+        nchains
+    )
+    pos_beta = mu_0[1, 0] + 1.0 / np.sqrt(tau_prior_mean * s_0) * np.random.randn(
+        nchains
+    )
+    pos_tau = tau_prior_mean + tau_prior_std * (
+        np.random.rand(nchains) - 0.5
+    )  # avoid negative tau
 
     """
     Concatenate these positions into a single variable 'pos'.
-    """  
+    """
     pos = np.c_[pos_alpha, pos_beta, pos_tau]
-     
+
     # Start timer.
     clock = time.process_time()
 
-    #===========================================================================
-    # Run Emcee to recover posterior samples 
-    #===========================================================================
-    hm.logs.info_log('Run sampling...')
+    # ===========================================================================
+    # Run Emcee to recover posterior samples
+    # ===========================================================================
+    hm.logs.info_log("Run sampling...")
     """
     Feed emcee the ln_posterior function, starting positions and recover chains.
     """
@@ -400,46 +410,46 @@ def run_example(model_1=True, nchains=100, samples_per_chain=1000,
         args = (y, x, n, mu_0, r_0, s_0, a_0, b_0)
     else:
         args = (y, z, n, mu_0, r_0, s_0, a_0, b_0)
-    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, \
-        args=args)
+    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=args)
     rstate = np.random.get_state()
     sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
-    samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])
-    lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])
+    samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+    lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
 
-    #===========================================================================
+    # ===========================================================================
     # Configure emcee chains for harmonic
-    #===========================================================================
-    hm.logs.info_log('Configure chains...')
+    # ===========================================================================
+    hm.logs.info_log("Configure chains...")
     """
     Configure chains for the cross-validation stage.
     """
     chains = hm.Chains(ndim)
     chains.add_chains_3d(samples, lnprob)
-    chains_train, chains_test = hm.utils.split_data(chains, \
-        training_proportion=training_proportion)
-        
-    #===========================================================================
-    # Fit learnt model for container function 
-    #===========================================================================
-    hm.logs.info_log('Select model...')
+    chains_train, chains_test = hm.utils.split_data(
+        chains, training_proportion=training_proportion
+    )
+
+    # ===========================================================================
+    # Fit learnt model for container function
+    # ===========================================================================
+    hm.logs.info_log("Select model...")
     """
     This could simply use the cross-validation results to choose the model which 
     has the smallest validation variance -- i.e. the best model for the job. Here
     however we manually select the hyper-sphere model.
     """
 
-    hm.logs.info_log('Using HyperSphere')
-    model = hm.model.HyperSphere(ndim, domains_sphere, hyper_parameters=None)            
-        
+    hm.logs.info_log("Using HyperSphere")
+    model = hm.model.HyperSphere(ndim, domains_sphere, hyper_parameters=None)
+
     fit_success = model.fit(chains_train.samples, chains_train.ln_posterior)
-    hm.logs.debug_log('fit_success = {}'.format(fit_success))    
-    hm.logs.debug_log('model.R = {}'.format(model.R))
-    
-    #===========================================================================
+    hm.logs.debug_log("fit_success = {}".format(fit_success))
+    hm.logs.debug_log("model.R = {}".format(model.R))
+
+    # ===========================================================================
     # Computing evidence using learnt model and emcee chains
-    #===========================================================================
-    hm.logs.info_log('Compute evidence...')
+    # ===========================================================================
+    hm.logs.info_log("Compute evidence...")
     """
     Instantiates the evidence class with a given model. Adds some chains and 
     computes the log-space evidence (marginal likelihood).
@@ -447,187 +457,205 @@ def run_example(model_1=True, nchains=100, samples_per_chain=1000,
     ev = hm.Evidence(chains_test.nchains, model)
     ev.add_chains(chains_test)
     ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
-    evidence_std_log_space = np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    evidence_std_log_space = (
+        np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    )
 
-    #===========================================================================
+    # ===========================================================================
     # End Timer.
     clock = time.process_time() - clock
-    hm.logs.info_log('execution_time = {}s'.format(clock))
-    
-    #===========================================================================
-    # Display evidence results 
-    #===========================================================================
-    hm.logs.info_log('ln_evidence = {} +/- {}'.format(ln_evidence, evidence_std_log_space))
-    hm.logs.info_log('kurtosis = {}'.format(ev.kurtosis))
-    hm.logs.info_log('sqrt( 2/(n_eff-1) ) = {}'.format(np.sqrt(2.0/(ev.n_eff-1))))
+    hm.logs.info_log("execution_time = {}s".format(clock))
+
+    # ===========================================================================
+    # Display evidence results
+    # ===========================================================================
+    hm.logs.info_log(
+        "ln_evidence = {} +/- {}".format(ln_evidence, evidence_std_log_space)
+    )
+    hm.logs.info_log("kurtosis = {}".format(ev.kurtosis))
+    hm.logs.info_log("sqrt( 2/(n_eff-1) ) = {}".format(np.sqrt(2.0 / (ev.n_eff - 1))))
     check = np.exp(0.5 * ev.ln_evidence_inv_var_var - ev.ln_evidence_inv_var)
-    hm.logs.info_log('sqrt(evidence_inv_var_var) / evidence_inv_var = {}'.format(check))
-    ln_evidence_analytic_model1 = \
-        ln_evidence_analytic(x, y, n, mu_0, r_0, s_0, a_0, b_0)
-    hm.logs.info_log('ln_evidence_analytic_model1 = {}'
-                         .format(ln_evidence_analytic_model1[0][0]))
-    ln_evidence_analytic_model2 = \
-        ln_evidence_analytic(z, y, n, mu_0, r_0, s_0, a_0, b_0)
-    hm.logs.info_log('ln_evidence_analytic_model2 = {}'
-                         .format(ln_evidence_analytic_model2[0][0]))
-
-    #===========================================================================
+    hm.logs.info_log("sqrt(evidence_inv_var_var) / evidence_inv_var = {}".format(check))
+    ln_evidence_analytic_model1 = ln_evidence_analytic(
+        x, y, n, mu_0, r_0, s_0, a_0, b_0
+    )
+    hm.logs.info_log(
+        "ln_evidence_analytic_model1 = {}".format(ln_evidence_analytic_model1[0][0])
+    )
+    ln_evidence_analytic_model2 = ln_evidence_analytic(
+        z, y, n, mu_0, r_0, s_0, a_0, b_0
+    )
+    hm.logs.info_log(
+        "ln_evidence_analytic_model2 = {}".format(ln_evidence_analytic_model2[0][0])
+    )
+
+    # ===========================================================================
     # Display more technical details
-    #===========================================================================
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('Technical Details')
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-        .format(ev.lnargmax, ev.lnargmin))
-    hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-        .format(ev.lnprobmax, ev.lnprobmin))
-    hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-        .format(ev.lnpredictmax, ev.lnpredictmin))
-    hm.logs.debug_log('---------------------------------')
-    hm.logs.debug_log('shift = {}, shift setting = {}'
-        .format(ev.shift_value, ev.shift))
-    hm.logs.debug_log('running sum total = {}'
-        .format(sum(ev.running_sum)))
-    hm.logs.debug_log('running sum = \n{}'
-        .format(ev.running_sum))
-    hm.logs.debug_log('nsamples per chain = \n{}'
-        .format(ev.nsamples_per_chain))
-    hm.logs.debug_log('nsamples eff per chain = \n{}'
-        .format(ev.nsamples_eff_per_chain))
-    hm.logs.debug_log('===============================')
-   
-    #===========================================================================
+    # ===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("Technical Details")
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin))
+    hm.logs.debug_log(
+        "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+    )
+    hm.logs.debug_log(
+        "lnpredictmax = {}, lnpredictmin = {}".format(ev.lnpredictmax, ev.lnpredictmin)
+    )
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("shift = {}, shift setting = {}".format(ev.shift_value, ev.shift))
+    hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+    hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+    hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+    hm.logs.debug_log("nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain))
+    hm.logs.debug_log("===============================")
+
+    # ===========================================================================
     # Plotting and prediction functions
-    #===========================================================================
+    # ===========================================================================
 
     # Create corner/triangle plot.
     created_plots = False
     if plot_corner:
-        
         utils.plot_corner(samples.reshape((-1, ndim)))
         if savefigs:
-            plt.savefig('examples/plots/radiatapine_corner.png',
-                        bbox_inches='tight')
-        
+            plt.savefig("examples/plots/radiatapine_corner.png", bbox_inches="tight")
+
         utils.plot_getdist(samples.reshape((-1, ndim)))
         if savefigs:
-            plt.savefig('examples/plots/radiatapine_getdist.png',
-                        bbox_inches='tight')
-        
-        plt.show(block=False)  
+            plt.savefig("examples/plots/radiatapine_getdist.png", bbox_inches="tight")
+
+        plt.show(block=False)
         created_plots = True
 
     # Plot model over first two parameters.
 
-    def model_predict_x0x1(x_2d):         
-        x2 = 1.4E-5
+    def model_predict_x0x1(x_2d):
+        x2 = 1.4e-5
         x = np.append(x_2d, [x2])
         # print("x01x1: x = {}".format(x))
         return model.predict(x)
-        
-    model_grid, x_grid, y_grid = \
-        utils.eval_func_on_grid(model_predict_x0x1, 
-                                xmin=2900.0, xmax=3100.0, 
-                                ymin=185.0-30.0, ymax=185.0+30.0,
-                                nx=1000, ny=1000)
+
+    model_grid, x_grid, y_grid = utils.eval_func_on_grid(
+        model_predict_x0x1,
+        xmin=2900.0,
+        xmax=3100.0,
+        ymin=185.0 - 30.0,
+        ymax=185.0 + 30.0,
+        nx=1000,
+        ny=1000,
+    )
 
     # Plot model.
-    ax = utils.plot_image(model_grid, x_grid, y_grid, 
-                          colorbar_label=r'$\log \varphi$')   
-    plt.xlabel('$x_0$')
-    plt.ylabel('$x_1$')   
-    #plt.axis('equal')    
+    ax = utils.plot_image(model_grid, x_grid, y_grid, colorbar_label=r"$\log \varphi$")
+    plt.xlabel("$x_0$")
+    plt.ylabel("$x_1$")
+    # plt.axis('equal')
     if savefigs:
-        plt.savefig('examples/plots/radiatapine_model_x0x1_image.png',
-                    bbox_inches='tight')
+        plt.savefig(
+            "examples/plots/radiatapine_model_x0x1_image.png", bbox_inches="tight"
+        )
 
     # Plot exponential of model.
-    ax = utils.plot_image(np.exp(model_grid), x_grid, y_grid,
-                          colorbar_label=r'$\varphi$')    
-    plt.xlabel('$x_0$')
-    plt.ylabel('$x_1$')   
-    #plt.axis('equal')    
+    ax = utils.plot_image(
+        np.exp(model_grid), x_grid, y_grid, colorbar_label=r"$\varphi$"
+    )
+    plt.xlabel("$x_0$")
+    plt.ylabel("$x_1$")
+    # plt.axis('equal')
     if savefigs:
-        plt.savefig('examples/plots/radiatapine_modelexp_x0x1_image.png',
-                    bbox_inches='tight')
+        plt.savefig(
+            "examples/plots/radiatapine_modelexp_x0x1_image.png", bbox_inches="tight"
+        )
 
     # Plot model over second and third parameters.
 
-    def model_predict_x1x2(x_2d): 
+    def model_predict_x1x2(x_2d):
         x0 = 3000.0
         x = np.append([x0], x_2d)
         # print("x1x2: x = {}".format(x))
         return model.predict(x)
-        
-    model_grid, x_grid, y_grid = \
-        utils.eval_func_on_grid(model_predict_x1x2, 
-                               xmin=185.0-30.0, xmax=185.0+30.0, 
-                               ymin=a_0 / b_0 - 0.5E-5, ymax=a_0 / b_0 + 0.5E-5, 
-                               nx=1000, ny=1000)
+
+    model_grid, x_grid, y_grid = utils.eval_func_on_grid(
+        model_predict_x1x2,
+        xmin=185.0 - 30.0,
+        xmax=185.0 + 30.0,
+        ymin=a_0 / b_0 - 0.5e-5,
+        ymax=a_0 / b_0 + 0.5e-5,
+        nx=1000,
+        ny=1000,
+    )
 
     # Plot model.
-    ax = utils.plot_image(model_grid, x_grid, y_grid, 
-                          colorbar_label=r'$\log \varphi$')   
-    plt.xlabel('$x_1$')
-    plt.ylabel('$x_2$')   
-    #plt.axis('equal')    
+    ax = utils.plot_image(model_grid, x_grid, y_grid, colorbar_label=r"$\log \varphi$")
+    plt.xlabel("$x_1$")
+    plt.ylabel("$x_2$")
+    # plt.axis('equal')
     if savefigs:
-        plt.savefig('examples/plots/radiatapine_model_x1x2_image.png',
-                    bbox_inches='tight')
+        plt.savefig(
+            "examples/plots/radiatapine_model_x1x2_image.png", bbox_inches="tight"
+        )
 
     # Plot exponential of model.
-    ax = utils.plot_image(np.exp(model_grid), x_grid, y_grid,
-                          colorbar_label=r'$\varphi$')    
-    plt.xlabel('$x_1$')
-    plt.ylabel('$x_2$')   
-    #plt.axis('equal')    
+    ax = utils.plot_image(
+        np.exp(model_grid), x_grid, y_grid, colorbar_label=r"$\varphi$"
+    )
+    plt.xlabel("$x_1$")
+    plt.ylabel("$x_2$")
+    # plt.axis('equal')
     if savefigs:
-        plt.savefig('examples/plots/radiatapine_modelexp_x1x2_image.png',
-                    bbox_inches='tight')
+        plt.savefig(
+            "examples/plots/radiatapine_modelexp_x1x2_image.png", bbox_inches="tight"
+        )
 
     # Plot model over first and third parameters.
 
-    def model_predict_x0x2(x_2d): 
+    def model_predict_x0x2(x_2d):
         x1 = 185.0
         x = np.append(x_2d[0], [x1])
         x = np.append(x, x_2d[1])
         return model.predict(x)
-        
-    model_grid, x_grid, y_grid = \
-        utils.eval_func_on_grid(model_predict_x0x2, 
-                               xmin=2900.0, xmax=3100.0, 
-                               ymin=a_0 / b_0 - 0.5E-5, ymax=a_0 / b_0 + 0.5E-5, 
-                               nx=1000, ny=1000)
-                                                                
+
+    model_grid, x_grid, y_grid = utils.eval_func_on_grid(
+        model_predict_x0x2,
+        xmin=2900.0,
+        xmax=3100.0,
+        ymin=a_0 / b_0 - 0.5e-5,
+        ymax=a_0 / b_0 + 0.5e-5,
+        nx=1000,
+        ny=1000,
+    )
+
     # Plot model.
-    ax = utils.plot_image(model_grid, x_grid, y_grid, 
-                          colorbar_label=r'$\log \varphi$')   
-    plt.xlabel('$x_0$')
-    plt.ylabel('$x_1$')   
-    #plt.axis('equal')
-    
+    ax = utils.plot_image(model_grid, x_grid, y_grid, colorbar_label=r"$\log \varphi$")
+    plt.xlabel("$x_0$")
+    plt.ylabel("$x_1$")
+    # plt.axis('equal')
+
     if savefigs:
-        plt.savefig('examples/plots/radiatapine_model_x0x2_image.png',
-                    bbox_inches='tight')
+        plt.savefig(
+            "examples/plots/radiatapine_model_x0x2_image.png", bbox_inches="tight"
+        )
 
     # Plot exponential of model.
-    ax = utils.plot_image(np.exp(model_grid), x_grid, y_grid,
-                          colorbar_label=r'$\varphi$')    
-    plt.xlabel('$x_0$')
-    plt.ylabel('$x_2$')   
-    #plt.axis('equal')    
+    ax = utils.plot_image(
+        np.exp(model_grid), x_grid, y_grid, colorbar_label=r"$\varphi$"
+    )
+    plt.xlabel("$x_0$")
+    plt.ylabel("$x_2$")
+    # plt.axis('equal')
     if savefigs:
-        plt.savefig('examples/plots/radiatapine_modelexp_x0x2_image.png',
-                    bbox_inches='tight')
+        plt.savefig(
+            "examples/plots/radiatapine_modelexp_x0x2_image.png", bbox_inches="tight"
+        )
 
-    plt.show(block=False)  
+    plt.show(block=False)
 
     if created_plots:
         input("\nPress Enter to continue...")
 
 
-if __name__ == '__main__':
-
+if __name__ == "__main__":
     # Setup logging config.
     hm.logs.setup_logging()
 
@@ -638,22 +666,22 @@ def model_predict_x0x2(x_2d):
     nburn = 2000
     np.random.seed(2)
 
-    hm.logs.info_log('Radiata Pine example')
+    hm.logs.info_log("Radiata Pine example")
 
     if model_1:
-        hm.logs.info_log('Using Model 1')
+        hm.logs.info_log("Using Model 1")
     else:
-        hm.logs.info_log('Using Model 2')
+        hm.logs.info_log("Using Model 2")
 
-    hm.logs.debug_log('-- Selected Parameters --')
+    hm.logs.debug_log("-- Selected Parameters --")
 
-    hm.logs.debug_log('Number of chains = {}'.format(nchains))
-    hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-    hm.logs.debug_log('Burn in = {}'.format(nburn))
-    
-    hm.logs.debug_log('-------------------------')
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
 
-    # Run example.
-    samples = run_example(model_1, nchains, samples_per_chain, nburn, 
-                          plot_corner=True, plot_surface=True)
+    hm.logs.debug_log("-------------------------")
 
+    # Run example.
+    samples = run_example(
+        model_1, nchains, samples_per_chain, nburn, plot_corner=True, plot_surface=True
+    )
diff --git a/examples/radiata_pine_nvp.py b/examples/radiata_pine_nvp.py
new file mode 100644
index 00000000..36dc194a
--- /dev/null
+++ b/examples/radiata_pine_nvp.py
@@ -0,0 +1,591 @@
+import numpy as np
+import sys
+import emcee
+import scipy.special as sp
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+import flows
+
+
+def ln_likelihood(y, x, n, alpha, beta, tau):
+    """Compute log_e of Radiata Pine likelihood.
+
+    Args:
+
+        y: Compression strength along grain.
+
+        x: Predictor (density or density adjusted for resin content).
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+    Returns:
+
+        double: Value of log_e likelihood at specified point in parameter
+            space.
+
+    """
+
+    ln_like = 0.5 * n * np.log(tau)
+    ln_like -= 0.5 * n * np.log(2.0 * np.pi)
+
+    s = np.sum((y - alpha - beta * x) ** 2)
+
+    ln_like -= 0.5 * tau * s
+
+    return ln_like
+
+
+def ln_prior_alpha(alpha, tau, mu_0, r_0):
+    """Compute log_e of alpha / beta prior (Normal prior).
+
+    Args:
+
+        alpha: Model term (bias or linear term).
+
+        tau: Prior precision factor.
+
+        mu_0: Prior mean.
+
+        r_0: Prior precision constant factor.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+    ln_pr_alpha = 0.5 * np.log(tau)
+    ln_pr_alpha += 0.5 * np.log(r_0)
+    ln_pr_alpha -= 0.5 * np.log(2.0 * np.pi)
+    ln_pr_alpha -= 0.5 * tau * r_0 * (alpha - mu_0) ** 2
+
+    return ln_pr_alpha
+
+
+def ln_prior_tau(tau, a_0, b_0):
+    """Compute log_e of tau prior (Gamma prior).
+
+    Args:
+
+        tau: Prior precision factor.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e tau prior at specified point in parameter
+            space.
+
+    """
+
+    if tau < 0:
+        return -np.inf
+
+    ln_pr_tau = a_0 * np.log(b_0)
+    ln_pr_tau += (a_0 - 1.0) * np.log(tau)
+    ln_pr_tau -= b_0 * tau
+    ln_pr_tau -= sp.gammaln(a_0)
+
+    return ln_pr_tau
+
+
+def ln_prior_separated(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of prior (combining individual prior functions).
+
+    Args:
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+    ln_pr = ln_prior_alpha(alpha, tau, mu_0[0, 0], r_0)
+    ln_pr += ln_prior_alpha(beta, tau, mu_0[1, 0], s_0)
+    ln_pr += ln_prior_tau(tau, a_0, b_0)
+
+    return ln_pr
+
+
+def ln_prior_combined(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of combined prior (jointly computing total prior).
+
+    Args:
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+    if tau < 0:
+        return -np.inf
+
+    ln_pr = a_0 * np.log(b_0)
+    ln_pr += a_0 * np.log(tau)
+    ln_pr -= b_0 * tau
+    ln_pr -= np.log(2.0 * np.pi)
+    ln_pr -= sp.gammaln(a_0)
+    ln_pr += 0.5 * np.log(r_0)
+    ln_pr += 0.5 * np.log(s_0)
+    ln_pr -= (
+        0.5 * tau * (r_0 * (alpha - mu_0[0, 0]) ** 2 + s_0 * (beta - mu_0[1, 0]) ** 2)
+    )
+
+    return ln_pr
+
+
+def ln_prior(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of combined prior.
+
+    Can be used to easily switch with prior function using (e.g.
+    ln_prior_separated or ln_prior_combined). There should be (and is) not
+    difference (both implemented just as an additional consistency check).
+
+    Args:
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+
+    return ln_prior_combined(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0)
+
+
+def ln_posterior(theta, y, x, n, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of posterior.
+
+    Args:
+
+        theta: Position (alpha, beta, tau) at which to evaluate posterior.
+
+        y: Compression strength along grain.
+
+        x: Predictor (density or density adjusted for resin content).
+
+        n: Number of specimens.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e posterior at specified theta (alpha, beta,
+            tau) point.
+
+    """
+
+    alpha, beta, tau = theta
+
+    ln_pr = ln_prior(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0)
+
+    if not np.isfinite(ln_pr):
+        return -np.inf
+
+    ln_L = ln_likelihood(y, x, n, alpha, beta, tau)
+
+    return ln_L + ln_pr
+
+
+def ln_evidence_analytic(x, y, n, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of analytic evidence.
+
+    Args:
+
+        x: Predictor (density or density adjusted for resin content).
+
+        y: Compression strength along grain.
+
+        n: Number of specimens.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e of analytic evidence for model.
+
+    """
+
+    Q_0 = np.diag([r_0, s_0])
+    X = np.c_[np.ones((n, 1)), x]
+    M = X.T.dot(X) + Q_0
+    nu_0 = np.linalg.inv(M).dot(X.T.dot(y) + Q_0.dot(mu_0))
+
+    quad_terms = y.T.dot(y) + mu_0.T.dot(Q_0).dot(mu_0) - nu_0.T.dot(M).dot(nu_0)
+
+    ln_evidence = -0.5 * n * np.log(np.pi)
+    ln_evidence += a_0 * np.log(2.0 * b_0)
+    ln_evidence += sp.gammaln(0.5 * n + a_0) - sp.gammaln(a_0)
+    ln_evidence += 0.5 * np.log(np.linalg.det(Q_0)) - 0.5 * np.log(np.linalg.det(M))
+
+    ln_evidence += -(0.5 * n + a_0) * np.log(quad_terms + 2.0 * b_0)
+
+    return ln_evidence
+
+
+def run_example(
+    model_1=True, nchains=100, samples_per_chain=1000, nburn=500, plot_corner=False
+):
+    """Run Radiata Pine example.
+
+    Args:
+
+        model_1: Consider model 1 if true, otherwise model 2.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+    """
+
+    ndim = 3
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+
+    # Set general parameters.
+    savefigs = True
+
+    training_proportion = 0.5
+    temperature = 0.9
+    epochs_num = 50
+    n_scaled = 3
+    n_unscaled = 3
+    learning_rate = 0.001
+    standardize = True
+
+    # ===========================================================================
+    # Set-up Priors
+    # ===========================================================================
+    # Define prior variables
+    mu_0 = np.array([[3000.0], [185.0]])
+    r_0 = 0.06
+    s_0 = 6.0
+    a_0 = 3.0
+    b_0 = 2.0 * 300**2
+
+    hm.logs.debug_log("r_0 = {}".format(r_0))
+    hm.logs.debug_log("s_0 = {}".format(s_0))
+    hm.logs.debug_log("a_0 = {}".format(a_0))
+    hm.logs.debug_log("b_0 = {}".format(b_0))
+
+    # ===========================================================================
+    # Load Radiata Pine data.
+    # ===========================================================================
+    hm.logs.info_log("Loading data ...")
+
+    # Imports data file
+    data = np.loadtxt("examples/data/radiata_pine.dat")
+    id = data[:, 0]
+    y = data[:, 1]
+    x = data[:, 2]
+    z = data[:, 3]
+    n = len(x)
+
+    # Ensure column vectors
+    y = y.reshape(n, 1)
+    x = x.reshape(n, 1)
+    z = z.reshape(n, 1)
+
+    # Remove means from covariates.
+    x = x - np.mean(x)
+    z = z - np.mean(z)
+
+    # Set up and run sampler.
+    tau_prior_mean = a_0 / b_0
+    tau_prior_std = np.sqrt(a_0) / b_0
+
+    # ===========================================================================
+    # Compute random positions to draw from for emcee sampler.
+    # ===========================================================================
+    """
+    Initial positions for each chain for each covariate \in [0,8).
+    Simply drawn from directly from each covariate prior.
+    """
+    pos_alpha = mu_0[0, 0] + 1.0 / np.sqrt(tau_prior_mean * r_0) * np.random.randn(
+        nchains
+    )
+    pos_beta = mu_0[1, 0] + 1.0 / np.sqrt(tau_prior_mean * s_0) * np.random.randn(
+        nchains
+    )
+    pos_tau = tau_prior_mean + tau_prior_std * (
+        np.random.rand(nchains) - 0.5
+    )  # avoid negative tau
+
+    """
+    Concatenate these positions into a single variable 'pos'.
+    """
+    pos = np.c_[pos_alpha, pos_beta, pos_tau]
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Run Emcee to recover posterior samples
+    # ===========================================================================
+    hm.logs.info_log("Run sampling...")
+    """
+    Feed emcee the ln_posterior function, starting positions and recover chains.
+    """
+    if model_1:
+        args = (y, x, n, mu_0, r_0, s_0, a_0, b_0)
+    else:
+        args = (y, z, n, mu_0, r_0, s_0, a_0, b_0)
+    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=args)
+    rstate = np.random.get_state()
+    sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+    samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+    lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+    # ===========================================================================
+    # Configure emcee chains for harmonic
+    # ===========================================================================
+    hm.logs.info_log("Configure chains...")
+    """
+    Configure chains for the cross-validation stage.
+    """
+    chains = hm.Chains(ndim)
+    chains.add_chains_3d(samples, lnprob)
+    chains_train, chains_test = hm.utils.split_data(
+        chains, training_proportion=training_proportion
+    )
+
+    # =======================================================================
+    # Fit model
+    # =======================================================================
+    hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+    """
+    Fit model by selecing the configuration of hyper-parameters which 
+    minimises the validation variances.
+    """
+    model = model_nf.RealNVPModel(
+        ndim,
+        n_scaled_layers=n_scaled,
+        n_unscaled_layers=n_unscaled,
+        learning_rate=learning_rate,
+        standardize=standardize,
+        temperature=temperature,
+    )
+    # model = model_nf.RQSplineFlow(ndim)
+    model.fit(chains_train.samples, epochs=epochs_num)
+
+    # ===========================================================================
+    # Computing evidence using learnt model and emcee chains
+    # ===========================================================================
+    hm.logs.info_log("Compute evidence...")
+    """
+    Instantiates the evidence class with a given model. Adds some chains and 
+    computes the log-space evidence (marginal likelihood).
+    """
+    ev = hm.Evidence(chains_test.nchains, model)
+    ev.add_chains(chains_test)
+    ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+    evidence_std_log_space = (
+        np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    )
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("execution_time = {}s".format(clock))
+
+    # ===========================================================================
+    # Display evidence results
+    # ===========================================================================
+    hm.logs.info_log(
+        "ln_evidence = {} +/- {}".format(ln_evidence, evidence_std_log_space)
+    )
+    hm.logs.info_log("kurtosis = {}".format(ev.kurtosis))
+    hm.logs.info_log("sqrt( 2/(n_eff-1) ) = {}".format(np.sqrt(2.0 / (ev.n_eff - 1))))
+    check = np.exp(0.5 * ev.ln_evidence_inv_var_var - ev.ln_evidence_inv_var)
+    hm.logs.info_log("sqrt(evidence_inv_var_var) / evidence_inv_var = {}".format(check))
+    ln_evidence_analytic_model1 = ln_evidence_analytic(
+        x, y, n, mu_0, r_0, s_0, a_0, b_0
+    )
+    hm.logs.info_log(
+        "ln_evidence_analytic_model1 = {}".format(ln_evidence_analytic_model1[0][0])
+    )
+    ln_evidence_analytic_model2 = ln_evidence_analytic(
+        z, y, n, mu_0, r_0, s_0, a_0, b_0
+    )
+    hm.logs.info_log(
+        "ln_evidence_analytic_model2 = {}".format(ln_evidence_analytic_model2[0][0])
+    )
+
+    # ===========================================================================
+    # Display more technical details
+    # ===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("Technical Details")
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin))
+    hm.logs.debug_log(
+        "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+    )
+    hm.logs.debug_log(
+        "lnpredictmax = {}, lnpredictmin = {}".format(ev.lnpredictmax, ev.lnpredictmin)
+    )
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("shift = {}, shift setting = {}".format(ev.shift_value, ev.shift))
+    hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+    hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+    hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+    hm.logs.debug_log("nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain))
+    hm.logs.debug_log("===============================")
+
+    # ===========================================================================
+    # Plotting and prediction functions
+    # ===========================================================================
+
+    # Create corner/triangle plot.
+    created_plots = False
+    if plot_corner:
+        utils.plot_corner(samples.reshape((-1, ndim)))
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_corner.png", bbox_inches="tight"
+            )
+
+        utils.plot_getdist(samples.reshape((-1, ndim)))
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_getdist.png", bbox_inches="tight"
+            )
+
+        plt.show(block=False)
+
+        # =======================================================================
+        # Visualise distributions
+        # =======================================================================
+
+        num_samp = chains_train.samples.shape[0]
+        # samps = np.array(model.sample(num_samp, temperature=1.))
+        samps_compressed = np.array(model.sample(num_samp, temperature=temperature))
+
+        utils.plot_getdist_compare(chains_train.samples, samps_compressed)
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_corner_all.png", bbox_inches="tight"
+            )
+
+        utils.plot_getdist(samps_compressed)
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_flow_getdist.png", bbox_inches="tight"
+            )
+
+        created_plots = True
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    model_1 = True
+    nchains = 400
+    # nchains = 10
+    samples_per_chain = 20000
+    # samples_per_chain = 2000
+    nburn = 2000
+    # nburn = 100
+    np.random.seed(2)
+
+    hm.logs.info_log("Radiata Pine example")
+
+    if model_1:
+        hm.logs.info_log("Using Model 1")
+    else:
+        hm.logs.info_log("Using Model 2")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(model_1, nchains, samples_per_chain, nburn, plot_corner=True)
diff --git a/examples/radiata_pine_splines.py b/examples/radiata_pine_splines.py
new file mode 100644
index 00000000..70c6eff3
--- /dev/null
+++ b/examples/radiata_pine_splines.py
@@ -0,0 +1,582 @@
+import numpy as np
+import sys
+import emcee
+import scipy.special as sp
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+import flows
+
+
+def ln_likelihood(y, x, n, alpha, beta, tau):
+    """Compute log_e of Radiata Pine likelihood.
+
+    Args:
+
+        y: Compression strength along grain.
+
+        x: Predictor (density or density adjusted for resin content).
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+    Returns:
+
+        double: Value of log_e likelihood at specified point in parameter
+            space.
+
+    """
+
+    ln_like = 0.5 * n * np.log(tau)
+    ln_like -= 0.5 * n * np.log(2.0 * np.pi)
+
+    s = np.sum((y - alpha - beta * x) ** 2)
+
+    ln_like -= 0.5 * tau * s
+
+    return ln_like
+
+
+def ln_prior_alpha(alpha, tau, mu_0, r_0):
+    """Compute log_e of alpha / beta prior (Normal prior).
+
+    Args:
+
+        alpha: Model term (bias or linear term).
+
+        tau: Prior precision factor.
+
+        mu_0: Prior mean.
+
+        r_0: Prior precision constant factor.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+    ln_pr_alpha = 0.5 * np.log(tau)
+    ln_pr_alpha += 0.5 * np.log(r_0)
+    ln_pr_alpha -= 0.5 * np.log(2.0 * np.pi)
+    ln_pr_alpha -= 0.5 * tau * r_0 * (alpha - mu_0) ** 2
+
+    return ln_pr_alpha
+
+
+def ln_prior_tau(tau, a_0, b_0):
+    """Compute log_e of tau prior (Gamma prior).
+
+    Args:
+
+        tau: Prior precision factor.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e tau prior at specified point in parameter
+            space.
+
+    """
+
+    if tau < 0:
+        return -np.inf
+
+    ln_pr_tau = a_0 * np.log(b_0)
+    ln_pr_tau += (a_0 - 1.0) * np.log(tau)
+    ln_pr_tau -= b_0 * tau
+    ln_pr_tau -= sp.gammaln(a_0)
+
+    return ln_pr_tau
+
+
+def ln_prior_separated(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of prior (combining individual prior functions).
+
+    Args:
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+    ln_pr = ln_prior_alpha(alpha, tau, mu_0[0, 0], r_0)
+    ln_pr += ln_prior_alpha(beta, tau, mu_0[1, 0], s_0)
+    ln_pr += ln_prior_tau(tau, a_0, b_0)
+
+    return ln_pr
+
+
+def ln_prior_combined(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of combined prior (jointly computing total prior).
+
+    Args:
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+    if tau < 0:
+        return -np.inf
+
+    ln_pr = a_0 * np.log(b_0)
+    ln_pr += a_0 * np.log(tau)
+    ln_pr -= b_0 * tau
+    ln_pr -= np.log(2.0 * np.pi)
+    ln_pr -= sp.gammaln(a_0)
+    ln_pr += 0.5 * np.log(r_0)
+    ln_pr += 0.5 * np.log(s_0)
+    ln_pr -= (
+        0.5 * tau * (r_0 * (alpha - mu_0[0, 0]) ** 2 + s_0 * (beta - mu_0[1, 0]) ** 2)
+    )
+
+    return ln_pr
+
+
+def ln_prior(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of combined prior.
+
+    Can be used to easily switch with prior function using (e.g.
+    ln_prior_separated or ln_prior_combined). There should be (and is) not
+    difference (both implemented just as an additional consistency check).
+
+    Args:
+
+        alpha: Model bias term.
+
+        beta: Model linear term.
+
+        tau: Prior precision factor.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e prior at specified point in parameter space.
+
+    """
+
+    return ln_prior_combined(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0)
+
+
+def ln_posterior(theta, y, x, n, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of posterior.
+
+    Args:
+
+        theta: Position (alpha, beta, tau) at which to evaluate posterior.
+
+        y: Compression strength along grain.
+
+        x: Predictor (density or density adjusted for resin content).
+
+        n: Number of specimens.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e posterior at specified theta (alpha, beta,
+            tau) point.
+
+    """
+
+    alpha, beta, tau = theta
+
+    ln_pr = ln_prior(alpha, beta, tau, mu_0, r_0, s_0, a_0, b_0)
+
+    if not np.isfinite(ln_pr):
+        return -np.inf
+
+    ln_L = ln_likelihood(y, x, n, alpha, beta, tau)
+
+    return ln_L + ln_pr
+
+
+def ln_evidence_analytic(x, y, n, mu_0, r_0, s_0, a_0, b_0):
+    """Compute log_e of analytic evidence.
+
+    Args:
+
+        x: Predictor (density or density adjusted for resin content).
+
+        y: Compression strength along grain.
+
+        n: Number of specimens.
+
+        mu_0: Prior means.
+
+        r_0: Prior precision constant factor for bias term.
+
+        s_0: Prior precision constant factor for linear term.
+
+        a_0: Gamma prior shape parameter.
+
+        b_0: Gamma prior rate parameter.
+
+    Returns:
+
+        double: Value of log_e of analytic evidence for model.
+
+    """
+
+    Q_0 = np.diag([r_0, s_0])
+    X = np.c_[np.ones((n, 1)), x]
+    M = X.T.dot(X) + Q_0
+    nu_0 = np.linalg.inv(M).dot(X.T.dot(y) + Q_0.dot(mu_0))
+
+    quad_terms = y.T.dot(y) + mu_0.T.dot(Q_0).dot(mu_0) - nu_0.T.dot(M).dot(nu_0)
+
+    ln_evidence = -0.5 * n * np.log(np.pi)
+    ln_evidence += a_0 * np.log(2.0 * b_0)
+    ln_evidence += sp.gammaln(0.5 * n + a_0) - sp.gammaln(a_0)
+    ln_evidence += 0.5 * np.log(np.linalg.det(Q_0)) - 0.5 * np.log(np.linalg.det(M))
+
+    ln_evidence += -(0.5 * n + a_0) * np.log(quad_terms + 2.0 * b_0)
+
+    return ln_evidence
+
+
+def run_example(
+    model_1=True, nchains=100, samples_per_chain=1000, nburn=500, plot_corner=False
+):
+    """Run Radiata Pine example.
+
+    Args:
+
+        model_1: Consider model 1 if true, otherwise model 2.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+    """
+
+    ndim = 3
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+
+    # Set general parameters.
+    savefigs = True
+
+    training_proportion = 0.5
+    temperature = 0.8
+    epochs_num = 30
+    standardize = True
+
+    # ===========================================================================
+    # Set-up Priors
+    # ===========================================================================
+    # Define prior variables
+    mu_0 = np.array([[3000.0], [185.0]])
+    r_0 = 0.06
+    s_0 = 6.0
+    a_0 = 3.0
+    b_0 = 2.0 * 300**2
+
+    hm.logs.debug_log("r_0 = {}".format(r_0))
+    hm.logs.debug_log("s_0 = {}".format(s_0))
+    hm.logs.debug_log("a_0 = {}".format(a_0))
+    hm.logs.debug_log("b_0 = {}".format(b_0))
+
+    # ===========================================================================
+    # Load Radiata Pine data.
+    # ===========================================================================
+    hm.logs.info_log("Loading data ...")
+
+    # Imports data file
+    data = np.loadtxt("examples/data/radiata_pine.dat")
+    id = data[:, 0]
+    y = data[:, 1]
+    x = data[:, 2]
+    z = data[:, 3]
+    n = len(x)
+
+    # Ensure column vectors
+    y = y.reshape(n, 1)
+    x = x.reshape(n, 1)
+    z = z.reshape(n, 1)
+
+    # Remove means from covariates.
+    x = x - np.mean(x)
+    z = z - np.mean(z)
+
+    # Set up and run sampler.
+    tau_prior_mean = a_0 / b_0
+    tau_prior_std = np.sqrt(a_0) / b_0
+
+    # ===========================================================================
+    # Compute random positions to draw from for emcee sampler.
+    # ===========================================================================
+    """
+    Initial positions for each chain for each covariate \in [0,8).
+    Simply drawn from directly from each covariate prior.
+    """
+    pos_alpha = mu_0[0, 0] + 1.0 / np.sqrt(tau_prior_mean * r_0) * np.random.randn(
+        nchains
+    )
+    pos_beta = mu_0[1, 0] + 1.0 / np.sqrt(tau_prior_mean * s_0) * np.random.randn(
+        nchains
+    )
+    pos_tau = tau_prior_mean + tau_prior_std * (
+        np.random.rand(nchains) - 0.5
+    )  # avoid negative tau
+
+    """
+    Concatenate these positions into a single variable 'pos'.
+    """
+    pos = np.c_[pos_alpha, pos_beta, pos_tau]
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Run Emcee to recover posterior samples
+    # ===========================================================================
+    hm.logs.info_log("Run sampling...")
+    """
+    Feed emcee the ln_posterior function, starting positions and recover chains.
+    """
+    if model_1:
+        args = (y, x, n, mu_0, r_0, s_0, a_0, b_0)
+    else:
+        args = (y, z, n, mu_0, r_0, s_0, a_0, b_0)
+    sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=args)
+    rstate = np.random.get_state()
+    sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+    samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+    lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+    # ===========================================================================
+    # Configure emcee chains for harmonic
+    # ===========================================================================
+    hm.logs.info_log("Configure chains...")
+    """
+    Configure chains for the cross-validation stage.
+    """
+    chains = hm.Chains(ndim)
+    chains.add_chains_3d(samples, lnprob)
+    chains_train, chains_test = hm.utils.split_data(
+        chains, training_proportion=training_proportion
+    )
+
+    # =======================================================================
+    # Fit model
+    # =======================================================================
+    hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+    """
+    Fit model by selecing the configuration of hyper-parameters which 
+    minimises the validation variances.
+    """
+    model = model_nf.RQSplineModel(
+        ndim, standardize=standardize, temperature=temperature
+    )
+    model.fit(chains_train.samples, epochs=epochs_num)
+
+    # ===========================================================================
+    # Computing evidence using learnt model and emcee chains
+    # ===========================================================================
+    hm.logs.info_log("Compute evidence...")
+    """
+    Instantiates the evidence class with a given model. Adds some chains and 
+    computes the log-space evidence (marginal likelihood).
+    """
+    ev = hm.Evidence(chains_test.nchains, model)
+    ev.add_chains(chains_test)
+    ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+    evidence_std_log_space = (
+        np.log(np.exp(ln_evidence) + np.exp(ln_evidence_std)) - ln_evidence
+    )
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("execution_time = {}s".format(clock))
+
+    # ===========================================================================
+    # Display evidence results
+    # ===========================================================================
+    hm.logs.info_log(
+        "ln_evidence = {} +/- {}".format(ln_evidence, evidence_std_log_space)
+    )
+    hm.logs.info_log("kurtosis = {}".format(ev.kurtosis))
+    hm.logs.info_log("sqrt( 2/(n_eff-1) ) = {}".format(np.sqrt(2.0 / (ev.n_eff - 1))))
+    check = np.exp(0.5 * ev.ln_evidence_inv_var_var - ev.ln_evidence_inv_var)
+    hm.logs.info_log("sqrt(evidence_inv_var_var) / evidence_inv_var = {}".format(check))
+    ln_evidence_analytic_model1 = ln_evidence_analytic(
+        x, y, n, mu_0, r_0, s_0, a_0, b_0
+    )
+    hm.logs.info_log(
+        "ln_evidence_analytic_model1 = {}".format(ln_evidence_analytic_model1[0][0])
+    )
+    ln_evidence_analytic_model2 = ln_evidence_analytic(
+        z, y, n, mu_0, r_0, s_0, a_0, b_0
+    )
+    hm.logs.info_log(
+        "ln_evidence_analytic_model2 = {}".format(ln_evidence_analytic_model2[0][0])
+    )
+
+    # ===========================================================================
+    # Display more technical details
+    # ===========================================================================
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("Technical Details")
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin))
+    hm.logs.debug_log(
+        "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+    )
+    hm.logs.debug_log(
+        "lnpredictmax = {}, lnpredictmin = {}".format(ev.lnpredictmax, ev.lnpredictmin)
+    )
+    hm.logs.debug_log("---------------------------------")
+    hm.logs.debug_log("shift = {}, shift setting = {}".format(ev.shift_value, ev.shift))
+    hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+    hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+    hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+    hm.logs.debug_log("nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain))
+    hm.logs.debug_log("===============================")
+
+    # ===========================================================================
+    # Plotting and prediction functions
+    # ===========================================================================
+
+    # Create corner/triangle plot.
+    created_plots = False
+    if plot_corner:
+        utils.plot_corner(samples.reshape((-1, ndim)))
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_corner.png", bbox_inches="tight"
+            )
+
+        utils.plot_getdist(samples.reshape((-1, ndim)))
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_getdist.png", bbox_inches="tight"
+            )
+
+        plt.show(block=False)
+
+        # =======================================================================
+        # Visualise distributions
+        # =======================================================================
+
+        num_samp = chains_train.samples.shape[0]
+        # samps = np.array(model.sample(num_samp, temperature=1.))
+        samps_compressed = np.array(model.sample(num_samp))
+
+        utils.plot_getdist_compare(chains_train.samples, samps_compressed)
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_corner_all.png", bbox_inches="tight"
+            )
+
+        utils.plot_getdist(samps_compressed)
+        if savefigs:
+            plt.savefig(
+                "examples/plots/nvp_radiatapine_flow_getdist.png", bbox_inches="tight"
+            )
+
+        created_plots = True
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    model_1 = True
+    nchains = 400
+    # nchains = 10
+    samples_per_chain = 20000
+    # samples_per_chain = 2000
+    nburn = 2000
+    # nburn = 100
+    np.random.seed(2)
+
+    hm.logs.info_log("Radiata Pine example")
+
+    if model_1:
+        hm.logs.info_log("Using Model 1")
+    else:
+        hm.logs.info_log("Using Model 2")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(model_1, nchains, samples_per_chain, nburn, plot_corner=True)
diff --git a/examples/rastrigin.py b/examples/rastrigin.py
index 6165e1d8..2e075f0c 100644
--- a/examples/rastrigin.py
+++ b/examples/rastrigin.py
@@ -1,11 +1,13 @@
 import numpy as np
 import sys
 import emcee
-import time 
+import time
 import matplotlib.pyplot as plt
 from functools import partial
+
 sys.path.append(".")
 import harmonic as hm
+
 sys.path.append("examples")
 import utils
 
@@ -30,12 +32,12 @@ def ln_prior_uniform(x, xmin=-6.0, xmax=6.0, ymin=-6.0, ymax=6.0):
         double: Value of prior at specified point.
 
     """
-        
-    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:        
-        return 1.0 / ( (xmax - xmin) * (ymax - ymin) )
+
+    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:
+        return 1.0 / ((xmax - xmin) * (ymax - ymin))
     else:
         return 0.0
-        
+
 
 def ln_likelihood(x):
     """Compute log_e of likelihood defined by Rastrigin function.
@@ -49,13 +51,13 @@ def ln_likelihood(x):
         double: Value of Rastrigin at specified point.
 
     """
-    
+
     ndim = x.size
 
     f = 10.0 * ndim
 
     for i_dim in range(ndim):
-        f += x[i_dim]**2 - 10.0 * np.cos( 2.0 * np.pi * x[i_dim] )
+        f += x[i_dim] ** 2 - 10.0 * np.cos(2.0 * np.pi * x[i_dim])
 
     return -f
 
@@ -74,7 +76,7 @@ def ln_posterior(x, ln_prior):
         double: Posterior at specified point.
 
     """
-    
+
     ln_L = ln_likelihood(x)
 
     if not np.isfinite(ln_L):
@@ -83,9 +85,14 @@ def ln_posterior(x, ln_prior):
         return ln_prior(x) + ln_L
 
 
-
-def run_example(ndim=2, nchains=100, samples_per_chain=1000, 
-                nburn=500, plot_corner=False, plot_surface=False):
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_surface=False,
+):
     """Run Rastrigin example.
 
     Args:
@@ -105,12 +112,11 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     """
 
     if ndim != 2:
-        raise ValueError("Only ndim=2 is supported (ndim={} specified)"
-            .format(ndim))
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
 
-    #===========================================================================
+    # ===========================================================================
     # Configure Parameters.
-    #===========================================================================
+    # ===========================================================================
     """
     Configure machine learning parameters
     """
@@ -119,309 +125,347 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     nhyper = 2
     step = -2
     domain = []
-    hyper_parameters = [[10**(R)] for R in range(-nhyper+step,step)]
-    hm.logs.debug_log('Hyper-parameters = {}'.format(hyper_parameters))
+    hyper_parameters = [[10 ** (R)] for R in range(-nhyper + step, step)]
+    hm.logs.debug_log("Hyper-parameters = {}".format(hyper_parameters))
     """
     Set prior parameters.
     """
     use_uniform_prior = True
-    if use_uniform_prior:        
+    if use_uniform_prior:
         xmin = -6.0
         xmax = 6.0
         ymin = -6.0
         ymax = 6.0
-        hm.logs.debug_log('xmin, xmax, ymin, ymax = {}, {}, {}, {}'
-            .format(xmin, xmax, ymin, ymax))   
-        ln_prior = partial(ln_prior_uniform, \
-                           xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) 
+        hm.logs.debug_log(
+            "xmin, xmax, ymin, ymax = {}, {}, {}, {}".format(xmin, xmax, ymin, ymax)
+        )
+        ln_prior = partial(ln_prior_uniform, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
 
     # Start timer.
-    clock = time.process_time() 
+    clock = time.process_time()
 
-    #===========================================================================
+    # ===========================================================================
     # Begin multiple realisations of estimator
-    #===========================================================================
+    # ===========================================================================
     """
     Set up and run multiple simulations
     """
     n_realisations = 100
-    evidence_inv_summary = np.zeros((n_realisations,3))
+    evidence_inv_summary = np.zeros((n_realisations, 3))
     for i_realisation in range(n_realisations):
-
         if n_realisations > 1:
-            hm.logs.info_log('Realisation number = {}/{}'
-                .format(i_realisation+1, n_realisations))
-    
-        #=======================================================================
+            hm.logs.info_log(
+                "Realisation number = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+
+        # =======================================================================
         # Run Emcee to recover posterior samples
-        #=======================================================================
-        hm.logs.info_log('Run sampling...')
+        # =======================================================================
+        hm.logs.info_log("Run sampling...")
         """
         Feed emcee the ln_posterior function, starting positions and recover 
         chains.
         """
-        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.5    
-        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, \
-                                        args=[ln_prior])
+        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.5
+        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=[ln_prior])
         rstate = np.random.get_state()
         sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
-        samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])
-        lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
 
-        #=======================================================================
+        # =======================================================================
         # Configure emcee chains for harmonic
-        #=======================================================================
-        hm.logs.info_log('Configure chains...')
+        # =======================================================================
+        hm.logs.info_log("Configure chains...")
         """
         Configure chains for the cross-validation stage.
         """
         chains = hm.Chains(ndim)
         chains.add_chains_3d(samples, lnprob)
-        chains_train, chains_test = hm.utils.split_data(chains, 
-                                                        training_proportion=0.5)
+        chains_train, chains_test = hm.utils.split_data(chains, training_proportion=0.5)
 
-        #=======================================================================
+        # =======================================================================
         # Perform cross-validation
-        #=======================================================================
-        hm.logs.info_log('Perform cross-validation...')
+        # =======================================================================
+        hm.logs.info_log("Perform cross-validation...")
         """
         There are several different machine learning models. Cross-validation
         allows the software to select the optimal model and the optimal model 
         hyper-parameters for a given situation.
         """
-        validation_variances = \
-            hm.utils.cross_validation(chains_train, \
-                                  domain, \
-                                  hyper_parameters, \
-                                  nfold=nfold, \
-                                  modelClass=hm.model.KernelDensityEstimate, \
-                                  seed=0)
-
-        hm.logs.debug_log('Validation variances = {}'
-            .format(validation_variances))
+        validation_variances = hm.utils.cross_validation(
+            chains_train,
+            domain,
+            hyper_parameters,
+            nfold=nfold,
+            modelClass=hm.model.KernelDensityEstimate,
+            seed=0,
+        )
+
+        hm.logs.debug_log("Validation variances = {}".format(validation_variances))
         best_hyper_param_ind = np.argmin(validation_variances)
         best_hyper_param = hyper_parameters[best_hyper_param_ind]
-        hm.logs.debug_log('Best hyper-parameter = {}'
-            .format(best_hyper_param))
+        hm.logs.debug_log("Best hyper-parameter = {}".format(best_hyper_param))
 
-        #=======================================================================
+        # =======================================================================
         # Fit optimal model hyper-parameters
-        #=======================================================================
-        hm.logs.info_log('Fit model...')
+        # =======================================================================
+        hm.logs.info_log("Fit model...")
         """
         Fit model by selecing the configuration of hyper-parameters which 
         minimises the validation variances.
         """
-        model = hm.model.KernelDensityEstimate(ndim, 
-                                            domain, 
-                                            hyper_parameters=best_hyper_param)
+        model = hm.model.KernelDensityEstimate(
+            ndim, domain, hyper_parameters=best_hyper_param
+        )
         fit_success = model.fit(chains_train.samples, chains_train.ln_posterior)
-        hm.logs.debug_log('Fit success = {}'.format(fit_success)) 
+        hm.logs.debug_log("Fit success = {}".format(fit_success))
 
-        #=======================================================================
+        # =======================================================================
         # Computing evidence using learnt model and emcee chains
-        #=======================================================================
-        hm.logs.info_log('Compute evidence...')
+        # =======================================================================
+        hm.logs.info_log("Compute evidence...")
         """
         Instantiates the evidence class with a given model. Adds some chains and 
         computes the log-space evidence (marginal likelihood).
         """
-        ev = hm.Evidence(chains_test.nchains, model)    
+        ev = hm.Evidence(chains_test.nchains, model)
         ev.add_chains(chains_test)
         ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
 
         # Compute analytic evidence.
         if ndim == 2:
-            hm.logs.debug_log('Compute evidence by numerical integration...')
+            hm.logs.debug_log("Compute evidence by numerical integration...")
             ln_posterior_func = partial(ln_posterior, ln_prior=ln_prior)
-            ln_posterior_grid, x_grid, y_grid = \
-                utils.eval_func_on_grid(ln_posterior_func, 
-                                        xmin=-6.0, xmax=6.0, 
-                                        ymin=-6.0, ymax=6.0, 
-                                        nx=1000, ny=1000)
-            dx = x_grid[0,1] - x_grid[0,0]
-            dy = y_grid[1,0] - y_grid[0,0]
-            evidence_numerical_integration = \
-                                     np.sum(np.exp(ln_posterior_grid)) * dx * dy  
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=-6.0,
+                xmax=6.0,
+                ymin=-6.0,
+                ymax=6.0,
+                nx=1000,
+                ny=1000,
+            )
+            dx = x_grid[0, 1] - x_grid[0, 0]
+            dy = y_grid[1, 0] - y_grid[0, 0]
+            evidence_numerical_integration = np.sum(np.exp(ln_posterior_grid)) * dx * dy
 
         # ======================================================================
         # Display evidence computation results.
         # ======================================================================
-        hm.logs.debug_log('Evidence: numerical = {}, estimate = {}'
-            .format(evidence_numerical_integration, np.exp(ln_evidence)))
-        hm.logs.debug_log('Evidence: std = {}, std / estimate = {}'
-            .format(np.exp(ln_evidence_std), \
-                np.exp(ln_evidence_std - ln_evidence)))
-        diff = np.log(np.abs(evidence_numerical_integration - \
-                      np.exp(ln_evidence)))
-        hm.logs.info_log('Evidence: |numerical - estimate| / estimate = {}'
-            .format(np.exp(diff - ln_evidence)))
+        hm.logs.debug_log(
+            "Evidence: numerical = {}, estimate = {}".format(
+                evidence_numerical_integration, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.debug_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_numerical_integration - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |numerical - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
 
         # ======================================================================
         # Display inverse evidence computation results.
         # ======================================================================
-        hm.logs.debug_log('Inv Evidence: numerical = {}, estimate = {}'
-            .format(1.0/evidence_numerical_integration, ev.evidence_inv))
-        hm.logs.debug_log('Inv Evidence: std = {}, std / estimate = {}'
-            .format(np.sqrt(ev.evidence_inv_var), 
-                np.sqrt(ev.evidence_inv_var)/ev.evidence_inv))
-        hm.logs.debug_log('Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}'
-            .format(ev.kurtosis, np.sqrt(2.0/(ev.n_eff-1))))    
-        hm.logs.debug_log('Inv Evidence: sqrt( var(var) )/ var = {}'
-            .format(np.sqrt(ev.evidence_inv_var_var)/ev.evidence_inv_var))    
-        hm.logs.info_log('Inv Evidence: |numerical - estimate| / estimate = {}'
-            .format(np.abs(1.0 / evidence_numerical_integration - \
-                ev.evidence_inv) / ev.evidence_inv))
-
-        #===========================================================================
+        hm.logs.debug_log(
+            "Inv Evidence: numerical = {}, estimate = {}".format(
+                1.0 / evidence_numerical_integration, ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) )/ var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |numerical - estimate| / estimate = {}".format(
+                np.abs(1.0 / evidence_numerical_integration - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
         # Display more technical details
-        #===========================================================================
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('Technical Details')
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-            .format(ev.lnargmax, ev.lnargmin))
-        hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-            .format(ev.lnprobmax, ev.lnprobmin))
-        hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-            .format(ev.lnpredictmax, ev.lnpredictmin))
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('shift = {}, shift setting = {}'
-            .format(ev.shift_value, ev.shift))
-        hm.logs.debug_log('running sum total = {}'
-            .format(sum(ev.running_sum)))
-        hm.logs.debug_log('running sum = \n{}'
-            .format(ev.running_sum))
-        hm.logs.debug_log('nsamples per chain = \n{}'
-            .format(ev.nsamples_per_chain))
-        hm.logs.debug_log('nsamples eff per chain = \n{}'
-            .format(ev.nsamples_eff_per_chain))
-        hm.logs.debug_log('===============================')
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
 
         # Create corner/triangle plot.
         created_plots = False
         if plot_corner and i_realisation == 0:
-            
             utils.plot_corner(samples.reshape((-1, ndim)))
             if savefigs:
-                plt.savefig('examples/plots/rastrigin_corner.png',
-                            bbox_inches='tight')
-            
+                plt.savefig("examples/plots/rastrigin_corner.png", bbox_inches="tight")
+
             utils.plot_getdist(samples.reshape((-1, ndim)))
             if savefigs:
-                plt.savefig('examples/plots/rastrigin_getdist.png',
-                            bbox_inches='tight')
-            
-            plt.show(block=False)  
+                plt.savefig("examples/plots/rastrigin_getdist.png", bbox_inches="tight")
+
+            plt.show(block=False)
             created_plots = True
-                
-        # In 2D case, plot surface/image and samples.    
+
+        # In 2D case, plot surface/image and samples.
         if plot_surface and ndim == 2 and i_realisation == 0:
-            
             # Plot ln_posterior surface.
-            # ln_posterior_grid[ln_posterior_grid<-100.0] = -100.0 
+            # ln_posterior_grid[ln_posterior_grid<-100.0] = -100.0
             i_chain = 0
-            ax = utils.plot_surface(ln_posterior_grid, x_grid, y_grid, 
-                                    samples[i_chain,:,:].reshape((-1, ndim)), 
-                                    lnprob[i_chain,:].reshape((-1, 1)))
-            # ax.set_zlim(-100.0, 0.0)                
-            ax.set_zlabel(r'$\log \mathcal{L}$')        
+            ax = utils.plot_surface(
+                ln_posterior_grid,
+                x_grid,
+                y_grid,
+                samples[i_chain, :, :].reshape((-1, ndim)),
+                lnprob[i_chain, :].reshape((-1, 1)),
+            )
+            # ax.set_zlim(-100.0, 0.0)
+            ax.set_zlabel(r"$\log \mathcal{L}$")
             if savefigs:
-                plt.savefig('examples/plots/rastrigin_lnposterior_surface.png',
-                            bbox_inches='tight')
-            
+                plt.savefig(
+                    "examples/plots/rastrigin_lnposterior_surface.png",
+                    bbox_inches="tight",
+                )
+
             # Plot posterior image.
-            ax = utils.plot_image(np.exp(ln_posterior_grid), x_grid, y_grid, 
-                                  samples.reshape((-1,ndim)),
-                                  colorbar_label=r'$\mathcal{L}$')
+            ax = utils.plot_image(
+                np.exp(ln_posterior_grid),
+                x_grid,
+                y_grid,
+                samples.reshape((-1, ndim)),
+                colorbar_label=r"$\mathcal{L}$",
+            )
             # ax.set_clim(vmin=0.0, vmax=0.003)
             if savefigs:
-                plt.savefig('examples/plots/rastrigin_posterior_image.png',
-                            bbox_inches='tight')
+                plt.savefig(
+                    "examples/plots/rastrigin_posterior_image.png", bbox_inches="tight"
+                )
 
             # Evaluate model on grid.
-            model_grid, x_grid, y_grid = \
-                utils.eval_func_on_grid(model.predict, 
-                                        xmin=-6.0, xmax=6.0, 
-                                        ymin=-6.0, ymax=6.0, 
-                                        nx=1000, ny=1000)
-            # model_grid[model_grid<-100.0] = -100.0 
-            
+            model_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                model.predict,
+                xmin=-6.0,
+                xmax=6.0,
+                ymin=-6.0,
+                ymax=6.0,
+                nx=1000,
+                ny=1000,
+            )
+            # model_grid[model_grid<-100.0] = -100.0
+
             # Plot model.
-            ax = utils.plot_image(model_grid, x_grid, y_grid, 
-                                  colorbar_label=r'$\log \varphi$') 
+            ax = utils.plot_image(
+                model_grid, x_grid, y_grid, colorbar_label=r"$\log \varphi$"
+            )
             # ax.set_clim(vmin=-2.0, vmax=2.0)
             if savefigs:
-                plt.savefig('examples/plots/rastrigin_model_image.png',
-                            bbox_inches='tight')
-            
+                plt.savefig(
+                    "examples/plots/rastrigin_model_image.png", bbox_inches="tight"
+                )
+
             # Plot exponential of model.
-            ax = utils.plot_image(np.exp(model_grid), x_grid, y_grid,
-                                  colorbar_label=r'$\varphi$')
-            # ax.set_clim(vmin=0.0, vmax=6.0)        
+            ax = utils.plot_image(
+                np.exp(model_grid), x_grid, y_grid, colorbar_label=r"$\varphi$"
+            )
+            # ax.set_clim(vmin=0.0, vmax=6.0)
             if savefigs:
-                plt.savefig('examples/plots/rastrigin_modelexp_image.png',
-                            bbox_inches='tight')
+                plt.savefig(
+                    "examples/plots/rastrigin_modelexp_image.png", bbox_inches="tight"
+                )
 
-                plt.show(block=False)  
+                plt.show(block=False)
                 created_plots = True
 
         # Save out realisations for voilin plot.
-        evidence_inv_summary[i_realisation,0] = ev.evidence_inv
-        evidence_inv_summary[i_realisation,1] = ev.evidence_inv_var
-        evidence_inv_summary[i_realisation,2] = ev.evidence_inv_var_var
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
 
-    #===========================================================================
+    # ===========================================================================
     # End Timer.
     clock = time.process_time() - clock
-    hm.logs.info_log('Execution time = {}s'.format(clock))
+    hm.logs.info_log("Execution time = {}s".format(clock))
 
-    #===========================================================================
+    # ===========================================================================
     # Save out realisations of statistics for analysis.
     if n_realisations > 1:
-        np.savetxt("examples/data/rastrigin_evidence_inv" +
-                   "_realisations.dat",
-                   evidence_inv_summary)
+        np.savetxt(
+            "examples/data/rastrigin_evidence_inv" + "_realisations.dat",
+            evidence_inv_summary,
+        )
         evidence_inv_analytic_summary = np.zeros(1)
         evidence_inv_analytic_summary[0] = 1.0 / evidence_numerical_integration
-        np.savetxt("examples/data/rastrigin_evidence_inv" +
-                   "_analytic.dat",
-                   evidence_inv_analytic_summary)
+        np.savetxt(
+            "examples/data/rastrigin_evidence_inv" + "_analytic.dat",
+            evidence_inv_analytic_summary,
+        )
 
     if created_plots:
         input("\nPress Enter to continue...")
-    
-    return samples
 
+    return samples
 
-if __name__ == '__main__':
 
+if __name__ == "__main__":
     # Setup logging config.
     hm.logs.setup_logging()
 
     # Define parameters.
-    ndim = 2 
+    ndim = 2
     nchains = 200
     samples_per_chain = 5000
     nburn = 2000
     np.random.seed(20)
 
+    hm.logs.info_log("Rastrigin example")
 
-    hm.logs.info_log('Rastrigin example')
-
-    hm.logs.debug_log('-- Selected Parameters --')
-
-    hm.logs.debug_log('Dimensionality = {}'.format(ndim))
-    hm.logs.debug_log('Number of chains = {}'.format(nchains))
-    hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-    hm.logs.debug_log('Burn in = {}'.format(nburn))
-    
-    hm.logs.debug_log('-------------------------')
-    
-    # Run example.
-    samples = run_example(ndim, nchains, samples_per_chain, nburn, 
-                          plot_corner=True, plot_surface=True)
-    
+    hm.logs.debug_log("-- Selected Parameters --")
 
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
 
+    hm.logs.debug_log("-------------------------")
 
+    # Run example.
+    samples = run_example(
+        ndim, nchains, samples_per_chain, nburn, plot_corner=True, plot_surface=True
+    )
diff --git a/examples/rastrigin_splines.py b/examples/rastrigin_splines.py
new file mode 100644
index 00000000..c12462ca
--- /dev/null
+++ b/examples/rastrigin_splines.py
@@ -0,0 +1,411 @@
+import numpy as np
+import sys
+import emcee
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+
+sys.path.append("harmonic")
+import model_nf
+import flows
+
+
+def ln_prior_uniform(x, xmin=-6.0, xmax=6.0, ymin=-6.0, ymax=6.0):
+    """Compute log_e of uniform prior.
+
+    Args:
+
+        x: Position at which to evaluate prior.
+
+        xmin: Uniform prior minimum x edge (first dimension).
+
+        xmax: Uniform prior maximum x edge (first dimension).
+
+        ymin: Uniform prior minimum y edge (second dimension).
+
+        ymax: Uniform prior maximum y edge (second dimension).
+
+    Returns:
+
+        double: Value of prior at specified point.
+
+    """
+
+    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:
+        return 1.0 / ((xmax - xmin) * (ymax - ymin))
+    else:
+        return 0.0
+
+
+def ln_likelihood(x):
+    """Compute log_e of likelihood defined by Rastrigin function.
+
+    Args:
+
+        x: Position at which to evaluate likelihood.
+
+    Returns:
+
+        double: Value of Rastrigin at specified point.
+
+    """
+
+    ndim = x.size
+
+    f = 10.0 * ndim
+
+    for i_dim in range(ndim):
+        f += x[i_dim] ** 2 - 10.0 * np.cos(2.0 * np.pi * x[i_dim])
+
+    return -f
+
+
+def ln_posterior(x, ln_prior):
+    """Compute log_e of posterior.
+
+    Args:
+
+        x: Position at which to evaluate posterior.
+
+        ln_prior: Prior function.
+
+    Returns:
+
+        double: Posterior at specified point.
+
+    """
+
+    ln_L = ln_likelihood(x)
+
+    if not np.isfinite(ln_L):
+        return -np.inf
+    else:
+        return ln_prior(x) + ln_L
+
+
+def run_example(
+    ndim=2, nchains=100, samples_per_chain=1000, nburn=500, plot_corner=False
+):
+    """Run Rastrigin example.
+
+    Args:
+
+        ndim: Dimension.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+    """
+
+    if ndim != 2:
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
+
+    # ===========================================================================
+    # Configure Parameters.
+    # ===========================================================================
+    """
+    Configure machine learning parameters
+    """
+    savefigs = True
+    nhyper = 2
+    step = -2
+    hyper_parameters = [[10 ** (R)] for R in range(-nhyper + step, step)]
+    hm.logs.debug_log("Hyper-parameters = {}".format(hyper_parameters))
+
+    temperature = 0.8
+    epochs_num = 30
+
+    # Spline params
+    n_layers = 13
+    n_bins = 8
+    hidden_size = [64, 64]
+    spline_range = (-10.0, 10.0)
+    standardize = True
+
+    # Optimizer params
+    learning_rate = 0.001
+    momentum = 0.9
+
+    """
+    Set prior parameters.
+    """
+    use_uniform_prior = True
+    if use_uniform_prior:
+        xmin = -6.0
+        xmax = 6.0
+        ymin = -6.0
+        ymax = 6.0
+        hm.logs.debug_log(
+            "xmin, xmax, ymin, ymax = {}, {}, {}, {}".format(xmin, xmax, ymin, ymax)
+        )
+        ln_prior = partial(ln_prior_uniform, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Begin multiple realisations of estimator
+    # ===========================================================================
+    """
+    Set up and run multiple simulations
+    """
+    n_realisations = 1
+    evidence_inv_summary = np.zeros((n_realisations, 3))
+    for i_realisation in range(n_realisations):
+        if n_realisations > 1:
+            hm.logs.info_log(
+                "Realisation number = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+
+        # =======================================================================
+        # Run Emcee to recover posterior samples
+        # =======================================================================
+        hm.logs.info_log("Run sampling...")
+        """
+        Feed emcee the ln_posterior function, starting positions and recover 
+        chains.
+        """
+        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.5
+        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, args=[ln_prior])
+        rstate = np.random.get_state()
+        sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # =======================================================================
+        # Configure emcee chains for harmonic
+        # =======================================================================
+        hm.logs.info_log("Configure chains...")
+        """
+        Configure chains for the cross-validation stage.
+        """
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(chains, training_proportion=0.5)
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        """
+        Fit model.
+        """
+        model = model_nf.RQSplineModel(
+            ndim,
+            n_layers=n_layers,
+            n_bins=n_bins,
+            hidden_size=hidden_size,
+            spline_range=spline_range,
+            standardize=standardize,
+            learning_rate=learning_rate,
+            momentum=momentum,
+            temperature=temperature,
+        )
+        model.fit(chains_train.samples, epochs=epochs_num)
+
+        # =======================================================================
+        # Computing evidence using learnt model and emcee chains
+        # =======================================================================
+        hm.logs.info_log("Compute evidence...")
+        """
+        Instantiates the evidence class with a given model. Adds some chains and 
+        computes the log-space evidence (marginal likelihood).
+        """
+        ev = hm.Evidence(chains_test.nchains, model)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        if ndim == 2:
+            hm.logs.debug_log("Compute evidence by numerical integration...")
+            ln_posterior_func = partial(ln_posterior, ln_prior=ln_prior)
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=-6.0,
+                xmax=6.0,
+                ymin=-6.0,
+                ymax=6.0,
+                nx=1000,
+                ny=1000,
+            )
+            dx = x_grid[0, 1] - x_grid[0, 0]
+            dy = y_grid[1, 0] - y_grid[0, 0]
+            evidence_numerical_integration = np.sum(np.exp(ln_posterior_grid)) * dx * dy
+
+        # ======================================================================
+        # Display evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log(
+            "Evidence: numerical = {}, estimate = {}".format(
+                evidence_numerical_integration, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.debug_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_numerical_integration - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |numerical - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+
+        # ======================================================================
+        # Display inverse evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log(
+            "Inv Evidence: numerical = {}, estimate = {}".format(
+                1.0 / evidence_numerical_integration, ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) )/ var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |numerical - estimate| / estimate = {}".format(
+                np.abs(1.0 / evidence_numerical_integration - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # Create corner/triangle plot.
+        created_plots = False
+        if plot_corner and i_realisation == 0:
+            utils.plot_corner(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/spline_rastrigin_corner.png", bbox_inches="tight"
+                )
+
+            utils.plot_getdist(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/spline_rastrigin_getdist.png", bbox_inches="tight"
+                )
+
+            # =======================================================================
+            # Visualise distributions
+            # =======================================================================
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(chains_train.samples, samps_compressed)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/spline_rastrigin_corner_all.png",
+                    bbox_inches="tight",
+                )
+
+            plt.show(block=False)
+
+            created_plots = True
+
+        # Save out realisations for voilin plot.
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("Execution time = {}s".format(clock))
+
+    # ===========================================================================
+    # Save out realisations of statistics for analysis.
+    if n_realisations > 1:
+        np.savetxt(
+            "examples/data/spline_rastrigin_evidence_inv" + "_realisations.dat",
+            evidence_inv_summary,
+        )
+        evidence_inv_analytic_summary = np.zeros(1)
+        evidence_inv_analytic_summary[0] = 1.0 / evidence_numerical_integration
+        np.savetxt(
+            "examples/data/spline_rastrigin_evidence_inv" + "_analytic.dat",
+            evidence_inv_analytic_summary,
+        )
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+    return samples
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 2
+    nchains = 200
+    samples_per_chain = 5000
+    nburn = 2000
+    np.random.seed(20)
+
+    hm.logs.info_log("Rastrigin example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(ndim, nchains, samples_per_chain, nburn, plot_corner=True)
diff --git a/examples/rosenbrock.py b/examples/rosenbrock.py
index 8389c77a..36ccf256 100644
--- a/examples/rosenbrock.py
+++ b/examples/rosenbrock.py
@@ -1,11 +1,13 @@
 import numpy as np
 import sys
 import emcee
-import time 
+import time
 import matplotlib.pyplot as plt
 from functools import partial
+
 sys.path.append(".")
 import harmonic as hm
+
 sys.path.append("examples")
 import utils
 
@@ -30,14 +32,14 @@ def ln_prior_uniform(x, xmin=-10.0, xmax=10.0, ymin=-5.0, ymax=15.0):
         double: Value of prior at specified point.
 
     """
-        
-    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:        
-        return 1.0 / ( (xmax - xmin) * (ymax - ymin) )
+
+    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:
+        return 1.0 / ((xmax - xmin) * (ymax - ymin))
     else:
         return 0.0
-        
-        
-def ln_prior_gaussian(x, mu=1.0, sigma=5.):
+
+
+def ln_prior_gaussian(x, mu=1.0, sigma=5.0):
     """Compute log_e of Gaussian prior.
 
     Args:
@@ -53,9 +55,10 @@ def ln_prior_gaussian(x, mu=1.0, sigma=5.):
         double: Value of prior at specified point.
 
     """
-        
-    return - 0.5 * np.dot(x-mu, x-mu) / sigma**2 \
-           - 0.5 * x.size * np.log(2 * np.pi * sigma)
+
+    return -0.5 * np.dot(x - mu, x - mu) / sigma**2 - 0.5 * x.size * np.log(
+        2 * np.pi * sigma
+    )
 
 
 def ln_likelihood(x, a=1.0, b=100.0):
@@ -74,13 +77,13 @@ def ln_likelihood(x, a=1.0, b=100.0):
         double: Value of Rosenbrock at specified point.
 
     """
-    
+
     ndim = x.size
 
     f = 0.0
 
-    for i_dim in range(ndim-1):
-        f += b*(x[i_dim+1]-x[i_dim]**2)**2 + (a-x[i_dim])**2
+    for i_dim in range(ndim - 1):
+        f += b * (x[i_dim + 1] - x[i_dim] ** 2) ** 2 + (a - x[i_dim]) ** 2
 
     return -f
 
@@ -103,7 +106,7 @@ def ln_posterior(x, ln_prior, a=1.0, b=100.0):
         double: Posterior at specified point.
 
     """
-    
+
     ln_L = ln_likelihood(x, a=a, b=b)
 
     if not np.isfinite(ln_L):
@@ -112,8 +115,14 @@ def ln_posterior(x, ln_prior, a=1.0, b=100.0):
         return ln_prior(x) + ln_L
 
 
-def run_example(ndim=2, nchains=100, samples_per_chain=1000, 
-                nburn=500, plot_corner=False, plot_surface=False):
+def run_example(
+    ndim=2,
+    nchains=100,
+    samples_per_chain=1000,
+    nburn=500,
+    plot_corner=False,
+    plot_surface=False,
+):
     """Run Rosenbrock example.
 
     Args:
@@ -133,12 +142,11 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     """
 
     if ndim != 2:
-        raise ValueError("Only ndim=2 is supported (ndim={} specified)"
-            .format(ndim))
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
 
-    #===========================================================================
+    # ===========================================================================
     # Configure Parameters.
-    #===========================================================================
+    # ===========================================================================
     """
     Configure machine learning parameters
     """
@@ -147,312 +155,357 @@ def run_example(ndim=2, nchains=100, samples_per_chain=1000,
     nhyper = 2
     step = -2
     domain = []
-    hyper_parameters = [[10**(R)] for R in range(-nhyper+step,step)]
-    hm.logs.debug_log('Hyper-parameters = {}'.format(hyper_parameters))
+    hyper_parameters = [[10 ** (R)] for R in range(-nhyper + step, step)]
+    hm.logs.debug_log("Hyper-parameters = {}".format(hyper_parameters))
     a = 1.0
     b = 100.0
     """
     Set prior parameters.
     """
     use_uniform_prior = True
-    if use_uniform_prior:        
+    if use_uniform_prior:
         xmin = -10.0
         xmax = 10.0
         ymin = -5.0
         ymax = 15.0
-        hm.logs.debug_log('xmin, xmax, ymin, ymax = {}, {}, {}, {}'
-            .format(xmin, xmax, ymin, ymax))   
-        ln_prior = partial(ln_prior_uniform, xmin=xmin, xmax=xmax, 
-                           ymin=ymin, ymax=ymax)     
-    else: # Use Gaussian prior
+        hm.logs.debug_log(
+            "xmin, xmax, ymin, ymax = {}, {}, {}, {}".format(xmin, xmax, ymin, ymax)
+        )
+        ln_prior = partial(ln_prior_uniform, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
+    else:  # Use Gaussian prior
         mu = 1.0
         sigma = 50.0
-        hm.logs.debug_log('a, b, mu, sigma = {}, {}, {}, {}'
-            .format(a, b, mu, sigma))   
+        hm.logs.debug_log("a, b, mu, sigma = {}, {}, {}, {}".format(a, b, mu, sigma))
         ln_prior = partial(ln_prior_gaussian, mu=mu, sigma=sigma)
 
     # Start timer.
     clock = time.process_time()
 
-    #===========================================================================
+    # ===========================================================================
     # Begin multiple realisations of estimator
-    #===========================================================================
+    # ===========================================================================
     """
     Set up and run multiple simulations
     """
     n_realisations = 100
-    evidence_inv_summary = np.zeros((n_realisations,3))
+    evidence_inv_summary = np.zeros((n_realisations, 3))
     for i_realisation in range(n_realisations):
-
         if n_realisations > 1:
-            hm.logs.info_log('Realisation number = {}/{}'
-                .format(i_realisation+1, n_realisations))
-        
-        #=======================================================================
-        # Run Emcee to recover posterior samples 
-        #=======================================================================
-        hm.logs.info_log('Run sampling...')
+            hm.logs.info_log(
+                "Realisation number = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+
+        # =======================================================================
+        # Run Emcee to recover posterior samples
+        # =======================================================================
+        hm.logs.info_log("Run sampling...")
         """
         Feed emcee the ln_posterior function, starting positions and recover 
         chains.
         """
-        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.1    
-        sampler = emcee.EnsembleSampler(nchains, ndim, ln_posterior, 
-                                        args=[ln_prior, a, b])
+        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.1
+        sampler = emcee.EnsembleSampler(
+            nchains, ndim, ln_posterior, args=[ln_prior, a, b]
+        )
         rstate = np.random.get_state()
         sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
-        samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])
-        lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
 
-        #=======================================================================
+        # =======================================================================
         # Configure emcee chains for harmonic
-        #=======================================================================
-        hm.logs.info_log('Configure chains...')
+        # =======================================================================
+        hm.logs.info_log("Configure chains...")
         """
         Configure chains for the cross-validation stage.
         """
         chains = hm.Chains(ndim)
         chains.add_chains_3d(samples, lnprob)
-        chains_train, chains_test = hm.utils.split_data(chains, 
-                                                        training_proportion=0.5)
-        
-        #=======================================================================
+        chains_train, chains_test = hm.utils.split_data(chains, training_proportion=0.5)
+
+        # =======================================================================
         # Perform cross-validation
-        #=======================================================================
-        hm.logs.info_log('Perform cross-validation...')
+        # =======================================================================
+        hm.logs.info_log("Perform cross-validation...")
         """
         There are several different machine learning models. Cross-validation
         allows the software to select the optimal model and the optimal model 
         hyper-parameters for a given situation.
         """
-        validation_variances = \
-            hm.utils.cross_validation(chains_train, \
-                                    domain, \
-                                    hyper_parameters, \
-                                    nfold=nfold, \
-                                    modelClass=hm.model.KernelDensityEstimate, \
-                                    seed=0)
-        hm.logs.debug_log('validation_variances = {}'
-            .format(validation_variances))
+        validation_variances = hm.utils.cross_validation(
+            chains_train,
+            domain,
+            hyper_parameters,
+            nfold=nfold,
+            modelClass=hm.model.KernelDensityEstimate,
+            seed=0,
+        )
+        hm.logs.debug_log("validation_variances = {}".format(validation_variances))
         best_hyper_param_ind = np.argmin(validation_variances)
         best_hyper_param = hyper_parameters[best_hyper_param_ind]
-        hm.logs.debug_log('Best hyper-parameter = {}'
-            .format(best_hyper_param))
-        
-        #=======================================================================
+        hm.logs.debug_log("Best hyper-parameter = {}".format(best_hyper_param))
+
+        # =======================================================================
         # Fit optimal model hyper-parameters
-        #=======================================================================
-        hm.logs.info_log('Fit model...')
+        # =======================================================================
+        hm.logs.info_log("Fit model...")
         """
         Fit model by selecing the configuration of hyper-parameters which 
         minimises the validation variances.
         """
-        model = hm.model.KernelDensityEstimate(ndim, 
-                                            domain, 
-                                            hyper_parameters=best_hyper_param)
+        model = hm.model.KernelDensityEstimate(
+            ndim, domain, hyper_parameters=best_hyper_param
+        )
         fit_success = model.fit(chains_train.samples, chains_train.ln_posterior)
-        hm.logs.debug_log('Fit success = {}'.format(fit_success))    
+        hm.logs.debug_log("Fit success = {}".format(fit_success))
 
-        #=======================================================================
+        # =======================================================================
         # Computing evidence using learnt model and emcee chains
-        #=======================================================================
-        hm.logs.info_log('Compute evidence...')
+        # =======================================================================
+        hm.logs.info_log("Compute evidence...")
         """
         Instantiates the evidence class with a given model. Adds some chains and 
         computes the log-space evidence (marginal likelihood).
         """
-        ev = hm.Evidence(chains_test.nchains, model)    
+        ev = hm.Evidence(chains_test.nchains, model)
         ev.add_chains(chains_test)
         ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
-        
+
         # Compute analytic evidence.
         if ndim == 2:
-            hm.logs.debug_log('Compute evidence by numerical integration...')
-            ln_posterior_func = partial(ln_posterior, ln_prior=ln_prior, \
-                                        a=a, b=b)
-            ln_posterior_grid, x_grid, y_grid = \
-                utils.eval_func_on_grid(ln_posterior_func, 
-                                        xmin=-10.0, xmax=10.0, 
-                                        ymin=-5.0, ymax=15.0, 
-                                        nx=1000, ny=1000)
-            dx = x_grid[0,1] - x_grid[0,0]
-            dy = y_grid[1,0] - y_grid[0,0]
-            evidence_numerical_integration = \
-                                     np.sum(np.exp(ln_posterior_grid)) * dx * dy  
-        
+            hm.logs.debug_log("Compute evidence by numerical integration...")
+            ln_posterior_func = partial(ln_posterior, ln_prior=ln_prior, a=a, b=b)
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=-10.0,
+                xmax=10.0,
+                ymin=-5.0,
+                ymax=15.0,
+                nx=1000,
+                ny=1000,
+            )
+            dx = x_grid[0, 1] - x_grid[0, 0]
+            dy = y_grid[1, 0] - y_grid[0, 0]
+            evidence_numerical_integration = np.sum(np.exp(ln_posterior_grid)) * dx * dy
+
         # ======================================================================
         # Display evidence computation results.
         # ======================================================================
-        hm.logs.debug_log('Evidence: numerical = {}, estimate = {}'
-            .format(evidence_numerical_integration, np.exp(ln_evidence)))
-        hm.logs.debug_log('Evidence: std = {}, std / estimate = {}'
-            .format(np.exp(ln_evidence_std), \
-                    np.exp(ln_evidence_std - ln_evidence)))
-        diff = np.log(np.abs(evidence_numerical_integration - \
-                                                    np.exp(ln_evidence)))
-        hm.logs.info_log('Evidence: |numerical - estimate| / estimate = {}'
-            .format(np.exp(diff - ln_evidence)))
+        hm.logs.debug_log(
+            "Evidence: numerical = {}, estimate = {}".format(
+                evidence_numerical_integration, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.debug_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_numerical_integration - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |numerical - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
 
         # ======================================================================
         # Display inverse evidence computation results.
         # ======================================================================
-        hm.logs.debug_log('Inv Evidence: numerical = {}, estimate = {}'
-            .format(1.0/evidence_numerical_integration, ev.evidence_inv))
-        hm.logs.debug_log('Inv Evidence: std = {}, std / estimate = {}'
-            .format(np.sqrt(ev.evidence_inv_var), \
-                    np.sqrt(ev.evidence_inv_var)/ev.evidence_inv))
-        hm.logs.debug_log('Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}'
-            .format(ev.kurtosis, np.sqrt(2.0/(ev.n_eff-1))))    
-        hm.logs.debug_log('Inv Evidence: sqrt( var(var) )/ var = {}'
-            .format(np.sqrt(ev.evidence_inv_var_var)/ev.evidence_inv_var))    
-        hm.logs.info_log('Inv Evidence: |numerical - estimate| / estimate = {}'
-            .format(np.abs(1.0 / evidence_numerical_integration \
-                    - ev.evidence_inv) / ev.evidence_inv))
-
-        #===========================================================================
+        hm.logs.debug_log(
+            "Inv Evidence: numerical = {}, estimate = {}".format(
+                1.0 / evidence_numerical_integration, ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) )/ var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |numerical - estimate| / estimate = {}".format(
+                np.abs(1.0 / evidence_numerical_integration - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
         # Display more technical details
-        #===========================================================================
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('Technical Details')
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('lnargmax = {}, lnargmin = {}'
-            .format(ev.lnargmax, ev.lnargmin))
-        hm.logs.debug_log('lnprobmax = {}, lnprobmin = {}'
-            .format(ev.lnprobmax, ev.lnprobmin))
-        hm.logs.debug_log('lnpredictmax = {}, lnpredictmin = {}'
-            .format(ev.lnpredictmax, ev.lnpredictmin))
-        hm.logs.debug_log('---------------------------------')
-        hm.logs.debug_log('shift = {}, shift setting = {}'
-            .format(ev.shift_value, ev.shift))
-        hm.logs.debug_log('running sum total = {}'
-            .format(sum(ev.running_sum)))
-        hm.logs.debug_log('running sum = \n{}'
-            .format(ev.running_sum))
-        hm.logs.debug_log('nsamples per chain = \n{}'
-            .format(ev.nsamples_per_chain))
-        hm.logs.debug_log('nsamples eff per chain = \n{}'
-            .format(ev.nsamples_eff_per_chain))
-        hm.logs.debug_log('===============================')
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
 
         # Create corner/triangle plot.
         created_plots = False
         if plot_corner and i_realisation == 0:
-            
             utils.plot_corner(samples.reshape((-1, ndim)))
             if savefigs:
-                plt.savefig('examples/plots/rosenbrock_corner.png',
-                            bbox_inches='tight')
-            
+                plt.savefig("examples/plots/rosenbrock_corner.png", bbox_inches="tight")
+
             utils.plot_getdist(samples.reshape((-1, ndim)))
             if savefigs:
-                plt.savefig('examples/plots/rosenbrock_getdist.png',
-                            bbox_inches='tight')
-            
-            plt.show(block=False)  
+                plt.savefig(
+                    "examples/plots/rosenbrock_getdist.png", bbox_inches="tight"
+                )
+
+            plt.show(block=False)
             created_plots = True
-                
-        # In 2D case, plot surface/image and samples.    
+
+        # In 2D case, plot surface/image and samples.
         if plot_surface and ndim == 2 and i_realisation == 0:
-            
             # Plot ln_posterior surface.
-            # ln_posterior_grid[ln_posterior_grid<-100.0] = -100.0 
+            # ln_posterior_grid[ln_posterior_grid<-100.0] = -100.0
             i_chain = 0
-            ax = utils.plot_surface(ln_posterior_grid, x_grid, y_grid, 
-                                    samples[i_chain,:,:].reshape((-1, ndim)), 
-                                    lnprob[i_chain,:].reshape((-1, 1)))
-            # ax.set_zlim(-100.0, 0.0)                
-            ax.set_zlabel(r'$\log \mathcal{L}$')        
+            ax = utils.plot_surface(
+                ln_posterior_grid,
+                x_grid,
+                y_grid,
+                samples[i_chain, :, :].reshape((-1, ndim)),
+                lnprob[i_chain, :].reshape((-1, 1)),
+            )
+            # ax.set_zlim(-100.0, 0.0)
+            ax.set_zlabel(r"$\log \mathcal{L}$")
             if savefigs:
-                plt.savefig('examples/plots/rosenbrock_lnposterior_surface.png',
-                            bbox_inches='tight')
-            
+                plt.savefig(
+                    "examples/plots/rosenbrock_lnposterior_surface.png",
+                    bbox_inches="tight",
+                )
+
             # Plot posterior image.
-            ax = utils.plot_image(np.exp(ln_posterior_grid), x_grid, y_grid, 
-                                  samples.reshape((-1,ndim)),
-                                  colorbar_label=r'$\mathcal{L}$')
+            ax = utils.plot_image(
+                np.exp(ln_posterior_grid),
+                x_grid,
+                y_grid,
+                samples.reshape((-1, ndim)),
+                colorbar_label=r"$\mathcal{L}$",
+            )
             # ax.set_clim(vmin=0.0, vmax=0.003)
             if savefigs:
-                plt.savefig('examples/plots/rosenbrock_posterior_image.png',
-                            bbox_inches='tight')
+                plt.savefig(
+                    "examples/plots/rosenbrock_posterior_image.png", bbox_inches="tight"
+                )
 
             # Evaluate model on grid.
-            model_grid, x_grid, y_grid = \
-                utils.eval_func_on_grid(model.predict, 
-                                        xmin=-10.0, xmax=10.0, 
-                                        ymin=-5.0, ymax=15.0, 
-                                        nx=1000, ny=1000)
-            # model_grid[model_grid<-100.0] = -100.0 
-            
+            model_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                model.predict,
+                xmin=-10.0,
+                xmax=10.0,
+                ymin=-5.0,
+                ymax=15.0,
+                nx=1000,
+                ny=1000,
+            )
+            # model_grid[model_grid<-100.0] = -100.0
+
             # Plot model.
-            ax = utils.plot_image(model_grid, x_grid, y_grid, 
-                                  colorbar_label=r'$\log \varphi$') 
+            ax = utils.plot_image(
+                model_grid, x_grid, y_grid, colorbar_label=r"$\log \varphi$"
+            )
             # ax.set_clim(vmin=-2.0, vmax=2.0)
             if savefigs:
-                plt.savefig('examples/plots/rosenbrock_model_image.png',
-                            bbox_inches='tight')
-            
+                plt.savefig(
+                    "examples/plots/rosenbrock_model_image.png", bbox_inches="tight"
+                )
+
             # Plot exponential of model.
-            ax = utils.plot_image(np.exp(model_grid), x_grid, y_grid,
-                                  colorbar_label=r'$\varphi$')
-            # ax.set_clim(vmin=0.0, vmax=10.0)        
+            ax = utils.plot_image(
+                np.exp(model_grid), x_grid, y_grid, colorbar_label=r"$\varphi$"
+            )
+            # ax.set_clim(vmin=0.0, vmax=10.0)
             if savefigs:
-                plt.savefig('examples/plots/rosenbrock_modelexp_image.png',
-                            bbox_inches='tight')
+                plt.savefig(
+                    "examples/plots/rosenbrock_modelexp_image.png", bbox_inches="tight"
+                )
 
-            plt.show(block=False)  
+            plt.show(block=False)
             created_plots = True
 
         # Save out realisations for voilin plot.
-        evidence_inv_summary[i_realisation,0] = ev.evidence_inv
-        evidence_inv_summary[i_realisation,1] = ev.evidence_inv_var
-        evidence_inv_summary[i_realisation,2] = ev.evidence_inv_var_var
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
 
-    #===========================================================================
+    # ===========================================================================
     # End Timer.
     clock = time.process_time() - clock
-    hm.logs.info_log('Execution time = {}s'.format(clock))
+    hm.logs.info_log("Execution time = {}s".format(clock))
 
-    #===========================================================================
+    # ===========================================================================
     # Save out realisations of statistics for analysis.
     if n_realisations > 1:
-        np.savetxt("examples/data/rosenbrock_evidence_inv" +
-                   "_realisations.dat",
-                   evidence_inv_summary)
+        np.savetxt(
+            "examples/data/rosenbrock_evidence_inv" + "_realisations.dat",
+            evidence_inv_summary,
+        )
         evidence_inv_analytic_summary = np.zeros(1)
         evidence_inv_analytic_summary[0] = 1.0 / evidence_numerical_integration
-        np.savetxt("examples/data/rosenbrock_evidence_inv" +
-                   "_analytic.dat",
-                   evidence_inv_analytic_summary)
+        np.savetxt(
+            "examples/data/rosenbrock_evidence_inv" + "_analytic.dat",
+            evidence_inv_analytic_summary,
+        )
 
     if created_plots:
         input("\nPress Enter to continue...")
-    
-    return samples
 
+    return samples
 
-if __name__ == '__main__':
 
+if __name__ == "__main__":
     # Setup logging config.
     hm.logs.setup_logging()
 
     # Define parameters.
-    ndim = 2 
+    ndim = 2
     nchains = 200
     samples_per_chain = 5000
     nburn = 2000
     np.random.seed(20)
 
-    hm.logs.info_log('Rosenbrock example')
+    hm.logs.info_log("Rosenbrock example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
 
-    hm.logs.debug_log('-- Selected Parameters --')
+    hm.logs.debug_log("-------------------------")
 
-    hm.logs.debug_log('Dimensionality = {}'.format(ndim))
-    hm.logs.debug_log('Number of chains = {}'.format(nchains))
-    hm.logs.debug_log('Samples per chain = {}'.format(samples_per_chain))
-    hm.logs.debug_log('Burn in = {}'.format(nburn))
-    
-    hm.logs.debug_log('-------------------------')
-    
     # Run example.
-    samples = run_example(ndim, nchains, samples_per_chain, nburn, 
-                          plot_corner=True, plot_surface=True)
+    samples = run_example(
+        ndim, nchains, samples_per_chain, nburn, plot_corner=True, plot_surface=True
+    )
diff --git a/examples/rosenbrock_nvp.py b/examples/rosenbrock_nvp.py
new file mode 100644
index 00000000..71749c8a
--- /dev/null
+++ b/examples/rosenbrock_nvp.py
@@ -0,0 +1,431 @@
+import numpy as np
+import sys
+import emcee
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+from harmonic import model_nf
+
+
+def ln_prior_uniform(x, xmin=-10.0, xmax=10.0, ymin=-5.0, ymax=15.0):
+    """Compute log_e of uniform prior.
+
+    Args:
+
+        x: Position at which to evaluate prior.
+
+        xmin: Uniform prior minimum x edge (first dimension).
+
+        xmax: Uniform prior maximum x edge (first dimension).
+
+        ymin: Uniform prior minimum y edge (second dimension).
+
+        ymax: Uniform prior maximum y edge (second dimension).
+
+    Returns:
+
+        double: Value of prior at specified point.
+
+    """
+
+    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:
+        return 1.0 / ((xmax - xmin) * (ymax - ymin))
+    else:
+        return 0.0
+
+
+def ln_prior_gaussian(x, mu=1.0, sigma=5.0):
+    """Compute log_e of Gaussian prior.
+
+    Args:
+
+        x: Position at which to evaluate prior.
+
+        mu: Mean (centre) of the prior.
+
+        sigma: Standard deviation of prior.
+
+    Returns:
+
+        double: Value of prior at specified point.
+
+    """
+
+    return -0.5 * np.dot(x - mu, x - mu) / sigma**2 - 0.5 * x.size * np.log(
+        2 * np.pi * sigma
+    )
+
+
+def ln_likelihood(x, a=1.0, b=100.0):
+    """Compute log_e of likelihood defined by Rosenbrock function.
+
+    Args:
+
+        x: Position at which to evaluate likelihood.
+
+        a: First parameter of Rosenbrock function.
+
+        b: First parameter of Rosenbrock function.
+
+    Returns:
+
+        double: Value of Rosenbrock at specified point.
+
+    """
+
+    ndim = x.size
+
+    f = 0.0
+
+    for i_dim in range(ndim - 1):
+        f += b * (x[i_dim + 1] - x[i_dim] ** 2) ** 2 + (a - x[i_dim]) ** 2
+
+    return -f
+
+
+def ln_posterior(x, ln_prior, a=1.0, b=100.0):
+    """Compute log_e of posterior.
+
+    Args:
+
+        x: Position at which to evaluate posterior.
+
+        a: First parameter of Rosenbrock function.
+
+        b: First parameter of Rosenbrock function.
+
+        ln_prior: Prior function.
+
+    Returns:
+
+        double: Posterior at specified point.
+
+    """
+
+    ln_L = ln_likelihood(x, a=a, b=b)
+
+    if not np.isfinite(ln_L):
+        return -np.inf
+    else:
+        return ln_prior(x) + ln_L
+
+
+def run_example(
+    ndim=2, nchains=100, samples_per_chain=1000, nburn=500, plot_corner=False
+):
+    """Run Rosenbrock example.
+
+    Args:
+
+        ndim: Dimension.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+    """
+
+    if ndim != 2:
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
+
+    # ===========================================================================
+    # Configure Parameters.
+    # ===========================================================================
+    """
+    Configure machine learning parameters
+    """
+    savefigs = True
+    a = 1.0
+    b = 100.0
+    epochs_num = 8
+    temperature = 0.9
+    training_proportion = 0.5
+    standardize = False
+    """
+    Set prior parameters.
+    """
+    use_uniform_prior = True
+    if use_uniform_prior:
+        xmin = -10.0
+        xmax = 10.0
+        ymin = -5.0
+        ymax = 15.0
+        hm.logs.debug_log(
+            "xmin, xmax, ymin, ymax = {}, {}, {}, {}".format(xmin, xmax, ymin, ymax)
+        )
+        ln_prior = partial(ln_prior_uniform, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
+    else:  # Use Gaussian prior
+        mu = 1.0
+        sigma = 50.0
+        hm.logs.debug_log("a, b, mu, sigma = {}, {}, {}, {}".format(a, b, mu, sigma))
+        ln_prior = partial(ln_prior_gaussian, mu=mu, sigma=sigma)
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Begin multiple realisations of estimator
+    # ===========================================================================
+    """
+    Set up and run multiple simulations
+    """
+    n_realisations = 1
+    evidence_inv_summary = np.zeros((n_realisations, 3))
+    for i_realisation in range(n_realisations):
+        if n_realisations > 1:
+            hm.logs.info_log(
+                "Realisation number = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+
+        # =======================================================================
+        # Run Emcee to recover posterior samples
+        # =======================================================================
+        hm.logs.info_log("Run sampling...")
+        """
+        Feed emcee the ln_posterior function, starting positions and recover 
+        chains.
+        """
+        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.1
+        sampler = emcee.EnsembleSampler(
+            nchains, ndim, ln_posterior, args=[ln_prior, a, b]
+        )
+        rstate = np.random.get_state()
+        sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # =======================================================================
+        # Configure emcee chains for harmonic
+        # =======================================================================
+        hm.logs.info_log("Configure chains...")
+        """
+        Configure chains for the cross-validation stage.
+        """
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        model = model_nf.RealNVPModel(
+            ndim, standardize=standardize, temperature=temperature
+        )
+        model.fit(chains_train.samples, epochs=epochs_num)
+
+        # =======================================================================
+        # Computing evidence using learnt model and emcee chains
+        # =======================================================================
+        hm.logs.info_log("Compute evidence...")
+        """
+        Instantiates the evidence class with a given model. Adds some chains and 
+        computes the log-space evidence (marginal likelihood).
+        """
+        ev = hm.Evidence(chains_test.nchains, model)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        if ndim == 2:
+            hm.logs.debug_log("Compute evidence by numerical integration...")
+            ln_posterior_func = partial(ln_posterior, ln_prior=ln_prior, a=a, b=b)
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=-10.0,
+                xmax=10.0,
+                ymin=-5.0,
+                ymax=15.0,
+                nx=1000,
+                ny=1000,
+            )
+            dx = x_grid[0, 1] - x_grid[0, 0]
+            dy = y_grid[1, 0] - y_grid[0, 0]
+            evidence_numerical_integration = np.sum(np.exp(ln_posterior_grid)) * dx * dy
+
+        # ======================================================================
+        # Display evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log(
+            "Evidence: numerical = {}, estimate = {}".format(
+                evidence_numerical_integration, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.debug_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_numerical_integration - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |numerical - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+
+        # ======================================================================
+        # Display inverse evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log(
+            "Inv Evidence: numerical = {}, estimate = {}".format(
+                1.0 / evidence_numerical_integration, ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) )/ var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |numerical - estimate| / estimate = {}".format(
+                np.abs(1.0 / evidence_numerical_integration - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # Create corner/triangle plot.
+        created_plots = False
+        if plot_corner and i_realisation == 0:
+            utils.plot_corner(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_rosenbrock_corner.png", bbox_inches="tight"
+                )
+
+            utils.plot_getdist(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_rosenbrock_getdist.png", bbox_inches="tight"
+                )
+
+            plt.show(block=False)
+
+            # =======================================================================
+            # Visualise distributions
+            # =======================================================================
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(
+                chains_train.samples, samps_compressed, legend_fontsize=12.5
+            )
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/nvp_rosenbrock_corner_all_T"
+                    + str(temperature)
+                    + ".png",
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+            created_plots = True
+
+            created_plots = True
+
+        # Save out realisations for voilin plot.
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("Execution time = {}s".format(clock))
+
+    # ===========================================================================
+    # Save out realisations of statistics for analysis.
+    if n_realisations > 1:
+        np.savetxt(
+            "examples/data/nvp_rosenbrock_evidence_inv_T"
+            + str(temperature)
+            + "_realisations.dat",
+            evidence_inv_summary,
+        )
+        evidence_inv_analytic_summary = np.zeros(1)
+        evidence_inv_analytic_summary[0] = 1.0 / evidence_numerical_integration
+        np.savetxt(
+            "examples/data/nvp_rosenbrock_evidence_inv" + "_analytic.dat",
+            evidence_inv_analytic_summary,
+        )
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+    return samples
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 2
+    nchains = 200
+    samples_per_chain = 5000
+    nburn = 2000
+    np.random.seed(20)
+
+    hm.logs.info_log("Rosenbrock example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(ndim, nchains, samples_per_chain, nburn, plot_corner=True)
diff --git a/examples/rosenbrock_splines.py b/examples/rosenbrock_splines.py
new file mode 100644
index 00000000..2ca873f8
--- /dev/null
+++ b/examples/rosenbrock_splines.py
@@ -0,0 +1,421 @@
+import numpy as np
+import sys
+import emcee
+import time
+import matplotlib.pyplot as plt
+from functools import partial
+
+sys.path.append(".")
+import harmonic as hm
+
+sys.path.append("examples")
+import utils
+from harmonic import model_nf
+
+
+def ln_prior_uniform(x, xmin=-10.0, xmax=10.0, ymin=-5.0, ymax=15.0):
+    """Compute log_e of uniform prior.
+
+    Args:
+
+        x: Position at which to evaluate prior.
+
+        xmin: Uniform prior minimum x edge (first dimension).
+
+        xmax: Uniform prior maximum x edge (first dimension).
+
+        ymin: Uniform prior minimum y edge (second dimension).
+
+        ymax: Uniform prior maximum y edge (second dimension).
+
+    Returns:
+
+        double: Value of prior at specified point.
+
+    """
+
+    if x[0] >= xmin and x[0] <= xmax and x[1] >= ymin and x[1] <= ymax:
+        return 1.0 / ((xmax - xmin) * (ymax - ymin))
+    else:
+        return 0.0
+
+
+def ln_prior_gaussian(x, mu=1.0, sigma=5.0):
+    """Compute log_e of Gaussian prior.
+
+    Args:
+
+        x: Position at which to evaluate prior.
+
+        mu: Mean (centre) of the prior.
+
+        sigma: Standard deviation of prior.
+
+    Returns:
+
+        double: Value of prior at specified point.
+
+    """
+
+    return -0.5 * np.dot(x - mu, x - mu) / sigma**2 - 0.5 * x.size * np.log(
+        2 * np.pi * sigma
+    )
+
+
+def ln_likelihood(x, a=1.0, b=100.0):
+    """Compute log_e of likelihood defined by Rosenbrock function.
+
+    Args:
+
+        x: Position at which to evaluate likelihood.
+
+        a: First parameter of Rosenbrock function.
+
+        b: First parameter of Rosenbrock function.
+
+    Returns:
+
+        double: Value of Rosenbrock at specified point.
+
+    """
+
+    ndim = x.size
+
+    f = 0.0
+
+    for i_dim in range(ndim - 1):
+        f += b * (x[i_dim + 1] - x[i_dim] ** 2) ** 2 + (a - x[i_dim]) ** 2
+
+    return -f
+
+
+def ln_posterior(x, ln_prior, a=1.0, b=100.0):
+    """Compute log_e of posterior.
+
+    Args:
+
+        x: Position at which to evaluate posterior.
+
+        a: First parameter of Rosenbrock function.
+
+        b: First parameter of Rosenbrock function.
+
+        ln_prior: Prior function.
+
+    Returns:
+
+        double: Posterior at specified point.
+
+    """
+
+    ln_L = ln_likelihood(x, a=a, b=b)
+
+    if not np.isfinite(ln_L):
+        return -np.inf
+    else:
+        return ln_prior(x) + ln_L
+
+
+def run_example(
+    ndim=2, nchains=100, samples_per_chain=1000, nburn=500, plot_corner=False
+):
+    """Run Rosenbrock example.
+
+    Args:
+
+        ndim: Dimension.
+
+        nchains: Number of chains.
+
+        samples_per_chain: Number of samples per chain.
+
+        nburn: Number of burn in samples for each chain.
+
+        plot_corner: Plot marginalised distributions if true.
+    """
+
+    if ndim != 2:
+        raise ValueError("Only ndim=2 is supported (ndim={} specified)".format(ndim))
+
+    # ===========================================================================
+    # Configure Parameters.
+    # ===========================================================================
+    """
+    Configure machine learning parameters
+    """
+    savefigs = True
+    a = 1.0
+    b = 100.0
+    epochs_num = 5
+    temperature = 0.8
+    training_proportion = 0.5
+    standardize = True
+    """
+    Set prior parameters.
+    """
+    use_uniform_prior = True
+    if use_uniform_prior:
+        xmin = -10.0
+        xmax = 10.0
+        ymin = -5.0
+        ymax = 15.0
+        hm.logs.debug_log(
+            "xmin, xmax, ymin, ymax = {}, {}, {}, {}".format(xmin, xmax, ymin, ymax)
+        )
+        ln_prior = partial(ln_prior_uniform, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
+    else:  # Use Gaussian prior
+        mu = 1.0
+        sigma = 50.0
+        hm.logs.debug_log("a, b, mu, sigma = {}, {}, {}, {}".format(a, b, mu, sigma))
+        ln_prior = partial(ln_prior_gaussian, mu=mu, sigma=sigma)
+
+    # Start timer.
+    clock = time.process_time()
+
+    # ===========================================================================
+    # Begin multiple realisations of estimator
+    # ===========================================================================
+    """
+    Set up and run multiple simulations
+    """
+    n_realisations = 1
+    evidence_inv_summary = np.zeros((n_realisations, 3))
+    for i_realisation in range(n_realisations):
+        if n_realisations > 1:
+            hm.logs.info_log(
+                "Realisation number = {}/{}".format(i_realisation + 1, n_realisations)
+            )
+
+        # =======================================================================
+        # Run Emcee to recover posterior samples
+        # =======================================================================
+        hm.logs.info_log("Run sampling...")
+        """
+        Feed emcee the ln_posterior function, starting positions and recover 
+        chains.
+        """
+        pos = np.random.rand(ndim * nchains).reshape((nchains, ndim)) * 0.1
+        sampler = emcee.EnsembleSampler(
+            nchains, ndim, ln_posterior, args=[ln_prior, a, b]
+        )
+        rstate = np.random.get_state()
+        sampler.run_mcmc(pos, samples_per_chain, rstate0=rstate)
+        samples = np.ascontiguousarray(sampler.chain[:, nburn:, :])
+        lnprob = np.ascontiguousarray(sampler.lnprobability[:, nburn:])
+
+        # =======================================================================
+        # Configure emcee chains for harmonic
+        # =======================================================================
+        hm.logs.info_log("Configure chains...")
+        """
+        Configure chains for the cross-validation stage.
+        """
+        chains = hm.Chains(ndim)
+        chains.add_chains_3d(samples, lnprob)
+        chains_train, chains_test = hm.utils.split_data(
+            chains, training_proportion=training_proportion
+        )
+
+        # =======================================================================
+        # Fit model
+        # =======================================================================
+        hm.logs.info_log("Fit model for {} epochs...".format(epochs_num))
+        model = model_nf.RQSplineModel(
+            ndim, standardize=standardize, temperature=temperature
+        )
+        model.fit(chains_train.samples, epochs=epochs_num)
+
+        # =======================================================================
+        # Computing evidence using learnt model and emcee chains
+        # =======================================================================
+        hm.logs.info_log("Compute evidence...")
+        """
+        Instantiates the evidence class with a given model. Adds some chains and 
+        computes the log-space evidence (marginal likelihood).
+        """
+        ev = hm.Evidence(chains_test.nchains, model)
+        ev.add_chains(chains_test)
+        ln_evidence, ln_evidence_std = ev.compute_ln_evidence()
+
+        # Compute analytic evidence.
+        if ndim == 2:
+            hm.logs.debug_log("Compute evidence by numerical integration...")
+            ln_posterior_func = partial(ln_posterior, ln_prior=ln_prior, a=a, b=b)
+            ln_posterior_grid, x_grid, y_grid = utils.eval_func_on_grid(
+                ln_posterior_func,
+                xmin=-10.0,
+                xmax=10.0,
+                ymin=-5.0,
+                ymax=15.0,
+                nx=1000,
+                ny=1000,
+            )
+            dx = x_grid[0, 1] - x_grid[0, 0]
+            dy = y_grid[1, 0] - y_grid[0, 0]
+            evidence_numerical_integration = np.sum(np.exp(ln_posterior_grid)) * dx * dy
+
+        # ======================================================================
+        # Display evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log(
+            "Evidence: numerical = {}, estimate = {}".format(
+                evidence_numerical_integration, np.exp(ln_evidence)
+            )
+        )
+        hm.logs.debug_log(
+            "Evidence: std = {}, std / estimate = {}".format(
+                np.exp(ln_evidence_std), np.exp(ln_evidence_std - ln_evidence)
+            )
+        )
+        diff = np.log(np.abs(evidence_numerical_integration - np.exp(ln_evidence)))
+        hm.logs.info_log(
+            "Evidence: |numerical - estimate| / estimate = {}".format(
+                np.exp(diff - ln_evidence)
+            )
+        )
+
+        # ======================================================================
+        # Display inverse evidence computation results.
+        # ======================================================================
+        hm.logs.debug_log(
+            "Inv Evidence: numerical = {}, estimate = {}".format(
+                1.0 / evidence_numerical_integration, ev.evidence_inv
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: std = {}, std / estimate = {}".format(
+                np.sqrt(ev.evidence_inv_var),
+                np.sqrt(ev.evidence_inv_var) / ev.evidence_inv,
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: kurtosis = {}, sqrt( 2 / ( n_eff - 1 ) ) = {}".format(
+                ev.kurtosis, np.sqrt(2.0 / (ev.n_eff - 1))
+            )
+        )
+        hm.logs.debug_log(
+            "Inv Evidence: sqrt( var(var) )/ var = {}".format(
+                np.sqrt(ev.evidence_inv_var_var) / ev.evidence_inv_var
+            )
+        )
+        hm.logs.info_log(
+            "Inv Evidence: |numerical - estimate| / estimate = {}".format(
+                np.abs(1.0 / evidence_numerical_integration - ev.evidence_inv)
+                / ev.evidence_inv
+            )
+        )
+
+        # ===========================================================================
+        # Display more technical details
+        # ===========================================================================
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log("Technical Details")
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "lnargmax = {}, lnargmin = {}".format(ev.lnargmax, ev.lnargmin)
+        )
+        hm.logs.debug_log(
+            "lnprobmax = {}, lnprobmin = {}".format(ev.lnprobmax, ev.lnprobmin)
+        )
+        hm.logs.debug_log(
+            "lnpredictmax = {}, lnpredictmin = {}".format(
+                ev.lnpredictmax, ev.lnpredictmin
+            )
+        )
+        hm.logs.debug_log("---------------------------------")
+        hm.logs.debug_log(
+            "shift = {}, shift setting = {}".format(ev.shift_value, ev.shift)
+        )
+        hm.logs.debug_log("running sum total = {}".format(sum(ev.running_sum)))
+        hm.logs.debug_log("running sum = \n{}".format(ev.running_sum))
+        hm.logs.debug_log("nsamples per chain = \n{}".format(ev.nsamples_per_chain))
+        hm.logs.debug_log(
+            "nsamples eff per chain = \n{}".format(ev.nsamples_eff_per_chain)
+        )
+        hm.logs.debug_log("===============================")
+
+        # Create corner/triangle plot.
+        created_plots = False
+        if plot_corner and i_realisation == 0:
+            utils.plot_getdist(samples.reshape((-1, ndim)))
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/spline_rosenbrock_getdist.png", bbox_inches="tight"
+                )
+
+            plt.show(block=False)
+
+            # =======================================================================
+            # Visualise distributions
+            # =======================================================================
+
+            num_samp = chains_train.samples.shape[0]
+            samps_compressed = np.array(model.sample(num_samp))
+
+            utils.plot_getdist_compare(chains_train.samples, samps_compressed)
+            if savefigs:
+                plt.savefig(
+                    "examples/plots/spline_rosenbrock_corner_all_T"
+                    + str(temperature)
+                    + ".png",
+                    bbox_inches="tight",
+                    dpi=300,
+                )
+            created_plots = True
+
+        # Save out realisations for voilin plot.
+        evidence_inv_summary[i_realisation, 0] = ev.evidence_inv
+        evidence_inv_summary[i_realisation, 1] = ev.evidence_inv_var
+        evidence_inv_summary[i_realisation, 2] = ev.evidence_inv_var_var
+
+    # ===========================================================================
+    # End Timer.
+    clock = time.process_time() - clock
+    hm.logs.info_log("Execution time = {}s".format(clock))
+
+    # ===========================================================================
+    # Save out realisations of statistics for analysis.
+    if n_realisations > 1:
+        np.savetxt(
+            "examples/data/spline_rosenbrock_evidence_inv_T"
+            + str(temperature)
+            + "_realisations.dat",
+            evidence_inv_summary,
+        )
+        evidence_inv_analytic_summary = np.zeros(1)
+        evidence_inv_analytic_summary[0] = 1.0 / evidence_numerical_integration
+        np.savetxt(
+            "examples/data/spline_rosenbrock_evidence_inv" + "_analytic.dat",
+            evidence_inv_analytic_summary,
+        )
+
+    if created_plots:
+        input("\nPress Enter to continue...")
+
+    return samples
+
+
+if __name__ == "__main__":
+    # Setup logging config.
+    hm.logs.setup_logging()
+
+    # Define parameters.
+    ndim = 2
+    nchains = 200
+    samples_per_chain = 5000
+    nburn = 2000
+    np.random.seed(20)
+
+    hm.logs.info_log("Rosenbrock example")
+
+    hm.logs.debug_log("-- Selected Parameters --")
+
+    hm.logs.debug_log("Dimensionality = {}".format(ndim))
+    hm.logs.debug_log("Number of chains = {}".format(nchains))
+    hm.logs.debug_log("Samples per chain = {}".format(samples_per_chain))
+    hm.logs.debug_log("Burn in = {}".format(nburn))
+
+    hm.logs.debug_log("-------------------------")
+
+    # Run example.
+    samples = run_example(ndim, nchains, samples_per_chain, nburn, plot_corner=True)
diff --git a/examples/temperature_diagram.py b/examples/temperature_diagram.py
new file mode 100644
index 00000000..9a081335
--- /dev/null
+++ b/examples/temperature_diagram.py
@@ -0,0 +1,145 @@
+import jax.numpy as jnp
+import jax
+from sklearn.datasets import make_swiss_roll
+import sys
+import matplotlib.pyplot as plt
+import numpy as np
+from getdist import plots, MCSamples
+import tensorflow_probability as tfp
+
+tfp = tfp.substrates.jax
+tfd = tfp.distributions
+tfb = tfp.bijectors
+from harmonic import model_nf
+from harmonic import flows
+
+
+def get_moon(sigma, resolution=1024):
+    """
+    Generate a TFP approximate distribution of the two moons dataset
+    Parameters
+    ----------
+    sigma: float
+    Spread of the 2 moons distribution.
+    resolution: int
+    Number of components in the gaussian mixture approximation of the
+    distribution (default: 1024)
+    Returns
+    -------
+    distribution: TFP distribution
+    Two moons distribution
+    """
+
+    outer_circ_x = jnp.cos(jnp.linspace(0, jnp.pi, resolution))
+    outer_circ_y = jnp.sin(jnp.linspace(0, jnp.pi, resolution))
+    # inner_circ_x = 1 - jnp.cos(jnp.linspace(0, jnp.pi, resolution))
+    # inner_circ_y = 1 - jnp.sin(jnp.linspace(0, jnp.pi, resolution)) - .5
+
+    X = outer_circ_x
+    Y = outer_circ_y
+    coords = jnp.vstack([X, Y])
+    # coords = X
+
+    distribution = tfd.MixtureSameFamily(
+        mixture_distribution=tfd.Categorical(probs=jnp.ones(resolution) / resolution),
+        components_distribution=tfd.MultivariateNormalDiag(
+            loc=coords.T, scale_diag=jnp.full(coords.shape[0], sigma)
+        ),
+    )
+    return distribution
+
+
+def get_two_moons(sigma, resolution=1024):
+    """
+    Generate a TFP approximate distribution of the two moons dataset
+    Parameters
+    ----------
+    sigma: float
+      Spread of the 2 moons distribution.
+    resolution: int
+      Number of components in the gaussian mixture approximation of the
+      distribution (default: 1024)
+    Returns
+    -------
+    distribution: TFP distribution
+      Two moons distribution
+    """
+
+    outer_circ_x = jnp.cos(jnp.linspace(0, jnp.pi, resolution))
+    outer_circ_y = jnp.sin(jnp.linspace(0, jnp.pi, resolution))
+    inner_circ_x = 1 - jnp.cos(jnp.linspace(0, jnp.pi, resolution))
+    inner_circ_y = 1 - jnp.sin(jnp.linspace(0, jnp.pi, resolution)) - 0.5
+
+    X = jnp.append(outer_circ_x, inner_circ_x)
+    Y = jnp.append(outer_circ_y, inner_circ_y)
+    coords = jnp.vstack([X, Y])
+
+    distribution = tfd.MixtureSameFamily(
+        mixture_distribution=tfd.Categorical(
+            probs=jnp.ones(2 * resolution) / resolution / 2
+        ),
+        components_distribution=tfd.MultivariateNormalDiag(
+            loc=coords.T, scale_diag=jnp.full(coords.shape[0], sigma)
+        ),
+    )
+    return distribution
+
+
+# Let's build a function that can sample from a two-moons distribution
+batch_size = 200000
+seed = jax.random.PRNGKey(42)
+
+
+@jax.jit
+def get_batch(seed):
+    return get_moon(0.05).sample(batch_size, seed=seed)
+
+
+ndim = 2
+n_scaled = 4
+n_unscaled = 2
+epochs_num = 300
+
+posterior_samples = np.array(get_moon(0.05).sample(batch_size, seed=seed))
+model = model_nf.RealNVPModel(
+    ndim,
+    flow=flows.RealNVP(ndim, n_scaled_layers=n_scaled, n_unscaled_layers=n_unscaled),
+)
+model.fit(posterior_samples, jnp.zeros(batch_size), epochs=epochs_num)
+
+num_samp = batch_size * 5
+
+# samps = np.array(model.sample(num_samp, temperature=1.))
+samps2 = np.array(model.sample(num_samp, temperature=0.7))
+samps3 = np.array(model.sample(num_samp, temperature=0.4))
+
+# Get the getdist MCSamples objects for the samples, specifying same parameter
+# names and labels; if not specified weights are assumed to all be unity
+names = ["x%s" % i for i in range(ndim)]
+labels = ["x_%s" % i for i in range(ndim)]
+samples_train = MCSamples(
+    samples=posterior_samples, names=names, labels=labels, label="Posterior"
+)
+# samples = MCSamples(samples=samps,names = names, labels = labels, label = 'T = 1.0')
+samples2 = MCSamples(samples=samps2, names=names, labels=labels, label="T = 0.7")
+samples3 = MCSamples(samples=samps3, names=names, labels=labels, label="T = 0.4")
+
+# 2D line contour comparison plot with extra bands and markers
+g = plots.get_single_plotter()
+g.settings.linewidth = 1
+g.plot_2d(
+    [samples_train, samples2, samples3],
+    "x0",
+    "x1",
+    colors=["orange", "green", "#800080"],
+    filled=True,
+)
+# colors=[('#F31212', '#F68A8A'), ('#17DB1E', '#83DB86'), ('#0080FF', '#89C4FF')])
+g.add_legend(["Posterior", "T=0.7", "T=0.4"], colored_text=True)
+plt.tick_params(
+    top=False, bottom=False, left=False, right=False, labelleft=False, labelbottom=False
+)
+plt.xlim(-1.2, 1.2)
+plt.ylim(-0.2, 1.2)
+plt.savefig("examples/plots/temperature_diag.png", bbox_inches="tight", dpi=300)
+plt.show()
diff --git a/examples/utils.py b/examples/utils.py
index 98b7f499..79090fc7 100644
--- a/examples/utils.py
+++ b/examples/utils.py
@@ -7,14 +7,15 @@
 from getdist import plots, MCSamples
 import getdist
 
+
 def plot_corner(samples, labels=None):
     """
     Plot triangle plot of marginalised distributions using corner package.
 
     Args:
-        - samples: 
+        - samples:
             2D array of shape (ndim, nsamples) containing samples.
-        - labels: 
+        - labels:
             Array of strings containing axis labels.
     Returns:
         - None
@@ -22,7 +23,7 @@ def plot_corner(samples, labels=None):
 
     ndim = samples.shape[1]
     if labels is None:
-        labels_corner =  ["$x_%s$"%i for i in range(ndim)]
+        labels_corner = ["$x_%s$" % i for i in range(ndim)]
     else:
         labels_corner = labels
     fig = corner.corner(samples, labels=labels_corner)
@@ -33,9 +34,9 @@ def plot_getdist(samples, labels=None):
     Plot triangle plot of marginalised distributions using getdist package.
 
     Args:
-        - samples: 
+        - samples:
             2D array of shape (ndim, nsamples) containing samples.
-        - labels: 
+        - labels:
             Array of strings containing axis labels.
 
     Returns:
@@ -45,42 +46,89 @@ def plot_getdist(samples, labels=None):
     getdist.chains.print_load_details = False
 
     ndim = samples.shape[1]
-    names = ["x%s"%i for i in range(ndim)]
+    names = ["x%s" % i for i in range(ndim)]
     if labels is None:
-        labels =  ["x_%s"%i for i in range(ndim)]
+        labels = ["x_%s" % i for i in range(ndim)]
 
-    mcsamples = MCSamples(samples=samples,
-                          names=names, labels=labels)
+    mcsamples = MCSamples(samples=samples, names=names, labels=labels)
     g = plots.getSubplotPlotter()
     g.triangle_plot([mcsamples], filled=True)
 
 
+def plot_getdist_compare(
+    samples1, samples2, labels=None, fontsize=17, legend_fontsize=15
+):
+    """
+    Plot triangle plot of marginalised distributions using getdist package.
+
+    Args:
+        - samples:
+            2D array of shape (ndim, nsamples) containing samples.
+        - labels:
+            Array of strings containing axis labels.
+
+    Returns:
+        - None
+    """
+
+    getdist.chains.print_load_details = False
+    # getdist.plots.GetDistPlotSettings(constrained_layout=True)
+
+    ndim = samples1.shape[1]
+    names = ["x%s" % i for i in range(ndim)]
+    if labels is None:
+        labels = ["x_%s" % i for i in range(ndim)]
+
+    mcsamples1 = MCSamples(
+        samples=samples1, names=names, labels=labels, label="Posterior"
+    )
+
+    mcsamples2 = MCSamples(
+        samples=samples2, names=names, labels=labels, label="Concentrated flow"
+    )
+
+    g = plots.getSubplotPlotter(width_inch=10.5 / 2.54)
+    # g.settings.scaling = False
+    g.settings.axes_fontsize = fontsize
+    g.settings.legend_fontsize = legend_fontsize  # 17.5
+    g.settings.axes_labelsize = fontsize
+    g.settings.linewidth = 2
+    g.settings.constrained_layout = True
+    g.settings.legend_loc = "upper right"
+    g.triangle_plot(
+        [mcsamples1, mcsamples2],
+        filled=True,
+        contour_colors=["red", "tab:blue"],
+        line_args=[{"ls": "-", "color": "red"}, {"ls": "--", "color": "blue"}],
+    )
+
+
 def eval_func_on_grid(func, xmin, xmax, ymin, ymax, nx, ny):
     """
     Evalute 2D function on a grid.
 
     Args:
-        - func: 
+        - func:
             Function to evalate.
-        - xmin: 
+        - xmin:
             Minimum x value to consider in grid domain.
-        - xmax: 
+        - xmax:
             Maximum x value to consider in grid domain.
-        - ymin: 
+        - ymin:
             Minimum y value to consider in grid domain.
-        - ymax: 
+        - ymax:
             Maximum y value to consider in grid domain.
-        - nx: 
+        - nx:
             Number of samples to include in grid in x direction.
-        - ny: 
+        - ny:
             Number of samples to include in grid in y direction.
 
     Returns:
-        - func_eval_grid: 
+        - func_eval_grid:
             Function values evaluated on the 2D grid.
-        - x_grid: 
+        - x_grid:
             x values over the 2D grid.
-        - y_grid: 
+        - y_grid:
             y values over the 2D grid.
     """
 
@@ -88,75 +136,95 @@ def eval_func_on_grid(func, xmin, xmax, ymin, ymax, nx, ny):
     x = np.linspace(xmin, xmax, nx)
     y = np.linspace(ymin, ymax, ny)
     x_grid, y_grid = np.meshgrid(x, y)
-    func_eval_grid = np.zeros((nx,ny))
+    func_eval_grid = np.zeros((nx, ny))
     for i in range(nx):
         for j in range(ny):
-            func_eval_grid[i,j] = \
-                 func( np.array([x_grid[i,j], y_grid[i,j]]) )
+            func_eval_grid[i, j] = func(np.array([x_grid[i, j], y_grid[i, j]]))
 
     return func_eval_grid, x_grid, y_grid
 
 
-def plot_surface(func_eval_grid, x_grid, y_grid, samples=None, vals=None,
-                 contour_z_offset=None, contours=None, alpha=0.3):
+def plot_surface(
+    func_eval_grid,
+    x_grid,
+    y_grid,
+    samples=None,
+    vals=None,
+    contour_z_offset=None,
+    contours=None,
+    alpha=0.3,
+):
     """
-    Plot surface defined by 2D function on a grid.  
+    Plot surface defined by 2D function on a grid.
 
     Samples may also be optionally plotted.
 
     Args:
-        - func_eval_grid: 
+        - func_eval_grid:
             Function evalated over 2D grid.
-        - x_grid: 
+        - x_grid:
             x values over the 2D grid.
-        - y_grid: 
+        - y_grid:
             y values over the 2D grid.
-        - samples: 
+        - samples:
             2D array of shape (ndim, nsamples) containing samples.
-        - vals: 
-            1D array of function values at sample locations.  Both samples and 
+        - vals:
+            1D array of function values at sample locations.  Both samples and
             vals must be provided if they are to be plotted.
-        - contour_z_offset: 
+        - contour_z_offset:
             If not None then plot contour in plane specified by z offset.
-        - contours: 
+        - contours:
             Values at which to draw contours (must be in increasing order).
         - alpha:
             Opacity of surface plot.
 
     Returns:
-        - ax: 
+        - ax:
             Plot axis.
     """
 
     # Set up axis for surface plot.
-    fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
+    fig, ax = plt.subplots(subplot_kw=dict(projection="3d"))
 
     # Create an instance of a LightSource and use it to illuminate
     # the surface.
     light = LightSource(60, 120)
-    rgb = np.ones((func_eval_grid.shape[0],
-                   func_eval_grid.shape[1],
-                   3))
-    illuminated_surface = \
-        light.shade_rgb(rgb * np.array([0,0.0,1.0]),
-                        func_eval_grid)
+    rgb = np.ones((func_eval_grid.shape[0], func_eval_grid.shape[1], 3))
+    illuminated_surface = light.shade_rgb(rgb * np.array([0, 0.0, 1.0]), func_eval_grid)
 
     # Plot surface.
-    ax.plot_surface(x_grid, y_grid, func_eval_grid,
-                    alpha=alpha, linewidth=0, antialiased=False,
-                    # cmap=cm.coolwarm,
-                    facecolors=illuminated_surface)
+    ax.plot_surface(
+        x_grid,
+        y_grid,
+        func_eval_grid,
+        alpha=alpha,
+        linewidth=0,
+        antialiased=False,
+        # cmap=cm.coolwarm,
+        facecolors=illuminated_surface,
+    )
 
     # Plot contour.
     if contour_z_offset is not None:
         if contours is not None:
-            cset = ax.contour(x_grid, y_grid, func_eval_grid, contours,
-                              zdir='z', offset=contour_z_offset,
-                              cmap=cm.coolwarm)
+            cset = ax.contour(
+                x_grid,
+                y_grid,
+                func_eval_grid,
+                contours,
+                zdir="z",
+                offset=contour_z_offset,
+                cmap=cm.coolwarm,
+            )
         else:
-            cset = ax.contour(x_grid, y_grid, func_eval_grid,
-                              zdir='z', offset=contour_z_offset,
-                              cmap=cm.coolwarm)
+            cset = ax.contour(
+                x_grid,
+                y_grid,
+                func_eval_grid,
+                zdir="z",
+                offset=contour_z_offset,
+                cmap=cm.coolwarm,
+            )
 
     # Set domain.
     xmin = np.min(x_grid)
@@ -164,11 +232,10 @@ def plot_surface(func_eval_grid, x_grid, y_grid, samples=None, vals=None,
     ymin = np.min(y_grid)
     ymax = np.max(y_grid)
 
-
     # # Plot samples.
     if samples is not None and vals is not None:
-        xplot = samples[:,0]
-        yplot = samples[:,1]
+        xplot = samples[:, 0]
+        yplot = samples[:, 1]
         # Manually remove samples outside of plot region
         # (since Matplotlib clipping cannot do this in 3D; see
         # https://github.com/matplotlib/matplotlib/issues/749).
@@ -177,98 +244,106 @@ def plot_surface(func_eval_grid, x_grid, y_grid, samples=None, vals=None,
         yplot[yplot < ymin] = np.nan
         yplot[yplot > ymax] = np.nan
         zplot = vals
-        ax.scatter(xplot, yplot, zplot, c='r', s=5, marker='.')
+        ax.scatter(xplot, yplot, zplot, c="r", s=5, marker=".")
 
     # Define additional plot settings.
     ax.set_xlim(xmin, xmax)
     ax.set_ylim(ymin, ymax)
     ax.view_init(elev=15.0, azim=110.0)
-    ax.set_xlabel('$x_0$')
-    ax.set_ylabel('$x_1$')
+    ax.set_xlabel("$x_0$")
+    ax.set_ylabel("$x_1$")
     ax.set_zlim(zmin=contour_z_offset)
 
     return ax
 
 
-def plot_image(func_eval_grid, x_grid, y_grid, samples=None,
-               colorbar_label=None, plot_contour=False, 
-               contours=None, markersize=1.0):
+def plot_image(
+    func_eval_grid,
+    x_grid,
+    y_grid,
+    samples=None,
+    colorbar_label=None,
+    plot_contour=False,
+    contours=None,
+    markersize=1.0,
+):
     """
-    Plot image defined by 2D function on a grid.  
+    Plot image defined by 2D function on a grid.
 
     Samples may also be optionally plotted.
 
     Args:
-        - func_eval_grid: 
+        - func_eval_grid:
             Function evalated over 2D grid.
-        - x_grid: 
+        - x_grid:
             x values over the 2D grid.
-        - y_grid: 
+        - y_grid:
             y values over the 2D grid.
-        - samples: 
+        - samples:
             2D array of shape (ndim, nsamples) containing samples.
-        - colorbar_label: 
+        - colorbar_label:
             Text label to include on colorbar.
-        - contours: 
+        - contours:
             Values at which to draw contours (must be in increasing order).
         - markersize:
             Size of markers for plotting overlaid samples.
 
     Returns:
-        - ax: 
+        - ax:
             Plot axis.
     """
 
     plt.figure()
-    ax = plt.imshow(func_eval_grid, origin='lower', aspect='auto',
-                    extent=[np.min(x_grid), np.max(x_grid),
-                            np.min(y_grid), np.max(y_grid)])
+    ax = plt.imshow(
+        func_eval_grid,
+        origin="lower",
+        aspect="auto",
+        extent=[np.min(x_grid), np.max(x_grid), np.min(y_grid), np.max(y_grid)],
+    )
 
     if plot_contour:
         if contours is not None:
-            plt.contour(x_grid, y_grid, func_eval_grid, contours,
-                        cmap=cm.coolwarm)
+            plt.contour(x_grid, y_grid, func_eval_grid, contours, cmap=cm.coolwarm)
         else:
             plt.contour(x_grid, y_grid, func_eval_grid, cmap=cm.coolwarm)
 
     if samples is not None:
-        plt.plot(samples[:,0],
-                 samples[:,1],
-                 'r.', markersize=markersize)
+        plt.plot(samples[:, 0], samples[:, 1], "r.", markersize=markersize)
 
     if colorbar_label is not None:
         plt.colorbar(label=colorbar_label)
     else:
         plt.colorbar()
 
-    plt.xlabel('$x_0$')
-    plt.ylabel('$x_1$')
+    plt.xlabel("$x_0$")
+    plt.ylabel("$x_1$")
 
     return ax
 
 
-def plot_realisations(mc_estimates, std_estimated,
-                      analytic_val=None, analytic_text=None):
+def plot_realisations(
+    mc_estimates, std_estimated, analytic_val=None, analytic_text=None
+):
     """
-    Violin plot of estimated quantity from Monte Carlo (MC) simulations, 
+    Violin plot of estimated quantity from Monte Carlo (MC) simulations,
     compared with error bar from estimated standard deviation.
-    
+
     Also plot analytic value if specified.
 
     Args:
-        - mc_estimates: 
+        - mc_estimates:
             1D array of quanties estimate many times by MC simulation.
-        - std_estimate: 
-            Standard deviation estimate to be compared with standard deviation 
+        - std_estimate:
+            Standard deviation estimate to be compared with standard deviation
             from MC simulations.
-        - analytic_val: 
-            Plot horizonal line if analytic value of quantity estimated is 
+        - analytic_val:
+            Plot horizonal line if analytic value of quantity estimated is
             provided.
-        - analytic_text: 
+        - analytic_text:
             Text to include next to line specifying analytic value, if provided.
 
     Returns:
-        - ax: 
+        - ax:
             Plot axis.
     """
 
@@ -278,23 +353,43 @@ def plot_realisations(mc_estimates, std_estimated,
     plot_aspect_ratio = 1.33
     plot_x_size = 9
 
-    fig, ax = plt.subplots(figsize=(plot_x_size,
-                                    plot_x_size/plot_aspect_ratio))
+    fig, ax = plt.subplots(figsize=(plot_x_size, plot_x_size / plot_aspect_ratio))
 
-    ax.violinplot(mc_estimates, showmeans=False, showmedians=False,
-            showextrema=True, bw_method=1.0)
+    ax.violinplot(
+        mc_estimates,
+        showmeans=False,
+        showmedians=False,
+        showextrema=True,
+        bw_method=1.0,
+    )
 
     if analytic_val is not None:
-        plt.plot(np.arange(4),np.zeros(4)+analytic_val, 'r--')
+        plt.plot(np.arange(4), np.zeros(4) + analytic_val, "r--")
         ymin, ymax = ax.get_ylim()
-        ax.text(1.8, analytic_val+(ymax-ymin)*0.03, analytic_text, color='red')
-
-    plt.errorbar(np.zeros(1)+1.0, mean, yerr=std_measured,
-        fmt='--o', color='C4', capsize=7, capthick=3,
-        linewidth=3, elinewidth=3)
-    plt.errorbar(np.zeros(1)+1.5, mean, yerr=std_estimated,
-        fmt='--o', color='C2', capsize=7, capthick=3,
-        linewidth=3, elinewidth=3)
+        ax.text(1.8, analytic_val + (ymax - ymin) * 0.03, analytic_text, color="red")
+
+    plt.errorbar(
+        np.zeros(1) + 1.0,
+        mean,
+        yerr=std_measured,
+        fmt="--o",
+        color="C4",
+        capsize=7,
+        capthick=3,
+        linewidth=3,
+        elinewidth=3,
+    )
+    plt.errorbar(
+        np.zeros(1) + 1.5,
+        mean,
+        yerr=std_estimated,
+        fmt="--o",
+        color="C2",
+        capsize=7,
+        capthick=3,
+        linewidth=3,
+        elinewidth=3,
+    )
 
     ymin, ymax = ax.get_ylim()
     print("ymim = {}, ymax = {}".format(ymin, ymax))
@@ -302,10 +397,10 @@ def plot_realisations(mc_estimates, std_estimated,
         ymin = 0
     ax.set_ylim([ymin, ymax])
 
-    ax.get_xaxis().set_tick_params(direction='out')
-    ax.xaxis.set_ticks_position('bottom')
+    ax.get_xaxis().set_tick_params(direction="out")
+    ax.xaxis.set_ticks_position("bottom")
     ax.set_xticks([1.0, 1.5])
-    ax.set_xticklabels(['Measured', 'Estimated'])
+    ax.set_xticklabels(["Measured", "Estimated"])
 
     ax.set_xlim([0.5, 2.0])
 
diff --git a/harmonic/evidence.pyx b/harmonic/evidence.pyx
index 6fdb4be3..78d355ed 100644
--- a/harmonic/evidence.pyx
+++ b/harmonic/evidence.pyx
@@ -6,7 +6,7 @@ from math import fsum
 import warnings
 from enum import Enum
 import scipy.special as sp
-import pickle
+import cloudpickle
 import logs as lg 
 
 class Shifting(Enum):
@@ -30,7 +30,7 @@ class Evidence:
     """
 
     def __init__(self, long nchains, model not None, \
-                 shift=Shifting.MEAN_SHIFT, batch_calculation=False):
+                 shift=Shifting.MEAN_SHIFT):
         """Construct evidence class for computing inverse evidence values from
         set number of chains and initialised posterior model.
 
@@ -93,8 +93,8 @@ class Evidence:
 
         self.chains_added = False
 
-        self.model = model
-        self.batch_calculation = batch_calculation
+        self.model = model 
+        self.batch_calculation = hasattr(self.model, "flow")
 
         # Technical details
         self.lnargmax = -np.inf
@@ -462,7 +462,7 @@ class Evidence:
         """
 
         file = open(filename, "wb")
-        pickle.dump(self, file)
+        cloudpickle.dump(self, file)
         file.close()
 
         return
@@ -482,7 +482,7 @@ class Evidence:
 
         """
         file = open(filename,"rb")
-        ev = pickle.load(file)
+        ev = cloudpickle.load(file)
         file.close()
 
         return ev
diff --git a/harmonic/flows.py b/harmonic/flows.py
new file mode 100644
index 00000000..a910b940
--- /dev/null
+++ b/harmonic/flows.py
@@ -0,0 +1,438 @@
+from typing import Sequence, Callable
+import flax.linen as nn
+import jax
+import jax.numpy as jnp
+import tensorflow_probability as tfp
+import distrax
+
+tfp = tfp.substrates.jax
+tfd = tfp.distributions
+tfb = tfp.bijectors
+
+
+# ===============================================================================
+# NVP Flow
+# ===============================================================================
+
+
+class RealNVP(nn.Module):
+    """
+    Real-valued non-volume preserving flow using flax and tfp-jax.
+
+    Args:
+        n_features (int): Number of features in the data.
+
+        n_scaled_layers (int, optional): Non-zero number of layers in the flow. Defaults to 2.
+
+        n_unscaled_layers (int, optional): Number of unscaled layers in the flow. Defaults to 4.
+
+    """
+
+    n_features: int
+    n_scaled_layers: int = 2
+    n_unscaled_layers: int = 4
+
+    def setup(self):
+        self.scaled_layers = [AffineCoupling() for i in range(self.n_scaled_layers)]
+        self.unscaled_layers = [
+            AffineCoupling(apply_scaling=False) for i in range(self.n_unscaled_layers)
+        ]
+
+    def make_flow(self, temperature: float = 1.0):
+        """
+        Make tfp-jax distribution object containing the RealNVP flow.
+
+        Args:
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+
+        Returns:
+            tfb.Distribution: Base Gaussian transformed by scaled contained in the scaled_layers
+                attribute, followed by unscaled affine coupling layers contained in the
+                unscaled_layers attribute.
+
+        Raises:
+
+            ValueError: If n_scaled_layers is not positive.
+
+        """
+
+        if self.n_scaled_layers <= 0:
+            raise ValueError("Number of scaled layers must be greater than zero.")
+
+        chain = []
+        ix = jnp.arange(self.n_features)
+        permutation = [ix[-1], *ix[:-1]]
+
+        # assume n_scaled_layers is not 0
+        for i in range(self.n_scaled_layers - 1):
+            chain.append(
+                tfb.RealNVP(fraction_masked=0.5, bijector_fn=self.scaled_layers[i])
+            )
+            chain.append(tfb.Permute(permutation))
+
+        chain.append(
+            tfb.RealNVP(fraction_masked=0.5, bijector_fn=self.scaled_layers[-1])
+        )
+
+        for i in range(self.n_unscaled_layers):
+            chain.append(tfb.Permute(permutation))
+            chain.append(
+                tfb.RealNVP(
+                    fraction_masked=0.5,
+                    bijector_fn=self.unscaled_layers[i],
+                )
+            )
+
+        # Computes the likelihood of these x
+        chain = tfb.Chain(chain)
+
+        nvp = tfd.TransformedDistribution(
+            distribution=tfd.MultivariateNormalDiag(
+                loc=jnp.zeros(self.n_features),
+                scale_diag=jnp.full(self.n_features, temperature),
+            ),
+            bijector=chain,
+        )
+
+        return nvp
+
+    def __call__(self, x: jnp.ndarray, temperature: int = 1.0) -> jnp.array:
+        """
+        Evaluate the log probability of the flow for non-batched input x.
+
+        Args:
+            x (jnp.ndarray (ndim)): Sample at which to predict posterior value.
+
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            float: Predicted log_e posterior value.
+        """
+
+        flow = self.make_flow(temperature=temperature)
+        return flow.log_prob(x)
+
+    def sample(
+        self, rng: jax.random.PRNGKey, num_samples: int, temperature: float = 1.0
+    ) -> jnp.array:
+        """ "
+        Sample from the flow.
+
+        Args:
+            rng (Union[Array, PRNGKeyArray])): Key used in random number generation process.
+
+            num_samples (int): Number of samples generated.
+
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            jnp.array (num_samples, ndim): Samples from fitted distribution.
+        """
+        nvp = self.make_flow(temperature=temperature)
+        samples = nvp.sample(num_samples, seed=rng)
+
+        return samples
+
+    def log_prob(self, x: jnp.array, temperature: float = 1.0) -> jnp.array:
+        """
+        Evaluate the log probability of the flow for a batched input.
+
+        Args:
+            x (jnp.ndarray (batch_size, ndim)): Sample for which to predict posterior values.
+
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            jnp.ndarray (batch_size,): Predicted log_e posterior value.
+        """
+        get_logprob = jax.vmap(self.__call__, in_axes=[0, None])
+        logprob = get_logprob(x, temperature)
+
+        return logprob
+
+
+class AffineCoupling(nn.Module):
+    """
+    Affine coupling layer used in RealNVP flow class.
+
+    Args:
+        apply_scaling (bool, optional): If true shift is followed by a scaling. Defaults to True.
+    """
+
+    apply_scaling: bool = True
+
+    @nn.compact
+    def __call__(self, x, nunits):
+        net = nn.leaky_relu(nn.Dense(128)(x))
+
+        # Shift parameter:
+        shift = nn.Dense(nunits)(net)
+
+        if self.apply_scaling:
+            scaler = tfb.Scale(jnp.clip(nn.softplus(nn.Dense(nunits)(net)), 1e-3, 1e3))
+        else:
+            scaler = tfb.Identity()
+        return tfb.Chain([tfb.Shift(shift), scaler])
+
+
+# ===============================================================================
+# RQSpline Flow
+# ===============================================================================
+
+
+class RQSpline(nn.Module):
+    """
+    Rational quadratic spline normalizing flow model using distrax.
+
+    Args:
+        n_features (int): Number of features in the data.
+
+        num_layers (int): Number of layers in the flow.
+
+        num_bins (int): Number of bins in the spline.
+
+        hidden_size (Sequence[int]): Size of the hidden layers in the conditioner.
+
+        spline_range (Sequence[float], optional): Range of the spline. Defaults to (-10, 10)
+
+    Note:
+        Adapted from github.com/kazewong/flowMC
+    """
+
+    n_features: int
+    num_layers: int
+    hidden_size: Sequence[int]
+    num_bins: int
+    spline_range: Sequence[float] = (-10.0, 10.0)
+
+    def setup(self):
+        conditioner = []
+        scalar = []
+        for i in range(self.num_layers):
+            conditioner.append(
+                Conditioner(self.n_features, self.hidden_size, 3 * self.num_bins + 1)
+            )
+            scalar.append(Scalar(self.n_features))
+
+        self.conditioner = conditioner
+        self.scalar = scalar
+
+        self.vmap_call = jax.vmap(self.__call__)
+
+        def bijector_fn(params: jnp.ndarray):
+            return distrax.RationalQuadraticSpline(
+                params, range_min=self.spline_range[0], range_max=self.spline_range[1]
+            )
+
+        self.bijector_fn = bijector_fn
+
+    def make_flow(self, temperature: float = 1.0):
+        """
+        Make distrax distribution containing the rational quadratic spline flow.
+
+        Args:
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            Base Gaussian transformed by rational quadratic spline flow.
+        """
+        mask = (jnp.arange(0, self.n_features) % 2).astype(bool)
+        mask_all = (jnp.zeros(self.n_features)).astype(bool)
+        layers = []
+        for i in range(self.num_layers):
+            layers.append(
+                distrax.MaskedCoupling(
+                    mask=mask_all, bijector=scalar_affine, conditioner=self.scalar[i]
+                )
+            )
+            layers.append(
+                distrax.MaskedCoupling(
+                    mask=mask,
+                    bijector=self.bijector_fn,
+                    conditioner=self.conditioner[i],
+                )
+            )
+            mask = jnp.logical_not(mask)
+
+        flow = distrax.Inverse(distrax.Chain(layers))
+        base_dist = distrax.Independent(
+            distrax.MultivariateNormalFullCovariance(
+                loc=jnp.zeros(self.n_features),
+                covariance_matrix=jnp.eye(self.n_features) * temperature,
+            )
+        )
+
+        return base_dist, flow
+
+    def __call__(self, x: jnp.array, temperature: float = 1.0) -> jnp.array:
+        """
+        Evaluate the log probability of the flow for non-batched input x.
+
+        Args:
+            x (jnp.ndarray (ndim)): Sample at which to predict posterior value.
+
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            jnp.ndarray (float): Predicted log_e posterior value.
+        """
+        base_dist, flow = self.make_flow(temperature=temperature)
+
+        return distrax.Transformed(base_dist, flow).log_prob(x)
+
+    def sample(
+        self, rng: jax.random.PRNGKey, num_samples: int, temperature: float = 1.0
+    ) -> jnp.array:
+        """ "
+        Sample from the flow.
+
+        Args:
+            rng (Union[Array, PRNGKeyArray])): Key used in random number generation process.
+
+            num_samples (int): Number of samples generated.
+
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            jnp.array (num_samples, ndim): Samples from fitted distribution.
+        """
+
+        base_dist, flow = self.make_flow(temperature=temperature)
+        samples = distrax.Transformed(base_dist, flow).sample(
+            seed=rng, sample_shape=(num_samples)
+        )
+
+        return samples
+
+    def log_prob(self, x: jnp.array, temperature: float = 1.0) -> jnp.array:
+        """
+        Evaluate the log probability of the flow for a batched input.
+
+        Args:
+            x (jnp.ndarray (batch_size, ndim)): Sample for which to predict posterior values.
+
+            temperature (float, optional): Factor by which base Gaussian unit covariance matrix is scaled.
+                Should be between 0 and 1 for use in evidence estimation. Defaults to 1.
+
+        Returns:
+            jnp.ndarray (batch_size,): Predicted log_e posterior value.
+        """
+
+        get_logprob = jax.vmap(self.__call__, in_axes=[0, None])
+        logprob = get_logprob(x, temperature)
+
+        return logprob
+
+
+class Reshape(nn.Module):
+    shape: Sequence[int]
+
+    def __call__(self, x: jnp.ndarray):
+        return jnp.reshape(x.T, self.shape)
+
+
+class Conditioner(nn.Module):
+    """
+    Conditioner used to construct the bijector function in RQSpline class.
+
+    Adapted from github.com/kazewong/flowMC
+    """
+
+    n_features: int
+    hidden_size: Sequence[int]
+    num_bijector_params: int
+
+    def setup(self):
+        self.conditioner = nn.Sequential(
+            [
+                MLP(
+                    [self.n_features] + list(self.hidden_size),
+                    nn.tanh,
+                    init_weight_scale=1e-2,
+                ),
+                nn.Dense(
+                    self.n_features * self.num_bijector_params,
+                    kernel_init=jax.nn.initializers.zeros,
+                    bias_init=jax.nn.initializers.zeros,
+                ),
+                Reshape((self.n_features, self.num_bijector_params)),
+            ]
+        )
+
+    def __call__(self, x):
+        return self.conditioner(x)
+
+
+class Scalar(nn.Module):
+    """
+    Scalar used to construct the spline flow in RQSpline class.
+
+    Adapted from github.com/kazewong/flowMC
+    """
+
+    n_features: int
+
+    def setup(self):
+        self.shift = self.param(
+            "shifts", lambda rng, shape: jnp.zeros(shape), (self.n_features)
+        )
+        self.scale = self.param(
+            "scales", lambda rng, shape: jnp.ones(shape), (self.n_features)
+        )
+
+    def __call__(self, x):
+        return self.scale, self.shift
+
+
+def scalar_affine(params: jnp.ndarray):
+    return distrax.ScalarAffine(scale=params[0], shift=params[1])
+
+
+class MLP(nn.Module):
+    """
+    Multi-layer perceptron in Flax. We use a gaussian kernel with a standard deviation
+    of `init_weight_scale=1e-4` by default.
+
+    Args:
+        features (list of int): The number of features in each layer.
+
+        activation (callable, optional): The activation function at each level Defaults to nn.relu.
+
+        use_bias (bool, optional): Whether to use bias in the layers. Defaults to True.
+
+        init_weight_scale (float, optional): The initial weight scale for the layers. Defaults to 1e-4.
+
+        kernel_init (callable, optional): The kernel initializer for the layers. Defaults to jax.nn.initializers.variance_scaling.
+
+    Adapted from github.com/kazewong/flowMC
+    """
+
+    features: Sequence[int]
+    activation: Callable = nn.relu
+    use_bias: bool = True
+    init_weight_scale: float = 1e-4
+    kernel_i: Callable = jax.nn.initializers.variance_scaling
+
+    def setup(self):
+        self.layers = [
+            nn.Dense(
+                feat,
+                use_bias=self.use_bias,
+                kernel_init=self.kernel_i(self.init_weight_scale, "fan_in", "normal"),
+            )
+            for feat in self.features
+        ]
+
+    def __call__(self, x):
+        for l, layer in enumerate(self.layers[:-1]):
+            x = self.activation(layer(x))
+        x = self.layers[-1](x)
+        return x
diff --git a/harmonic/model.pyx b/harmonic/model.pyx
index 433e195c..c68c130b 100644
--- a/harmonic/model.pyx
+++ b/harmonic/model.pyx
@@ -7,7 +7,7 @@ import scipy.optimize as so
 from sklearn import preprocessing
 from sklearn.cluster import KMeans
 import logs as lg 
-import pickle
+import cloudpickle
 
 
 class Model(metaclass=abc.ABCMeta):
@@ -20,7 +20,7 @@ class Model(metaclass=abc.ABCMeta):
 
 
     @abc.abstractmethod
-    def __init__(self, long ndim, list domains not None, hyper_parameters=None):
+    def __init__(self, long ndim):
         """Constructor setting the hyper-parameters and domains of the model.
         
         Must be implemented by derived class (currently abstract).
@@ -28,14 +28,6 @@ class Model(metaclass=abc.ABCMeta):
         Args: 
 
             ndim (long): Dimension of the problem to solve.
-
-            domains (list): List of 1D numpy ndarrays containing the domains
-                for each parameter of model. Each domain is of length two,
-                specifying a lower and upper bound for real hyper-parameters
-                but can be different in other cases if required.
-
-            hyper_parameters (list): Hyper-parameters for model.
-
         """
 
 
@@ -76,17 +68,20 @@ class Model(metaclass=abc.ABCMeta):
             (double): Predicted log_e posterior value.
 
         """
-
-    @abc.abstractmethod
+        
     def is_fitted(self):
         """Specify whether model has been fitted.
-        
+            
         Returns:
 
             (bool): Whether the model has been fitted.
 
         """
 
+        return self.fitted
+
+
+
     def serialize(self, filename):
         """Serialize Model object.
 
@@ -97,7 +92,7 @@ class Model(metaclass=abc.ABCMeta):
         """
 
         file = open(filename, "wb")
-        pickle.dump(self, file)
+        cloudpickle.dump(self, file)
         file.close()
 
         return
@@ -117,7 +112,7 @@ class Model(metaclass=abc.ABCMeta):
 
         """
         file = open(filename,"rb")
-        model = pickle.load(file)
+        model = cloudpickle.load(file)
         file.close()
 
         return model
@@ -235,18 +230,6 @@ class HyperSphere(Model):
         self.fitted             = False
 
 
-    def is_fitted(self):
-        """Specify whether model has been fitted.
-            
-        Returns:
-
-            (bool): Whether the model has been fitted.
-
-        """
-
-        return self.fitted
-
-
     def set_R(self, double R):
         """Set the radius of the hyper-sphere and calculate its volume.
 
@@ -671,17 +654,6 @@ class KernelDensityEstimate(Model):
 
         return
 
-
-    def is_fitted(self):
-        """Specify whether model has been fitted.
-            
-        Returns:
-
-            (bool): Whether the model has been fitted.
-
-        """
-
-        return self.fitted
         
 
     def set_scales(self, np.ndarray[double, ndim=2, mode="c"] X):
@@ -1417,18 +1389,6 @@ class ModifiedGaussianMixtureModel(Model):
         self.fitted              = False
 
 
-    def is_fitted(self):
-        """Specify whether model has been fitted.
-
-        Returns:
-
-            (bool): Whether the model has been fitted.
-
-        """
-
-        return self.fitted
-
-
     def set_weights(self, np.ndarray[double, ndim=1, mode="c"] weights_in):
         """Set the weights of the Gaussians.
         
diff --git a/harmonic/model_nf.py b/harmonic/model_nf.py
new file mode 100644
index 00000000..59a36b81
--- /dev/null
+++ b/harmonic/model_nf.py
@@ -0,0 +1,418 @@
+from typing import Sequence, Union
+from harmonic import model as md
+from harmonic import flows
+import jax
+import jax.numpy as jnp
+import optax
+from tqdm import trange
+from flax.training import train_state  # Useful dataclass to keep train state
+
+
+def make_training_loop(model):
+    """
+    Create a function that trains an NF model.
+
+    Args:
+        model: a neural network model with a `log_prob` method.
+
+    Returns:
+        train_flow (Callable): wrapper function that trains the model.
+
+    Note:
+        Adapted from github.com/kazewong/flowMC
+    """
+
+    def train_step(batch, state, variables):
+        def loss(params):
+            log_det = model.apply(
+                {"params": params, "variables": variables},
+                batch,
+                temperature=1.0,
+                method=model.log_prob,
+            )
+            return -jnp.mean(log_det)
+
+        grad_fn = jax.value_and_grad(loss)
+        value, grad = grad_fn(state.params)
+        state = state.apply_gradients(grads=grad)
+        return value, state
+
+    train_step = jax.jit(train_step)
+
+    def train_epoch(rng, state, variables, train_ds, batch_size):
+        """Train for a single epoch."""
+        train_ds_size = len(train_ds)
+        steps_per_epoch = train_ds_size // batch_size
+        if steps_per_epoch > 0:
+            perms = jax.random.permutation(rng, train_ds_size)
+
+            perms = perms[: steps_per_epoch * batch_size]  # skip incomplete batch
+            perms = perms.reshape((steps_per_epoch, batch_size))
+            for perm in perms:
+                batch = train_ds[perm, ...]
+                value, state = train_step(batch, state, variables)
+        else:
+            value, state = train_step(train_ds, state, variables)
+
+        return value, state
+
+    def train_flow(
+        rng,
+        state,
+        variables,
+        data: jnp.ndarray,
+        num_epochs: int,
+        batch_size: int,
+        verbose: bool = False,
+    ):
+        loss_values = jnp.zeros(num_epochs)
+        if verbose:
+            pbar = trange(num_epochs, desc="Training NF", miniters=int(num_epochs / 10))
+        else:
+            pbar = range(num_epochs)
+        best_state = state
+        best_loss = 1e9
+        for epoch in pbar:
+            # Use a separate PRNG key to permute image data during shuffling
+            rng, input_rng = jax.random.split(rng)
+            # Run an optimization step over a training batch
+            value, state = train_epoch(input_rng, state, variables, data, batch_size)
+            loss_values = loss_values.at[epoch].set(value)
+            if loss_values[epoch] < best_loss:
+                best_state = state
+                best_loss = loss_values[epoch]
+            if verbose:
+                if num_epochs > 10:
+                    if epoch % int(num_epochs / 10) == 0:
+                        pbar.set_description(f"Training NF, current loss: {value:.3f}")
+                else:
+                    if epoch == num_epochs:
+                        pbar.set_description(f"Training NF, current loss: {value:.3f}")
+
+        return rng, best_state, loss_values
+
+    return train_flow, train_epoch, train_step
+
+
+class FlowModel(md.Model):
+    """Normalizing flow model to approximate the log_e posterior by a normalizing
+    flow."""
+
+    def __init__(
+        self,
+        ndim_in: int,
+        learning_rate: float = 0.001,
+        momentum: float = 0.9,
+        standardize: bool = False,
+        temperature: float = 0.8,
+    ):
+        if ndim_in < 1:
+            raise ValueError("Dimension must be greater than 0.")
+
+        self.ndim = ndim_in
+        self.fitted = False
+        self.state = None
+
+        # Model parameters
+        self.learning_rate = learning_rate
+        self.momentum = momentum
+        self.standardize = standardize
+        self.temperature = temperature
+        self.flow = None
+
+    def create_train_state(self, rng):
+        params = self.flow.init(rng, jnp.ones((1, self.ndim)))["params"]
+        tx = optax.adam(self.learning_rate, self.momentum)
+        return train_state.TrainState.create(
+            apply_fn=self.flow.apply, params=params, tx=tx
+        )
+
+    def fit(
+        self,
+        X: jnp.ndarray,
+        batch_size: int = 64,
+        epochs: int = 3,
+        key=jax.random.PRNGKey(1000),
+        verbose: bool = False,
+    ):
+        """Fit the parameters of the model.
+
+        Args:
+
+            X (jnp.ndarray (nsamples, ndim)): Training samples.
+
+            batch_size (int, optional): Batch size used when training flow. Default =
+            64.
+
+            epochs (int, optional): Number of epochs flow is trained for. Default = 3.
+
+            key (Union[jax.Array, jax.random.PRNGKeyArray], optional): Key used in
+            random number generation process.
+
+            verbose (bool, optional): Controls if progress bar and current loss are
+            displayed when training. Default = False.
+
+
+        Raises:
+
+            ValueError: Raised if the second dimension of X is not the same as ndim.
+
+            NotImplementedError: If called directly from FlowModel class.
+
+        """
+
+        if self.flow is None:
+            raise NotImplementedError(
+                "This method cannot be used in the FlowModel class directly. Use a class with a specific flow implemented (RealNVPModel, RQSplineModel)."
+            )
+
+        if X.shape[1] != self.ndim:
+            raise ValueError("X second dimension not the same as ndim.")
+
+        key, rng_model, rng_init, rng_train = jax.random.split(key, 4)
+
+        variables = self.flow.init(rng_model, jnp.ones((1, self.ndim)))
+        state = self.create_train_state(rng_init)
+
+        # set up standardisation
+        if self.standardize:
+            # self.pre_offset = jnp.min(X, axis = 0) #maxmin
+            self.pre_offset = jnp.mean(X, axis=0)
+            # self.pre_amp = (jnp.max(X, axis=0) - self.pre_offset)
+            self.pre_amp = jnp.sqrt(jnp.diag(jnp.cov(X.T)))
+
+            X = (X - self.pre_offset) / self.pre_amp
+
+        train_flow, train_epoch, train_step = make_training_loop(self.flow)
+        rng, state, loss_values = train_flow(
+            rng_train, state, variables, X, epochs, batch_size, verbose=verbose
+        )
+
+        self.state = state
+        self.variables = variables
+        self.fitted = True
+
+        return
+
+    def predict(self, x: jnp.ndarray) -> jnp.ndarray:
+        """Predict the value of log_e posterior at batched input x.
+
+        Args:
+
+            x (jnp.ndarray (batch_size, ndim)): Batched sample for which to
+                predict posterior values.
+
+        Returns:
+
+            jnp.ndarray (batch_size,): Predicted log_e posterior value.
+
+        Raises:
+
+            ValueError: If temperature is negative or greater than 1.
+
+        """
+
+        temperature = self.temperature
+
+        if temperature > 1:
+            raise ValueError("Scaling must not be greater than 1.")
+
+        if temperature <= 0:
+            raise ValueError("Scaling must be positive.")
+
+        if self.standardize:
+            x = (x - self.pre_offset) / self.pre_amp
+
+        # TODO: support 1D arrays here, not at the user level.
+        logprob = self.flow.apply(
+            {"params": self.state.params, "variables": self.variables},
+            x,
+            temperature,
+            method=self.flow.log_prob,
+        )
+
+        if self.standardize:
+            logprob -= sum(jnp.log(self.pre_amp))
+
+        return logprob
+
+    def sample(self, n_sample: int, rng_key=jax.random.PRNGKey(0)) -> jnp.ndarray:
+        """Sample from trained flow.
+
+        Args:
+            nsample (int): Number of samples generated.
+
+            rng_key (Union[jax.Array, jax.random.PRNGKeyArray]), optional): Key used in random number generation process.
+
+        Raises:
+
+            ValueError: If temperature is negative or greater than 1.
+
+        Returns:
+
+            jnp.array (n_sample, ndim): Samples from fitted distribution.
+        """
+
+        temperature = self.temperature
+
+        if temperature > 1:
+            raise ValueError("Scaling must not be greater than 1.")
+
+        if temperature <= 0:
+            raise ValueError("Scaling must be positive.")
+
+        samples = self.flow.apply(
+            {"params": self.state.params, "variables": self.variables},
+            rng_key,
+            n_sample,
+            temperature,
+            method=self.flow.sample,
+        )
+
+        if self.standardize:
+            samples = (samples * self.pre_amp) + self.pre_offset
+
+        return samples
+
+
+# ===============================================================================
+# NVP Flow
+# ===============================================================================
+
+
+class RealNVPModel(FlowModel):
+    """Normalizing flow model to approximate the log_e posterior by a NVP normalizing flow."""
+
+    def __init__(
+        self,
+        ndim_in: int,
+        n_scaled_layers: int = 2,
+        n_unscaled_layers: int = 4,
+        learning_rate: float = 0.001,
+        momentum: float = 0.9,
+        standardize: bool = False,
+        temperature: float = 0.8,
+    ):
+        """Constructor setting the hyper-parameters of the model.
+
+        Args:
+
+            ndim_in (int): Dimension of the problem to solve.
+
+            n_scaled_layers (int, optional): Number of layers with scaler in RealNVP
+            flow. Default = 2.
+
+            n_unscaled_layers (int, optional): Number of layers without scaler in
+            RealNVP flow. Default = 4.
+
+            learning_rate (float, optional): Learning rate for adam optimizer used in
+            the fit method. Default = 0.001.
+
+            momentum (float, optional): Learning rate for Adam optimizer used in the fit
+            method. Default = 0.9
+
+            standardize(bool, optional): Indicates if mean and variance should be
+            removed from training data when training the flow. Default = False
+
+            temperature (float, optional): Scale factor by which the base distribution
+            Gaussian is compressed in the prediction step. Should be positive and <=1.
+            Default = 0.8.
+
+        Raises:
+
+            ValueError: If the ndim_in is not positive.
+
+            ValueError: If n_scaled_layers is not positive.
+
+        """
+
+        if n_scaled_layers <= 0:
+            raise ValueError("Number of scaled layers must be greater than 0.")
+
+        FlowModel.__init__(
+            self,
+            ndim_in,
+            learning_rate,
+            momentum,
+            standardize,
+            temperature,
+        )
+
+        # Model parameters
+        self.n_scaled_layers = n_scaled_layers
+        self.n_unscaled_layers = n_unscaled_layers
+        self.flow = flows.RealNVP(ndim_in, self.n_scaled_layers, self.n_unscaled_layers)
+
+
+# ===============================================================================
+# Rational Quadratic Spline Flow
+# ===============================================================================
+
+
+class RQSplineModel(FlowModel):
+    """Rational quadratic spline flow model to approximate the log_e posterior by a normalizing flow."""
+
+    def __init__(
+        self,
+        ndim_in: int,
+        n_layers: int = 8,
+        n_bins: int = 8,
+        hidden_size: Sequence[int] = [64, 64],
+        spline_range: Sequence[float] = (-10.0, 10.0),
+        standardize: bool = False,
+        learning_rate: float = 0.001,
+        momentum: float = 0.9,
+        temperature: float = 0.8,
+    ):
+        """Constructor setting the hyper-parameters and domains of the model.
+
+        Must be implemented by derived class (currently abstract).
+
+        Args:
+
+            ndim_in (int): Dimension of the problem to solve.
+
+            n_layers (int, optional): Number of layers in the flow. Defaults to 8.
+
+            n_bins (int, optional): Number of bins in the spline. Defaults to 8.
+
+            hidden_size (Sequence[int], optional): Size of the hidden layers in the
+            conditioner. Defaults to [64, 64].
+
+            spline_range (Sequence[float], optional): Range of the spline. Defaults to
+            (-10.0, 10.0).
+
+            standardize (bool, optional): Indicates if mean and variance should be
+            removed from training data when training the flow. Defaults to False.
+
+            learning_rate (float, optional): Learning rate for adam optimizer used in
+            the fit method. Defaults to 0.001.
+
+            momentum (float, optional): Learning rate for Adam optimizer used in the fit
+            method. Defaults to 0.9.
+
+            temperature (float, optional): Scale factor by which the base distribution
+            Gaussian is compressed in the prediction step. Should be positive and <=1.
+            Defaults to 0.8.
+
+        Raises:
+
+            ValueError: If the ndim_in is not positive.
+
+        """
+
+        FlowModel.__init__(
+            self,
+            ndim_in,
+            learning_rate,
+            momentum,
+            standardize,
+            temperature,
+        )
+
+        # Flow parameters
+        self.n_layers = n_layers
+        self.hidden_size = hidden_size
+        self.n_bins = n_bins
+        self.spline_range = spline_range
+        self.flow = flows.RQSpline(ndim_in, n_layers, hidden_size, n_bins, spline_range)
diff --git a/notebooks/basic_usage.ipynb b/notebooks/basic_usage.ipynb
index c4044483..feca65b9 100644
--- a/notebooks/basic_usage.ipynb
+++ b/notebooks/basic_usage.ipynb
@@ -1,29 +1,10 @@
 {
-  "nbformat": 4,
-  "nbformat_minor": 0,
-  "metadata": {
-    "colab": {
-      "name": "basic_usage.ipynb",
-      "provenance": [],
-      "collapsed_sections": [],
-      "toc_visible": true,
-      "authorship_tag": "ABX9TyOuisDf5/8oQjWLVUhGuTZA",
-      "include_colab_link": true
-    },
-    "kernelspec": {
-      "name": "python3",
-      "display_name": "Python 3"
-    },
-    "language_info": {
-      "name": "python"
-    }
-  },
   "cells": [
     {
       "cell_type": "markdown",
       "metadata": {
-        "id": "view-in-github",
-        "colab_type": "text"
+        "colab_type": "text",
+        "id": "view-in-github"
       },
       "source": [
         "<a href=\"https://colab.research.google.com/github/astro-informatics/harmonic/blob/main/notebooks/basic_usage.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
@@ -48,37 +29,37 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 1,
       "metadata": {
         "id": "XSCBJ_S4XvDe"
       },
+      "outputs": [],
       "source": [
         "%%capture\n",
         "# Install packages\n",
-        "%pip install harmonic emcee corner getdist"
-      ],
-      "execution_count": 1,
-      "outputs": []
+        "%pip install harmonic emcee getdist"
+      ]
     },
     {
       "cell_type": "code",
+      "execution_count": 2,
       "metadata": {
         "id": "VZ7rtL7kcEV-"
       },
+      "outputs": [],
       "source": [
         "%%capture\n",
         "# Google Colaboratory-specific: clone useful utils module from harmonic repo\n",
-        "!git clone https://github.com/astro-informatics/harmonic.git\n",
-        "import sys\n",
-        "sys.path.append(\"harmonic/examples\")"
-      ],
-      "execution_count": 2,
-      "outputs": []
+        "!git clone https://github.com/astro-informatics/harmonic.git"
+      ]
     },
     {
       "cell_type": "code",
+      "execution_count": 1,
       "metadata": {
         "id": "1OtVkBf7OAIF"
       },
+      "outputs": [],
       "source": [
         "%%capture\n",
         "# Import modules\n",
@@ -87,10 +68,9 @@
         "import harmonic as hm\n",
         "from functools import partial\n",
         "import emcee\n",
-        "import utils"
-      ],
-      "execution_count": 3,
-      "outputs": []
+        "from harmonic import model_nf\n",
+        "import jax.numpy as jnp"
+      ]
     },
     {
       "cell_type": "markdown",
@@ -114,9 +94,11 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 2,
       "metadata": {
         "id": "8PgO6f4VQSpD"
       },
+      "outputs": [],
       "source": [
         "def ln_posterior(x, inv_cov):\n",
         "    \"\"\"Compute log_e of posterior of n dimensional multivariate Gaussian.\n",
@@ -131,10 +113,8 @@
         "\n",
         "    \"\"\"\n",
         "\n",
-        "    return -np.dot(x,np.dot(inv_cov,x))/2.0"
-      ],
-      "execution_count": 4,
-      "outputs": []
+        "    return -jnp.dot(x,jnp.dot(inv_cov,x))/2.0"
+      ]
     },
     {
       "cell_type": "markdown",
@@ -152,12 +132,14 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 3,
       "metadata": {
         "id": "us1kBuWlQZTy"
       },
+      "outputs": [],
       "source": [
         "# Define parameters for emcee sampling\n",
-        "ndim = 2                    # Dimensions\n",
+        "ndim = 5                    # Dimensions\n",
         "nchains = 200                # total number of chains to compute\n",
         "samples_per_chain = 5000    # number of samples per chain\n",
         "nburn = 2000                # number of samples to discard as burn in\n",
@@ -166,9 +148,7 @@
         "inv_cov = np.zeros((ndim,ndim))\n",
         "diag_cov = np.ones(ndim)\n",
         "np.fill_diagonal(inv_cov, diag_cov) "
-      ],
-      "execution_count": 5,
-      "outputs": []
+      ]
     },
     {
       "cell_type": "markdown",
@@ -181,9 +161,11 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 4,
       "metadata": {
         "id": "Eel3bSORQZW0"
       },
+      "outputs": [],
       "source": [
         "# initialise random seed\n",
         "np.random.seed(1)\n",
@@ -199,9 +181,7 @@
         "# Collect samples into contiguous numpy arrays (discarding burn in)\n",
         "samples = np.ascontiguousarray(sampler.chain[:,nburn:,:])\n",
         "lnprob = np.ascontiguousarray(sampler.lnprobability[:,nburn:])"
-      ],
-      "execution_count": 6,
-      "outputs": []
+      ]
     },
     {
       "cell_type": "markdown",
@@ -230,16 +210,16 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 5,
       "metadata": {
         "id": "eSxxNW1KQZZc"
       },
+      "outputs": [],
       "source": [
         "# Instantiate harmonic's chains class \n",
         "chains = hm.Chains(ndim)\n",
         "chains.add_chains_3d(samples, lnprob)"
-      ],
-      "execution_count": 7,
-      "outputs": []
+      ]
     },
     {
       "cell_type": "markdown",
@@ -252,16 +232,16 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 6,
       "metadata": {
         "id": "efPNgW8qQZcW"
       },
+      "outputs": [],
       "source": [
         "# Split the chains into the ones which will be used to train the machine \n",
         "# learning model and for inference\n",
         "chains_train, chains_infer = hm.utils.split_data(chains, training_proportion=0.5)"
-      ],
-      "execution_count": 8,
-      "outputs": []
+      ]
     },
     {
       "cell_type": "markdown",
@@ -271,27 +251,81 @@
       "source": [
         "## Train the machine learning model\n",
         "\n",
-        "For simplicity we will manually select hyper-parameters and train a machine learning model using `chains_train`. See other tutorials that use cross-validation to select hyper-parameters and different models.\n",
-        "\n",
-        "In this case, we simply select the model we wish to adopt and fit the model."
+        "We simply select the model we wish to adopt and fit the model."
       ]
     },
     {
       "cell_type": "code",
+      "execution_count": 7,
       "metadata": {
         "id": "WrB47hA3QZfM"
       },
+      "outputs": [
+        {
+          "name": "stderr",
+          "output_type": "stream",
+          "text": [
+            "Training NF, current loss: 6.950: 100%|██████████| 20/20 [01:51<00:00,  5.58s/it]\n"
+          ]
+        }
+      ],
       "source": [
-        "domains = [np.array([1E-1,1E1])]  # hyper-sphere bounding domain\n",
+        "# Select RealNVP Model\n",
         "\n",
-        "# Select model\n",
-        "model = hm.model.HyperSphere(ndim, domains)\n",
+        "n_scaled_layers = 2\n",
+        "n_unscaled_layers = 4\n",
+        "temperature = 0.9\n",
         "\n",
+        "model = model_nf.RealNVPModel(ndim, standardize=True, temperature=temperature)\n",
+        "epochs_num = 20\n",
         "# Train model\n",
-        "fit_success = model.fit(chains_train.samples, chains_train.ln_posterior)"
+        "model.fit(jnp.array(chains_train.samples), epochs=epochs_num, verbose= True)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "-Nh_lQnzbGGv"
+      },
+      "source": [
+        "## Posterior triangle plot\n",
+        "\n",
+        "Let's also plot slices of the posterior using these samples to see what we're working with! It is important for the concentrated flow (here we set temperature equal to 0.9) to be contained within the posterior. If this is not the case, the evidence estimate will not be accurate. If the flow is not contained within the posterior, try reducing the temperature or learning the posterior better by training for longer or changing the model architecture or hyperparameters."
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 8,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 297
+        },
+        "id": "AUOB38wrQxEu",
+        "outputId": "1b1bc316-b9b2-4ae6-fff0-e059bf65e849"
+      },
+      "outputs": [
+        {
+          "data": {
+            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbEAAAGxCAYAAADs0IixAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACtrUlEQVR4nOydd3hUVfrHP3dKZia9J6RRQg29SFWxoMhvdRVXV1x1UUQBBRVRF0GkKaKioNgboivqgrouFkQUlCK9KCKoSEkhvSfT7/39cZMYQsokmRY4n+fJA5m5c+5739w533vOec/7SoqiKAgEAoFA0AbR+NoAgUAgEAhaihAxgUAgELRZhIgJBAKBoM0iREwgEAgEbRYhYgKBQCBoswgREwgEAkGbRYiYQCAQCNosQsQEAoFA0GbR+doAbyLLMllZWYSEhCBJkq/N8UsURaGsrIyEhAQ0Gs884zidTux2u0faFgjcgVarRafTiX6iDXBOiVhWVhbJycm+NqNNkJ6eTlJSktvbLS8vJyMjA5EoRuDvBAYG0q5dOwICAnxtiqARzikRCwkJAdQOOjQ01MfW+CelpaUkJyfX+MqdOJ1OMjIyCAwMJCYmRjzlCvwSRVGw2Wzk5eVx7NgxunTp4rFZCUHrOadErLrTDA0NFSLWBJ4QGLvdjqIoxMTEYDKZ3N6+QOAuTCYTer2eEydOYLPZMBqNvjZJ0ADi8ULgdcQITNAWEKOvtoH4KwkEAoGgzXJOTSf6HYWFHN1bQtyQDgSHnMOjk5MnIT/fO+eKjoaUFO+cy0v07NmThx56iPHjx/vaFIHA6wgR8xUffYQ8Zy5zCp/nQKWOT36Ip2tPva+t8j4nT0K3bmCxeOd8RiMcOeKykF100UVs27aNgIAANBoNKSkpTJ8+ndtvv73FJhw/fpyOHTvy22+/0blz5xa3U83PP//c6jYEgraKmE70BYcOsXvmGgo/3cyq7Ev48Kb/Efryk762yjfk53tPwEA9VzNHfQ899BDl5eUUFRUxc+ZMJk6cyKZNmzxjXzOw2WxuaUfs2RO0ZYSI+QDlnnu5U/M6FQERAPRaPgm+/54fPzvpY8sEjaHVarn55puJiopiz549fP755wwcOJCwsDC6du3KkiVLkGUZUAXmrrvuIj4+npCQEDp06MDy5csBdfoPoG/fvgQHBzN58mQALBYLs2bNIjU1lYiICC688EL27dtXc/558+Zx/vnnM2fOHBISEujXrx8AHTp04I033qg57ocffuCCCy4gIiKCjh07MnPmTKxWa837HTp0YO7cuVxxxRWEhITwzDPPeNRvAoEnEdOJ3mbXLnZW9KTHoGDat696TaejaMIM7p1kZmOmT60TNILD4eD999+nsLCQQYMGcdlll/Hvf/+ba6+9lgMHDnDVVVeh0+m47777WLlyJT/88AMHDx4kOjqa7OxssrKyAHX6r2PHjhw4cOC06cTJkyeTkZHBd999R3x8PK+88gqjR4/m119/JTw8HIDt27dz+eWX88cff9QIZm1OnjzJqFGjePzxx/nmm284ceIE11xzDRaLhWXLltUc9+qrr/LJJ58wdOhQzGazR/0mEHgSMRLzNs8+y5DH/8pbb53+co9po3AUl5N5sMg3dgkaZMmSJYSHhxMfH8+yZctYsWIFq1at4i9/+Qt///vf0el0DBw4kAcffJBXXnkFgICAAMrLyzl06BB2u534+HgGDBjQ4DkKCgpYuXIlL774IklJSeh0OqZOnUpYWBifffZZzXFxcXHMmTMHo9FIYGDgGe289957dO3alfvuu4+AgAC6dOnCY489xmuvvXZalpTbbruNYcOGIUlSve0IBG0FIWLepKSEigO/827GxRgMdd7Tavny/g0kbvnQJ6YJGuaBBx6guLiY/Px89uzZw/jx40lPTyc1NfW04zp37szJk+qU8M0338ykSZN48MEHiY6OZsyYMezZs6fBc/z+++8ADBkyhPDw8JqfzMxMMjIyao5r3759o/vsGrLLbDaTl5dX81rHjh1dd4BA4McIEfMmH3/M2u4P8tPB+juhoAk38J+lYj6xLZCcnMzRo0dPe+3o0aOkVEU9arVaHnjgAXbs2EFmZiY9evTg6quvBurfRBsfHw/Ajz/+SHFxcc1PZWUlM2fOrDmuqQ24ycnJ/PHHH2fYZTKZiImJcbkdgaCtIO5kb7JqFVsDL2Ps2Prfljp24OnMf5D/Y5Z37RI0mwkTJvD555/z0Ucf4XQ62bdvH08//TR33nknAN9++y27d++uSVkUHByMVqsFICYmBo1Gw5EjR2raa9++Pddccw133303J06cAKCsrIwvv/ySU6dOuWzXP/7xD44cOcLy5cux2WwcPXqUOXPmMHHiRJEpRXBWIkTMWxQXQ34+z78bwdChDR82enAR3z5/0GtmCVrGkCFDWLNmDY8//jgRERFcf/313HPPPdx7770A5ObmcuuttxIZGUlMTAzfffcda9asAdS8fIsWLWLixImEh4dz1113AbBq1SoGDhzIZZddRkhICN26deP1119vVsb/9u3bs379ej788ENiY2O55JJLGDNmDE899ZT7nSAQ+AGScg7VxCgtLSUsLIySkhLvJwBes4Yj646R+Y8HueSShg+zHDiC4aF7kb5a5z3bauFJH1ksFo4dO0bHjh3/TKjq55udBecu9d6vAr9DhNh7i3Xr+NAxi5QmtoIZ+3bj48M9GFtahhTq/nIofkdKiioqIu2UQCBoAULEvIGiwJYtbO/QgTtGN334G9Id9Hp/N10nXex52/yBlBQhLAKBoEWINTFvcPAgdOvG519qaNeu6cMvHCmxeXW25+0SCASCNo4QMW/w1VccG3Q9W7a4dvg9TyYyPm+JZ20SCASCswAhYt7g++/53HIptdLgNUpgfCgb7COhGaHVAoFAcC4iRMzTyDIcOcLuzPhGQ+vr8pR5Kic//MFzdgkEAsFZgBAxT3PoEPTowRtvSAwc6PrHLrxIy/cf5TV9oEAgEJzDCBHzNFu2UDxoFFu2QFXCBpeY/mQ8f8t/1XN2CQQCwVmACLH3NJs3s2XAo3zzKVx0kesfC4s1sNUwjBGZmZCY6DHz/IHMYjNFFe4p8NgUEUEBJIabvHIugUDgeYSIeZq9e9nRvnOz1sOquevUI2z7egtBt17vfrv8hMxiM5cs2YTVcWZtLE9g0Gn49oGLhJD5CUlJSTz22GPceuut9b5fVFTETTfdxLZt2wgODiYjIwNJkvj6668ZNWqUd40V+CViOtGTnDgBCQk8/IiWv/61+R8/r7+T3f/NaPrANkxRhc1rAgZgdcjNHvX99NNPjBs3jnbt2hEcHEyHDh248cYb2bt3r4esdD/VVaHdwaZNm5AkCYfD4Zb2GuOVV14hOzubnJyc00rSCATVCBHzJJs3I4+4gN27wdSCB//5y8IY+Mdq99slcJlNmzYxePBg4uLi+OGHHygrK2P//v1cdtllrF59dv1tFEXxijA1h6NHj5KWlobhjAJ8AoGKEDFPsmULv6SM5tlnW/bx+C4hnJSToLDQvXYJXGbSpElcd911PPfcc3To0AFJkggPD2fChAk88cQTNce9/fbb9OrVi9DQUHr16sXKlStr3jt+/DiSJLFy5Ur69u1LSEgIQ4cO5dChQzXHOBwOnn32WdLS0ggJCSEpKYknn3yy5v0dO3Zw0UUXERUVRfv27ZkzZ85pgiNJEsuXL2fEiBEEBwfTu3dvtlTtrn/vvfdYtGgRP/zwA8HBwQQHB7N58+Yau95880369u1LYGAgu3fvZtOmTQwfPpyoqCgiIiK45JJL2L9/PwAnT55kzJgxAISHhxMcHMyiRYsAKC4uZsqUKbRv356oqCj+7//+77TaZuXl5dx+++1ERUWRmJjIc88916jvL7nkElauXMmHH35IcHAwkydPrve4zz//nIEDBxIWFkbXrl1ZsmQJsqyO7mfMmMGNN95Yc+xtt92GVqulqKioxq9BQUFYrdZGbRH4L0LEPMn27eyw9W/RehiAJMHYzBdQtmx1r10Cl/jtt9/49ddf+ec//9nocR999BH33HMPzz33HEVFRSxbtoy7776b//73v6cd9+6777Ju3Try8vJISkpiypQpNe/NmzePl156ibfffpuSkhJ+/PFHLrzwQgCOHDnCpZdeyuTJk8nJyeH777/nf//732kiB/DGG2+wcuVKiouLufTSS7npppsAuOmmm5g1axbDhg2jvLyc8vJyLrjggprPvfXWW3z22WeUl5fTv39/9Ho9S5Ys4dSpU5w8eZLOnTtz9dVXY7PZSElJ4csvvwRU0SovL2fWrFkoisLYsWMpLS1l3759ZGVl0bt3b6688krsdjsA999/P/v27ePAgQP8+uuv7N+/n+zshtOrffvtt9x0003ccMMNlJeX88orr5xxzK5duxg7diz/+te/KCgo4P333+fZZ5/l+eefB+Cyyy5jw4YNNeVs1q9fT2pqKhs2bADg66+/5sILLxQjvTaMEDFPUVAABgPX/sNIVZ3EZqPRQPsUheOfifpiviA3NxdQgw8a47XXXuP222/n0ksvRavVMmrUKG6//fYzOt1HH32Udu3aYTQaue2229i1axegTuM999xzLF68mMGDB6PRaIiMjGTYsGEAvPjii1x11VWMGzcOnU5H+/bteeihh1ixYsVp7c+YMYPOnTuj0+m44447OHnyJDk5OU1e56OPPkpycjJarRaDwcCIESMYPnw4AQEBhISE8OSTT3Ly5MnTinjWZd++fWzdupVXX32VyMhIDAYDixYt4tixY+zYsQNZlnnnnXeYP38+SUlJBAUFsWzZspoRU0t54403+Mtf/sLf//53dDodAwcO5MEHH6zx/ciRIykrK2Pv3r0cPHgQvV7PlClT+OqrrwBV1C6//PJW2SDwLULEPMWWLTBiBMePQ2Rky5t57kUd0Qe+cZtZAteJjY0FaDKgID09ndTU1NNe69y5MydPnl53JyEhoeb/QUFBmM1mHA4H+fn5lJeX061bt3rb/+233/jkk08IDw+v+ZkyZcoZo5i67YNaHbopOnbseNrvP/74I1dddRWJiYmEhobWvF8t6g3Z6HA4SEpKqrExKioKUP2Tl5eH1Wo97VxhYWFEtubLQdO+N5lMnH/++axfv75GsC6//HK++uorysrK2L59uxCxNo4QMU+xZQtlgy5mwoTWNdN1WBRlpQpUVrrHLoHLdOnSha5du/Luu+82elxycjJHjx497bWjR4+S4mJ5mejoaIKDg/n111/rfT8+Pp5//OMfFBcX1/yUlpZSXl7u2oUAGk3DX/W6711//fWkpqZy8OBBSktLOXbsGEDNlFx9bcXHxxMQEEBeXt5pdprNZm688UZiYmIwGAwcP3685jMlJSU1a1MtxRXfX3bZZTUiNnr0aHr27ImiKLz00kvExMTQs2fPVtkg8C1CxDzFli3sNl3Aeee1rpmSEhhb8Dps3+4euwTN4tVXX2X16tXcf//9nDhxAkVRKC0t5Z133mH27NkATJw4kbfeeotNmzbhdDr59ttvefPNN7nTxXlkSZKYNm0aDz/8MLt370ZRFAoLC/nhBzV35l133cWaNWtYvXo1NpsNp9PJ77//zrp1rlf/jo+P5+TJk1hcqKBdXdU7LCyMwsJCZsyYcUZbwGnTi+effz69evViypQpNSO2oqIiPvroIyorK9FoNNx8883MmzePzMxMKioqmDFjBpIkuXwN9TFhwgQ+//xzPvroI5xOJ/v27ePpp58+zfeXX34527ZtY+vWrVx66aU1rz3xxBNcdtllrTq/wPcIEfMElZVQUUHfkeE8/HDrmoqMBLMhAvO3IhmwL7jooovYsWMHmZmZDB48mJCQEPr06cO6deu47rrrAHXk8swzz3DXXXcRHh7OtGnTeO6557j22mtdPs+CBQuYOHEiN910U805Nm/eDMB5553H119/zeuvv05iYiJRUVFcd911nDhxwuX2b7jhBrp160ZCQgLh4eE1kYv18dZbb7F69eqaKMrqaMRqunbtyrRp07j44osJDw9n8eLFaLVavv76awIDAxkyZAghISH07duXTz75pEaoli5dSu/evenduzddu3ald+/eNYLYUoYMGcKaNWt4/PHHiYiI4Prrr+eee+7h3nvvrTmmX79+hIeH06tXL8LDwwEYPXo0JSUlYirxLEBSqucIzgFKS0sJCwuredL0GJs2wXvvcXz266SkqAEarWHrmlP0f+F2Ajd94RbzGsOTPrJYLBw7doyOHTtiNBoBkbFD4L/Ud78K/A+RdsoTbNmCMuJ8Lr8cDh9ufXPDr42nbFYu2GwQEND6Bv2IxHAT3z5wkcidKBAIWoQQMU+wZQsnHn6V9u1bPwoDOPSLxGzzq/x3924YPrz1DfoZieEmISwCgaBFiDUxd+N0wrFjhPdJoc5e1BbTowf8Yk9F2fSdexoUCASCswQhYu7mxx+hTx8UJPr3d0+TGg08Pa8Sx6aGF+MFAoHgXESImLvZsgXOP58rroBWboE5jb9OagfHj4OfJWhtCedQLJGgDSPu07aBEDF3s2UL5kEXYDa3LlNHXb7fLDFNXgZtqPxHXbRVpa1tNu8EcQgEraGyKsGAXq/3sSWCxhCBHe5EUeDAAZw9+7Q4c31D9O8P91r7wfcrYfBg9zbuJXQ6HYGBgeTl5aHX6xvNIiEQ+ApFUaisrCQ3N5fw8PCahy+BfyJEzJ0cPw4pKehNOtxddDYkBO6dbEPZ9B3SAw+4t3EvIUkS7dq149ixY83aqCsQ+ILw8PBWb8YWeB4hYu7k++/h/PO58UZ47DFIS3Nv87fOSkDp9qsaAdlGnw4DAgLo0qWLmFIU+DV6vV6MwNoIQsTcyTffoNw5iUOroIGE5K3if2sl9ukWMPfAARgwwP0n8BIajUZkQBAIBG5BLEq4C0WBnTux9x/MvHmeGSj17g1bGQ5ff+3+xgUCgaANIkTMXRw+DKmp6Ex6xo3zzCk6dIArrw+E9es9cwKBQCBoYwgRcxfffAOXXsott8BPP3nmFJIE98yPgtxcqKjwzEkEAoGgDSFEzF1s2IBy6Sh271bTRHmKzz6DxSGPq5nyBQKB4BxHiJg7cDrhl1+wdunFrFmg82C4TN++sK5sBHz1ledOIhAIBG0EIWLu4IcfYNAgDCYN48d79lTJyfCXG4LFSEwgEAgQIuYe1q6Fv/6V66+HP/7w/OkefMQAcXHw66+eP5lAIBD4MULE3MG6dVguuoKff4aOHT1/ui1b4BHdYvjkE8+fTCAQCPwYIWKt5bffICaGCl0Yc+eqEYSepl8/+O+xPiif/NfzJxMIBAI/RohYa6maSoyMxGP7w+oSHAx3TNFjkUyQnu6dkwoEAoEfIkSstaxejXLNWIYOBbvde6e9914wXfcXMaUoEAjOaYSItYbffwejkd05ySQngzfLDpWXwxVr74L33/feSQUCgcDPECLWGlatgn/8A4MBvF0dJTgYzIqJo9qu8PPP3j25QCAQ+AlCxFqKLMPq1chj/0ZaGgwd6n0THnkELFffACtWeP/kAoFA4AcIEWspGzZA//5sPBDJ1Km+MeGyy6D7XZcgr/0cRH0ugUBwDiJErKW88AJMncrLL8PNN/vOjHseMrKx733q1KZAIBCcYwgRawm//QZ5eTB4MDffDCNG+M6Uf/4Tni34Jzz3nDrFKRAIBOcQQsRawoIFMGsWx47B1Vd7Z4NzQwwZAhOnmlD69Yf//td3hggEAoEPECLWXA4dgt9+I3/olfzlL97dG9YQY8fCkZsXwrx5Ym1MIBCcUwgRaw6KAnffDU8/zZJnJO65BwICfG2UataUxxLZOWAyLF/ua3MEAoHAa3iw8tVZyGuvQefOcMEF3N8NIiN9bZCKJMGSJfDwQ5NYv38gjB4NvXr52iyBQCDwOGIk5ip798LLL1O58BkeeQRiYjxb/LK5DBwIaz7RwltvqdEeJSW+NkkgEAg8jhAxVzh8GG65Bee7q7hjRijh4Q7mz5+H1Wr1tWU1WK1Wnn12Hi9s6807g5fDNddARYWvzRIIBAKPIkSsKb7/Hq69Ft59F2tqGiNGwB13VDJ//ny/E7H58+dzzTVmXvlxBB92nQOXXAInTvjaNIFAIPAYQsQaIjcXpk6Ff/2LHxZuYMILAzAa4a67fBtS3xShofD11zBiziVUznuKPy6bBE8+CaWlvjZNIBAI3I4frer4GIcD/vgDtm5F/mIdRw5Y6P7QX1lzwXO8/qqWxYtB00YkPyhI/TlcPpJ/hp5P/Nt/sOLN0YQO7o50xWh05/VXA1S0Wl+bKhAIBK3inBIxp9MJwMS0L+luLOSB+PdYcfISviwYTJDOyoohr/Jh4K0s2fccqT1NvPkXicsDKxg9Rv189WCmtOo/pX40uqnPpoQE+PobOHAgDqn753z/3iGm3hePbLHybuyVREglPFA4kwC9xMSO3zAs4QTTD1wDQGFhoS8uo02gKAplZWUkJCSgaStPNgLBWYqkKIriayO8xa5duxg8eLCvzRCcJaSnp5OUlORrMwSCc5pzaiTWuXNnAH7++WdCQ0Nb3E5ZWRlpaWkcOnSIkJAQd5nXKtxlU2ZmJkOHDiU9Pb1VPjqbKS0tJTk52W/+9gLBucw5JWLaqjWgpKSkVnXQ1VN2iYmJftPRu9um0NBQv7k2f0Xy5wgfgeAcQUzoCwQCgaDNIkRMIBAIBG0WIWItwGAwMHfuXAwGg69NqcEfbRIIBAJPI0SsBRgMBubNm+dXguGPNrWaU6d4tturdAv4g9WjXgWLxdcWCQQCP0OImMAvMRdZOH7FZO5flsL27I4UBSXCpEm+NksgEPgZQsQEfsmLN21lZeR0GDOGiEiJO//7F35LN8KXX/raNIFA4EcIERP4Hc6iUt78pgP3v3/eny9KEvcpz7JzxodqFVCBQCBAiJjAD9H+53023vNfQuKDTnt9yowg3nL8E9at85FlAoHA3xAiJvA71j17iMjJfz/j9dGjYeaTEfDGGz6wSiAQ+CNtOmPHmjVr+OCDD9izZw/Z2dkkJSVx5ZVXMmfOHCIjI31t3pnYbGqJl4ICeO899XenU63toteDyaTWeomLO2czzFv3/8J9mQ9wqGPyGe/p9aA/rx/HZlvomJenltcWCATnNG1axJYsWUJSUhILFiwgJSWFX375hXnz5vHll1+yb98+TCaTbw2UZbUo5dKlqnBJEoSEqHVSTCb1/1qtusZjt0NlJcyfr6bLDw5W09A/8ojae58jbH5hPyMH9Guw7M3efRJfRy7k+ffeg/vu86ptAoHA/2jTWezz8vKIqfM0vn79ekaPHs0777zDLbfcctp7paWlhIWFUVJS4tm8gDYbzJkDJ09CeDi0awdRUa6LkaJASQlkZUFOjjrimD8fIiI8Z3MVGRkZJCcne95HDaAMH0HFyjUEd2lX7/uVlXBePxsH40Yhbf7ey9apeO0+EggETdKmR2J1BQxg0KBBgNoZex1FUcXr6FFISoLzzz9NuCwylMkaLIqETZGQAQkIkBSMkkKoRsaoQR2xhYerP927w6lTMG2aWrZ58WL137OR3Fw+yx/KVQ0IGEBgIHy5IQCuNatTs7GxXjRQIBD4G21axOpj48aNAPTo0aPBY+oWszQYDK3PdFFRoQqN0QgjRkBAAGZZ4p3EwRSaHZjtTvQaCaNeQ4BWg06SqE6C7pAVrE4Zs13GISuEGLREmvTcfHI7Bo0GEhPVqcWsLHXDb6dO8Nhj4IYs6larFavVWvN7WVlZq9tsKafe+5YnLfdyVRPHBQXB7+ffSpf//Q8mTvSKbQKBwD9p09OJdSksLGTgwIGEhISwb9++mtIr1VRPA9Vl7ty5zJs3r+UnzsqCGTOgRw+Ij+eUQ8Pbsf2xOmRigvREmXQE6jUule6QFYUyq5MCs4NCswODViImSM+t6TvQS6hrZz/9BBoNLF/e6gCQefPmMX/+/DNe98VU2QcXvMjB1Kt57O3GC01u2wYvLyriXe2t8Omn3jGuFmI6USDwH84aEbNYLIwZM4b9+/ezdetW0tLSzjimuvOpW/CxVSOxRYvg4EEYOJCCwDBejuqHTiORHBZAqKH1A91Ku5Occjv5lXZigvTceWoXBhT47TcoLoZXX22VkNUdiWVmZpKWluaTDtqe1hfL9zsJiW78b2GzQd++Coec3ZF+Puj1wBchYgKB/3BWTCfa7Xauu+46du7cyfr16+sVsNq4reDj4sXw888og4fwUqeR5JvtdIkwEmJwX3h8oF5Lxwgt7cMNnCqz8XhoP1LCDEyUJPj1V7j7bnj55RZPLdYV8LpTrV4jM5PvdJcyqgkBAwgIgP/+V4LHhsDOner0rUAgOCdp85udHQ4H48aNY8OGDXz66aeM8FaHVlAABw4gDzqPJcnnY3XK9IsPcquA1UYjSSSGGugXH0SB2cHTUQNxdO6ijsIefNAj5/QmuV/s5tHCe10+Pi4OMgZeDevXe9AqgUDg77RpEZNlmfHjx7N27VpWr17NqFGjvHNimw3uvRelbz+eSbkAo15DlygTGldHQznZ9f+4gF6rIS0mkGCDliciB2Lr1Rvy8mDhwlZckO/Z8b8chp4nu3z83r3w+N4rYMMGD1olEAj8nTY9nTh16lRWrVrFjBkziImJYfv27TXvxcTEkJqa6pkTT58Oycm8kHY5WqdCh3Bj059xRaRqHxMX3+ihSaEG9BqJpxjEwwNsaLf/oG6SbqNrNJdmrGTYmhtcPn7QIHjggSCQK6CsTN04LhAIzjnatIh98cUXADzzzDM888wzp703fvx43n77bfef9LHHwGwmJ60fxWYHfeokqT2DBsRr4UHzab/P6VUnu0j15xoRs7jgAGxOhaXJ5/NAaYmaweLNN90Seu9VSkr4xdyB/h3PjBxtiNBQNaaFt4bB9u1w2WWes08gEPgtbVrEjh8/7t0TOp3wyy84Bw7i5ah+9IhuYgqxloDVFa3grPTTfl/I6bkCa0QtJ7tRIUsOM/BzbiVvDbqaCadegUcfbXNTi/K27fwj51kON1N7u3eHogGXELF5sxAxgeAcpU2LmNeZMwdiY3mj6yVEOmUC9Y0EcVQJWG3xqitctan9XnlCcs3n5vQyNTkq6xJl5EB2BdYeaRi2bQWrFVq7eduLHP70CN1TezZ7ALl6NeQevYyZO17yjGECgcDvadOBHV5FluH4cZROqWSV20gKDWj42DoCFpyV3qiA1aX28XVHcPURoNWQGGrgleQRkJICs2e7fC5/oP2hL1n6XPOjOocOhe2Hw9X8kjab+w0TCAR+jxAxV/n9d4iIIEdrJEivQa9twHX1CFhLOUPIGgkOiQ/WU2h2YElpD5mZ6tRnW8BmIzfLQfLghvMlNkT37mqmLwYOhD173G+bQCDwe4SIucqzz0JyMu8nDCAmqIEMEW4UsGpcFTKNJBEXrGdF++EQHa1m9GgL7N3L3wpfx9z0gPMMNBo1x7Jl6EWwebPbTRMIBP6PEDFXkGU1xVNEBMUWJxHGppcS3SFgddtqamoxNkhPXoVdTRa8bJnbzu9Jyr/ZgRwU3OKdAY8/Dv+1XCFETCA4R/GIiB07dswTzfqO7GwIC8OsaNBJoNU0HIHgyhpWSzhNFBsYjRl06p/TEhapim4bSIup3b6V158uafHnhw6FH04kqCm4ZNc3SwsEgrMDj4jYyJEjPdGs73jxRQgLI9+pIbihtFK1hMWdo7DaBGelNymS4UYd2YpOrQydn+8RO9yGomA+dIwBf+vY4iaGD4er/iqpFQR++cWNxgkEgrZAi0PsP/744wbfM7dkgcOfqaiAyEg+az+QID/fSBxi0PJp0gDuPX4YnntO3Zztrxw5wh2VzzH3Fw19+rSsifBwuPhikPeej2brVujZ060mCgQC/6bFInb99dczcuRI6qvkUl5e3iqj/A6zGUwm7E4Fo8H3y4gLD5qZQ/2boE06DbkVdjWlhb+PxLZs4ZBzbKt158474Y6hoxm6+Wn1F4FAcM7QYhHr3Lkzb731Fh06dDjjveTk5DM/0Jax2SAgAEWBxsZhCw+aPTaVWE1wVjrlCQ37V6+VsDsVtfzxyZMetaW1yN9v4ekHhqPVRrWqnQEDYHtJD4aKMHuB4JyjxcOKcePGkZ1df4DBbbfd1mKD/BKnE3Q6NBqQ/TxWQiNJyIqiZuyoVezSH7Hv2s9f7ml9kuYxY6DPAJ26tSAryw2WCQSCtkKzRGzLli01/58/fz5Dhw6t97gFCxa0zip/Q5ZBktBpJOz+rmLVaLX+veE5K4u55Q/w3y9bnx6rUyd1XYwRI2Dr1tbbJhAI2gzNErHRo0fz+eefe8oW/0WjAUXh/07swepoPIy7sak+byArCpIk1Qiv37J1Kzs1wxg0yD3NXX01ZKddArUetAQCwdlPs0Rs2rRpjB07lnffffeM97744gv69u3rNsP8Cp0OHA7CNTIV9oZF7IxyKj7AKYNOwv+TAG/ezK1/KyMpyT3Nde0KO6Sh8MMP7mlQIBC0CZolYosXL+bJJ5/ktttuY+nSpQDs2rWLiy++mCuvvBK9voF0TG0doxEsFkI1Cma7H0/RAXZZRqeV1G0BQU3UOvMhyuYt/HNuR7cNFq+7DqLaB4PdrhbJFAgE5wTNDuyYPn06K1eu5OGHH2bIkCEMHTqUU6dO8eGHH7J7925P2Oh7AgOhvBxJApNeS4XNd0JWM13ZQFkWq0PBoNWoGTvCXC8y6VWKilhRdA0vvOs++4YOVfMoMnQo7NjhtnYFAoF/02wRy83NZefOnYA6Chs2bBiHDh3i+uuvd7txfsOdd0KJmhopyqQjr9J+5jFVojKnl8nj62KNTVtaHDJj0vdBXh48+KBH7WgxW7awI2QUaWnubXbMGHAMu0CsiwkE5xDNErGZM2eSmprKihUrmD17NqtWrWLv3r1MmTKl3k3PZw3JyVBYCMD4jB3kVziavF5fBXiU25xE2M1qYEdLs+p6mu++Y+hIg9uCOqoJCoKfYy4SEYoCwTlEszY7L126lDvvvJNHH32UmJgYABISErj66qvJz8/n/fffJyCgkWKRbRWTSY1QtFgwGI2EGbXkVTqIrVuSJS4ecrKZ08vkkUTA5QnJ6iisgalEqBKxnHRo1/z6XF5j82Zu+3wmuFljb7oJnLHtICMDHA41IEcgEJzVNGskdujQIZYvX14jYAAXXnghGzduZNu2bVx++eVuN9BviI+HU6cAuDNnDydLrOqm4gZw97SiK21V2p0YtRLajAx49FG3ndutlJWxtbAHjyyLdnvTY8fCgIES9O0LBw64vX2BQOB/NEvEUlPrz67Qr18/tmzZQnq6Z1Mu+ZTZsyE9HRSFYI1ClElHZqntzOPqjJLcKWRNjcLyKxwkVhap82rBwW47r1vZto3vo8bSseWJ6xtElmHcONQIDzGlKBCcE7gtm21qairbtm1zV3P+R1iYGqVYUADAlOxd5FTYMde3b6xWkAeoQtZSMav+bFMCpigKuZV2btiyBh55pEXn8gobN5I0KJ5LL3V/0xoN/PEHFPS+CDZtcv8JBAKB3+HWlOxxcXHubM7/WLgQDh8GRUEvQedII0fyK+ufVqwlZLXFrDlUH9+UgAHkVzqIspRhdNrVQBR/ZcMGbpnfmXryRruF226D3Og0OHRIXRcTCARnNb6vK9KWiIuDkBB1WhG45fgPhJt0/FFkaeD4+DPErHpk1Zig1X5/zqUdmxQwWVE4UWxhwuYPYP78FlyYl8jJ4Udbd2Y/27qs9Y0xZQr06KmBQYNg1y6PnUcgEPgHInyruTzzDEyYAFFREBTE1FO7eDJqIOklVpLDGkjzVBW1CH9OMS48aG5QyGr2gTUhXtUcL7bSvjSXYC2QktKsy/EqX3/Ndwk3EhvruVM4nXDHHfDW5ZfD+vUwbJjnTiYQCHxOmx2JrVmzhuuuu46OHTtiMpno0qUL06dPp7BqP5fHMBph8WLYswfsdiQJHizYQ4nVybEiS8P7x6pHZXVGZmf8VI+8XBSw/Eo7FaUV3LrpPVVg/ZmvviJ0UBcuu8xzp9Bq1ZnEvAGjVRETCARnNW1WxJYsWQKoZV/WrVvHjBkzeP/99xk+fDhms/v3aJ1GSgp06KAKmdOJVoJ/FezBqSgczK3E5mw80/1pglb3pxmUWBycyK/g/q/fRJOWpk51+iuyDDt3Mn5uR7dn6qjLzTfDKTlOrchdXOzZkwkEAp/SZkVs7dq1rFmzhltuuYWRI0cyefJk3nnnHY4cOcKaNWs8b8Bjj0FsrLruYrejkWB67m7igwP4MbuSgvpSU7mR/Eo7R3PLmfHtWxiTE2DOHI+er9Xs3Mn+9lfz+FOeTxI9dSr06QNceil8/bXHzycQCHxHmxWx2huuqxlUlccoIyPDO0YsXgwJCWr5j4oKACakb+dfJfvJKrNxJN+MvalRWTORFYXjxRZO5RTzr69eJSQhDh5/3K3n8AgffcSXUTd7dD2sGlmGyZOBa66BTz7x/AkFAoHPaLMiVh8bN24EoEePHo0eV1paetqP1Wpt+Ukfewx69oSdO2s2QwdqFGYV7SXMqGV/dgVZpbZGs3u4SpnVyYHsCkxZGcxc9zLGHl3V87cCq9V6mi/KPFHGRFHgiy8w9unK6NHub74uGg38/jscjR2mTvm25u8rEAj8Gkk5SzL3FhYWMnDgQEJCQti3bx9arfaMY0pLSwmrpzzJ3LlzmTdvXusMMJthxgyorIQePSAiAgCrAq/Gn0d+pZ2YID2xQXoC9Wfa1hglFgeZZTacFZXcufVDomUrLFvmlnph8+bNY349YfklJSWEuiuB8P79MHcufPqpe9pzgXfeUbfLXfzhZLXs85gxbmu7+j5yq48EAkGL8AsR27RpExdffHGTx3Xr1o3Dhw+f8brFYmHMmDHs37+frVu3ktZA5EB155Oenn5a52MwGDC4qwpyZqaaMUOjgdRUNRRfkrAp8HbSEPIq7dicCmEGLSEGLYF6DQatBq1GQlHAISvYnDKVdpkym5MSi4NgZG7Z/T/anToO3bqpeRHdVE3SarWeNhLNzMwkLS3NvR30rFmsNY+ieMAl3HKLe5p0mfXrYfVqeP11tzUpREwg8B/8QsQqKys5efJkk8cZDAY61km6Z7fbGTt2LBs3bmT9+vWMGDGiwc97tfPJzFQzfJSVqetm7drV5DO0K5Dn1PJJ8kAq7U6sDgVZUZAArUYiQKshUCcx9tAmYrKOo8vLhfbt1alDD2dmz8jIIDk52X0+cjqhVy/G9fyRe2fovbpta8YMeHSmjbDze8OPP4KbHlSEiAkE/oNfbHYODAyke/fuzf6cw+Fg3LhxbNiwgc8++6xRAfM6iYnwyivq9OJjj8HBg+qUY0QE+rAwEgIDubuiUBUlRVE7e4tFDRApKVFDw4OCICkJlj6rboBqi3zzDQwdSkqMniFDvHvqgAD4bH0AN40ZA//7H5zNhVsFgnMUvxCxliDLMuPHj2ft2rV89NFHjBo1ytcm1U9gICxapP7fboesLFXc8vLAZvszv59Op44UgoJg1ix15HY21MNasQImT+apkd4/9c03w4YNqAkVH35YiJhAcBbSZnvJqVOnsmrVKmbMmEFMTAzbt2+veS8mJqbBsjE+Ra9XpwWfeMLXlniHU6fg0CHu/PeFPNLR+xmxevZUf6Av5Oer0aP+nBxZIBA0G79YE2sJHTp04MSJE/W+N378eN5+++0zXhdrGU3j1jWxRx4hN7wro9/7J3v3ui0WpVl8+aVa6PkO/dvqlG5VppfWIO4jgcB/aLMjsePHj/vaBEFjVFbCmjUUvL+fOZ18I2AAAwbAv/4Ft+/8B5r+fdXMJvVssxAIBG2Ts2qzs8CPeP55nDf9k659jFx7re/MiIuD22+HoooAtfrAiy/6zhiBQOB2hIgJ3E9hIbzzDh8k3s/Chb42Bu69tyo38l13wXvvQU6Or00SCARuQoiYwP3MmYN92nSeft7IhAm+Nkbl7rvhm+1BMHMmzJ7ta3MEAoGbECImcC+bN8PBgyi33c6yZf5To/P++9WdC85xN6lRip995muTBAKBG2izgR0CPyQvD+66i6NL/0f5EQ0XXeRrg/6kRw94+mmQtBp179qoUdCrl1oXTiAQtFnESKwFWK1W5s2b17rs927G5zZVVMD111P28CJunNWRwkKb3/loyBArd975Blv+iIZXX1VLtYj1MYGgTdNm94m1BHft7/HHfULusqlF+8Ryc9VsGDfdxEdRd1JaCn/7m7/6aADdux/h/fe19Mv6Qo2//+CD6l3RzWjHv65NIDhXEdOJgpajKPCf/8CCBXx/48vYOl3I36qyf5WW+ta0hjnK22+b+fXXYPr9/f+QY+PR3HQT/P3vcN99apowgUDQZhDTiYLm4XTCgQPw1FMo/frDV19xe++dLNpyIQkJvjbONXr0kPn73+HoURgwcQDP3byL0gqtujN61izYt0+9ToFA4PecUyMxZ1XHVNrKYUL151vbjjtxl03FxcUALL9+LVd0yKJLYBbPbR2EpaiSwc4fuFi7mYflx9huu5Yhl/2TxcsC+Ve2k/j46vO71x53UtemmBj48EP1J3v8FA6P+icPTikj5d+HuUd6iI6xlbxm/SeGcBNDU/PonVLKmiO9iQovANRCrIIzURSFsrIyEhIS0GjEc7LAs5xTa2K7du1i8ODBvjZDIDgnSE9PJykpyddmCM5yzqmRWOfOnQH4+eefW7UgX1ZWRlpaGocOHSIkJMRd5rUKd9mUnp7O8OHDhY8aITMzk6FDh55RIVygUlpaSnJyst/83QVnN+eUiGmrCksmJSW1OjoRIDEx0W86MXfbJHzUNKGhoX5zbf6I5Kusz4JzCjFhLRAIBII2ixAxgUAgELRZhIi1AIPBwNy5czEYDL42pQZ/s8nf7AH/tEkgELSOcyo6UWRaaBq3VnY+S/EbH9ls8OSTLFqZgL1LTx7532C0et8/l4rvmcCb+P6OFwgEzUdROHjto+Tmwt2fXEb+yQqWjRKZ+QXnHudUdKJAcLagrP2MCdtuZ8XizvTsJbFsfxLyZaNhYwhcfLGvzRMIvIYYiQkEbQ1ZZueMD+k6sh09e6lh7Fq9huInXuap8T+rOS0FgnMEIWICQVvjiy8YcqGBtz4MPu3lqCGdea/0KvI//t5HhgkE3keImEDQxih4diXvdnyUgIDTX9doYNJEJ4df2OAbwwQCHyBETCBoS+Tlseb3fpyU2tf79l1Pd+T8orWQn+9lwwQC39CmAzvWrFnDBx98wJ49e8jOziYpKYkrr7ySOXPmEBkZ6WvzzqS8XK0k/Oabani0wwGSBFotBATAbbepqdXDwtTXz0UcDrUDLiiAd99Vf1cUdZih18P48RAVBZGR6mvnGh9/zA/RVzL77w28L0nMiniZGW99StRDt3vVNIHAF7RpEVuyZAlJSUksWLCAlJQUfvnlF+bNm8eXX37Jvn37MJlMvjVQUSA9HZYsUTtmvR5CQyEoSP2pyuWI0wlWKyxfrgpdZSWEhEB8PMyeDUajb6/D0xQXw6JFqsDbbKqPgoPBYFD9JEkgy2C3w8svqz6qqFD9EhMDM2eqonYusHo1b39wMXRp+BC5Sze+ee9b/v6Q98wSCHxFmxaxtWvXEhMTU/P7yJEj6dSpE6NHj2bNmjXccsstvjFMlmHuXDh2TO2EExOhe3fQ6bDIUChrWdt+EHanjLMqkEwjgVaSuOLEXkI1TiLKi9HmZKujs8hIWLBAHYGcLSiKWpVy0SJ1tJWYiKP/AAoDgiiVNWzoMICLju9FBwRICgGSgkECkyRjrB6AVVZCbi7cf7/6e6dOMGfO2TuKLS1l98lYKk51ZWTXhg/7662R/PSFrBZ3E5uNBWc5bVrEagtYNYMGDQLUrAo+4eRJtTpwRAScdx6YTJTJEm8nDKKg0oFGguAALSZFwaTToNGoHa6igENW+DylP2a7TEWkjLY9hAdouOnAOqLuu0/tkJ58Uh2ltGUyMmpGmEr3HrzdZzTZZTYsDoUQgxaTXoMOiU0dBuBUwCkrOGQFu6xgc8jYZYUArYaweC3jAvYQ3b4DUkU5/P47/POfsHAhdOjg66t0Pxs38m7QZC4tafyw4cNh+E3lsG4d/L2heUeB4OygTYtYfWzcuBGAHj16NHhM3WrDBoOh9fn0HA51RFBSAv37Q0gI+U4Nr0f2x+6Uiddq6BMXiF7r+jqO3SlTZHbwWs/R2LtfTteSLG644w6kjh3h8cfdMuKwWq1Yrdaa38vKygAP+chmgxkzoLQUpWcv3uh1ORmlNsLMDtqHGwkxaF232yFTbHHwVlx/Km0yMe10TAwMxlhZDvPng8kEzz7rlqnYhnzkddav55A0n8dc2Mu8tHISf33/JVKFiAnOcs6qlfHCwkIeeOABevfuzVVXXdXgccnJyYSFhdX8PPHEE607cXGxOu1nNMLw4ViCQng6eiAvR/UjOTSA/u2CaRcS0CwBA9BrNcQGB9ArLojuMYFkxiaz8Mp7yC2zwO23q9NpreSJJ544zRdpaWmAB3yUl6f6KCiI/CHns6D9RZRZnfSJC6RLlKlZAgZg0GmICw4gLSaQfu2C0GkkFoX148XOl+AcMlSdgr39djh1qnV207CPvM5337F+cyCu1Jo0x6bw3XaD2PgsOOs5axIAWywWxowZw/79+9m6dWu9HU11YtK6FXlbNcrIylJHF717Q3Q0b6cM5Y8iCylhBuKCA5r+fDOpsDn5tcBMQtEpbt/8IdJzz0F0dIvbqzvKyMzMJC0tzb0+evxxOHgQpV9/XuxxGYVmB12iTAQHNE+4msIpK6SXWikyO5hetJ+Q8hLYt09dj5wzp8XtNuQjrya4PXaM3X9/CucLLzNkSNOHb98OP9z6KtPXXgJdGokC8QAiAbDAm5wVIzG73c51113Hzp07+eyzz5p8Uq6uyFv90+LOOT0dHngAzjsPJSqa5fHnkVFqo09ckEcEDCAoQEvf+CDK4pNYeukEnNPugezsFrdnMBhO80V1SXm3+ejxx+HQIRxDhrI49VLsskLf+CC3CxiAViPRIdxIpwgjS8L6kR8UDkOGwJEj6jpZC2nIR17lu+/40PBPlweWQ4fC9AklUDW9LhCcrbR5EXM4HIwbN44NGzbw6aefMmLECO+cOCcHHnoIzjsPOSiYJdEDsTsVescFYtB51q0aSaJzpImA6Eieu+IOlOnToajIo+dsEbm58PPP2AcPYXHCCCKNOjpHmtB4OHowzKgjLdbE8vC+5OsDVSE7fFgdNbdVtmzhR2s3hg51/SPLsseR+8Vuz9kkEPgBbVrEZFlm/PjxrF27ltWrVzNq1CjvnLiiAqZPh4EDUQKDeDp6IEa9hi5RLnbQOdn1/zST9uFGCA3jzdETYNo0NXDCX7DZYPp05H79eTphODFBehJCmzk6bYWPAvXaGiEr1Rth4EB11Gw2N/NC/ISdO/lyo5H4eNc/ckqbxOZdRrEuJjiradPRiVOnTmXVqlXMmDGDmJgYtm/fXvNeTEwMqamp7j+posA990C3bhAayrLYQRgldRqrUerpgBcerNOhHjzGnF5VG7TjXOutUiONHLCFk5vUidjp0+HFF136nMeZMQNSUniu66UESxIJIS4ImCsiVfeYRvwUqNfSNdrIs1I/Zkn7COjcGe67D159tenz+BP5+RzXppJ5IJDmTDRcMkrDkS97qaPQRqJ1BYK2TJsO7OjQoQMnTpyo973x48fz9ttvn/aaWxacH3kECguhXz/eTBpCbqWDnjEmpIZGYLU63TNECwjOSj/t9/KE5Jr/z+llcknMKmxODudX8uj6V9Ckpqp7sFqIW6oWV+0DWzHufk6V2+kVG9iwf6qp8lN9PmqIGsGHRv2UVWajzOrkgbxdsHs3JCfDo4+6fJ66eL2y8//+xwvLHDivvpZ7723mZxcvVrOa3O69FFQisEPgTdr0SOz48ePePaHVqmaZGDGCSlniZImNvvGNdND1CFhd0apL9fvlCcnqZ6pHZ4100kEBWiKMet4efRsT/veKmjHEV3kFFQVmzcLSqw/Hiq30jQ9qXMDqiFdT/qmmxj9UiVlOdoM+SggJ4KfKCt7pMJx/WiywY4e6r0/XRm7/LVvYKU/l7mash1WzNPN6Jh96CpMXRUwg8CZtek3M68yaBSkpEBDAizEDaB9uaHjvV63OeeFBM8FZ6S530MBpxy88aG5yqi0pLIATihElKkpNeeUrFiyAoCBe7noJSaEGAhrbG5eT7T7/VLXXEKmRRo4VW9RN0O3aqSPqtsLWrbz+rpGqZDTN4sfi9uzZ0kbXAQUCFxAi5ip2uxrd1qEDJU4Js0MmJkhf/7EtHF3Uh6tCFqDVEKTXkNu+C3h7hFqNosBvv1HZpRslFifxwQ34B2oEDNzon6p26yNQryVQpyHToVVzLJ44oSZe9nfMZoqLFHadiK3JF90cLrhYx29SV1GaRXDWIkTMVRYsULPKa7W8GT+QpNAG9k25UcCqcbWN6EA976eer2bLz81t9XmbzbFjEBzMm51HkhAS0Og0oif805SQJYQG8O+4fqp/oqPh119bfW6Ps3s3mxNu4OOPW/bxCRPgtmuKYNs299olEPgJQsRcJTMTkpNxKFBscRAVWM96Sp3O0x0ddO22mhqNRZh0FFsckJAATz3ltnO7zOLF0KED+RWOhkepUHMN7vYPNB4YEmrQUWmTsSlAUhIsW+a283uMrVvZbryoWfvD6vJiyc0oW7a6zyaBwI8QIuYKVqv6ExxMpkNLhEnX6H6w5kTYNZfGhEynkZAksMXEqrkKvYnFAuXlFIVGYdRr0GkaDnbxlH9OE8UGfBRp0nHKoYXwcDVZsyx7xBa3sXUrM+ebaCQVaJOsPZrG0W/rj+IVCNo6LRaxo0ePcuGFF5KSksK9996LxWKpeW/AgAFuMc5vOHmyppbXf+L7ERPYyCijCneOMprTZkiAlnx9kLqGZ7e73YYGeeIJiIvjP+0HE+ZCMl9P+KeaxkQywqRjTUJ/tQJASIj3xb45KArOX49y0NqF1tR3HXGJgQM58eqDmEBwltFiEbv77ru5/vrr+eSTTygsLOSSSy6pKVHRhree1c8rr6j1wYAym7P+jOu11sI82UE3RXCAlv8mD4SwsFblVGw2+fkQHU2l3UlQI3kRPTlKhaZHY6EGLaXWqoAOb/uoufz2Gz/HXsySZ1qXpmv2bPjbRQWwd6+bDBMI/IcWi1hOTg7Tpk1j4MCBvPvuu4wePZpRo0ZRWlra9MbWtkZpKYSEUC5LGHQaj+f+c4kGpsuCAjRU2JxqB/3yy96zp7QUwsOxOhSMusb3zflS5LUaCY2Eui4WEgJ1NsT7FT/8wPaov7RqPQzULYPv6m4VwR2Cs5IWi5i5Tg66uXPnctVVVzF69GjKy8tbbZhfYTZDUBDFTg2Bet8uI9YEeDSASafF7JAhKEjN8egtHA7Q63EqClp/EPlGCNJrKXZqIDDQv3MpbtvG38YHM2FC65tatmMYpZvESExw9tHiHrlLly58++23p732yCOPcMUVV/D777+32jC/wukErZYKRcLYSIZ6T0+VuYJeK+GQFXVTr7c66FrTx/4wk9yU0Bt0EpWKBAaDGpDir+zcSVbCoOrl2FYxZKSR3ft1/vEHEgjcSLNEbMuWLTX/f/fddxk4cOAZx8ydO5eDBw+23jI/5Jv2AxqOuqvCl1Nlp6HXey+wQ1HUQAmgCff4BRpJwqFIEBDg3eCX5lBSQpEcxoSpgW5p7tmlGi5Oy4E//nBLewKBv9AsERs9ejSff/45AOHh4YSFhdV7nM/Kt3sYWVHaxJ4ESZKQkbz31C39eS6NJOH086d9p6yglxR1schfQ+x37GBnynUuVXF2BZ0Ovoi9FTZvdk+DAoGf0Kw+edq0aYwdO5Z33333jPe++OIL+vbt6zbD/BGNJNFUl1c7C71PqZoC9QrVa2CKgk4jYXf6VsTKE5JPz3BfB4tDJljj30LLDz8wYEwcDzzgnua0Wpjx/V+Rv9/S9MECQRuiWSK2ePFinnzySW677TaWLl0KwK5du7j44ou58sor0eub3j/VJql6YtdqqtabGqCxjtObKIqCxmZV13y8RUAA2GwYdBI2H4tYU1TYZcI0sn+vD23bhrnPYNq3d09zkgRp/Qz8/H2BexoUCPyEZs+OTZ8+nZUrV/Lwww8zZMgQhg4dyqlTp/jwww/ZvfssLYUeGAgVFfzf8d3YHH7S8TVVZ6yiQo1Q9BbBwVBWxl9O7qPC3nBi3Tm9TD4drdqdMloJdBLqepg/PnjJMvKxE1x2e4pbm31rpZae8QVqImuB4Cyh2SKWm5vLzp07AXUUNmzYMA4dOsT111/vduP8hrAwKC4mXCs32kF7g6amyqwOWS1/UlwMkyZ5z7CICCguJlrnpMzagI9crFbdGpoSyGKLkzBjVd5Ls5lWpcLwFD/9xKGUK+jRQ8KduxUMBtiR+g+xLiY4q2iWiM2cOZPU1FRWrFjB7NmzWbVqFXv37mXKlClnX5aO2kybBvn5hGgULA65/mut1UH7cqRRanUSEqCBggK19pm3uP9+yMsjVKNgdSqNTruCZ31UI/L1iGZBpZ0bMveov1RtYvc7vv+e+It7MG+ee5uVJLjju5uEiAnOKpolYkuXLuXWW2/l6NGjzJkzh3HjxrFu3Tr+85//cN1112Gz2Txlp29JToaiIpBlQgJqpS2qB0+ui9V0/I2MaArNDq4//J06BRoQ4DFbziAyEmw2sNuJMunIq2g4dN1TPmpKGB2yQpnNSZy2KjynqAimTPGILa3iu+/QXzgMd8dJGY0QEmsi77tD7m1YIPAhzRKxQ4cOsXz5cmJiYmpeu/DCC9m4cSPbtm3j8ssvd7uBfoFGAzExkJvLuOz95DTUQXtwNFbdXmMCICsKpVYHcRlHVeH1NvHxcOoUt53aTXZ5Aw80HvLRGf6pR+hzym3EBOnVKTpF8f5o1RUUBX76iQun9vbIXvXVH+uIMFRCYaH7GxcIfECzRCw1NbXe1/v168eWLVtIT/eTjb6eYNYsOH6cRJ2TEouj0TDy6o7U3UI2p5ep0VFYfoWdaI0TbWEhzJnj1nO7xCOPwMmThGgUArQaisyOBg/1hI8aE3inrJBVZuOOrF3qCwUF6lqnvwV2HD5MYYcBGAwSge7Z53wakZHwW89rxJSi4KzBbXt3U1NT2XY2Jxht1w4UBU1pCe1CAsgsa6CsRZXIuKuTLk9IbjKYA9Sw+owyG7f9sAY6dlRHj94mIkKNHigo4I68fZws8byP6vVPPUKfXmolPjgAY7Vbjh2Dhx9u8Xk9xvffoxkxjMWLPdN8cTFM3nsnrF/vmRMIBF7GrT1dXFycO5vzP+bNgyNHuCNzJ3kVdmzOBrY+u6mTdmWKrJrcCjthTishedmwYEGLzucW5s+HI0eI0jjRaSQKzY1PvbbGR/VOsdbjozKrk0Kzg0lZalQtxcVqeL2/TSUCfPcdxouHccklnmk+IQFybeHYN4lKz4Kzg7aQRcl/qNp5qsvLJTnMwIniRooM1tNJu9JRVx9XPbqomUJsRMDsTpmTJVYmf/cedOvmvUwd9ZGQoEYQnDrF3fl7+aPIirOhSMVW+qj2Z2u3Vxu7U+FIgZl7C/ejlVDTTP30k1rE09+y7csy7NnDBdMHUVrqudOs+ViDFBcr8igKzgp0vjagzfH003D33UyMiOSx6PMoszZQJBPUTjUnu6ajXXjQ7FIn3VTHXBtFUThSYKGHJR+j0+6btbC6PP003HknITExxAfrOVZsoXNkA9Oh1ULGn0VFq31UN5lybd+54iNZUTicX0lyaADhpVVCeugQxMaq08P+xo8/Uth1KGRLhIZ67jQdO0L20GtIWr8eJk/23IkEAi8gRKy5RERASgrSL4eY2jOA55S+9G8X1HChzOoOtpaYuYSLG4OPFVsxyXZuXP+uWoHaH0YXISGQmgo//cRknZ4nIgeQU24jLriRkP86YgawkNMF/wz/NeGjo4UWggO0TMzYob5w7Ji6wfm551y/Fm/y1VfIF13CY708e5rDh+GpvTfywS8ThIgJ2jxtVsTWrFnDBx98wJ49e8jOziYpKYkrr7ySOXPmEBkZ6dmTP/EETJxIRHY6sUmDOFpooUtUEwLlgWwVx4stWOxOHlr/BnTtqkbb+QsLF8KkSUjpJ5kBLJb7o9dKRJqaiAas5ac5cZxewboZPjxaaEFW4J6cqmjEY8cgOxtee80/hL4+vvqK8FffZHQXz56mTx84cCIc5EP+m3pLIHCRNrsmtmTJEgAWLFjAunXrmDFjBu+//z7Dhw8/o+q025EkeOEF+P13phxaj11WyChtZH3MzciKwm8FZsw2Jw9u+TdSYKAaUOFPSBIsXw7Hj2MoyOehov0cK7KSX9nM+l3V64EuCpisKBzJN+OQFWbk7Vb3XR05Anl5qoD5a4ddXg55eQy9sSOVlZ49lU4Hb7whoQwfIULtBW2eNitia9euZc2aNdxyyy2MHDmSyZMn884773DkyBHWrFnjeQNMJnjuOaT9+3jg+HcUmh2kNxRS7kbMdpkfcyrRayQe/OF9NGYzLFvm8fO2CIMBXnwRDh7EVJjHzOJ9ZJRYPeYnq0P1jVEn8UD+biTZCXv3gtXq3wIG8PXXHD/vekJD8cj+sLoMHgwFo26Ajz/2/MkEAg/SZkWsdtaQagYNGgRARkaGd4yIjoZnn0W7exczj22k3ObkcF5lk3kDW4LNKXOsyMLPeZW0D9ExbcNbSBUV8NJLvtkT5iphYfDyy3DoEMaTx5lVtJdKu8zB3MqGtyg0E0VRNzL/lFNJSpiBqdm71Cz+27apu3uXL/dvHwF8/DGaMaO9Fpezaxfc99ko2LDBfwuDCgQu0GbXxOpj48aNAPTo0aPR40rrxC8bDAYMLa291a4dPPcc2unTeaislNeG/I19p8pJCjUQF6xvOODDBawOmSKLg4JKB2aHTEJwAI9mb0P75V5VQJ9/vtXrO1arFav1z5FRWVkZ4GYfhYXBW2/Bffehzc7mgV4VvNVjFAeyK0gKNRAfrEdqwXUoikJepToCDjNqmVW6D0MZaqmRI0dg8WLcUZCrIR+5DZsNdu4k4Y1BpHipBNyQIXDbbVocfQeg27kThg71zokFAjcjKWdJ+vnCwkIGDhxISEgI+/btQ1vPXqnS0lLC6gl+mDt3LvNamzLcbIbp08Fux9KjJ691uZj8SjvhRh1RgTrCDDq0moY7allRqLTLlFmdlFqdlNmc6CQIN+m4MXMP0ZIDTpyA48ehVy+YPbt19lYxb9485ruwnuYWHwE8/jj88guEhWFN7cJLnS6kxOIkMTSA2CDXRF9WFHLK7WSW2QgzaLkzZ49aqdlqhZ9/BodDjUB0Uz21hnxUUlJCqDti4devp2LVp1z084vs2tX65lxl61Y478QaAvbtULdFuInq75nb/CMQNIJfiNimTZu4+OKLmzyuW7duHD58+IzXLRYLY8aMYf/+/WzdupW0tLR6P1/95UpPTz/ty9WqUUZd/vhDzexhMuFs34FToTH8J3EAJVYnWgkC9VoMOgkJcMpgdcpU2mVkRSEoQEtIgJa/pe8mSiv/WbgxK0sVr5gYePJJt1ZsrjvKyMzMJC0tzbM+UhQ1EOX33yE4mPKOnXmz26UUmO2EBGgJM+oIDlD9pKsSfrtTocKuZt4oMjuIDtQz8dRuAjWKKlrHj0N6uhqlOW+eWyMQG/KR2zrpO+/kk7jJbCod4NXof0WBrN8qSLzmPHUDuJs2yQsRE3gTvxCxyspKTp482eRxBoOBjh07nvaa3W5n7NixbNy4kfXr1zNixIgGP++1L5eiqKOmJ55Qa1bFxkJsLJaISErRU6lIyIqEXlII1CiEamT01X2u3Q5lZWqZkIICqKxUpyznzfNK7auMjAySk5O90wEpiir6ixaB3Y6ckEh+bCKru5xPhc15Wl0yvUYiKEDDdZn7SNA50Sqy6qOsLMjPh6QkdZTnhfIzbvVRZSUMGMDutw8SFKajiZlwt1JRoQZ4/HTeBDT/GAduqkIhREzgTfxCxFqKw+Hghhtu4PPPP+ezzz5j1KhRjR7vky+XzQZHj6oh+cXF6msmkzqa0mjURXW7HSwW9V+tFkJDITwc7rtPHX15cV+TV0WsNiUlqgjl5Kh+CA1VpwONRjUmXFHU1ysr1QcDs1ndeH7vvdCli1dTbbnVR++9h23vQaTFT/gkePKWW+D2QQe4aPtieP99t7QpREzgTdpsYIcsy4wfP561a9fy0UcfNSlgPiMgAHr0UEPNQZ36Ki5WO2OnU+18jUZ1lGU0+u9GXE8TFgZPPaX+3+GA3Fx1JPrvf6trXaD68u671ZFtWNjZ4asVK1h9+UqOLPRN3uYFCyA4qA+8/pM6so2I8L4RAkEraLMiNnXqVFatWsWMGTOIiYlh+/btNe/FxMQ0WPvM5+h0amShoGF0OjWRcEKCugZ4tlIVhPLmukReeME3JnTsCL/9JhE47naC3ngDHnzQN4YIBC2kzU4ndujQgRMnTtT73vjx43n77bfPeF1MczSNz6YT2xBu89Ftt6GMvZZ1+qsYM8Z99jWX+fMhOrCSu1cMgv37W72uKL5nAm/i5ztAG+b48eMoilLvT30CJhD4FenpsG8fv3f7C1dc4VtTpkyB1/4diHLFGLetiwkE3qLNiphA0KaZM4eMKY9z3d81Pk+YERsLX38N0gMz1P1iVu/lARUIWosQMYHA2+zZA7//zpJD/8dDD/m2hmk1sbHw2mcJ2K++Dp8t0AkELaDNBnYIBG0SqxUmTYI33mBWgkRUlK8N+pPffoNX42Yy9a2BcN11bknZJRB4GjESEwi8yUMPYfm/a5n7ST9iYvxjFFbNnDmwdY8RZekyuO02dQuIQODnCBFrAVarlXnz5p2WisjX+JtN/mYP+IFNr7yCcvwEUzMfxmRyMH++f/nHYLDSrds8yoZeiHng+XDPPeomc4HAj2mzIfYtwV2hv/4YQuwum9wVPi58VAtFURMSf/op1jVree7NYCZNKiU83D/9s2JFBf/9xMQHwRMxRgfDM8+oe/ea2Y4/XZvg7EWMxAQCT3LqFNxwA4Vbf+GBPuuRQoJ56CH/TjZy7bUOLrhQ4rEOb6gVOq+4Qk3WLBD4ISKwQyBwN4oCP/4IK1bAhg3su/kZ/rFyNLOv9kp+YrcwYwY4HBJFZU+waNJxJvx1Bt0HBSNNvB2GD2/WyEwg8CTn1J3orFqorlvwsblUf7617bgTd9lUXJWkWPioYap9tPXF7xiRaiYzEw4dljBnFnG58hWZBwtZbJvBT9r7ef3dR+jWO4Bv/llKcLCau9idtriT+mySZeh6WST3FL3FQ913k7Z8BS9cs524SAcjB5aR2sPA9tI0NGEhJCQoJCXCj+nqMLOwsNAn19EWUBSFsrIyEhIS0Ph71XE/55xaE9u1axeDBw/2tRkCgUAAQHp6OklJSb42o01zTo3EOnfuDMDPP//cqgXnsrIy0tLSOHToECFeqPHlCu6yKT09neHDhwsfNYI7fHQ2+yczM5OhQ4eeUVhV8CelpaUkJyf7zd++LXNOiZi2alNOUlJSqyPvABITE/3mS+pum4SPmqY1PjoX/BMaGuo31+avSP4c4dNGEJOxAoFAIGizCBETCAQCQZtFiFgLMBgMzJ07F4PB4GtTavA3m/zNHvAvm/zJlmr80SaBoCnOqehEkUmgaURRzKYRPmocf/OPvGcf7y04Ska7QUxZ3IHwcF9bJPoidyJGYgKB4Ozlm28ov3UqJw2dMX35CUde/tbXFgnczDkVnSgQCM4hSks5dMdSoj9ezex+CZCfhPP8kRwYsY2+F4b52jqBmxAjMYFAcHby+OPcKb1Gri5B/T06Gtvd07lhrJWKCt+aJnAfQsQEAsHZR3ExP6/5hYju8fTq9efLpkn/5DrpY/7zrv+UwBG0DiFiAoHg7OPNN+k56Xw++E+dLi4ggAdvy2ec8r5v7BK4HSFiAoHg7EJRsL35Lv8OvYugoDPfDpt4PQdW7KWgwPumCdxPmxaxNWvWcN1119GxY0dMJhNdunRh+vTp/p092+mEggI4fhx++02t05SRASUlasrwcx1FUX2RkQF//KH+ZGRAURE4HL62zj9QFLDZoLxc9VVpKVRWqveWAHbs4Lvov/HdnuD63+/Wja8zevDfd8u8a5fAI7Tp6MQlS5aQlJTEggULSElJ4ZdffmHevHl8+eWX7Nu3D5PJ5GsT1Q7nxAlYtkwVL4cDgoLAaFRrMsmy+prFAmaz+lp4OEyaBB07Uu+j5NlGWRk89hjk5qo+CAxU/VOV6xKnE6xW1UeyDMHBEBYG99wDSUlwtpeyKCmBxYuhsFD1laKovtHp1GtXlD/vI1kGvR5CQyEyEv71r3PjHqrN++/zbcxdXH11w4dcda2ehe/kcft9IgFvW6dNi9jatWuJiYmp+X3kyJF06tSJ0aNHs2bNGm655RbfGWexwJw56igiLAzi46Fz55qqiDYFip0aymUNX3UYgCyDjILObiOwpJCL31xFaEEOodjRJCbCo4+CP4iyO8nLU31UXg6JidCnD7bAIMyyhFWRUAAJMEgKRo2CQULtpMvLobgYFi1S/42KgtmzISHBp5fjVhQFjh1Txd3hgHbtcHZKpTQojHJJh0WR+Lr9AJyKgqyARoIAjYYrT+4m0mHBVFasit6kSaqotW+v3kNnu+A7HPDVVyza9yRKI4lH+twxhLf+mA2ItbG2TpsWsdoCVs2gQYMANWuAT3A4YNYsSE+HlBQ4/3zQ61EUyHVqeD92IMUWB5IEQXotRp2EXpLQ60CSNMg6I5WGdnxy0fWY7TLW8krC87LoPHspo+U8dE8savtiVlkJDz0EJSWUdenOv/v/H8UWJ2aHjFYjYdBq0GkkJEntyx2ygs0p45AVNJKEKUZDkF7DNel7idHYMeTnwSOPqG0//ji0a+fb62stBQXw4IOg1VLarSfv9LiEQrMDpwwmvQaTToNeK6HXShglDZIEsgJ2p8x/EvpTbnXiiIXwnlr+kbWX2MoSpBPH4eabITUVFiyAszV7+vffk9Hn/zi02cjllzd8mKZPLzJ+KaPsDxtJndpIuW1BvbRpEauPjRs3AtCjR48Gj6lbTddgMLgnX9zjj8OhQ2onesEFoNNRLku8FTeI/Eo7IQFaogxa2ocb0Glc7ERiA6lMjuBoaWeW/HGCXrOX8pdgK9KC+a23F7BarVitf4Ybl5Wp6wQe89HcufDrr2R16sGKC8cjKxAtSXSMMBKo1zRZmsIpK5gdMhU2J6sT+1NicaKLkogdoGfCj1+i+9e/1OnYJUtqRr2txWs+UhT1AejkSUrS+vByt1HYnDLxWg09YwMJ0Lo+inLKCkUWByvj+lNhl4nvfikTj36P/rdfYfx41T+xsS23tRYN+ccnfP45n0feQukBGhUxJInv2o3D/PRJZrzc2WvmCdzPWZU7sbCwkIEDBxISEsK+fftq6odVU52vrC5z585l3rx5LT+xwwH336+uV/TpA0FBlMkSr8QMoNIh0y44gNggPVpXhasB7E6F9NwSdAd/4pbCnwl9/tk/141ayLx585g/v2lBbLWPrFa4917KZA0vjrwJDEbahxsIDmid/QBmu0x2uY38SjvtgvRM2vYhmvR0eOYZdRq3lXjFRw4HTJ2KotHw8mW3kWuFThFGIkytf860O2WyymzkVthJCjVwx0/rkH76UZ1ifPzxVo/KGvKPT/IC9u3L+J67mHZ/AFWTMg1yeNk6Zixvz+dHG37g9RQid6L7OGtEzGKxMGbMGPbv38/WrVtJS0s745jqG6duxdlWPUGXlcHdd0NcHHTujIzEy+0Gk1tpp0O4gehAfUsvqUFKzHay9v9Cr5Is/m/e3a0acdR9is7MzCQtLc29PioqgmnTWD3sGg5GppAaaXJL51wXh6xwsthKidXBvSc2E3Zgj7oO6YIANYbHfWS1wqRJ2OPiWTLoOox6DZ0ijGjcPOVnd8ocL7ZSYZe5L3cXIQcPqGtky5e36mGoIf94vYM+dgwmTsS5/hskqenlP6WwiJxLbiR+/zrv2FcLIWLu46wQMbvdztixY9m4cSPr169nxIgR9R7n9hunoACmTYO0NIiNpVKWeDayPyEGLR3CDW7vhGpjd8qc3H+Edrnp3LDwbjVSzQ24PQN5Tg7cdx+vXj6BguAIukeb0DdjWqwllFod/JpvoVMQjP/kJYiJgaeeclv7bvWRzQZ33IEtuT1P9r6K2OAAEkI8u0ZTbHHwe4GFDuEB3Pbtv9X7+JVX3Db96rMs9i+9RHqWluOjJ3HBBa595NeeY4lcu5LoTt4VEiFi7qPNhyo5HA7GjRvHhg0b+PTTTxsUMLdTUABTp0K/fhAbS75Tw5Nh/UgOC/DIU3Rd9FoNHQd0pygmnncWvKGup/gbBQVw3308f8WdlIdH0TM20OMCBhBq0NEnPpATZnj92nvUEPUZMzx+3mYjyzBlCo7EJJ7q81fiQzwvYADhRtU/GWV2Xh55k7qGO2lS29+H98UXrDdcxbZtrn9kdejtfLb8mOdsEnicNi1isiwzfvx41q5dy+rVqxk1apR3TlxRoe5R6tcPwsNZkTKU5RF9SYs1EWly//RhQ2gkiYQBvbDpAvj30g+8dl6XMJvhnnt49YqJ2EPD6Bplar6w52T/+dNMArQaescFcarSwYqrp6gbgh9+uNnteJT77kMJDWXJgGuJDtQRH+y9KLkArYY+cYEUW5y8OPR6Ncjj7rv982HIFWw2OHqUnentGDLE9Y8NuzyE7ZvMnrNL4HHadHTi1KlTWbVqFTNmzCAmJobt27fXvBcTE0Nqaqr7T6oo6hRily4QEcFriUPIKbHSN86FUUadznjhwfq/PHN6mSDOtYAESaMhdsQgMr76jpzDfxDXvZNLn/MoigL33MNHF15HnimM3pFG1z7XmFjV914TPtJpJHrGBvJjTiUlfQcStnObGsgwe7Zr9niS+fOhvJyXr5lGgFMhMdTFtbQqPzR070DV/QNN+kcjSaTFmPg5t5JXL7iRSV+8pkZHPvGEa7b4E3v2wHnn8fxyqVnLe8MndKfT2inAxx4zTeBZ2rSIffHFFwA888wzPPPMM6e9N378eN5++233n3T2bDWjREICrycNIbfcTu+4oMYjD2t1wHU7n+Cs9NN+L09IVo85qE5xzLm0Y5MmSXo98cMGsPqNtdz1+BQ0Bh/ve5k3D5tGy4+hSfSOdnEElpPdaMdcH3Oo8msjnXWAVkPXKCPLpIE8MsCKdsd2dSTtyywWFRVw+DDFQ88nt9JB/3Yu2FKPf+reO1Dr/gE4eKzJByJVyAL5MaeCf/9lIjevekbdJB0Z2axL8jnffUflkIvZtR1GjnT9Y8b2cTjLzZgLKjFFBXrOPoHHaNMidvz4ce+esKJCjYA6/3xyHRqyymz0aUzA6nlqrq/jqU3t98sTkln4zTGXhEwfFYmxXRz/ff59rn1wvAsX4yHsdvj1V14few9xJj0GXdOj0+b4p5ranXVTYhZq0BFp0vFq6kjuKi5St0O8+qpL5/EIDz4IXbvyYsIQOoc1EQBU5x5q9v1T9UDU2D2k1Uh0jw7k59xKbD16EvDgg/Dmm824ID/g++/ZeeOrvPfv5okYwFPah7lhxWEueWCAZ2wTeJQ2vSbmdWbNgtRUrDo9L0b0pUd0YMObluvpfFztoKupPn7hN8dcWhcK6Z1Gfnahuv7jKx55BGtiEjlOLYmhTYwIawlYc/1T+/gaEWzER8mhBrLLbVjiE9RcjI8/7vK53EpuLpSXcyo2GY0kEWZs5Dmyyj8LD5pbfP+4eg+Z9BoSQgN4sdcV6r6xEyeadS6f4nDA0aPsyExq1npYNcMu0PLDl8VuN0vgHYSIuYrFonZASUm8EDuIxNAATPoG3NfMp+fGqO6IXJlqCzTqKUruhO2ROS0+X6uQZTh5kreGXU+7kIAmRxgt7Zxr46qQaTUS7UICeCPhPHVLxOHDvgliePRR6NqVFbH9aB/WyDpYretojX9qf37hQXOjQtYuWE+JxUlp5+6t3lvnVQ4cgH79uOtuiXHjmv/xq6Ykc4P5bbebJfAOQsRcZf58SEykSNFRaXMSH9xAFKIbBawuC79pOhRYn5xIQWG5Gq3lbY4eRYmMJNsmERfU+CisWsDcgatCFh8cQF6FQ82CHxysJtf1JpWVUFZGSWQsTgVCDA1EINS6hzzmo3qQJImUMAOvd7vkz5JBbYHvv4eRI/n5Z/XP2lyi+iVjyjmOYrO73zaBxxEi5iqZmZCczBux/UkJN9af48+DAlbTVhPTioEGHd/3GQkLF7rt3C6zdCl58SkEBagJahvCFTFuLq500jqNhFGvodCpURPh/vGH2+1olMceg8REViae1/BDUBXNDXJxBVfuoehAHaUWJ5bk9r6bcm0u331HRtdLWr6DQpIYX/EiJ9b94lazBN6hVSKmKApyVSHHyspKvv76a99lj/ckFRWgKDiMJspsTiKMDcfwekLAqnFlWtGg01AYnwzZzd9b1SoUBYqKeL/npY2n2qrqQD3ln7rnqUuEUcuHyYPUeltOp7oR2ltkZ0N8PAVmR8M+cuM0Yn3U3EMN+EeSJGKD9bwz4Co124q/I8vwyy9sL+zaovWwaob0tbLjo7Ow7zoHaLGIffPNN0RERBAdHc3GjRu54IILmDVrFn379mXdOu/nIvMox49DdDR5Tg1hRl2TmdY90fmcRiNP0nqNREVwqFpzy5uVfgsLITCQUrtCmAsi70kaO0eIQUu5rcov7dqpxSa9gcMBZjOVgSHoNFKjVQx87aOYQD25DgkMBvXv6s8cOgRpaVx6mYb77mt5M7dNDWJQ9mduM0vgPVosYrNnz2bTpk18/PHH/P3vf2fZsmXs2rWLDRs2sGDBAnfa6Hteew0iIvg0ZRDB+sbXMbxBU1NmDhm1EKc3R2OZmRAejl1WmiwZ4kmRb2o0ZtRpsNjV2QNiYiA/32O2nEZ2NoSFUeDUEOpC5n6v+ageTHoNNqeMHB2tVgLwZ777Di68kJwcNQd3S+n8f12JO7qt7WYsOYdpsYjZbDb69evHRRddRGhoKBdUZdzs378/ZvNZlsalrAxCQrA5lcbXerzwBN1UB6SRJGRFUUXsxRc9bk8NK1ZgDwlzedO3r9BXizyoUQDl5d458UsvQVgYa1MGNhnV6jUaDbnXUhYW5d3p1pbw/fc4hl/Itde2sqKMVsuQnE+x//K720wTeIcWi1j1WhjAmDFjTnvP6c1pLG9gtYLJhFNRmqwJ5vGpxGoa6IA0EiigdtAVFd6xBaCiAmdQkN9HCkmShELV07ZGo/Z8te5lj1FeDsHB2JwKBp3/V1U2aCUqA70o8i1BUeDAAQ7q+tGzZ+ub69Wxgp/+I4I72hot7nM6duxYU9n2hRdeqHn95MmTBPkypY8nkOWmixP5CbICEoDJpO5t8xYWC1qjAS/IgXvR6byzHaHqQUhWlEb3z3ljNO8KGknCqTeodvsrv/0GnTrRvpMWd6xg3DfZStShza1vSOBVmtUzb9mypeb/n3zySb11cKKiovj0009bb5kfotNIOGX/njN3KooaNKDVere0hsOBXq/ze//UizfWQez2mppvTY3DvDaabwRZUWgqY5jP2bwZLrgAmw26dWt9c8Nu607Sz1+1viGBV2nWbTp69Gg+//zzRo8JCgoiNja2VUb5KwFaDVanf481atbtNBrvLlIrCmg06DQSdqf/CtkZIyGn023FIF1BI0k4G/m7zOllojwh2Wv2NITNqWCU/PfvCNSI2OjRauWf1mLVmBhy4j9tY2uBoIZmidi0adMYO3Ys77777hnvffHFF/Tt29dthvkVVaOaK0/sxuJoXMR83QFZHTIGrea0J3+voNGA00lwgJYym/+uidqdyunh7bJMs2p3tBS9Xh2taiVsfizy1VgcMsGSfz+wsWsXpd3OQ6NxT1ECgwEIDqbkq+1NHivwH5olYosXL+bJJ5/ktttuY+nSpQDs2rWLiy++mCuvvBK93nsFIb2KyQSVlURoZCpsDX+xa+o4eZDyhORGz2N2yFx5co+6lmFwsUaVOzAYwGbjb1n7KDY3Po3pS6G3ORUM1RGmNpsqLt7AYACLhf87uafJByFfIytq6IvOYfeef5pLVhZERSEZDSxb5r5mZ912CtvWXe5rUOBxmj3rPX36dFauXMnDDz/MkCFDGDp0KKdOneLDDz9k9+7dnrDR91SFYgdqFGxOGcWP95JU2JyEa2Xv18yqEvp2OieFjYiYp4W+KYGstDsxVoe4V22d8ApBQVBRQbhGafhByMVCqK2lqQehMquTYL1GvYcC/bTGVtVUolYLVbt73MK1D6YSs2+9+xoUeJxmi1hubi47d+4E1FHYsGHDOHToENdff73bjfMb7rgDiosBCNRrqbD7wZRiAx2exSETqlHU/T2TJ3vejmpCQqC0lAAJggI0FNUnZLVs9qSPGu2gbU6uSd+r/lJcrO6n8wZTpkBJCWEamUp709OtvhytFpodRJh0qsjXE7zlF2zeDOefz803q0k73MWJskhu/G2+f28tEJxGs0Rs5syZpKamsmLFCmbPns2qVavYu3cvU6ZM8evRSatJSalJvxNu1FJsabiD9uRIo6kn6AqbE1N1RpHiYkhI8JgtZzB1KhQVAXBrzj4yyxoOW/fGtCtQr9AXW5zE6apEpLAQ7r3XO7YkJEBRERpJfRAqszYsZJ6+h2qoxz+KolBQ6eCW9B3q3/OuuzxmS6vYtg2GD+fQIeje3X3NJifDXqU/yg9iXayt0CwRW7p0KbfeeitHjx5lzpw5jBs3jnXr1vGf//yH6667Dpsvyn94g+BgNQDAZuOmjF3kVzYduu7uJ2lX2surtBNl0qmhWjqdV6PuSEhQhVOWidXJOGSFiiYCPDzho6amyYxaiQAJ9e9ZVgbeiqTV6dQp17IyYgJ15FU0UPbDg6PV6vYa81GRxUGwQYMBRRX5pCS32uAWiotBo8EZHMb8+e6Ny9FoYMqVGVi/EyLWVmiWiB06dIjly5cTExNT89qFF17Ixo0b2bZtG5dffrnbDfQbYmIgN5cQjQKKgrm+KcU6ozF3d0I1nU89T9CyopBXYefWjB012dK9ikYDUVGQlwfAxPz9HCuqZ7O1h3x0Rjv1+CirzEZ8SJWwFxZCREQrcxU1k4QEyMpifPoOCsx2NT1YA3hqNNbYPQRwotjKHTl71ergQUHejXB1lapRmCTBDTe4v/n7Ho/BuH2T+xsWeIRmiVhqamq9r/fr148tW7aQnu77TZoe46GH1IgoID4kgKxGpsvAvZ30aSOMBjqfU2U2ogP16igjMxNmzWr1eZvNww/DyZMAtNPJIEkUmhsuNOguH7kywrA6ZMptTm49UfWEnZUF06e36rzNZs4cyMpCp8hEB+rJKW98NOauPWPlCclNjlIBcsttBOq1hGsVyMiAxMRWn9sjVAV1zJoFX37p/ua3Z6Ww8MBf1W0qAr/HbXvyU1NT2bZtm7ua8z/i49U0TmYzt6fvoMjiqD9UupbItLaTdrXzsTpkTpXZmZy9S51q0eu9F7BQm8RENbS/alF8Wv5e/ii04qibxcPNPqrdTt32qzlZYiU5NEAdeFVXLW7gocxjGAwQHQ2nTnHnqV1kltoaHY3Bn0Lmaf/YnDInSqxMzd2t+ic3VxVdf+T77+GCC9i8Gfr3d3/zHTpKrOdy2L/f/Y0L3I5bE8vEtaYWgr8jSdChAxw7hkaCjuEGfitoIE1AA510czoiVzsfRVE4UmCmY4RBHYUdPeqbURioPurSBY4cASBYo5AYGsDvhfX4yYs+qrA5KbM5mZC+Q30hI0N9KPHGJue6LFwIR49iRCY6SEdmaQMjei/6R1YUfskz0zHCiFEDpKertdb8cSqxpAQqKyE+nptv9syseXw8jOhfCbXS7An8F3/PjuZfzJ+vPqGazdx6cjtGnYaTxQ0kSK3TCbnSEVW/52rnA5BeaiNQr+HWk9vVL7jFAp06teDi3MQjj6ijsapIxTszdyAr6nTnGbTSR7U/U7e9ahRF4fdCC1MKDqijMFmGY8dUMfEF4eHqWlxmJpNP7SKn3N7w5ucm/FOfj5rrH4DfCy2EGbXcdnK7Ogo7dgwee6zFl+hRNm2Ciy9GUdRdC55i8RKdELE2gh8+avkxWi107apuTBk4kHtzd/NExAACyiXig+uJBIyLP61kSu0OZSH1d9JnTB02sgG20OygoNLO7OJ9oMjw00/wxBPeDVaoiySp1ZIfegiGDweNhun5e1gU1h+TXkO4sc4tV319VX6qvv6FB80NClm906uNiHxQgIZYXZVQHDumVk8MDm7+tbmLxYth4kT08fF0ijTwa4GZ3rGB9VcMb+weqsdHDU49N+CfY0UWZEVhWnZVooLff1fjzP11k/OGDTBmDIsWQZ8+cNVVnjnN2j96Ur6tPTcqim+/T4ImESLWXObOhTvvhMxMtImJPFS0lycZgKJAu5AGhAzOqP/lUvRZIwJWanXwR5GFfxXvR6sBDh9RowP9YTE+Ph4iI+GPP6BzZwIkeKB4P08r/egeYyK4vsrGjXTWjdKoyNv/FHkJNQNFejq8/XbzrsfdBAVBx45w+DDj9XqWxg7iZImV9uHG+o/30D10vNiC2SHzYP4e1T+lpWry25UrXbwQH/Ddd/DEE2x8Fm6+2XOnaZek5SnGcuNvv6kPrgK/pc1OJ65Zs4brrruOjh07YjKZ6NKlC9OnT6ewalOyx5AkeO45tZZRVYaKmUV7ya+0c6K4kfpdcfF//jSGC8cVmR38WmBhRvF+AjUKnDihTiX6Uyn5JUvUCMCCAkBdH5tevJ/DeWZKrQ3ss6t97Y35yYVjSiwO/iiy8kDRfrQSalmavXvVUZA39881xOOPqwEw2dnck7ObYouT3PIm9lm64h8XjpEVhd8KzFjsqoBpJNRIvH371L+bL9YKXSE9XZ2KDQ7miiugfXvPnap/f4hLDYavRGkWf6fNitiSJUsAWLBgAevWrWPGjBm8//77DB8+HLM76jI0RmAgPPus2ilWVqKXYGbhXqxOhUN5lU3X1Krb0bgqcEBmqZVjxeoILFSjqF/sjAx45RX/Ktyp08Hy5fDjj+oTPhCuVbi/eD+/FljIaarDhhb7qKDSzm+FFh6oFnlZhj171GmylJTWXpl7kCTVP7/8gra8jAeL9pJRZiO3oU3Q9dEC31gdMj/lVKLXSjxQLWBOp+qfTp28v7+wOaxbB1V7UR94wLOn0mph+VtBsHatZ08kaDV+1Os1j7Vr17JmzRpuueUWRo4cyeTJk3nnnXc4cuQIa9as8bwB8fHQuzfs3Anl5WgkmJG3m3Cjjv3ZFY2mFWoJdqfMobxKymxOZhXvUzvn48fVfVmvveYfo4u6RETA0qVqB1mVezJMqzCrZB95lQ4O51e6tfaYrCgcK7KQXmpjZvF+dWO6w6H+jaKiYNEit53LLQQHqw9De/ZgqKxgZvE+TpXZatap3ImsKKSXWPkpp5LkMAPTsnf9ud1gzx419N9XwS6u8umncPXVPPYYfP2150+3MbMr7xwapGZ2EfgtbVbEamcNqWbQoEEAZGRkeMeIhx+Gp5+GXbsgPx+ASZk7uLfoAL8VmvmjyHLmHqkWkFdhZ392BVEmHQ/m70GPooax5+TAG294t+RKc4mPV0ccBw6om7ABgwQPF+4hzKBjf3Y5OeW2VuXelBWFU2U29mZVoJUkZhftxaRR1Om6bdvUTBlPPeWuK3Iv7dqptu3ciaGkmFlFe5Ek2J9dUX8S5WbikBUySq3sySrHqSg8UrqP8Sd+UN80m+GHH9RsNP7qn2oqKtSgnJ49WbsW+vXz/Cnj4mC14SZYL7La+zNnVWDHxo0bAejRo0ejx5VWTW9VYzAYMLRUCBIT4eWX4Z571M6gSxcitDCneB+vJg5m36lyYoL0tAsOwNCMeu9yVSLWjFIrJp2GmSUHCCyrGln8+KM63/Haa61ev7BarVitf24TKKt66nSrj6KjVbGdNk1NS9WzJ+j13Jm5A4sMLxgGkVVmIznMQJRJV3+UXh0URaHE6iSvwk6xxUF0oJ6ZJfsxlSlqpObxE+pa4ZNPqtOIrcDjPkpKUoX+3nvRpKQwDYUSWcPL2gEcL7YQHxxAdKBerdjtIhU2J6fKbRSZHcQFBTCrZD/GMtQADlBTk/3yi/q3eOSR5ttci4b841bWr4fLL0dWJMaOVb9qnqZHD4jvEaFOKf7tb54/oaBFSMpZkn6+sLCQgQMHEhISwr59+9DW07mXlpYSVk8mi7lz5zJv3rzWGeB0woMPqvvIevZUp68AuwJvJQ0hp9yGJElEGLWEGHSYdBoCtBKSBIoCdlnB6lCLbpZYHZTbnESYdEw4tYcwbdWfqKhIFbCUFHVqzA2hv/PmzWP+/PlNHucWHykKzJ6tPlF366aOQqquocQp8XrcQMqsTmKCdEQH6gmqE8UoKwolFicFZjtFZgchAVrGZe8nQeekpn8vLFS3QISHq6NkN4xSveYjm01NhVVZCb16QUgIpbLEyoRBFFQ60EgQHKAlSK8lQCeh10hoqvxXff+U25yUWJ0YtOq2j/Ent3Oa9pWXq+IF6lSmG+qpNeSfkpISQt1VymX8eLjtNrjoIve05yp2u/q3+Oknt07ZV/dFbvXROYpfiNimTZu4+OKLmzyuW7duHD58+IzXLRYLY8aMYf/+/WzdupW0tLR6P19946Snp59247RqlFGX3FyYOVMNsujSRV0XqqJclng/ZTBlVicWh4xdVpAV0Eig10gEaDUEBWi47uRuorXynxpVXg6//qpO/zz9tFszr9d9is7MzCQtLc2zPiorg3/9S72uzp3V66kKSrEqsDJpCAWVdswOGb1GQquRsMsKTlkh1KDjhlP7ThcuRVGjIH//Xf190SK3Bih43UcnTqgpnwID1dRYVQ9eFhkKZC1rUwZidSrYnQqyoiABOq2EQavhmvQ9xGid6Os+35SUqFseysrU2iWtHH3VpiH/uK2DrqyEgQPh4EHGT9Ayb566Q8Eb/PorfDz+v8ycKcHVV7utXSFi7sMvRKyyspKTVYljG8NgMNCxzt1rt9sZO3YsGzduZP369YwYMaLBz3v1xjlxAhYsUJ/kEhPVTtXk4t4nRVG/uHl5cOqUGl2Xmqp2bB7eeJmRkUFycrJ3fJSfD48+qo4w27VTfRQWdto1WmRwIqGXFDWtVm0qK1X/ZGaqHf6jj3qldIhXfKQoqig/8YR6D7Vrp86hhYS4FoXqdKpRoXl56tShwaA+XHXu3PbuoQ8/hF27KJ+3hCFD4OBB7+0/tligf/dKDp13K9Lq/7itXSFi7sMv1sQCAwPp3oLKdg6Hg3HjxrFhwwY+++yzRgXM67RvD2++qUblLVqk7sGxWtWNroGBaqdSnZtOqVrrslrVBezKSlXwYmLUfTtVU5NnHdHR8NJL6nU/9pjaaZeWqv4JDYXAQIwBAeq6n6KonbnZrI7gSkvV6Z34eHV7gS8zcHiC6jyUb72ljp6eeEIdFpSXq/dN9T2k1arHyvKf91Blpeqv0FDVx23dP//+NyxcSHGxOoD0ZgINoxH+dpOJ/DVZxJSU+CaxtqBR/ELEWoIsy4wfP561a9fy0UcfMWrUKF+bVD/h4X9GfsmyumZTXKyKld2uvqbVqh1ycLA6/VhnNHLWYzD8Gd6tKOrILC9PzaxRVqaOKiRJ7bxvuUXtmOPi/DNBrScICTl9e4DF8uc9ZLOpPtNqVT8GBan3nF7vK2vdS0aGOtru25cEBW680fsmPPa4BMbRqpjefbf3DRA0SpvtBaZOncqqVauYMWMGMTExbN/+ZyXWmJiYBmuf+RSNRu2Ao6N9bYn/IklqyqrISHX0ITgTo9G/NyW7k1degUmTyMiUuOMOz9QPc4Ub9z7I20cvwHDXXefWA2YboM3uE/viiy8AeOaZZxg2bNhpPwv9fdOmQCBoGosF1qyBm25ixQr46199Z0pMspHPIm72zi5rQbNosyOx48eP+9oEgUDgSVasgLFjITCQPn3AhQBmj3HnnfDj2qth0a1w2WViNOZHtFkREwgEZzFms7oBfPNmsrLUUZgvdaNXL+jVqwPOvXFo16+H0aN9Z4zgNNrsdKJAIDiLWb4cbrgBS1AUl12mxkP5mh9/hHFlr6shko7WpwQTuAchYgKBwL84dgzefRceeIDnnoMbbvCPXSa9e0OxM5QtqePVckwCv0BMJwoEAv/B4YCJE9XaeEFBTJyo7hrwByRJ3daotd0ONw5RpxR79fK1Wec8YiTWAqxWK/PmzTst1Y6v8Teb/M0e8C+b/MmWavzCpocegmHDKB48khEjthAUZMXYQMFrX5CSYuXNVc+w+uaP4B//qCn6KvAhyjlESUmJAiglJSV+0Y47cZdN6enpwkdN4A4fCf/UQZYV5dFHFeVvf1OslQ7liitsCtzlV/5RlGofaZULL7QrK6ftUpTzzlOUnJwWtuNff/+2ihiJCQQC31JaCjfdpBZ5ff99HIqW66+3Ay/52rIGcPLee5XsZhDKo3PhkktgyxZfG3XOIkRMIBD4hoICde1ryBAqzh/NC4Pe5u836TGZ4Lrr/Dv6LzQUnn8e5DF/YWKPrey6512UG8bB9u1qGjCB1zinAjucTidwZjHD5lL9+da2407cZVNxcbFb2hE+8o4t7sTt/iksVEdZZjO2onJs6TkoGZmEpP/CiU3H+DU/kuy+o7ll/Xre/iSMjIwylixR02X6o3+gfruunyjx6LKnGJdykGsWP8mH29uT0D2EtGHhRKfFYo1NIiA2HCk4uCYBeHHV5wv9Ye+AH6IoCmVlZSQkJKBpomqDX5Ri8Ra7du1i8ODBvjZDIBAIBC6Qnp5OUhPllc6pkVjnzp0B+Pnnn1tVw6esrIy0tDQOHTpEiBsq47oDd9mUnp7O8OHDhY8awR0+Ev7xji3uxl12ZWZmMnTo0DMKqwpUSktLSU5OdsnH55SIabVqufukpKRW3TjVUwmJiYl+cwO62ybho6ZpjY+Ef7xri7twt12hoaF+dX3+huRCrjER2CEQCASCNosQMYFAIBC0WYSItQCDwcDcuXMxGAy+NqUGf7PJ3+wB/7LJn2ypxp9s8idbauOvdp3LnFPRiaWlpYSFhVFSUiLmoRsgIyOD5ORk4aNGED5qHOGfpvErHzmdZH+6g5+yorjozq7oA3xfK605fbUYiQkEAsG5SkUFXH45jn9/wHtPnOT/Ov2C3da2xjVCxAQCgeAcxXbnVGZqniLxo+d5O2MUCwatRbvsGV+b1SyEiAkEAsG5yJYtrNjfH2ngALVqtiQx7P17+P7lnyk9csrX1rmMEDGBQCA4F3nkEbLO/zvT7qm1BmYysXXQPbwzeavv7GomQsQEAoHgXGPfPjCZmP9qPAkJp781/qlerNmZAlU5MP0dIWICgUBwrvHyyzwV9ww7d575VlJHPese+AY++MD7drWANp12as2aNXzwwQfs2bOH7OxskpKSuPLKK5kzZw6RkZG+Nq9+HA7Iy4OiInj3XajKrI9OB7feCnFxEBbmUxN9jiyrT4GlpWCxqL9rtWAyqTUwQkOhiczWZzWKokaVFRZCeTmYzep9VO2jiAiIiVHvqXORav+UlKj/2u3qazqd6p/wcPUeciGl0VmJ1Yqy6Tv+bXyVyd3rPyR/9E0cnvQsoyZ717SW0Kbv8iVLlpCUlMSCBQtISUnhl19+Yd68eXz55Zfs27cPk8nkaxNV0Tp2DF54Qa2fJMsQEqKWZDAY1J/q41588c+OOzpaLdWelHT2f9lkGU6cUAs0FRSonU5goNrh6PWqYDmd6utms/qj10NUFNx7L6SknP2iVlgITz4J+flqx2wwqPeQ0finj2RZ9VFFhXofGY3Qrh3MmaP68mymvBwWLYLcXPX/BoN6DxkMqriD6h+bTb1/KitVn0RFwYwZEB9/9n/Pqlm/noqL/sLfEiUa2oKl6ZDCoyduZ9SxY9Cxo3ftayZtWsTWrl1LTExMze8jR46kU6dOjB49mjVr1nDLLbf4zrhTp+Cxx9TOJyoKYmNxdO5CrtbER0kDKLM6sToUFNQ9GTqNhEmvIdSgY9yxHwgrzIWFC9UvW4cOMG/e2fdkXVgI8+erHU9EBMTGonTsRKneSIms4bOUgVidMnanglM+3U9/++MH4opy0Tz5pDpqi49X2/L1xlF34nCo13TihCpS7drhSOtJvjGEj1POo8LmxOKQsVf5RquRMOk0hBl0/CNjJyHmcsjKgttvh8hI9X6KiPDxRbmZrCz1u1FZCQkJ0L07hIQgI1Hg1PBJ+/OwOGRsTgVZUb9tGtT7KNhu5uoDG4h69FG0lZWQmKj6+2wX/NWrCZ4wgbkXNXxIQgJooqMofv+/hM+6y2umtYQ23SvWFrBqBg0aBKg74n1Cfj7MnKmOHDp1wtqrNytThpFfacfqUAg1agnXaYgLCsCok2qyNDtkhQqbk1KrkxfjBmGNVojup+PWP7YQcvwo3HILdO4MCxa0/SfGkhLVR+Xl0KkTFV178E77oRSZHVgcMiadhqAALUaNRFCAHr1GQqep5Se7k/dShlIa5yB6gJ47M7djzMqEKVPUznrxYnWU0lZxOtXR07FjEBdHRd8BrOx6EYVmB3ZZISRAS6hGIiE0AJNOg16rjkKdskKlXabY4uC5yP7IikJsFz0T0rcTkJMN06apIvbUU22/oy4shH/9SxX6Ll3UB0VJIs+hYWXMAEqtDoIDtATLCiEGLXqNhEaS0EggK2CXFcx2DSsHXkmZ1UkIDsbt/4rECRPQJCTAE09AQICvr9L92GywYwe3697mmX7qzGpDbPrSjO7uj0GImHfZuHEjAD169GjwmLrVYg0GQ+tzoSmK+qXKzISePSmJiOH1uIGUWZ3EOGU6RRgJCtA2+HGdRiLMqCPMqCM5zIBDVsirsLO03VAMycOZnLGdsN9+UdfNlixR1zzcgNVqxWq11vxeVlYGeNBHDz8MJ09Ct25kRifydmw/nLJCDJAaaSRQ37CPqgkxaIkPVjvtnAo7iyIGEp88jMmJ29FkZcLEiZCaqo483CD4XvVRerrqo+hocodeyJuJg7E5ZWIl6BZtwqhreNpUq5EIMWgJMWhJDjNgdcjklNt5LHwA0Ql6JsXtwJCRDhMmQLdu6gjGDXjVP7KsPgBlZkKPHhAbC0COQ8Pr0f3QSpAQpKdrlLHpMh6mP7u/UquD/wy9mtI+NrrnHeeG8ePR9OoFs2e3zt4qGvKR19m2DfPgkWzfoWlUwAAq4lPZ9HsaV5eVqUsgfspZlTuxsLCQgQMHEhISwr59+2rqh1VTnY+rLnPnzmVea77QFgtMnQomE/YeabyYMJRii4P24QaiTDqXauI0RqHZwfFiC+FGHVN/WY/2px+hfXt4/PFWd9Lz5s1j/vz5TR7Xah+ZzaqPAgMp6daT5bGD0Gsl2ocZGhV3V3DKCidLrBRZHEwrPECkbIWfflJFc/nyVj9Re8VHigIPPgi5uZT37s8LHS/EqSi0DzMQZmzds6asKGSX2ckss5IcZuCOo9/DgQPqmtmyZa2epvbaPVRWpt5DERGqCGu1lMoSL0YPQFEUOoQbCTG07l6yO2XSS2yUFZcx5bt/ExUZoj40euh75vXciQ8/zMnEYXzs+Cv33df4oRYLDEjM5tDr2+Daa71iXjXNyZ141oiYxWJhzJgx7N+/n61bt5KWlnbGMdWOqVtNtVVPiKWlcNdd0LEjhQnteS6iL/HBehJDAlotXrWRFYWMEhv5ZjvT83YT+tN+dUH/+edbFdRQ9wkxMzOTtLQ09/qouBjuvhs6d+a1wdeSWWajS6Sx1Z1zXcqsTn4tMNMuOIBJWTvg+HF1ZPPyyxAc3OJ2Pe4jp1P1j1bLa1fcTma5k44RBqIC9S22uT7sTpmjRRYcMkzP243xj9/VSNlXXlEFrYV45R7KylIDMNLSIC4OpwIvtTuPgkoHnSKMRJjcey+V25wcya1gwB8H+GvWfjUwywPfM6+L2Hnnwbp16vSrC1w6oJB3ui0i8f0lHjbsdM45EbPb7YwdO5aNGzeyfv16RowYUe9xbs9iX1Skdj49e7Ji4FUcL7bSPdpEcCtHFo1RYnHwW4GFuwv3E/v7IXVd6ZVX/ozAaiVuz65d5SOlV2+eS7sCi0OmW7SpZo3L3ThlhV8LzOi1Evfl7EbKzYHDh+HVV1slZLVxq49kGaZMwRkczLPD/wFA5yjP+Qcgr8LOiWIr04oPEJ2drq69vfZaq4SsNm6/h554Qh1ZDxoEwcGUOCWWRfYjyqROvWs8tEZsdyoczKmgz8mfueb4TjV62E3n8kkW+9xc+OtfubX7dl599c/A6MawlVoIGD4IDh70vH21OKey2DscDsaNG8eGDRv49NNPGxQwt1NWpgpYnz680f9K0kus9IkL9KiAAYQZdfSMDeTFqH68O2aCuqfsrrvU6Sh/o7S0xkfP9hiNrCikxXi2g9ZqJLpHm1AUWBo7SN131727GvRR60nYb7jvPhymQBYPvZGgAC3dYwI96h+AmCA9abEmlkf05Z3h16kh1JMmqUES/kZOjipggwdDcDC5Dg3Phvejc6SR9uFGjwkYgF4r0SsukP3JaeRKBnXLS1vm668pvuAqfvrJNQEDsGBkveYK8FWgnAu0aRGTZZnx48ezdu1aVq9ezahRo7xzYrtdFY6ePXmz7/9xqsxG77ggArTNcGdO9p8/zcSk19A7NpDfCy3kduymrvk8+GCz2/EoNlvNKPX5HqMB6BJlav4Ua20/uegrSZLoHGnEKSu81G6wKmTt26t/M1lu7pV4jjlzUMxmlpx/E5FVo4pm0wL/AATqtfSODeRokYXcuGTVR9Om+dfDkMUC06fDgAEQGEiRU8OLEX3pGRtIqKEZ04et+K7ptRrSYoN4cfg4KvMK1HXotsrGjeT1HcU//+n6R7RaeDD3AfjuO8/Z1UradHTi1KlTWbVqFTNmzCAmJobt27fXvBcTE0NqaqpnTnzffZCYyL/P+yvphRb6xAU1/fRc5wu08KD5z18OHgNgTq9aYc9x8Y02Z9Bp6BkTyItKP+7vLhO2Y6sa9ZeS0pwr8QyKonaIycm8OeAqystt9IwNdO2ztfx0mo+o8k9tPzbiI0mS6Bpt4sfsCt5pP4x/gjr1+uCD8IwflJooKICjR3n5b/cRoNGQFOqigDXiH4A51OmoG/HRn/dQXx6SZIIO7FWj8RYtcs0WT3PffWqUaWgoFhmWRfSlR7QJk76Jh8XmfNea+J6B+tDYKdrEsktuY+aXL6GprFQ3Urc1duygy/Ll3NuM3RVBQaAPC6T4qx2E33ST52xrBW1axL744gsAnnnmGZ6p0zGNHz+et99+2/0nffxxMJux9OrDbwUWesYGotc2ImANdDrBWelnHLqQ5Jr/13RGjXzJTHoN3aJNPCsNYFbfSgwPPwwrV/p+U/Sjj4IkUZLUgZMlVvrGBTY97VPlp7odc20/1fYPNO0jjSTRIyaQH3MqqJQlArt3h23b1GCP5OR6P+MVZBnuv5/cXgPId0j0jXdhLaoe/zR5D1WLfhP3UKdII0u1A5jd24G0bau61zE6uhkX5AGOH1dH80lJACyNHkhKkL7xSNZGBL62r8oTkv98/+Ax1U9NiFmkSU9xWDDvXHADt95/v7oO3ZbIzYXQUB6aa2LWrMb3h9Xl2+/1hFy60WOmtZY2LWLHjx/37gllGQ4dgkGDeCHuPJKN2safCut0PPV1OrWpfr/2l2wOjXdCIQYtyaEBLNVdyMzMdPVJ+sknm3NV7qWyEn7/HWX4CJ6P6k+XCGPNZtwGycl2yUcNdUSNiZlBp6FjhJFluv7MKtoLffuq+7Deecd3qaoeeQRnWBgvdRpJ9yhT4wLvonjVfc9V/wBEmnQUmu281n4Ek0pL4IEHYMUK322qVxT1QahvX5AkXkscgt7qJDaogWjNZjwo1n29xk8uiFmHcAN7KxMot8sEt4F0TKexZQvyiAv473+b3z049Cb26IcyKCuLM1Le+wFtek3M68ybB5GRlBiDqbQ7iQtuZP9RMwWsNsFZ6TXHLzxobnI+Py44AI0EKy6+SZ1S9GUAw6xZ0LEjb6ReQFCAtvEw+pxsFn5zzD0+qmqvPqID9ei1Em8kDVE3bUZFqRkxfIHZDMeO8fpFNxMVqHNpZFHbP676qDn+AegQbiSzzIotMkrN5uHCvi+P8dhj6jxWSAiVskRmqY3UyAZGq/UIWKv81IiPNJJESriRFRfdpIqsP60fNsXmzRT0vYQrr2z+s0lpKTxc8i/YtMkjprUWIWKuoijwxx/QuTNvxg9sfBG+FQJWmzM6oUboFGHkeLmMMznZdx20zQY5OTiTU8gosdE+vHEftaTTqYurHXWnCCPpJTacCmr6rj/+8E2Qx+zZONp3IMuKS+tgbr+HGvCPTiMRFxzAGwmD1Y3Ev//uu0766FF1LQx4MWYA7cMN9a851xEwt91HjQhZdKCOPJ0Je0iomhGmrbB1KzFjBvHss83/aPv2kG5vh/O7Le63yw0IEXOVrCwwmXAYTRRbHEQ2tLnSTQJWjatfLoNOQ2SgjhXDrlfXfHzRQS9cCPHxvNV+GJEmnUvRmq31T+02GuuoA7Sqf95KHqLGF0dHe78Tstvh1CneHn490YH6xoOBqkS+NR1zNa4+DLUL1pNTUVVBIDhYXZfyNrm56lAhNJQSp4TZIRNT3zRiPQLWWlzxk0aSiArU8/4F16v769oCZWVgt/PCqkhOnGj+xyUJvvkGNNu3ud82NyBEzFUWL4bkZLIcWiJMukbXMdwlYNW4KmSJIQFk2lBXbX3RAZ06BYmJnCqzkRDa+FSruzqealxpq11wANnldvWXlBRV7L3JggUQH0+OVSYuuJFsHLVGqe7ClXtIr9UQqNeQ69Co/vHF2uoTT9QE3bwZP7DJ0arH7qNGvmdRJh05ASFqppWSEred22Ns3w7DhvHmm2p+7JZgjAriKKlqVK2fIUTMFRRFjdiKieE/7foTZWp6gdmdXyxX2zPoNGgliZJ2ybB0qVvP3ySyDOXllAaFopGkhhPVeqCDrk1jozGTXoOiKFTK6pM+lZXeXT88dQpHQiI2h+JSomOP3kMNdNKxQXreSxig9nbFxX8WbfUWubkQF4dDgWKLg6jAemY8GohkdSeNtR1i0FJmc6q12hYv9pgNbmPzZuzDLqRjx5bn8T18GBbaZ8LWre61zQ24VcSWervj9BaFhepit1ZLmdVJmLHhDsiTX6zgrPQmR2NRgTo+6Hu595+YqkJ430seXH/HUwd3d9Cuthlh0rEqZbA6RxId7b0pIVmGigoKA8MaT1LrgVFqXRq7RyOMOorMDjVyMzLSu6PV4mJ1475eT6YLMx7g4fuoge+ZRlJLA1miY9WHW39n82b0F5/Pxx+3vImBA2FPeTfYvNl9drmJFofYf1yPR5588knat28PwLVeznrsUTIzITISsyyh01D/F8uDo7DmEGbQkVHqVJMDl5e7LV9gk2RnQ1gYpVYnKY0EvXhS5Guf47SN47UIM+jIr6yaUoyOVpMDP/ecx20iLw9CQvikw2CCfBjVFpyVTnlC1V6yevaQaTUSJr2WIqeGiMhINV/g0097x7iTJ2vmu/7Trh/x9SVA9sIozBWCArQUB4YSX17uUzua5P/bO/P4KKp07/+qqtfsoCFhSwwQBUQFQRYHREbQ19EXZAzqjFeBGeXKNjOIC+pkDOB6FZdhHEWvMuC8cp3BOzoooqhBECQsSUQBgbAlgWxk6SS9Vled94/THRNIV3eT6qpqcr6fTz4f6O7UefqXc85zluc8JxBs9eGefsh1AJdffn6PsdmA9/+fH3jEeMEd5+3E8vLyMG7cOFjaXXPhcDiwcuVKcBx3YTmx1auBtDQ0ynyXrw3pKkmnK7Ac/ZGf0fn7iRYeTlGmy2U1Ndo5sTVrgLQ0uEV6qWWnBDqgWDr5cJ10koXHyabAEllaGo1S1IKAE/P4ZaR08bqQWNPDJuB/+o/C3JYG7fQBgLffbsuu3uKVcOlFyjrFuh4ptTO7iUcrEehg0cgZPPbuBUaOxDvvdH2Ls8+wnqhpsiLD6TTUpbPnvZz4xhtvgBCCV199FYWFhSgsLERmZiYKCwvx1VdfqWmj/rjdQEICNmSPUjzcrOnoUGGpAwCkxETaKWhFQCOZEAgxTmDbFcwCD1EOzISsVu32xN59F7Db4fPLsIaK2ozxfmGQtmXpEKRYTWj2SrRjdrlibk8bTieQnIxWmYNF4GOa3LermAUOn2fTnI5obNTbnNBs2wZMmIDmZnpyoits3gz82fowUFSkjm0qcd5ObM6cOVi7di0WLFiA559/HoQQVe/PMhQ+H2A2wyfJYcPG9VxKDGIVODitidoGLXi9cXOdO8dxkAnovhjPaxO8IIqAyQRRJmHzbOpdhxItPFyiTGcZoqhdwW43YLPBIfNItBg75kzgOEiE0HU2p1Nvc0LzzTfAhAnYsqXrCWrGjQO+9V1tuH2xLn2tQYMGobCwEG63GxMnToTP51PLLmMhSYAgQCaAgScZbVgEHh6ThTpfrRBFwGIBBw5Gv6LOxHPwBU00mWi29FhDCMDzkGSCUKutRoHnAn9DrQelsgwIAlwyF3q2ahA4LnAWXBC0dfTRIMvAoUP4tnEw9uzp+uP69QOeXk7i24l98825m3qCIKCgoAAvvPACFi5cqJphRiQO/BcA6mglQdD2wHOg0zMJ3E/LdQZF4AAx+NfUaibGcYAsg04A46Um6cOXl1xt6CVpoK26G5sDB4AhQ/DeOk6VIEqOA4ZN6Y2W8kZDOe6onNhNN92ETz75pNP3xowZg0cffVQVowxHwCGYeA6ipNxBtwUV6AiBDgcAA0NTm8DB6ze2E+uALGuT9d9kAiQpsJQZJ/rIsi5Jkn9+otjwGvllAoHj6EWikd4wqTWB/bD9+4ExY9R55Fv/zeHvKfOAkhJ1HqgCUdXQhQsXYvr06Xj33XfPeW/jxo246qqrVDPMUJhMgCjilvK9cPtDz25ChXVrjU8isMl+6ny1wmwGfD7YzTycosYHZKNEkgksXKCTFEW6rxFr7roL8PlgEcIPhPTGLweCc3Ta57RyRHeNwg1GfZKMm04WU40MFKnXgcB+2BdfAD16qPPIceOAb4XxhlpSjMqJPffcc3j++ecxe/bstoPNu3fvxqRJk3DrrbfCbFZIpRPPBKLYegoynD5jd9AA4PHLSPK6tO2A7HbA7ca0imK0htFI79mqKBNYOdCZRmCvKuYkJQFeLxLMgSMQIcgfZo+5PuGe33ZMwu2mf1etCCy5JvEEHoXBolYoDUo9foJkXqYapaRoaFUUFBfjSNIIVYMJr74amD3HHL9ODAAWLVqENWvW4LHHHsOYMWMwduxYVFVV4f3338ceNXYPjUgg1Dg50LjCLXXEshNqe3aIe48kmYADILicwMyZMbPjHJKTgeZmpAsSWrwhnFhGpraz1U408kkyzMH9Fi0Pg6elAU4nfllZDIfH3/lnIrhlWC2U7s5yiRISzDxNHKuVPgCdEbvdSOVlRUdvBJyihFReojMxI54RO3kS6NMH//rYjB9+UO+xVivws7svgW//EcNcRRO1E6utrcWuXbsA0FnYuHHjcODAAcyYMUN14wzD/ffTBg0gLZiW52wCHYIWnbRSGc1eiaY1am4GMkKc1IwFCxYADQ2w8XRPTpSUO6FYOfrWPv0V9XF4JKQE7zjT8gbjHj0AlwsZgoQmjz9sBKees1WnKGNqRTFNAzV3rnYFp6QALS0wcYCJA7xhlu5jWYeUkGQCmQBWr4c6XiNGeASWEouKgJ/9TN1HP/YEj8963EkTKhqAqJzYkiVLMHDgQKxevRpPPPEE3nvvPRQXF2Pu3LmGD6vuEr170wYN4D+qin/KhK5ALBpYJM9sdPtxe2Uxdbrp6arbEJK+falGsoyedhPOuELMNqDv3mGD2487Tu2l/6mqAh55RJuCA0vtJllCitWERk/oJddY6hNJHWrxSriIl2jO0H79YmbLOcyZQ8sEcFGCGXWuTtqZRrNVpZlqs1eiWVeamugM24hs2waMH49164DBg9V99NixwLdJUwyzpBiVE3v55Zcxa9YsHD16FPn5+bjrrruwadMm/OMf/0BeXt6Fe04sMZGGYYsieplk+CQZ7jD7GrFCqXERQlDvFtG3pY42Li1HiMGEsfX1mHl6D2paQ9SFdrar7eg7zMI60UgmBM1ePzIE+acDqmrteEdCairQ2Ih7q4tR1aK9PkGU6pBMCHySjMRWB633Wu5z5+TQ9FwAZp/ajepWUXFwHIvZWCTPq3eLuON0CZ3Jz5+vavmqsXMnqnPGoaRE/S3fSZOAydNT4tOJHThwACtXrkR6uxH+ddddh8LCQuzYsQM33nij6gYahvR0mqkd9EbeiuZOsmG06xjUbmDhlskAoNHjR7JVgPlUJbB4sWplR8yjjwInTyKFp9FtIffG8JOjV0ujiDoflz+QGR30vrWsLFXKjphevYDa2raBkEshilNtfYLPCleHmjwSUm0mmoxXy1kYQINIbDbA4YCdJ0izCZ2vepzlgNV2ZOGcfKPbjz6CnzqxSy5RtWxVqK8HrFZs2paIL75Q//EXXwxcf38u5D3F6j/8PIjKiQ0MXBl+NsOHD8c333yDCq0vGdSSJUvarqWYXb4TLV6p89lYDBpYh2coLKeUN3lxX0URXZIJ8beKKVlZNNeey4Xf1pXipCNE2quz9g+7qlHw95VmYQBwqsWH2VV7aSaT2lqgoKBL5UbN44/TpMyyjKxUK042aaPPOc9QqEN1ThF3nSyiHfSf/tTlcqMmKwvB64fnVu9BZbNXcX9VbY3COfk6p4iedjOEhno6szZiRPbWrcCECdixA7j22tgUMWuOBd8lXqv9xbKdoNpEc+DAgdixw5jXV6tCZiZdUmxpAccB2WlWHG9STleUP8zeNiM7n0bW/veURocAUN3qg90soEfFMSA7W5dDquA46jzLypBhkkEIQVOYSLyudkKROrAmjx8mnkOaQOgdYtnZ2p6jA+iRh4svBqqqMKt8J3wSCRup2F6fWNchUZLR4pXQ5+RhOsPQow7l59NBmMcDGw/0T7WirKGTdnbWqgfQdY3C1SFCCCqbfbiveg+tQ489FnVZmvDFF8DkyVixQv2gjiAjRgDfpk81xJKiqrU0Q8toOD249NK2iJzZ5TshyUQxUjHI+TSy9h1PuM7HLcqodPiwoHIHvfts+fKIyogJTz5JN7xbWzH/TAmONnhCH0noQkd9TuesgEwIjjd68EBdCZ2FnT6tn0ZPPQWUlQGyjIX1JShr8EAKlaarnT7RanS2PuHqEABUNPvQ3+wHV1Ojnz48DwwYQDUCcH9lEWQC1Ha2x3qWI+tqOzv7mWdT6xSRYhWQ1Oqg9Ujr5ehI2bYNjisn4NCh2B0VvfVWYMikTEM4MQ3y7VxAPPEEcN99dEkoIwML60vwon84rspMhFk4K4gi2BgCV6YEG8nyH9wdGlgwY/nZja5Dx6zQsPwywYE6F+Y3fgfbgR+oo9VziYPnaQe4fDlSx4xBeoIZJxq9GNAzRFaMjEygprrD912OyDqgSDWqdPiQajOhhyADBw7R2aJeGqWm0r2xY8eQOmgQMpPMOFLvxuD0EGeNAvoAoetQKCLVB6Bnwxrdfiwo/Bu9s0OLVFyhWLYMuPfetnN8fzizF8+mjkCSVUCC+azZc4TtLBTRtLNyhxePN5UA+/fTOm7E0PqKCqBHD2zekYhdu4BRo2JTTG4uMGjeUGAsc2Lxx0svAf/5n0BqKpJtNmSlWvDjGReG9UroPLFru04IOHfW0L7DPmdGEabjkQnB/loX+qVY0OtgBV3u1HqfpzNycugQsLIS88DhmZ5Xo84pIj0xhOMIo1FYFHRqcPtR7xbxRFMJ0OwAHA7gz3+O7vlq81//BcyaBaSn4wHswosXj8LxRg9yeig7+iBR6RNBSLpMCA6dcWPeoc0QJEmfvbD28Dzw9NN0Vj9uHGw8h4VN3+Ev3FW4MiMB5s4y3MdYo6MNHvRLscL2QzkNQDHqLOzLL4HJk/HVV8C0abEtasasRKxIuwrZZWXAoEGxLUwBY993oMD69euRl5eHnJwc2O125ObmYtGiRWgInDOJGSkpwNCh9MZUScJvK4uQZBFwuN4TOhw4I/Onn7MILoOcMyKMwIEdqHXhogQT7tv/OXDkCPDqq8YZHb78MlBWBs7lxOL6Ypx0eEPv/wCKGp3v55u9fhxr9OChxlIIRAb27aPX2+qtkdkMrFgBlJYCPh8erNsDt1/G8UaFPdbz1ScCDte70Yv3o9eP39NBmt76AHTPMiUFOHoUAHCxQINh9te5lZdfY6DR6RYfJEIw5+BmuhcWSLlnSL78ErjhBixdClx/fWyLuvpqYHOfmcCmTbEtKAxx68RefPFFAMCyZcuwadMmLF68GOvWrcO1114LtzvGt+Pm59NAj9JSQJbxu5rdEHjg0Bl3+Ozb7RtaqJ8wSDKdgaXaTJh7pBAoLgZeecVY6W/sduow9u6FTRLxUFMpjtR70OxVcGRBVNCoyePH4TMeLG4qRQJPgEOH6DGJ3r1V+HIqkJlJ12R27wYv+fHwmb3wSQQ/nnFpUoeClDW4IRCCBza+CQwZYqw8gC+9BFRX0+V7AL+p2ImMRDO+r3HCFyYjjFoa1bb6UNvqw4OV22k7W7HCuFnrZRnYtQsNA69BXV3szbz9dqD/lMHAp5/GtqAwxK0T27BhA9avX4977rkHEydOxAMPPIC1a9fi0KFDWL9+fewNeO452lHv2wcQgkW1e5BoEbCv2hnT5KU+Sca+GicuTjBjXlkhsHs3XZ7q1StmZZ43WVl0aXHPHiQTPx5sKsXheg8a3LG9i6i61YejDR487ChFCk9oIEdjIxAY+BiGggKgf/82R/bQmT1INAsorXYqniFTg+ASIpEJFn21mh5U13sZ8WxMJuCvf6XBVIFD0HNOFaFfqhX7ql2dB1WpSIXDi9OtIh6pLYJ5z27q5I0yCOqM3buB4cPx70/N0KILvOwy4Kb7+tPZqRYXy4Ygbp1YeicplUYFdjErKytjbwDH0WUFs5nerSPLmFe1C9lpNvxQ48LpZp/qqbga3X7sq3YhO82KOfs/A3btog6sv/53mIVk+XLa8IuKkCr78GhTKcqbvDjRpBC1eJ6IEu2Y611+PO4oQRJP6HmwI0doZ6hHyHg4nnqKpuwqKgJ8Psyr2oW59d/hQJ0b5U3emNyr5fXL+L7GBZvA4cEta+j1oEZz8EGSkoA33gAOHqQHsEEjgx9ylKLc4cWhM+7ws7IoESUZ+2tdcIkyHq/aAeuuIhoMlJ+vajmq89FHwLRp+OQT4Be/0KbIBQs5FA+5m55N0wkDturzp7CwEAAwZMgQxc81Nzd3+PF6Qxw6DQfH0WW85GTaCXk8uPfkt3iiuQQuv4TiKifqnGKXOyKXKOFgnQsVzV485CjFzB0fAN99B6xc2eWsCl6vt4MWLYFEx6ppBADPPEP3OHbsQEKrgwZZACiucqKm1ddlfWRCUNXiQ2l1K1KsApY07IWFA+30fvyRdoJdWGqNuUZPPUXDyr/9FmhpQS+TjHxHCWQQFJ920j0ZFW7LlgnB6RYf9tU4kZ3AYcEnr9M3/vKXLu2DxVyf5GTg7bfpbKy4GPD5kMwT/LGpGGk2AfuqXTjR5OmyMyOEoLbVh9JqJy5OMOGho19CKNpJI367GDAVSiNV2bgR+MUvsGIF3a/SgiuvBD5LzgM+/FCbAjuBIxdI5t6GhgaMHDkSycnJKCkpgdDJQdbm5makpqae8/qTTz6Jgq5G9S1bRkeLQ4bQ/Q4ALTKHtzJGosnjR3qiGRfZzTTDfATIhKDB7Ud1qwhRktE/1YrZR7fR8F6fjzowFe56KigowNKlS8N+ThWNqqqAhx6iM48BA+CEgLcyR6HBLSLFakKaTUCiRYDNxMMUwfX0PklGTauImlYRPewmPFC9GzYeVJ8ffqB7BCtXdnlzQDONTp+m+uTmUo0AeGTgrT7XoM4pwm7ikWozIckiINHCw9JZlF4nOH0S6t1+1LaKSLMLmPfDJlgP7qcHmp9++vztDaCZPoTQJc+yMmp7VhYgCJAI8N99x+B0qw+JZh4XJ5iRZhM6j2LsBLcoo95N61GyVcC86j2wV56kqcleflmVpfpQGjkcDqSosQ95+DAwfz6Ov7kZZrN2GcOqqoDCzX78+plhtM2pdDQj2FdHoo8hnNiWLVswadKksJ+77LLL8GMn6f89Hg9uvvlmlJaWYvv27Rg6dGinvx8UpqKiooMwVqsVVjV2QVtagAcfpJ3n4MH0TBAAHwHW9B+Depc/cFeTgCQLD7uJh1ngIfC0fYoygVuU4fBKcIsS0mwm3FNVjHRBAioraeMdOFDVMyper7fDCPnUqVMYOnRo7DQSRZpjsaaGdtaZmZA4HrUSj/X9RsIlyvD4ZUgyAc9xsJt5JJh52Ew8BI6DRAIaefyQAaQnmPGbU7uo85IkOvs6cYKG/C5bpopOmmrkcgG//z21e+jQDjPIJonDP7JGo9UnwSnKECUZPMfBKnAwCRyEwHcloOeavH4ZfpnAbuLR027CzH2bYDt6hD77+eeBiy7qmq0BNK9DHg/NllFdTR1Z//6AxQJCgDqJx7q+I+Hw+CHJgM3Mw2biYBV4els1aGCUKBG4/DLcogSLwOOiBBNmVe6G3dlMO2O7nTowlaIjQmmkmhN77jkgORkLf5yPm26ih5G1hMyaDe6e/wBuuEGV58WdE3O5XCgPrHcrYbVakZOT0+E1URQxffp0FBYW4vPPP8fPFPKsRCNMlzhxAli6lHYWWVn0Xq/AzJAQoEnm0SDx2Jg1AqJEIBECDhzMAge7iceM8t1I42Vwkp+Ozk+coFnpn38+5lehV1ZWon///rHXqKkJ+OMff7ruo3fvc76bnwDNMg+HzOHTrJHwyzSx8K3le5EuyDTqEKCHYisr6bCwd2+6fGkLceZKBWKuESF0oHLoEB0IZWeHvJXATwAX4eCVOQTDHAQAZg6wczJsbifV5dQpqu+f/hTzYbpmdcjtpmfJTp2izr5PHxqBGkhTIRO6GtJKeLhlDl9ccjUIgMknimHjCFJ4Gck8AQ9CA3+OH6fPXLYs5ol9VdWIEGDECMifbcbl16dj715tA5XXrAFai/ZjvvRnYNUqVZ4Zd07sfPH7/bjzzjvxySef4OOPP8bkyZMVP6+ZEwtSU0OXa+rqaAfSsyftlJKS6AivfaCBFLgl1umkF1rW19N/Z2bS9fhOlkFjgWYdUBCXi3bYVVV0GTA1lYZ5JybS0bDVSpco+OB0VaQdTUsLPbjc0EA7rb59aQcdqzw77dBMI0LoMtGLL1JH3bMnvTomKYn2UhbLT3VIlql+bjf9bFPTT9pkZtIBQ4wHQEE0r0OE0Przwgu0rQWvBUpNpftpiYkd82QSQmdzDgdtZ3V1VNMlS2g0rQbn5FTVaM8euq/64Yc4daptJVozjh0DfvsbGYVVQ2i0tgqz12j66rjN2CHLMmbOnIkNGzbggw8+COvAdCEjg2aHIIRmBX/lFboE0tpKHVb78QPP0z9+YiJteM88Q0eVRjh4GksSEoBnn6X/FkWqzxtv0A7Y7aYdsyhSrTiORoPabLTT+cMf6KzCqOd2ugrH0Tjmt96iGlRWAq+/TpdMg9rI8k+ftVioNsnJdEmyf/8LV5v2cBydhQUPITudNP3Sm2/SlQy3mw4S22Oz0cHSAw/QoJp41mnNGmDmTGzZQi+s1JoBA4AZd/Agh24G969/AXfdpWn5cTsTmzdvHl5//XUsXrwYeXl5Hd5LT0/v9NoYzWdicYjmo+g4hGmkDNMnPKpp1NwMjB0L97elGDHGgu+/1y8tqPe7H2H9w1wgECXeFaLpq+M2xH7jxo0AgBUrVmDcuHEdfpbrmcWdwWAwtOKdd4B77sFHn1pw2206OjAvMPrewZBkjkZpa0jcLieeOHFCbxMYDAZDP7xeutS8dSvu6AFMnaqfKVYrMHo08HH6s5j23HN0iVMj4nYmxmAwGN2aVauA6dNRVHYRior0T536xBPAwF+NBg4caLsPTguYE2MwGIx4o6UFeP11kIcexiOP6O/AAHoqIWcAh+NznqUeTSOYE2MwGIx447HHgAULUO5IxeDBwFVX6W0QpaICyHvjBvgdTuCzzzQpkzkxBoPBiCe++gr4/nv475+L7GzVzherwuDBwKRJHN6/6R1g8WJ6Di/GMCfGYDAY8cKJE8DChZD/thZ5d/DYtUtvg85l+XLgzoW9aPKBu++mZxxjCHNi54HX60VBQUHXMrurjNFsMpo9gLFsMpItQYxkk5FsaY+udlVWArfdBrz+Op59LxuZmRI2bjSeRjzvxbJlS3Hbe7fj5JD/E3tHRroRDoeDACAOh8MQz1ETtWyqqKhgGoVBDY2YPtrYoja6aVRURMjllxPvp18SUSSkvJyQhgZja/Tpp63k8stlUv3YK4RMmEBIRUXUz4jku7GZGIPBYBiV06eB3/0OWLgQhQ9vxPg//RzbttGMYp3cNmUorr1WwoYNHHo9/Xvs+9UzcEzJo3lga2pULSduDzszGAzGBUUwx+qxY8DOnTj9cTH2HU9G2p03Ycz2l7H+9wLefhu44gq9DY2c4KUj+5LH41fcDvzfb4rx3L9uRHX6FUiYfC1Sxg6l0SAZGeedJ7ZbOTESSBPZ3NzcpecEf7+rz1ETtWwy2nPUxEjfjemjjS1qo7ZGP8/YgZd6/RVZ5ircXP4miEnAw1d9gxnTJdzvfQr9fp6C6RM4tLicbXmyg0XHk0ZTp9I7zioqLkVz1lasLqjDB38lSFvZgP+95Hb87egErG2aChMnYmPuImxxDwHwU5+tRNwmAD4fgkk3GQwGg2F8Kioq0C/M/XfdyonJsozTp08jOTkZ3IV+xcl5IkkSysrKMGjQIAhGX3TXCaaRMkyf8DCNlCGEoKWlBX369AHPK4dudCsnxmAwGIwLCxadyGAwGIy4hTkxBoPBYMQtzIkxGAwGI25hTkwF1q9fj7y8POTk5MButyM3NxeLFi1CQ0PDBV12NDCNlGH6KKO3jXqXHwndtQ6xwA4VGDt2LPr164dp06YhKysLBw8eREFBAdLS0lBSUgK73X5Blh0NTCPj2sj0MX75RrdRV32iTozFOIfa2tpzXvvss88IALJ27doLtuxoYBopw/RRRm8b9S4/ErprHWLLiSqQnp5+zmujRo0CQA9YX6hlRwPTSBmmjzJ626h3+ZHQXesQc2IxorCwEAAwZMiQblV2NDCNlGH6KKO3jXqXHwndoQ6xPbEY0NDQgJEjRyI5ORklJSWansjXs+xoYBopw/RRRm8b9S4/ErpNHYrpYmUcUlhYSACE/bnssss6/X23202uv/56kpaWRvbv36+p7VqVzTRShumjTDzro1X58ayR1mV3qyz2kTB69GgcPHgw7OesVus5r4miiLy8POzatQuff/45hg4dGgsTO0XLsplGyjB9lIlXfbQsP1410qXsmLvJboIoiuSXv/wlsVqtZPPmzd2m7GhgGinD9FFGbxv1Lj8SumMdYk5MBSRJIr/+9a+J2Wwm//73v7tN2dHANFKG6aOM3jbqXX4kdNc6xJYTVWDBggV47733sHjxYqSnp2Pnzp1t76Wnp2PgwIEXZNnRwDRShumjjN426l1+JHTbOqSpy7xAyc7ODrnxOnPmzAu27GhgGinD9FFGbxv1Lj8SumsdYiH2DAaDwYhb2GFnBoPBYMQtzIkxGAwGI25hTozBYDAYcQtzYgwGg8GIW5gTYzAYDEbcwpwYg8FgMOIW5sQYDAaDEbcwJ8ZgMBiMuIU5MQaDwWDELcyJMRgMBiNuYU6MwWAwGHELc2IMBoPBiFuYE2MwGAxG3MKcGIPBYDDiFubENKCsrAwWiwWPPPJIh9eXLFkCu92OrVu36mSZcWAahYdppAzTJzwXpEYxva2M0cb8+fNJQkICqaqqIoQQ8tJLLxFBEMhHH32ks2XGgWkUHqaRMkyf8FxoGjEnphE1NTUkKSmJLFiwgPz9738nPM+T1atX622WoWAahYdppAzTJzwXmkbMiWnI0qVLicViIWazmbzwwgsd3jt+/Di57rrrSG5uLhk2bBjZunWrTlbqi5JG+fn5JDc3l3AcR/75z3/qZKH+hNKooaGB3HLLLSQ3N5dceeWVZMqUKeTIkSM6WqoPSnVo2rRp5IorriDDhw8n11xzDdm8ebNOVuqLkkZBVq1aRQAYvq0xJ6Yh69atIwDIqFGjznnvxhtvJK+99hohhJDt27eTPn36EK/Xq7WJuqOk0fbt28nRo0fJxIkTDd+wYkkojRobGzt0yq+++ioZP3681ubpjlIdamxsbPt3cXExSUlJIX6/X0PrjIGSRoQQcvjwYTJ27FgyduxYw7c15sQ04uuvvyY2m42MGzeOACA7d+5se6+uro4kJCQQj8fT9tqoUaPIpk2b9DBVN5Q0ak93dmKRakQIIbt37yZ9+/bV0Dr9iUafwsJCkpqaSkRR1NBC/QmnkSiKZPz48aSkpCQu2hqLTtSA0tJSTJ06FXfffTe2bt2KSy+9tEN0UHl5OTIyMmC1Wttey8nJwcmTJ/UwVxfCacSIXqNXXnkFt912m3YG6kyk+syfPx8DBgzA7bffjg8++AAmk0kHa/UhEo2WL1+Om2++GcOHD9fHyGjR24te6JSVlZGMjAwybdq0tmWL999/nwAgGzZsIIQQsnfvXpKTk9Ph92bMmEFWrVqlub16EIlG7YmH0aHaRKtRQUEBGTNmDHE6nVqbqgvR6kMIIVu2bCEjRowgLS0tWpqqG5FotHPnTjJ+/Pi29+OhrTEnFkOqqqpITk4OGT9+PHG73W2vy7JMRowYQYYNG0YkSerWy4mRatSeeGhYahKtRsuXLyejRo3qsP9zIXM+dSjIyJEjWTtrp9Gzzz5LevfuTbKzs0l2djaxWq0kPT2dvPjiizparwxzYgZhypQpHQI7evfu3S0DOyKhuzmxaCgoKCCjR4/uNg4sGlpaWsiJEyfa/l9cXEzS0tLIyZMndbTK2MRDW+MIIUTX9UwGAODYsWOYNWsWqqurYbFY8Nprr2HixIl6m2Uo8vPzsXr1atTV1SEpKQl2ux1ff/01Bg4cqLdphmD//v0YNmwYBg4ciKSkpLbXS0tL9TPKQNTU1GDatGlobW2FyWSCzWZDfn4+brnlFr1NMyzXX389FixYgLy8PL1NCQlzYgwGg8GIW1h0IoPBYDDiFubEGAwGgxG3MCfGYDAYjLiFOTEGg8FgxC3MiTEYDAYjbmFOjMFgMBhxC3NiDAaDwYhbmBNjMBgMRtzCnBiDwWAw4hbmxBgMBoMRtzAnxmAwGIy4hTkxBoPBYMQt/x8gRnZzRa9LXwAAAABJRU5ErkJggg==",
+            "text/plain": [
+              "<Figure size 413.386x413.386 with 15 Axes>"
+            ]
+          },
+          "metadata": {},
+          "output_type": "display_data"
+        }
       ],
-      "execution_count": 9,
-      "outputs": []
+      "source": [
+        "import sys\n",
+        "sys.path.append(\"../examples\")\n",
+        "import utils as ex_utils\n",
+        "\n",
+        "samples = samples.reshape((-1, ndim))\n",
+        "samp_num = samples.shape[0]\n",
+        "flow_samples = model.sample(samp_num)\n",
+        "ex_utils.plot_getdist_compare(samples, flow_samples)"
+      ]
     },
     {
       "cell_type": "markdown",
@@ -306,9 +340,11 @@
     },
     {
       "cell_type": "code",
+      "execution_count": 9,
       "metadata": {
         "id": "yP3AjUrCQZhh"
       },
+      "outputs": [],
       "source": [
         "# Instantiate harmonic's evidence class\n",
         "ev = hm.Evidence(chains_infer.nchains, model)\n",
@@ -316,9 +352,7 @@
         "# Pass the evidence class the inference chains and compute the evidence!\n",
         "ev.add_chains(chains_infer)\n",
         "evidence, evidence_std = ev.compute_evidence()"
-      ],
-      "execution_count": 10,
-      "outputs": []
+      ]
     },
     {
       "cell_type": "markdown",
@@ -329,7 +363,7 @@
         "# Results\n",
         "---\n",
         "\n",
-        "Let's check the evidence value computed and also plot the posterior."
+        "Let's check the evidence value computed."
       ]
     },
     {
@@ -338,16 +372,18 @@
         "id": "_37pQ-HWa1Cf"
       },
       "source": [
-        "## Numerical integration\n",
+        "## Analytic value\n",
         "\n",
-        "As this is a standard 2-dimensional Gaussian the evidence is analytic and given by"
+        "As this is a standard 2-dimensional Gaussian the evidence is analytically tractable and given by"
       ]
     },
     {
       "cell_type": "code",
+      "execution_count": 10,
       "metadata": {
         "id": "YMQ9m747TAOT"
       },
+      "outputs": [],
       "source": [
         "def ln_analytic_evidence(ndim, cov):\n",
         "    \"\"\"Compute analytic evidence for nD Gaussian.\n",
@@ -367,10 +403,10 @@
         "    ln_norm_lik = 0.5*ndim*np.log(2*np.pi) + 0.5*np.log(np.linalg.det(cov))\n",
         "    return ln_norm_lik\n",
         "\n",
-        "evidence_analytic = np.exp(ln_analytic_evidence(ndim, inv_cov))"
-      ],
-      "execution_count": 11,
-      "outputs": []
+        "ln_inv_evidence_analytic = -ln_analytic_evidence(ndim, inv_cov)\n",
+        "ln_inv_evidence = ev.ln_evidence_inv\n",
+        "err_ln_inv_evidence = ev.compute_ln_inv_evidence_errors()"
+      ]
     },
     {
       "cell_type": "markdown",
@@ -378,11 +414,12 @@
         "id": "0q64MMxea-oo"
       },
       "source": [
-        "Let's compare the value computed by `harmonic` and by numerical integration."
+        "Let's compare the value computed by `harmonic` and analytically"
       ]
     },
     {
       "cell_type": "code",
+      "execution_count": 11,
       "metadata": {
         "colab": {
           "base_uri": "https://localhost:8080/"
@@ -390,22 +427,21 @@
         "id": "AbPq2cP6QZoR",
         "outputId": "5605db26-d6ec-4fb6-8c52-ab068d065e11"
       },
-      "source": [
-        "print('evidence (harmonic) = {} +/- {}'.format(evidence, evidence_std))\n",
-        "print('evidence (analytic) = {}'.format(evidence_analytic))\n",
-        "print('nsigma = {}'.format(np.abs(evidence - evidence_analytic) / evidence_std))"
-      ],
-      "execution_count": 12,
       "outputs": [
         {
-          "output_type": "stream",
           "name": "stdout",
+          "output_type": "stream",
           "text": [
-            "evidence (harmonic) = 6.284739837844393 +/- 0.015104103169809813\n",
-            "evidence (analytic) = 6.283185307179585\n",
-            "nsigma = 0.10292108358442056\n"
+            "ln inverse evidence (harmonic) = -4.594318333913113 +/- (-0.0024400552018210885, 0.0024341158219083224)\n",
+            "ln inverse evidence (analytic) = -4.594692666023363\n",
+            "nsigma = [-0.15341133  0.15378566]\n"
           ]
         }
+      ],
+      "source": [
+        "print('ln inverse evidence (harmonic) = {} +/- {}'.format(ln_inv_evidence, err_ln_inv_evidence))\n",
+        "print('ln inverse evidence (analytic) = {}'.format(ln_inv_evidence_analytic))\n",
+        "print('nsigma = {}'.format(np.abs(ln_inv_evidence - ln_inv_evidence_analytic) / err_ln_inv_evidence))"
       ]
     },
     {
@@ -414,48 +450,36 @@
         "id": "slSx0i6mbExE"
       },
       "source": [
-        "As expected, the evidence computed by `harmonic` is close to that computed by numerical integration."
+        "As expected, the evidence computed by `harmonic` is close to that computed analytically."
       ]
+    }
+  ],
+  "metadata": {
+    "colab": {
+      "authorship_tag": "ABX9TyOuisDf5/8oQjWLVUhGuTZA",
+      "collapsed_sections": [],
+      "include_colab_link": true,
+      "name": "basic_usage.ipynb",
+      "provenance": [],
+      "toc_visible": true
     },
-    {
-      "cell_type": "markdown",
-      "metadata": {
-        "id": "-Nh_lQnzbGGv"
-      },
-      "source": [
-        "## Posterior triangle plot\n",
-        "\n",
-        "Out of interest let's also plot slices of the posterior using these samples to see what we're working with!"
-      ]
+    "kernelspec": {
+      "display_name": "Python 3",
+      "name": "python3"
     },
-    {
-      "cell_type": "code",
-      "metadata": {
-        "colab": {
-          "base_uri": "https://localhost:8080/",
-          "height": 297
-        },
-        "id": "AUOB38wrQxEu",
-        "outputId": "1b1bc316-b9b2-4ae6-fff0-e059bf65e849"
+    "language_info": {
+      "codemirror_mode": {
+        "name": "ipython",
+        "version": 3
       },
-      "source": [
-        "utils.plot_getdist(samples.reshape((-1, ndim)))"
-      ],
-      "execution_count": 13,
-      "outputs": [
-        {
-          "output_type": "display_data",
-          "data": {
-            "image/png": "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\n",
-            "text/plain": [
-              "<Figure size 288x288 with 3 Axes>"
-            ]
-          },
-          "metadata": {
-            "needs_background": "light"
-          }
-        }
-      ]
+      "file_extension": ".py",
+      "mimetype": "text/x-python",
+      "name": "python",
+      "nbconvert_exporter": "python",
+      "pygments_lexer": "ipython3",
+      "version": "3.9.18"
     }
-  ]
-}
\ No newline at end of file
+  },
+  "nbformat": 4,
+  "nbformat_minor": 0
+}
diff --git a/requirements/requirements-core.txt b/requirements/requirements-core.txt
index 78a12009..4875bac0 100644
--- a/requirements/requirements-core.txt
+++ b/requirements/requirements-core.txt
@@ -1,4 +1,6 @@
-numpy>=1.23.1
+numpy
+setuptools==68.0.0
+wheel==0.41.0
 cython>=0.29.30
 ipython>=8.4.0
 jupyter>=1.0.0
@@ -6,3 +8,10 @@ scikit-learn>=1.1.1
 scipy>=1.8.1
 colorlog>=6.6.0
 pyyaml>=6.0
+tqdm
+jax
+jaxlib
+flax
+#chex==0.1.6
+distrax==0.1.3
+cloudpickle
\ No newline at end of file


Test patch:
diff --git a/requirements/requirements-test.txt b/requirements/requirements-test.txt
index 86698ffc..6ece6eac 100644
--- a/requirements/requirements-test.txt
+++ b/requirements/requirements-test.txt
@@ -1,2 +1,3 @@
 pytest-cov
-codecov
\ No newline at end of file
+codecov
+emcee==3.1.4
\ No newline at end of file
diff --git a/tests/test_evidence.py b/tests/test_evidence.py
index 41cc974a..c7e32217 100644
--- a/tests/test_evidence.py
+++ b/tests/test_evidence.py
@@ -4,7 +4,7 @@
 import harmonic.chains as ch
 import harmonic.model as md
 import harmonic.evidence as cbe
-import harmonic.utils as utils
+import harmonic.model_nf as model_nf
 
 
 def test_constructor():
@@ -388,6 +388,33 @@ def test_serialization():
         assert ev1.running_sum[i_chain] == ev2.running_sum[i_chain]
         assert ev1.nsamples_per_chain[i_chain] == ev2.nsamples_per_chain[i_chain]
 
+    flow = model_nf.RealNVPModel(ndim)
+    flow.fit(chain.samples, epochs=20)
+
+    # Set up the evidence object
+    ev3 = cbe.Evidence(nchains, flow)
+    ev3.add_chains(chain)
+
+    # Serialize evidence
+    ev3.serialize(".test.dat")
+
+    # Deserialize evidence
+    ev4 = cbe.Evidence.deserialize(".test.dat")
+    # TODO: make sure model works correctly after deserializing.
+    # Test evidence objects the same
+    assert ev3.batch_calculation == ev4.batch_calculation
+    assert ev3.nchains == ev4.nchains
+    assert ev3.evidence_inv == ev4.evidence_inv
+    assert ev3.evidence_inv_var == ev4.evidence_inv_var
+    assert ev3.evidence_inv_var_var == ev4.evidence_inv_var_var
+    assert ev3.running_sum.size == ev2.running_sum.size
+    assert ev3.nsamples_per_chain.size == ev4.nsamples_per_chain.size
+    assert ev3.shift_value == ev4.shift_value
+    assert ev3.shift_set == ev4.shift_set
+    for i_chain in range(nchains):
+        assert ev3.running_sum[i_chain] == ev4.running_sum[i_chain]
+        assert ev3.nsamples_per_chain[i_chain] == ev4.nsamples_per_chain[i_chain]
+
 
 def test_n_eff():
     """Test calculation of effective sample size for chains with equal and unequal number of samples."""
diff --git a/tests/test_flow_model.py b/tests/test_flow_model.py
new file mode 100644
index 00000000..c1a5d263
--- /dev/null
+++ b/tests/test_flow_model.py
@@ -0,0 +1,324 @@
+import pytest
+import harmonic.model_nf as model_nf
+import jax.numpy as jnp
+import jax
+import harmonic as hm
+
+
+# TODO: move this to conftest.py to follow best practices.
+def standard_nd_gaussian_pdf(x):
+    """
+    Calculate the probability density function (PDF) of an n-dimensional Gaussian
+    distribution with zero mean and unit covariance.
+
+    Parameters:
+    - x: Input vector of length n.
+
+    Returns:
+    - pdf: log PDF value at input vector x.
+    """
+    n = len(x)
+
+    # The normalizing constant (coefficient)
+    C = -jnp.log(2 * jnp.pi) * n / 2
+
+    # Calculate the Mahalanobis distance
+    mahalanobis_dist = jnp.dot(x, x)
+
+    # Calculate the PDF value
+    pdf = C - 0.5 * mahalanobis_dist
+
+    return pdf
+
+
+def test_FlowModel_constructor():
+    with pytest.raises(NotImplementedError):
+        ndim = 3
+        model = model_nf.FlowModel(ndim)
+        training_samples = jnp.zeros((12, ndim))
+        model.fit(training_samples)
+
+
+def test_RealNVP_constructor():
+    with pytest.raises(ValueError):
+        RealNVP = model_nf.RealNVPModel(0)
+
+    with pytest.raises(ValueError):
+        RealNVP = model_nf.RealNVPModel(-1)
+
+    ndim = 3
+
+    with pytest.raises(ValueError):
+        RealNVP = model_nf.RealNVPModel(ndim, n_scaled_layers=0)
+
+    RealNVP = model_nf.RealNVPModel(ndim, standardize=True)
+
+    with pytest.raises(ValueError):
+        training_samples = jnp.zeros((12, ndim + 1))
+        RealNVP.fit(training_samples)
+
+    with pytest.raises(ValueError):
+        RealNVP.temperature = 1.2
+        RealNVP.predict(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        RealNVP.temperature = -0.5
+        RealNVP.predict(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        RealNVP.temperature = 0
+        RealNVP.predict(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        RealNVP.temperature = 1.2
+        RealNVP.sample(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        RealNVP.temperature = -0.5
+        RealNVP.sample(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        RealNVP.temperature = 0
+        RealNVP.sample(jnp.zeros(ndim))
+
+    assert RealNVP.is_fitted() == False
+    training_samples = jnp.zeros((12, ndim))
+    RealNVP.fit(training_samples, verbose=True, epochs=5)
+    assert RealNVP.is_fitted() == True
+
+
+def test_RealNVP_flow():
+    with pytest.raises(ValueError):
+        flow = hm.flows.RealNVP(3, n_scaled_layers=0)
+        flow.make_flow()
+
+
+def test_RQSpline_constructor():
+    with pytest.raises(ValueError):
+        spline = model_nf.RQSplineModel(0)
+
+    with pytest.raises(ValueError):
+        spline = model_nf.RQSplineModel(-1)
+
+    ndim = 3
+    spline = model_nf.RQSplineModel(ndim, standardize=True)
+
+    with pytest.raises(ValueError):
+        training_samples = jnp.zeros((12, ndim + 1))
+        spline.fit(training_samples)
+
+    with pytest.raises(ValueError):
+        spline.temperature = 1.2
+        spline.predict(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        spline.temperature = -0.5
+        spline.predict(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        spline.temperature = 0
+        spline.predict(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        spline.temperature = 1.2
+        spline.sample(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        spline.temperature = -0.5
+        spline.sample(jnp.zeros(ndim))
+
+    with pytest.raises(ValueError):
+        spline.temperature = 0
+        spline.sample(jnp.zeros(ndim))
+
+    assert spline.is_fitted() == False
+    training_samples = jnp.zeros((12, ndim))
+    spline.fit(training_samples)
+    assert spline.is_fitted() == True
+
+
+# TODO: combine tests into one test with a model variable.
+def test_RealNVP_gaussian():
+    # Define the number of dimensions and the mean of the Gaussian
+    ndim = 2
+    num_samples = 10000
+    epochs = 80
+    # Initialize a PRNG key (you can use any valid key)
+    key = jax.random.PRNGKey(0)
+    mean = jnp.zeros(ndim)
+    cov = jnp.eye(ndim)
+
+    # Generate random samples from the 2D Gaussian distribution
+    samples = jax.random.multivariate_normal(key, mean, cov, shape=(num_samples,))
+
+    RealNVP = model_nf.RealNVPModel(ndim, standardize=True)
+    RealNVP.fit(samples, epochs=epochs, verbose=True)
+
+    nsamples = 5000
+    RealNVP.temperature = 1.0
+    flow_samples = RealNVP.sample(nsamples)
+    sample_var = jnp.var(flow_samples, axis=0)
+    sample_mean = jnp.mean(flow_samples, axis=0)
+
+    test = jnp.ones(ndim) * 0.2
+    assert jnp.exp(RealNVP.predict(jnp.array([test])))[0] == pytest.approx(
+        jnp.exp(standard_nd_gaussian_pdf(test)), rel=0.1
+    ), "Real NVP probability density not in agreement with analytical value"
+
+    for i in range(ndim):
+        assert sample_mean[i] == pytest.approx(0.0, abs=0.15), (
+            "Sample mean in dimension " + str(i) + " is " + str(sample_mean[i])
+        )
+        assert sample_var[i] == pytest.approx(1.0, abs=0.15), (
+            "Sample variance in dimension " + str(i) + " is " + str(sample_var[i])
+        )
+
+    RealNVP.temperature = 0.8
+    flow_samples_concentrated = RealNVP.sample(nsamples)
+    sample_var_concentrated = jnp.var(flow_samples_concentrated, axis=0)
+
+    for i in range(ndim):
+        assert (
+            sample_var[i] > sample_var_concentrated[i]
+        ), "Reducing temperature increases variance in dimension " + str(i)
+
+
+def test_RQSpline_gaussian():
+    # Define the number of dimensions and the mean of the Gaussian
+    ndim = 2
+    num_samples = 10000
+    epochs = 20
+    # Initialize a PRNG key (you can use any valid key)
+    key = jax.random.PRNGKey(0)
+    mean = jnp.zeros(ndim)
+    cov = jnp.eye(ndim)
+
+    # Generate random samples from the 2D Gaussian distribution
+    samples = jax.random.multivariate_normal(key, mean, cov, shape=(num_samples,))
+
+    spline = model_nf.RQSplineModel(ndim, standardize=True)
+    spline.fit(samples, epochs=epochs, verbose=True)
+
+    nsamples = 5000
+    spline.temperature = 1.0
+    flow_samples = spline.sample(nsamples)
+    sample_var = jnp.var(flow_samples, axis=0)
+    sample_mean = jnp.mean(flow_samples, axis=0)
+
+    for i in range(ndim):
+        assert sample_mean[i] == pytest.approx(0.0, abs=0.11), (
+            "Sample mean in dimension " + str(i) + " is " + str(sample_mean[i])
+        )
+        assert sample_var[i] == pytest.approx(1.0, abs=0.11), (
+            "Sample variance in dimension " + str(i) + " is " + str(sample_var[i])
+        )
+
+    test = jnp.ones(ndim) * 0.2
+    assert jnp.exp(spline.predict(jnp.array([test])))[0] == pytest.approx(
+        jnp.exp(standard_nd_gaussian_pdf(test)), rel=0.1
+    ), "Spline probability density not in agreement with analytical value"
+
+    spline.temperature = 0.8
+    flow_samples_concentrated = spline.sample(nsamples)
+    sample_var_concentrated = jnp.var(flow_samples_concentrated, axis=0)
+
+    for i in range(ndim):
+        assert (
+            sample_var[i] > sample_var_concentrated[i]
+        ), "Reducing temperature increases variance in dimension " + str(i)
+
+
+def test_model_serialization():
+    # Define the number of dimensions and the mean of the Gaussian
+    ndim = 2
+    num_samples = 100
+    epochs_NVP = 50
+    epochs_spline = 5
+    # Initialize a PRNG key (you can use any valid key)
+    key = jax.random.PRNGKey(0)
+    mean = jnp.zeros(ndim)
+    cov = jnp.eye(ndim)
+
+    # Generate random samples from the 2D Gaussian distribution
+    samples = jax.random.multivariate_normal(key, mean, cov, shape=(num_samples,))
+
+    # NVP params
+    n_scaled = 13
+    n_unscaled = 6
+
+    # Spline params
+    n_layers = 13
+    n_bins = 8
+    hidden_size = [64, 64]
+    spline_range = (-10.0, 10.0)
+
+    # Optimizer params
+    learning_rate = 0.01
+    momentum = 0.8
+    standardize = True
+    temperature = 0.6
+
+    model_NVP = model_nf.RealNVPModel(
+        ndim,
+        n_scaled_layers=n_scaled,
+        n_unscaled_layers=n_unscaled,
+        learning_rate=learning_rate,
+        momentum=momentum,
+        standardize=standardize,
+        temperature=temperature,
+    )
+
+    model_NVP.fit(samples, epochs=epochs_NVP)
+
+    # Serialize model
+    model_NVP.serialize(".test.dat")
+
+    # Deserialize model
+    model_NVP2 = model_nf.RealNVPModel.deserialize(".test.dat")
+
+    assert model_NVP2.ndim == model_NVP.ndim
+    assert model_NVP2.is_fitted() == model_NVP.is_fitted()
+    assert model_NVP2.n_scaled_layers == model_NVP.n_scaled_layers
+    assert model_NVP2.n_unscaled_layers == model_NVP.n_unscaled_layers
+    assert model_NVP2.learning_rate == model_NVP.learning_rate
+    assert model_NVP2.momentum == model_NVP.momentum
+    assert model_NVP2.standardize == model_NVP.standardize
+    assert model_NVP2.temperature == model_NVP.temperature
+
+    test = jnp.array([jnp.ones(ndim)])
+    assert model_NVP2.predict(test) == model_NVP.predict(test), (
+        "Prediction for deserialized model is "
+        + str(model_NVP2.predict(test))
+        + ", not equal to "
+        + str(model_NVP.predict(test))
+    )
+
+    model_spline = model_nf.RQSplineModel(
+        ndim,
+        n_layers=n_layers,
+        n_bins=n_bins,
+        hidden_size=hidden_size,
+        spline_range=spline_range,
+        standardize=standardize,
+        learning_rate=learning_rate,
+        momentum=momentum,
+        temperature=temperature,
+    )
+    model_spline.fit(samples, epochs=epochs_spline)
+    # Serialize model
+    model_spline.serialize(".test.dat")
+
+    # Deserialize model
+    model_spline2 = model_nf.RQSplineModel.deserialize(".test.dat")
+    assert model_spline2.ndim == model_spline.ndim
+    assert model_spline2.is_fitted() == model_spline.is_fitted()
+    assert model_spline2.n_layers == model_spline.n_layers
+    assert model_spline2.n_bins == model_spline.n_bins
+    assert model_spline2.hidden_size == model_spline.hidden_size
+    assert model_spline2.spline_range == model_spline.spline_range
+    assert model_spline2.learning_rate == model_spline.learning_rate
+    assert model_spline2.momentum == model_spline.momentum
+    assert model_spline2.standardize == model_spline.standardize
+    assert model_spline2.temperature == model_spline.temperature
+
+    assert model_spline2.predict(test) == model_spline.predict(test)