'''This Python code is an automatically generated wrapper
for Fortran code made by 'fmodpy'. The original documentation
for the Fortran source code follows.

! TODO:
!
! - Thoroughly test NORMALIZE (make sure data is correctly normalized in all use cases).
!   Particularly, check that the initialization of the apositional output scale is working.
!
! - Reset convention to be order of processing (AX, AXI, AY, SIZES, X, XI, Y)
! - Enable a model that has no internal states for linear regression (*NS=0).
! - Enable a apositional without a following model (MDO=0) (no apositional shifting).
!
! - Update Python testing code to test all combinations of AX, AXI, AY, X, XI, and Y.
! - Update Python testing code to attempt different edge-case model sizes
!    (linear regression, no apositional, no model).
!
! - Make data normalization use the same work space as the fit procedure
!   (since these are not needed at the same time).
! - Pull normalization code out and have it be called separately from 'FIT'.
!   (decide how to handle encoding the normalization into the model, function?)
!   Goal is to achieve near-zero losses for doing a few steps at a time in
!   Python (allowing for easier cancellation, progress updates, ...).
!
! - Use LAPACK to do linear regression, implement simple SVD + gradient descent method
!   in MATRIX_OPERATIONS, compare speed of both methodologies.
! - Implement and test Fortran intrinsic version of matrix multiplication.
! - Implement and test Fortran intrinsic version of SSYRK (manually do loop).
!
! ---------------------------------------------------------------------------

! Module for matrix multiplication (absolutely crucial for APOS speed).
'''

import os
import ctypes
import platform
import numpy

# --------------------------------------------------------------------
#               CONFIGURATION
# 
_verbose = True
_fort_compiler = "gfortran"
_shared_object_name = "apos." + platform.machine() + ".so"
_this_directory = os.path.dirname(os.path.abspath(__file__))
_path_to_lib = os.path.join(_this_directory, _shared_object_name)
_compile_options = ['-fPIC', '-shared', '-O3', '-lblas', '-llapack', '-fopenmp', '-fcheck=bounds']
_ordered_dependencies = ['apos.f90', 'apos_c_wrapper.f90']
# 
# --------------------------------------------------------------------
#               AUTO-COMPILING
#
# Try to import the existing object. If that fails, recompile and then try.
try:
    clib = ctypes.CDLL(_path_to_lib)
except:
    # Remove the shared object if it exists, because it is faulty.
    if os.path.exists(_shared_object_name):
        os.remove(_shared_object_name)
    # Compile a new shared object.
    _command = " ".join([_fort_compiler] + _compile_options + ["-o", _shared_object_name] + _ordered_dependencies)
    if _verbose:
        print("Running system command with arguments")
        print("  ", _command)
    # Run the compilation command.
    import subprocess
    subprocess.run(_command, shell=True, cwd=_this_directory)
    # Import the shared object file as a C library with ctypes.
    clib = ctypes.CDLL(_path_to_lib)
# --------------------------------------------------------------------


class matrix_operations:
    ''''''

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine GEMM
    
    def gemm(self, op_a, op_b, out_rows, out_cols, inner_dim, ab_mult, a, a_rows, b, b_rows, c_mult, c, c_rows):
        '''! Convenience wrapper routine for calling matrix multiply.'''
        
        # Setting up "op_a"
        if (type(op_a) is not ctypes.c_char): op_a = ctypes.c_char(op_a)
        
        # Setting up "op_b"
        if (type(op_b) is not ctypes.c_char): op_b = ctypes.c_char(op_b)
        
        # Setting up "out_rows"
        if (type(out_rows) is not ctypes.c_int): out_rows = ctypes.c_int(out_rows)
        
        # Setting up "out_cols"
        if (type(out_cols) is not ctypes.c_int): out_cols = ctypes.c_int(out_cols)
        
        # Setting up "inner_dim"
        if (type(inner_dim) is not ctypes.c_int): inner_dim = ctypes.c_int(inner_dim)
        
        # Setting up "ab_mult"
        if (type(ab_mult) is not ctypes.c_float): ab_mult = ctypes.c_float(ab_mult)
        
        # Setting up "a"
        if ((not issubclass(type(a), numpy.ndarray)) or
            (not numpy.asarray(a).flags.f_contiguous) or
            (not (a.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'a' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            a = numpy.asarray(a, dtype=ctypes.c_float, order='F')
        a_dim_1 = ctypes.c_int(a.shape[0])
        a_dim_2 = ctypes.c_int(a.shape[1])
        
        # Setting up "a_rows"
        if (type(a_rows) is not ctypes.c_int): a_rows = ctypes.c_int(a_rows)
        
        # Setting up "b"
        if ((not issubclass(type(b), numpy.ndarray)) or
            (not numpy.asarray(b).flags.f_contiguous) or
            (not (b.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'b' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            b = numpy.asarray(b, dtype=ctypes.c_float, order='F')
        b_dim_1 = ctypes.c_int(b.shape[0])
        b_dim_2 = ctypes.c_int(b.shape[1])
        
        # Setting up "b_rows"
        if (type(b_rows) is not ctypes.c_int): b_rows = ctypes.c_int(b_rows)
        
        # Setting up "c_mult"
        if (type(c_mult) is not ctypes.c_float): c_mult = ctypes.c_float(c_mult)
        
        # Setting up "c"
        if ((not issubclass(type(c), numpy.ndarray)) or
            (not numpy.asarray(c).flags.f_contiguous) or
            (not (c.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'c' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            c = numpy.asarray(c, dtype=ctypes.c_float, order='F')
        c_dim_1 = ctypes.c_int(c.shape[0])
        c_dim_2 = ctypes.c_int(c.shape[1])
        
        # Setting up "c_rows"
        if (type(c_rows) is not ctypes.c_int): c_rows = ctypes.c_int(c_rows)
    
        # Call C-accessible Fortran wrapper.
        clib.c_gemm(ctypes.byref(op_a), ctypes.byref(op_b), ctypes.byref(out_rows), ctypes.byref(out_cols), ctypes.byref(inner_dim), ctypes.byref(ab_mult), ctypes.byref(a_dim_1), ctypes.byref(a_dim_2), ctypes.c_void_p(a.ctypes.data), ctypes.byref(a_rows), ctypes.byref(b_dim_1), ctypes.byref(b_dim_2), ctypes.c_void_p(b.ctypes.data), ctypes.byref(b_rows), ctypes.byref(c_mult), ctypes.byref(c_dim_1), ctypes.byref(c_dim_2), ctypes.c_void_p(c.ctypes.data), ctypes.byref(c_rows))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return c

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine ORTHONORMALIZE
    
    def orthonormalize(self, a):
        '''! Orthogonalize and normalize column vectors of A in order.'''
        
        # Setting up "a"
        if ((not issubclass(type(a), numpy.ndarray)) or
            (not numpy.asarray(a).flags.f_contiguous) or
            (not (a.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'a' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            a = numpy.asarray(a, dtype=ctypes.c_float, order='F')
        a_dim_1 = ctypes.c_int(a.shape[0])
        a_dim_2 = ctypes.c_int(a.shape[1])
    
        # Call C-accessible Fortran wrapper.
        clib.c_orthonormalize(ctypes.byref(a_dim_1), ctypes.byref(a_dim_2), ctypes.c_void_p(a.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return a

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine RANDOM_UNIT_VECTORS
    
    def random_unit_vectors(self, column_vectors):
        '''! Generate randomly distributed vectors on the N-sphere.'''
        
        # Setting up "column_vectors"
        if ((not issubclass(type(column_vectors), numpy.ndarray)) or
            (not numpy.asarray(column_vectors).flags.f_contiguous) or
            (not (column_vectors.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'column_vectors' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            column_vectors = numpy.asarray(column_vectors, dtype=ctypes.c_float, order='F')
        column_vectors_dim_1 = ctypes.c_int(column_vectors.shape[0])
        column_vectors_dim_2 = ctypes.c_int(column_vectors.shape[1])
    
        # Call C-accessible Fortran wrapper.
        clib.c_random_unit_vectors(ctypes.byref(column_vectors_dim_1), ctypes.byref(column_vectors_dim_2), ctypes.c_void_p(column_vectors.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return column_vectors

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine ORTHOGONALIZE
    
    def orthogonalize(self, a, lengths=None, rank=None, order=None):
        '''! Orthogonalize and normalize column vectors of A with pivoting.'''
        
        # Setting up "a"
        if ((not issubclass(type(a), numpy.ndarray)) or
            (not numpy.asarray(a).flags.f_contiguous) or
            (not (a.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'a' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            a = numpy.asarray(a, dtype=ctypes.c_float, order='F')
        a_dim_1 = ctypes.c_int(a.shape[0])
        a_dim_2 = ctypes.c_int(a.shape[1])
        
        # Setting up "lengths"
        if (lengths is None):
            lengths = numpy.zeros(shape=(a.shape[1]), dtype=ctypes.c_float, order='F')
        elif ((not issubclass(type(lengths), numpy.ndarray)) or
              (not numpy.asarray(lengths).flags.f_contiguous) or
              (not (lengths.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'lengths' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            lengths = numpy.asarray(lengths, dtype=ctypes.c_float, order='F')
        lengths_dim_1 = ctypes.c_int(lengths.shape[0])
        
        # Setting up "rank"
        rank_present = ctypes.c_bool(True)
        if (rank is None):
            rank_present = ctypes.c_bool(False)
            rank = ctypes.c_int()
        else:
            rank = ctypes.c_int(rank)
        
        # Setting up "order"
        order_present = ctypes.c_bool(True)
        if (order is None):
            order_present = ctypes.c_bool(False)
            order = numpy.zeros(shape=(1), dtype=ctypes.c_int, order='F')
        elif (type(order) == bool) and (order):
            order = numpy.zeros(shape=(a.shape[1]), dtype=ctypes.c_int, order='F')
        elif ((not issubclass(type(order), numpy.ndarray)) or
              (not numpy.asarray(order).flags.f_contiguous) or
              (not (order.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'order' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            order = numpy.asarray(order, dtype=ctypes.c_int, order='F')
        if (order_present):
            order_dim_1 = ctypes.c_int(order.shape[0])
        else:
            order_dim_1 = ctypes.c_int()
    
        # Call C-accessible Fortran wrapper.
        clib.c_orthogonalize(ctypes.byref(a_dim_1), ctypes.byref(a_dim_2), ctypes.c_void_p(a.ctypes.data), ctypes.byref(lengths_dim_1), ctypes.c_void_p(lengths.ctypes.data), ctypes.byref(rank_present), ctypes.byref(rank), ctypes.byref(order_present), ctypes.byref(order_dim_1), ctypes.c_void_p(order.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return a, lengths, (rank.value if rank_present else None), (order if order_present else None)

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine SVD
    
    def svd(self, a, s=None, vt=None, rank=None, steps=None, bias=None):
        '''! Compute the singular values and right singular vectors for matrix A.'''
        
        # Setting up "a"
        if ((not issubclass(type(a), numpy.ndarray)) or
            (not numpy.asarray(a).flags.f_contiguous) or
            (not (a.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'a' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            a = numpy.asarray(a, dtype=ctypes.c_float, order='F')
        a_dim_1 = ctypes.c_int(a.shape[0])
        a_dim_2 = ctypes.c_int(a.shape[1])
        
        # Setting up "s"
        if (s is None):
            s = numpy.zeros(shape=(min(a.shape[0],a.shape[1])), dtype=ctypes.c_float, order='F')
        elif ((not issubclass(type(s), numpy.ndarray)) or
              (not numpy.asarray(s).flags.f_contiguous) or
              (not (s.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 's' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            s = numpy.asarray(s, dtype=ctypes.c_float, order='F')
        s_dim_1 = ctypes.c_int(s.shape[0])
        
        # Setting up "vt"
        if (vt is None):
            vt = numpy.zeros(shape=(min(a.shape[0],a.shape[1]), min(a.shape[0],a.shape[1])), dtype=ctypes.c_float, order='F')
        elif ((not issubclass(type(vt), numpy.ndarray)) or
              (not numpy.asarray(vt).flags.f_contiguous) or
              (not (vt.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'vt' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            vt = numpy.asarray(vt, dtype=ctypes.c_float, order='F')
        vt_dim_1 = ctypes.c_int(vt.shape[0])
        vt_dim_2 = ctypes.c_int(vt.shape[1])
        
        # Setting up "rank"
        rank_present = ctypes.c_bool(True)
        if (rank is None):
            rank_present = ctypes.c_bool(False)
            rank = ctypes.c_int()
        else:
            rank = ctypes.c_int(rank)
        
        # Setting up "steps"
        steps_present = ctypes.c_bool(True)
        if (steps is None):
            steps_present = ctypes.c_bool(False)
            steps = ctypes.c_int()
        else:
            steps = ctypes.c_int(steps)
        if (type(steps) is not ctypes.c_int): steps = ctypes.c_int(steps)
        
        # Setting up "bias"
        bias_present = ctypes.c_bool(True)
        if (bias is None):
            bias_present = ctypes.c_bool(False)
            bias = ctypes.c_float()
        else:
            bias = ctypes.c_float(bias)
        if (type(bias) is not ctypes.c_float): bias = ctypes.c_float(bias)
    
        # Call C-accessible Fortran wrapper.
        clib.c_svd(ctypes.byref(a_dim_1), ctypes.byref(a_dim_2), ctypes.c_void_p(a.ctypes.data), ctypes.byref(s_dim_1), ctypes.c_void_p(s.ctypes.data), ctypes.byref(vt_dim_1), ctypes.byref(vt_dim_2), ctypes.c_void_p(vt.ctypes.data), ctypes.byref(rank_present), ctypes.byref(rank), ctypes.byref(steps_present), ctypes.byref(steps), ctypes.byref(bias_present), ctypes.byref(bias))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return s, vt, (rank.value if rank_present else None)

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine RADIALIZE
    
    def radialize(self, x, shift, vecs, invert_result=None, steps=None):
        '''! If there are at least as many data points as dimension, then
! compute the principal components and rescale the data by
! projecting onto those and rescaling so that each component has
! identical singular values (this makes the data more "radially
! symmetric").'''
        
        # Setting up "x"
        if ((not issubclass(type(x), numpy.ndarray)) or
            (not numpy.asarray(x).flags.f_contiguous) or
            (not (x.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'x' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            x = numpy.asarray(x, dtype=ctypes.c_float, order='F')
        x_dim_1 = ctypes.c_int(x.shape[0])
        x_dim_2 = ctypes.c_int(x.shape[1])
        
        # Setting up "shift"
        if ((not issubclass(type(shift), numpy.ndarray)) or
            (not numpy.asarray(shift).flags.f_contiguous) or
            (not (shift.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'shift' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            shift = numpy.asarray(shift, dtype=ctypes.c_float, order='F')
        shift_dim_1 = ctypes.c_int(shift.shape[0])
        
        # Setting up "vecs"
        if ((not issubclass(type(vecs), numpy.ndarray)) or
            (not numpy.asarray(vecs).flags.f_contiguous) or
            (not (vecs.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'vecs' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            vecs = numpy.asarray(vecs, dtype=ctypes.c_float, order='F')
        vecs_dim_1 = ctypes.c_int(vecs.shape[0])
        vecs_dim_2 = ctypes.c_int(vecs.shape[1])
        
        # Setting up "invert_result"
        invert_result_present = ctypes.c_bool(True)
        if (invert_result is None):
            invert_result_present = ctypes.c_bool(False)
            invert_result = ctypes.c_int()
        else:
            invert_result = ctypes.c_int(invert_result)
        if (type(invert_result) is not ctypes.c_int): invert_result = ctypes.c_int(invert_result)
        
        # Setting up "steps"
        steps_present = ctypes.c_bool(True)
        if (steps is None):
            steps_present = ctypes.c_bool(False)
            steps = ctypes.c_int()
        else:
            steps = ctypes.c_int(steps)
        if (type(steps) is not ctypes.c_int): steps = ctypes.c_int(steps)
    
        # Call C-accessible Fortran wrapper.
        clib.c_radialize(ctypes.byref(x_dim_1), ctypes.byref(x_dim_2), ctypes.c_void_p(x.ctypes.data), ctypes.byref(shift_dim_1), ctypes.c_void_p(shift.ctypes.data), ctypes.byref(vecs_dim_1), ctypes.byref(vecs_dim_2), ctypes.c_void_p(vecs.ctypes.data), ctypes.byref(invert_result_present), ctypes.byref(invert_result), ctypes.byref(steps_present), ctypes.byref(steps))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return x, shift, vecs

matrix_operations = matrix_operations()


class sort_and_select:
    '''! A module for fast sorting and selecting of data.'''

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine SWAP_INT
    
    def swap_int(self, v1, v2):
        '''! Swap the values of two integers.'''
        
        # Setting up "v1"
        if (type(v1) is not ctypes.c_int): v1 = ctypes.c_int(v1)
        
        # Setting up "v2"
        if (type(v2) is not ctypes.c_int): v2 = ctypes.c_int(v2)
    
        # Call C-accessible Fortran wrapper.
        clib.c_swap_int(ctypes.byref(v1), ctypes.byref(v2))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return v1.value, v2.value

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine ARGSELECT
    
    def argselect(self, values, k, indices, divisor=None, max_size=None, recursing=None):
        '''!                       FastSelect method
!
! Given VALUES list of numbers, rearrange the elements of INDICES
! such that the element of VALUES at INDICES(K) has rank K (holds
! its same location as if all of VALUES were sorted in INDICES).
! All elements of VALUES at INDICES(:K-1) are less than or equal,
! while all elements of VALUES at INDICES(K+1:) are greater or equal.
!
! This algorithm uses a recursive approach to exponentially shrink
! the number of indices that have to be considered to find the
! element of desired rank, while simultaneously pivoting values
! that are less than the target rank left and larger right.
!
! Arguments:
!
!   VALUES   --  A 1D array of real numbers. Will not be modified.
!   K        --  A positive integer for the rank index about which
!                VALUES should be rearranged.
! Optional:
!
!   DIVISOR  --  A positive integer >= 2 that represents the
!                division factor used for large VALUES arrays.
!   MAX_SIZE --  An integer >= DIVISOR that represents the largest
!                sized VALUES for which the worst-case pivot value
!                selection is tolerable. A worst-case pivot causes
!                O( SIZE(VALUES)^2 ) runtime. This value should be
!                determined heuristically based on compute hardware.
! Output:
!
!   INDICES  --  A 1D array of original indices for elements of VALUES.
!
!   The elements of the array INDICES are rearranged such that the
!   element at position VALUES(INDICES(K)) is in the same location
!   it would be if all of VALUES were referenced in sorted order in
!   INDICES. Also known as, VALUES(INDICES(K)) has rank K.
!
! Arguments'''
        
        # Setting up "values"
        if ((not issubclass(type(values), numpy.ndarray)) or
            (not numpy.asarray(values).flags.f_contiguous) or
            (not (values.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'values' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            values = numpy.asarray(values, dtype=ctypes.c_float, order='F')
        values_dim_1 = ctypes.c_int(values.shape[0])
        
        # Setting up "k"
        if (type(k) is not ctypes.c_int): k = ctypes.c_int(k)
        
        # Setting up "indices"
        if ((not issubclass(type(indices), numpy.ndarray)) or
            (not numpy.asarray(indices).flags.f_contiguous) or
            (not (indices.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'indices' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            indices = numpy.asarray(indices, dtype=ctypes.c_int, order='F')
        indices_dim_1 = ctypes.c_int(indices.shape[0])
        
        # Setting up "divisor"
        divisor_present = ctypes.c_bool(True)
        if (divisor is None):
            divisor_present = ctypes.c_bool(False)
            divisor = ctypes.c_int()
        else:
            divisor = ctypes.c_int(divisor)
        if (type(divisor) is not ctypes.c_int): divisor = ctypes.c_int(divisor)
        
        # Setting up "max_size"
        max_size_present = ctypes.c_bool(True)
        if (max_size is None):
            max_size_present = ctypes.c_bool(False)
            max_size = ctypes.c_int()
        else:
            max_size = ctypes.c_int(max_size)
        if (type(max_size) is not ctypes.c_int): max_size = ctypes.c_int(max_size)
        
        # Setting up "recursing"
        recursing_present = ctypes.c_bool(True)
        if (recursing is None):
            recursing_present = ctypes.c_bool(False)
            recursing = ctypes.c_int()
        else:
            recursing = ctypes.c_int(recursing)
        if (type(recursing) is not ctypes.c_int): recursing = ctypes.c_int(recursing)
    
        # Call C-accessible Fortran wrapper.
        clib.c_argselect(ctypes.byref(values_dim_1), ctypes.c_void_p(values.ctypes.data), ctypes.byref(k), ctypes.byref(indices_dim_1), ctypes.c_void_p(indices.ctypes.data), ctypes.byref(divisor_present), ctypes.byref(divisor), ctypes.byref(max_size_present), ctypes.byref(max_size), ctypes.byref(recursing_present), ctypes.byref(recursing))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return indices

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine ARGSORT
    
    def argsort(self, values, indices, min_size=None, init_inds=None):
        '''!                         FastSort
!
! This routine uses a combination of QuickSort (with modestly
! intelligent pivot selection) and Insertion Sort (for small arrays)
! to achieve very fast average case sort times for both random and
! partially sorted data. The pivot is selected for QuickSort as the
! median of the first, middle, and last values in the array.
!
! Arguments:
!
!   VALUES   --  A 1D array of real numbers.
!   INDICES  --  A 1D array of original indices for elements of VALUES.
!
! Optional:
!
!   MIN_SIZE --  An positive integer that represents the largest
!                sized VALUES for which a partition about a pivot
!                is used to reduce the size of a an unsorted array.
!                Any size less than this will result in the use of
!                INSERTION_ARGSORT instead of ARGPARTITION.
!
! Output:
!
!   The elements of the array INDICES contain ths sorted order of VALUES.'''
        
        # Setting up "values"
        if ((not issubclass(type(values), numpy.ndarray)) or
            (not numpy.asarray(values).flags.f_contiguous) or
            (not (values.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'values' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            values = numpy.asarray(values, dtype=ctypes.c_float, order='F')
        values_dim_1 = ctypes.c_int(values.shape[0])
        
        # Setting up "indices"
        if ((not issubclass(type(indices), numpy.ndarray)) or
            (not numpy.asarray(indices).flags.f_contiguous) or
            (not (indices.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'indices' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            indices = numpy.asarray(indices, dtype=ctypes.c_int, order='F')
        indices_dim_1 = ctypes.c_int(indices.shape[0])
        
        # Setting up "min_size"
        min_size_present = ctypes.c_bool(True)
        if (min_size is None):
            min_size_present = ctypes.c_bool(False)
            min_size = ctypes.c_int()
        else:
            min_size = ctypes.c_int(min_size)
        if (type(min_size) is not ctypes.c_int): min_size = ctypes.c_int(min_size)
        
        # Setting up "init_inds"
        init_inds_present = ctypes.c_bool(True)
        if (init_inds is None):
            init_inds_present = ctypes.c_bool(False)
            init_inds = ctypes.c_int()
        else:
            init_inds = ctypes.c_int(init_inds)
        if (type(init_inds) is not ctypes.c_int): init_inds = ctypes.c_int(init_inds)
    
        # Call C-accessible Fortran wrapper.
        clib.c_argsort(ctypes.byref(values_dim_1), ctypes.c_void_p(values.ctypes.data), ctypes.byref(indices_dim_1), ctypes.c_void_p(indices.ctypes.data), ctypes.byref(min_size_present), ctypes.byref(min_size), ctypes.byref(init_inds_present), ctypes.byref(init_inds))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return indices

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine INSERTION_ARGSORT
    
    def insertion_argsort(self, values, indices):
        '''! Insertion sort (best for small lists).'''
        
        # Setting up "values"
        if ((not issubclass(type(values), numpy.ndarray)) or
            (not numpy.asarray(values).flags.f_contiguous) or
            (not (values.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'values' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            values = numpy.asarray(values, dtype=ctypes.c_float, order='F')
        values_dim_1 = ctypes.c_int(values.shape[0])
        
        # Setting up "indices"
        if ((not issubclass(type(indices), numpy.ndarray)) or
            (not numpy.asarray(indices).flags.f_contiguous) or
            (not (indices.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'indices' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            indices = numpy.asarray(indices, dtype=ctypes.c_int, order='F')
        indices_dim_1 = ctypes.c_int(indices.shape[0])
    
        # Call C-accessible Fortran wrapper.
        clib.c_insertion_argsort(ctypes.byref(values_dim_1), ctypes.c_void_p(values.ctypes.data), ctypes.byref(indices_dim_1), ctypes.c_void_p(indices.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return indices

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine ARGPARTITION
    
    def argpartition(self, values, indices):
        '''! This function efficiently partitions values based on the median
! of the first, middle, and last elements of the VALUES array. This
! function returns the index of the pivot.'''
        
        # Setting up "values"
        if ((not issubclass(type(values), numpy.ndarray)) or
            (not numpy.asarray(values).flags.f_contiguous) or
            (not (values.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'values' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            values = numpy.asarray(values, dtype=ctypes.c_float, order='F')
        values_dim_1 = ctypes.c_int(values.shape[0])
        
        # Setting up "indices"
        if ((not issubclass(type(indices), numpy.ndarray)) or
            (not numpy.asarray(indices).flags.f_contiguous) or
            (not (indices.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'indices' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            indices = numpy.asarray(indices, dtype=ctypes.c_int, order='F')
        indices_dim_1 = ctypes.c_int(indices.shape[0])
        
        # Setting up "left"
        left = ctypes.c_int()
    
        # Call C-accessible Fortran wrapper.
        clib.c_argpartition(ctypes.byref(values_dim_1), ctypes.c_void_p(values.ctypes.data), ctypes.byref(indices_dim_1), ctypes.c_void_p(indices.ctypes.data), ctypes.byref(left))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return indices, left.value

sort_and_select = sort_and_select()


class apos:
    '''! An apositional (/aggregate) and positional piecewise linear regression model.'''
    
    # This defines a C structure that can be used to hold this defined type.
    class MODEL_CONFIG(ctypes.Structure):
        # (name, ctype) fields for this structure.
        _fields_ = [("adn", ctypes.c_int), ("adi", ctypes.c_int), ("ads", ctypes.c_int), ("ans", ctypes.c_int), ("ado", ctypes.c_int), ("ane", ctypes.c_int), ("ade", ctypes.c_int), ("mdn", ctypes.c_int), ("mdi", ctypes.c_int), ("mds", ctypes.c_int), ("mns", ctypes.c_int), ("mdo", ctypes.c_int), ("mne", ctypes.c_int), ("mde", ctypes.c_int), ("total_size", ctypes.c_int), ("num_vars", ctypes.c_int), ("asiv", ctypes.c_int), ("aeiv", ctypes.c_int), ("asis", ctypes.c_int), ("aeis", ctypes.c_int), ("assv", ctypes.c_int), ("aesv", ctypes.c_int), ("asss", ctypes.c_int), ("aess", ctypes.c_int), ("asov", ctypes.c_int), ("aeov", ctypes.c_int), ("asev", ctypes.c_int), ("aeev", ctypes.c_int), ("msiv", ctypes.c_int), ("meiv", ctypes.c_int), ("msis", ctypes.c_int), ("meis", ctypes.c_int), ("mssv", ctypes.c_int), ("mesv", ctypes.c_int), ("msss", ctypes.c_int), ("mess", ctypes.c_int), ("msov", ctypes.c_int), ("meov", ctypes.c_int), ("msev", ctypes.c_int), ("meev", ctypes.c_int), ("aiss", ctypes.c_int), ("aise", ctypes.c_int), ("aoss", ctypes.c_int), ("aose", ctypes.c_int), ("miss", ctypes.c_int), ("mise", ctypes.c_int), ("moss", ctypes.c_int), ("mose", ctypes.c_int), ("discontinuity", ctypes.c_float), ("initial_shift_range", ctypes.c_float), ("initial_output_scale", ctypes.c_float), ("step_factor", ctypes.c_float), ("step_mean_change", ctypes.c_float), ("step_curv_change", ctypes.c_float), ("faster_rate", ctypes.c_float), ("slower_rate", ctypes.c_float), ("min_update_ratio", ctypes.c_float), ("min_steps_to_stability", ctypes.c_int), ("num_threads", ctypes.c_int), ("print_delay_sec", ctypes.c_int), ("steps_taken", ctypes.c_int), ("logging_step_frequency", ctypes.c_int), ("num_to_update", ctypes.c_int), ("ax_normalized", ctypes.c_bool), ("axi_normalized", ctypes.c_bool), ("ay_normalized", ctypes.c_bool), ("x_normalized", ctypes.c_bool), ("xi_normalized", ctypes.c_bool), ("y_normalized", ctypes.c_bool), ("encode_normalization", ctypes.c_bool), ("apply_shift", ctypes.c_bool), ("keep_best", ctypes.c_bool), ("early_stop", ctypes.c_bool), ("rwork_size", ctypes.c_int), ("iwork_size", ctypes.c_int), ("na", ctypes.c_int), ("nm", ctypes.c_int), ("smg", ctypes.c_int), ("emg", ctypes.c_int), ("smgm", ctypes.c_int), ("emgm", ctypes.c_int), ("smgc", ctypes.c_int), ("emgc", ctypes.c_int), ("sbm", ctypes.c_int), ("ebm", ctypes.c_int), ("sfma", ctypes.c_int), ("efma", ctypes.c_int), ("saxs", ctypes.c_int), ("eaxs", ctypes.c_int), ("saxg", ctypes.c_int), ("eaxg", ctypes.c_int), ("saa", ctypes.c_int), ("eaa", ctypes.c_int), ("say", ctypes.c_int), ("eay", ctypes.c_int), ("sxt", ctypes.c_int), ("ext", ctypes.c_int), ("sayg", ctypes.c_int), ("eayg", ctypes.c_int), ("smxs", ctypes.c_int), ("emxs", ctypes.c_int), ("smxg", ctypes.c_int), ("emxg", ctypes.c_int), ("sma", ctypes.c_int), ("ema", ctypes.c_int), ("syg", ctypes.c_int), ("eyg", ctypes.c_int), ("saxr", ctypes.c_int), ("eaxr", ctypes.c_int), ("saxis", ctypes.c_int), ("eaxis", ctypes.c_int), ("saxir", ctypes.c_int), ("eaxir", ctypes.c_int), ("sayr", ctypes.c_int), ("eayr", ctypes.c_int), ("smxr", ctypes.c_int), ("emxr", ctypes.c_int), ("smxis", ctypes.c_int), ("emxis", ctypes.c_int), ("smxir", ctypes.c_int), ("emxir", ctypes.c_int), ("syr", ctypes.c_int), ("eyr", ctypes.c_int), ("sui", ctypes.c_int), ("eui", ctypes.c_int), ("sbas", ctypes.c_int), ("ebas", ctypes.c_int), ("sbae", ctypes.c_int), ("ebae", ctypes.c_int), ("sbms", ctypes.c_int), ("ebms", ctypes.c_int), ("sbme", ctypes.c_int), ("ebme", ctypes.c_int)]
        # Define an "__init__" that can take a class or keyword arguments as input.
        def __init__(self, value=0, **kwargs):
            # From whatever object (or dictionary) was given, assign internal values.
            self.adn = kwargs.get("adn", getattr(value, "adn", value))
            self.adi = kwargs.get("adi", getattr(value, "adi", value))
            self.ads = kwargs.get("ads", getattr(value, "ads", value))
            self.ans = kwargs.get("ans", getattr(value, "ans", value))
            self.ado = kwargs.get("ado", getattr(value, "ado", value))
            self.ane = kwargs.get("ane", getattr(value, "ane", value))
            self.ade = kwargs.get("ade", getattr(value, "ade", value))
            self.mdn = kwargs.get("mdn", getattr(value, "mdn", value))
            self.mdi = kwargs.get("mdi", getattr(value, "mdi", value))
            self.mds = kwargs.get("mds", getattr(value, "mds", value))
            self.mns = kwargs.get("mns", getattr(value, "mns", value))
            self.mdo = kwargs.get("mdo", getattr(value, "mdo", value))
            self.mne = kwargs.get("mne", getattr(value, "mne", value))
            self.mde = kwargs.get("mde", getattr(value, "mde", value))
            self.total_size = kwargs.get("total_size", getattr(value, "total_size", value))
            self.num_vars = kwargs.get("num_vars", getattr(value, "num_vars", value))
            self.asiv = kwargs.get("asiv", getattr(value, "asiv", value))
            self.aeiv = kwargs.get("aeiv", getattr(value, "aeiv", value))
            self.asis = kwargs.get("asis", getattr(value, "asis", value))
            self.aeis = kwargs.get("aeis", getattr(value, "aeis", value))
            self.assv = kwargs.get("assv", getattr(value, "assv", value))
            self.aesv = kwargs.get("aesv", getattr(value, "aesv", value))
            self.asss = kwargs.get("asss", getattr(value, "asss", value))
            self.aess = kwargs.get("aess", getattr(value, "aess", value))
            self.asov = kwargs.get("asov", getattr(value, "asov", value))
            self.aeov = kwargs.get("aeov", getattr(value, "aeov", value))
            self.asev = kwargs.get("asev", getattr(value, "asev", value))
            self.aeev = kwargs.get("aeev", getattr(value, "aeev", value))
            self.msiv = kwargs.get("msiv", getattr(value, "msiv", value))
            self.meiv = kwargs.get("meiv", getattr(value, "meiv", value))
            self.msis = kwargs.get("msis", getattr(value, "msis", value))
            self.meis = kwargs.get("meis", getattr(value, "meis", value))
            self.mssv = kwargs.get("mssv", getattr(value, "mssv", value))
            self.mesv = kwargs.get("mesv", getattr(value, "mesv", value))
            self.msss = kwargs.get("msss", getattr(value, "msss", value))
            self.mess = kwargs.get("mess", getattr(value, "mess", value))
            self.msov = kwargs.get("msov", getattr(value, "msov", value))
            self.meov = kwargs.get("meov", getattr(value, "meov", value))
            self.msev = kwargs.get("msev", getattr(value, "msev", value))
            self.meev = kwargs.get("meev", getattr(value, "meev", value))
            self.aiss = kwargs.get("aiss", getattr(value, "aiss", value))
            self.aise = kwargs.get("aise", getattr(value, "aise", value))
            self.aoss = kwargs.get("aoss", getattr(value, "aoss", value))
            self.aose = kwargs.get("aose", getattr(value, "aose", value))
            self.miss = kwargs.get("miss", getattr(value, "miss", value))
            self.mise = kwargs.get("mise", getattr(value, "mise", value))
            self.moss = kwargs.get("moss", getattr(value, "moss", value))
            self.mose = kwargs.get("mose", getattr(value, "mose", value))
            self.discontinuity = kwargs.get("discontinuity", getattr(value, "discontinuity", value))
            self.initial_shift_range = kwargs.get("initial_shift_range", getattr(value, "initial_shift_range", value))
            self.initial_output_scale = kwargs.get("initial_output_scale", getattr(value, "initial_output_scale", value))
            self.step_factor = kwargs.get("step_factor", getattr(value, "step_factor", value))
            self.step_mean_change = kwargs.get("step_mean_change", getattr(value, "step_mean_change", value))
            self.step_curv_change = kwargs.get("step_curv_change", getattr(value, "step_curv_change", value))
            self.faster_rate = kwargs.get("faster_rate", getattr(value, "faster_rate", value))
            self.slower_rate = kwargs.get("slower_rate", getattr(value, "slower_rate", value))
            self.min_update_ratio = kwargs.get("min_update_ratio", getattr(value, "min_update_ratio", value))
            self.min_steps_to_stability = kwargs.get("min_steps_to_stability", getattr(value, "min_steps_to_stability", value))
            self.num_threads = kwargs.get("num_threads", getattr(value, "num_threads", value))
            self.print_delay_sec = kwargs.get("print_delay_sec", getattr(value, "print_delay_sec", value))
            self.steps_taken = kwargs.get("steps_taken", getattr(value, "steps_taken", value))
            self.logging_step_frequency = kwargs.get("logging_step_frequency", getattr(value, "logging_step_frequency", value))
            self.num_to_update = kwargs.get("num_to_update", getattr(value, "num_to_update", value))
            self.ax_normalized = kwargs.get("ax_normalized", getattr(value, "ax_normalized", value))
            self.axi_normalized = kwargs.get("axi_normalized", getattr(value, "axi_normalized", value))
            self.ay_normalized = kwargs.get("ay_normalized", getattr(value, "ay_normalized", value))
            self.x_normalized = kwargs.get("x_normalized", getattr(value, "x_normalized", value))
            self.xi_normalized = kwargs.get("xi_normalized", getattr(value, "xi_normalized", value))
            self.y_normalized = kwargs.get("y_normalized", getattr(value, "y_normalized", value))
            self.encode_normalization = kwargs.get("encode_normalization", getattr(value, "encode_normalization", value))
            self.apply_shift = kwargs.get("apply_shift", getattr(value, "apply_shift", value))
            self.keep_best = kwargs.get("keep_best", getattr(value, "keep_best", value))
            self.early_stop = kwargs.get("early_stop", getattr(value, "early_stop", value))
            self.rwork_size = kwargs.get("rwork_size", getattr(value, "rwork_size", value))
            self.iwork_size = kwargs.get("iwork_size", getattr(value, "iwork_size", value))
            self.na = kwargs.get("na", getattr(value, "na", value))
            self.nm = kwargs.get("nm", getattr(value, "nm", value))
            self.smg = kwargs.get("smg", getattr(value, "smg", value))
            self.emg = kwargs.get("emg", getattr(value, "emg", value))
            self.smgm = kwargs.get("smgm", getattr(value, "smgm", value))
            self.emgm = kwargs.get("emgm", getattr(value, "emgm", value))
            self.smgc = kwargs.get("smgc", getattr(value, "smgc", value))
            self.emgc = kwargs.get("emgc", getattr(value, "emgc", value))
            self.sbm = kwargs.get("sbm", getattr(value, "sbm", value))
            self.ebm = kwargs.get("ebm", getattr(value, "ebm", value))
            self.sfma = kwargs.get("sfma", getattr(value, "sfma", value))
            self.efma = kwargs.get("efma", getattr(value, "efma", value))
            self.saxs = kwargs.get("saxs", getattr(value, "saxs", value))
            self.eaxs = kwargs.get("eaxs", getattr(value, "eaxs", value))
            self.saxg = kwargs.get("saxg", getattr(value, "saxg", value))
            self.eaxg = kwargs.get("eaxg", getattr(value, "eaxg", value))
            self.saa = kwargs.get("saa", getattr(value, "saa", value))
            self.eaa = kwargs.get("eaa", getattr(value, "eaa", value))
            self.say = kwargs.get("say", getattr(value, "say", value))
            self.eay = kwargs.get("eay", getattr(value, "eay", value))
            self.sxt = kwargs.get("sxt", getattr(value, "sxt", value))
            self.ext = kwargs.get("ext", getattr(value, "ext", value))
            self.sayg = kwargs.get("sayg", getattr(value, "sayg", value))
            self.eayg = kwargs.get("eayg", getattr(value, "eayg", value))
            self.smxs = kwargs.get("smxs", getattr(value, "smxs", value))
            self.emxs = kwargs.get("emxs", getattr(value, "emxs", value))
            self.smxg = kwargs.get("smxg", getattr(value, "smxg", value))
            self.emxg = kwargs.get("emxg", getattr(value, "emxg", value))
            self.sma = kwargs.get("sma", getattr(value, "sma", value))
            self.ema = kwargs.get("ema", getattr(value, "ema", value))
            self.syg = kwargs.get("syg", getattr(value, "syg", value))
            self.eyg = kwargs.get("eyg", getattr(value, "eyg", value))
            self.saxr = kwargs.get("saxr", getattr(value, "saxr", value))
            self.eaxr = kwargs.get("eaxr", getattr(value, "eaxr", value))
            self.saxis = kwargs.get("saxis", getattr(value, "saxis", value))
            self.eaxis = kwargs.get("eaxis", getattr(value, "eaxis", value))
            self.saxir = kwargs.get("saxir", getattr(value, "saxir", value))
            self.eaxir = kwargs.get("eaxir", getattr(value, "eaxir", value))
            self.sayr = kwargs.get("sayr", getattr(value, "sayr", value))
            self.eayr = kwargs.get("eayr", getattr(value, "eayr", value))
            self.smxr = kwargs.get("smxr", getattr(value, "smxr", value))
            self.emxr = kwargs.get("emxr", getattr(value, "emxr", value))
            self.smxis = kwargs.get("smxis", getattr(value, "smxis", value))
            self.emxis = kwargs.get("emxis", getattr(value, "emxis", value))
            self.smxir = kwargs.get("smxir", getattr(value, "smxir", value))
            self.emxir = kwargs.get("emxir", getattr(value, "emxir", value))
            self.syr = kwargs.get("syr", getattr(value, "syr", value))
            self.eyr = kwargs.get("eyr", getattr(value, "eyr", value))
            self.sui = kwargs.get("sui", getattr(value, "sui", value))
            self.eui = kwargs.get("eui", getattr(value, "eui", value))
            self.sbas = kwargs.get("sbas", getattr(value, "sbas", value))
            self.ebas = kwargs.get("ebas", getattr(value, "ebas", value))
            self.sbae = kwargs.get("sbae", getattr(value, "sbae", value))
            self.ebae = kwargs.get("ebae", getattr(value, "ebae", value))
            self.sbms = kwargs.get("sbms", getattr(value, "sbms", value))
            self.ebms = kwargs.get("ebms", getattr(value, "ebms", value))
            self.sbme = kwargs.get("sbme", getattr(value, "sbme", value))
            self.ebme = kwargs.get("ebme", getattr(value, "ebme", value))
    

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine NEW_MODEL_CONFIG
    
    def new_model_config(self, adn, mdn, mdo, ado=None, ads=None, ans=None, ane=None, ade=None, mds=None, mns=None, mne=None, mde=None, num_threads=None):
        '''! Generate a model configuration given state parameters for the model.
! Size related parameters.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "adn"
        if (type(adn) is not ctypes.c_int): adn = ctypes.c_int(adn)
        
        # Setting up "ado"
        ado_present = ctypes.c_bool(True)
        if (ado is None):
            ado_present = ctypes.c_bool(False)
            ado = ctypes.c_int()
        else:
            ado = ctypes.c_int(ado)
        if (type(ado) is not ctypes.c_int): ado = ctypes.c_int(ado)
        
        # Setting up "ads"
        ads_present = ctypes.c_bool(True)
        if (ads is None):
            ads_present = ctypes.c_bool(False)
            ads = ctypes.c_int()
        else:
            ads = ctypes.c_int(ads)
        if (type(ads) is not ctypes.c_int): ads = ctypes.c_int(ads)
        
        # Setting up "ans"
        ans_present = ctypes.c_bool(True)
        if (ans is None):
            ans_present = ctypes.c_bool(False)
            ans = ctypes.c_int()
        else:
            ans = ctypes.c_int(ans)
        if (type(ans) is not ctypes.c_int): ans = ctypes.c_int(ans)
        
        # Setting up "ane"
        ane_present = ctypes.c_bool(True)
        if (ane is None):
            ane_present = ctypes.c_bool(False)
            ane = ctypes.c_int()
        else:
            ane = ctypes.c_int(ane)
        if (type(ane) is not ctypes.c_int): ane = ctypes.c_int(ane)
        
        # Setting up "ade"
        ade_present = ctypes.c_bool(True)
        if (ade is None):
            ade_present = ctypes.c_bool(False)
            ade = ctypes.c_int()
        else:
            ade = ctypes.c_int(ade)
        if (type(ade) is not ctypes.c_int): ade = ctypes.c_int(ade)
        
        # Setting up "mdn"
        if (type(mdn) is not ctypes.c_int): mdn = ctypes.c_int(mdn)
        
        # Setting up "mdo"
        if (type(mdo) is not ctypes.c_int): mdo = ctypes.c_int(mdo)
        
        # Setting up "mds"
        mds_present = ctypes.c_bool(True)
        if (mds is None):
            mds_present = ctypes.c_bool(False)
            mds = ctypes.c_int()
        else:
            mds = ctypes.c_int(mds)
        if (type(mds) is not ctypes.c_int): mds = ctypes.c_int(mds)
        
        # Setting up "mns"
        mns_present = ctypes.c_bool(True)
        if (mns is None):
            mns_present = ctypes.c_bool(False)
            mns = ctypes.c_int()
        else:
            mns = ctypes.c_int(mns)
        if (type(mns) is not ctypes.c_int): mns = ctypes.c_int(mns)
        
        # Setting up "mne"
        mne_present = ctypes.c_bool(True)
        if (mne is None):
            mne_present = ctypes.c_bool(False)
            mne = ctypes.c_int()
        else:
            mne = ctypes.c_int(mne)
        if (type(mne) is not ctypes.c_int): mne = ctypes.c_int(mne)
        
        # Setting up "mde"
        mde_present = ctypes.c_bool(True)
        if (mde is None):
            mde_present = ctypes.c_bool(False)
            mde = ctypes.c_int()
        else:
            mde = ctypes.c_int(mde)
        if (type(mde) is not ctypes.c_int): mde = ctypes.c_int(mde)
        
        # Setting up "num_threads"
        num_threads_present = ctypes.c_bool(True)
        if (num_threads is None):
            num_threads_present = ctypes.c_bool(False)
            num_threads = ctypes.c_int()
        else:
            num_threads = ctypes.c_int(num_threads)
        if (type(num_threads) is not ctypes.c_int): num_threads = ctypes.c_int(num_threads)
        
        # Setting up "config"
        config = MODEL_CONFIG()
    
        # Call C-accessible Fortran wrapper.
        clib.c_new_model_config(ctypes.byref(adn), ctypes.byref(ado_present), ctypes.byref(ado), ctypes.byref(ads_present), ctypes.byref(ads), ctypes.byref(ans_present), ctypes.byref(ans), ctypes.byref(ane_present), ctypes.byref(ane), ctypes.byref(ade_present), ctypes.byref(ade), ctypes.byref(mdn), ctypes.byref(mdo), ctypes.byref(mds_present), ctypes.byref(mds), ctypes.byref(mns_present), ctypes.byref(mns), ctypes.byref(mne_present), ctypes.byref(mne), ctypes.byref(mde_present), ctypes.byref(mde), ctypes.byref(num_threads_present), ctypes.byref(num_threads), ctypes.byref(config))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return config

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine NEW_FIT_CONFIG
    
    def new_fit_config(self, nm, na, config):
        '''! Given a number of X points "NM", and a number of apositional X points
! "NA", update the "RWORK_SIZE" and "IWORK_SIZE" attributes in "CONFIG"
! as well as all related work indices for that size data.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "nm"
        if (type(nm) is not ctypes.c_int): nm = ctypes.c_int(nm)
        
        # Setting up "na"
        if (type(na) is not ctypes.c_int): na = ctypes.c_int(na)
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
    
        # Call C-accessible Fortran wrapper.
        clib.c_new_fit_config(ctypes.byref(nm), ctypes.byref(na), ctypes.byref(config))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return config

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine INIT_MODEL
    
    def init_model(self, config, model, seed=None):
        '''! Initialize the weights for a model, optionally provide a random seed.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
        
        # Setting up "model"
        if ((not issubclass(type(model), numpy.ndarray)) or
            (not numpy.asarray(model).flags.f_contiguous) or
            (not (model.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'model' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            model = numpy.asarray(model, dtype=ctypes.c_float, order='F')
        model_dim_1 = ctypes.c_int(model.shape[0])
        
        # Setting up "seed"
        seed_present = ctypes.c_bool(True)
        if (seed is None):
            seed_present = ctypes.c_bool(False)
            seed = ctypes.c_int()
        else:
            seed = ctypes.c_int(seed)
        if (type(seed) is not ctypes.c_int): seed = ctypes.c_int(seed)
    
        # Call C-accessible Fortran wrapper.
        clib.c_init_model(ctypes.byref(config), ctypes.byref(model_dim_1), ctypes.c_void_p(model.ctypes.data), ctypes.byref(seed_present), ctypes.byref(seed))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return model

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine CHECK_SHAPE
    
    def check_shape(self, config, model, ax, axi, sizes, x, xi, y):
        '''! Returnn nonzero INFO if any shapes or values do not match expectations.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
        
        # Setting up "model"
        if ((not issubclass(type(model), numpy.ndarray)) or
            (not numpy.asarray(model).flags.f_contiguous) or
            (not (model.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'model' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            model = numpy.asarray(model, dtype=ctypes.c_float, order='F')
        model_dim_1 = ctypes.c_int(model.shape[0])
        
        # Setting up "ax"
        if ((not issubclass(type(ax), numpy.ndarray)) or
            (not numpy.asarray(ax).flags.f_contiguous) or
            (not (ax.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ax' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ax = numpy.asarray(ax, dtype=ctypes.c_float, order='F')
        ax_dim_1 = ctypes.c_int(ax.shape[0])
        ax_dim_2 = ctypes.c_int(ax.shape[1])
        
        # Setting up "axi"
        if ((not issubclass(type(axi), numpy.ndarray)) or
            (not numpy.asarray(axi).flags.f_contiguous) or
            (not (axi.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'axi' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            axi = numpy.asarray(axi, dtype=ctypes.c_int, order='F')
        axi_dim_1 = ctypes.c_int(axi.shape[0])
        axi_dim_2 = ctypes.c_int(axi.shape[1])
        
        # Setting up "sizes"
        if ((not issubclass(type(sizes), numpy.ndarray)) or
            (not numpy.asarray(sizes).flags.f_contiguous) or
            (not (sizes.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'sizes' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            sizes = numpy.asarray(sizes, dtype=ctypes.c_int, order='F')
        sizes_dim_1 = ctypes.c_int(sizes.shape[0])
        
        # Setting up "x"
        if ((not issubclass(type(x), numpy.ndarray)) or
            (not numpy.asarray(x).flags.f_contiguous) or
            (not (x.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'x' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            x = numpy.asarray(x, dtype=ctypes.c_float, order='F')
        x_dim_1 = ctypes.c_int(x.shape[0])
        x_dim_2 = ctypes.c_int(x.shape[1])
        
        # Setting up "xi"
        if ((not issubclass(type(xi), numpy.ndarray)) or
            (not numpy.asarray(xi).flags.f_contiguous) or
            (not (xi.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'xi' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            xi = numpy.asarray(xi, dtype=ctypes.c_int, order='F')
        xi_dim_1 = ctypes.c_int(xi.shape[0])
        xi_dim_2 = ctypes.c_int(xi.shape[1])
        
        # Setting up "y"
        if ((not issubclass(type(y), numpy.ndarray)) or
            (not numpy.asarray(y).flags.f_contiguous) or
            (not (y.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'y' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            y = numpy.asarray(y, dtype=ctypes.c_float, order='F')
        y_dim_1 = ctypes.c_int(y.shape[0])
        y_dim_2 = ctypes.c_int(y.shape[1])
        
        # Setting up "info"
        info = ctypes.c_int()
    
        # Call C-accessible Fortran wrapper.
        clib.c_check_shape(ctypes.byref(config), ctypes.byref(model_dim_1), ctypes.c_void_p(model.ctypes.data), ctypes.byref(ax_dim_1), ctypes.byref(ax_dim_2), ctypes.c_void_p(ax.ctypes.data), ctypes.byref(axi_dim_1), ctypes.byref(axi_dim_2), ctypes.c_void_p(axi.ctypes.data), ctypes.byref(sizes_dim_1), ctypes.c_void_p(sizes.ctypes.data), ctypes.byref(x_dim_1), ctypes.byref(x_dim_2), ctypes.c_void_p(x.ctypes.data), ctypes.byref(xi_dim_1), ctypes.byref(xi_dim_2), ctypes.c_void_p(xi.ctypes.data), ctypes.byref(y_dim_1), ctypes.byref(y_dim_2), ctypes.c_void_p(y.ctypes.data), ctypes.byref(info))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return info.value

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine COMPUTE_BATCHES
    
    def compute_batches(self, num_batches, nm, na, sizes, batcha_starts, batcha_ends, batchm_starts, batchm_ends, info):
        '''! Given a number of batches, compute the batch start and ends for
!  the apositional and positional inputs. Store in (2,_) arrays.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "num_batches"
        if (type(num_batches) is not ctypes.c_int): num_batches = ctypes.c_int(num_batches)
        
        # Setting up "nm"
        if (type(nm) is not ctypes.c_int): nm = ctypes.c_int(nm)
        
        # Setting up "na"
        if (type(na) is not ctypes.c_int): na = ctypes.c_int(na)
        
        # Setting up "sizes"
        if ((not issubclass(type(sizes), numpy.ndarray)) or
            (not numpy.asarray(sizes).flags.f_contiguous) or
            (not (sizes.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'sizes' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            sizes = numpy.asarray(sizes, dtype=ctypes.c_int, order='F')
        sizes_dim_1 = ctypes.c_int(sizes.shape[0])
        
        # Setting up "batcha_starts"
        if ((not issubclass(type(batcha_starts), numpy.ndarray)) or
            (not numpy.asarray(batcha_starts).flags.f_contiguous) or
            (not (batcha_starts.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'batcha_starts' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            batcha_starts = numpy.asarray(batcha_starts, dtype=ctypes.c_int, order='F')
        batcha_starts_dim_1 = ctypes.c_int(batcha_starts.shape[0])
        
        # Setting up "batcha_ends"
        if ((not issubclass(type(batcha_ends), numpy.ndarray)) or
            (not numpy.asarray(batcha_ends).flags.f_contiguous) or
            (not (batcha_ends.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'batcha_ends' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            batcha_ends = numpy.asarray(batcha_ends, dtype=ctypes.c_int, order='F')
        batcha_ends_dim_1 = ctypes.c_int(batcha_ends.shape[0])
        
        # Setting up "batchm_starts"
        if ((not issubclass(type(batchm_starts), numpy.ndarray)) or
            (not numpy.asarray(batchm_starts).flags.f_contiguous) or
            (not (batchm_starts.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'batchm_starts' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            batchm_starts = numpy.asarray(batchm_starts, dtype=ctypes.c_int, order='F')
        batchm_starts_dim_1 = ctypes.c_int(batchm_starts.shape[0])
        
        # Setting up "batchm_ends"
        if ((not issubclass(type(batchm_ends), numpy.ndarray)) or
            (not numpy.asarray(batchm_ends).flags.f_contiguous) or
            (not (batchm_ends.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'batchm_ends' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            batchm_ends = numpy.asarray(batchm_ends, dtype=ctypes.c_int, order='F')
        batchm_ends_dim_1 = ctypes.c_int(batchm_ends.shape[0])
        
        # Setting up "info"
        if (type(info) is not ctypes.c_int): info = ctypes.c_int(info)
    
        # Call C-accessible Fortran wrapper.
        clib.c_compute_batches(ctypes.byref(num_batches), ctypes.byref(nm), ctypes.byref(na), ctypes.byref(sizes_dim_1), ctypes.c_void_p(sizes.ctypes.data), ctypes.byref(batcha_starts_dim_1), ctypes.c_void_p(batcha_starts.ctypes.data), ctypes.byref(batcha_ends_dim_1), ctypes.c_void_p(batcha_ends.ctypes.data), ctypes.byref(batchm_starts_dim_1), ctypes.c_void_p(batchm_starts.ctypes.data), ctypes.byref(batchm_ends_dim_1), ctypes.c_void_p(batchm_ends.ctypes.data), ctypes.byref(info))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return batcha_starts, batcha_ends, batchm_starts, batchm_ends, info.value

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine EMBED
    
    def embed(self, config, model, axi, xi, ax, x):
        '''! Given a model and mixed real and integer inputs, embed the integer
!  inputs into their appropriate real-value-only formats.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
        
        # Setting up "model"
        if ((not issubclass(type(model), numpy.ndarray)) or
            (not numpy.asarray(model).flags.f_contiguous) or
            (not (model.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'model' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            model = numpy.asarray(model, dtype=ctypes.c_float, order='F')
        model_dim_1 = ctypes.c_int(model.shape[0])
        
        # Setting up "axi"
        if ((not issubclass(type(axi), numpy.ndarray)) or
            (not numpy.asarray(axi).flags.f_contiguous) or
            (not (axi.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'axi' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            axi = numpy.asarray(axi, dtype=ctypes.c_int, order='F')
        axi_dim_1 = ctypes.c_int(axi.shape[0])
        axi_dim_2 = ctypes.c_int(axi.shape[1])
        
        # Setting up "xi"
        if ((not issubclass(type(xi), numpy.ndarray)) or
            (not numpy.asarray(xi).flags.f_contiguous) or
            (not (xi.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'xi' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            xi = numpy.asarray(xi, dtype=ctypes.c_int, order='F')
        xi_dim_1 = ctypes.c_int(xi.shape[0])
        xi_dim_2 = ctypes.c_int(xi.shape[1])
        
        # Setting up "ax"
        if ((not issubclass(type(ax), numpy.ndarray)) or
            (not numpy.asarray(ax).flags.f_contiguous) or
            (not (ax.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ax' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ax = numpy.asarray(ax, dtype=ctypes.c_float, order='F')
        ax_dim_1 = ctypes.c_int(ax.shape[0])
        ax_dim_2 = ctypes.c_int(ax.shape[1])
        
        # Setting up "x"
        if ((not issubclass(type(x), numpy.ndarray)) or
            (not numpy.asarray(x).flags.f_contiguous) or
            (not (x.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'x' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            x = numpy.asarray(x, dtype=ctypes.c_float, order='F')
        x_dim_1 = ctypes.c_int(x.shape[0])
        x_dim_2 = ctypes.c_int(x.shape[1])
    
        # Call C-accessible Fortran wrapper.
        clib.c_embed(ctypes.byref(config), ctypes.byref(model_dim_1), ctypes.c_void_p(model.ctypes.data), ctypes.byref(axi_dim_1), ctypes.byref(axi_dim_2), ctypes.c_void_p(axi.ctypes.data), ctypes.byref(xi_dim_1), ctypes.byref(xi_dim_2), ctypes.c_void_p(xi.ctypes.data), ctypes.byref(ax_dim_1), ctypes.byref(ax_dim_2), ctypes.c_void_p(ax.ctypes.data), ctypes.byref(x_dim_1), ctypes.byref(x_dim_2), ctypes.c_void_p(x.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return ax, x

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine EVALUATE
    
    def evaluate(self, config, model, ax, ay, sizes, x, y, a_states, m_states, info):
        '''! Evaluate the piecewise linear regression model, assume already-embedded inputs.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
        
        # Setting up "model"
        if ((not issubclass(type(model), numpy.ndarray)) or
            (not numpy.asarray(model).flags.f_contiguous) or
            (not (model.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'model' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            model = numpy.asarray(model, dtype=ctypes.c_float, order='F')
        model_dim_1 = ctypes.c_int(model.shape[0])
        
        # Setting up "ax"
        if ((not issubclass(type(ax), numpy.ndarray)) or
            (not numpy.asarray(ax).flags.f_contiguous) or
            (not (ax.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ax' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ax = numpy.asarray(ax, dtype=ctypes.c_float, order='F')
        ax_dim_1 = ctypes.c_int(ax.shape[0])
        ax_dim_2 = ctypes.c_int(ax.shape[1])
        
        # Setting up "ay"
        if ((not issubclass(type(ay), numpy.ndarray)) or
            (not numpy.asarray(ay).flags.f_contiguous) or
            (not (ay.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ay' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ay = numpy.asarray(ay, dtype=ctypes.c_float, order='F')
        ay_dim_1 = ctypes.c_int(ay.shape[0])
        ay_dim_2 = ctypes.c_int(ay.shape[1])
        
        # Setting up "sizes"
        if ((not issubclass(type(sizes), numpy.ndarray)) or
            (not numpy.asarray(sizes).flags.f_contiguous) or
            (not (sizes.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'sizes' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            sizes = numpy.asarray(sizes, dtype=ctypes.c_int, order='F')
        sizes_dim_1 = ctypes.c_int(sizes.shape[0])
        
        # Setting up "x"
        if ((not issubclass(type(x), numpy.ndarray)) or
            (not numpy.asarray(x).flags.f_contiguous) or
            (not (x.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'x' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            x = numpy.asarray(x, dtype=ctypes.c_float, order='F')
        x_dim_1 = ctypes.c_int(x.shape[0])
        x_dim_2 = ctypes.c_int(x.shape[1])
        
        # Setting up "y"
        if ((not issubclass(type(y), numpy.ndarray)) or
            (not numpy.asarray(y).flags.f_contiguous) or
            (not (y.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'y' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            y = numpy.asarray(y, dtype=ctypes.c_float, order='F')
        y_dim_1 = ctypes.c_int(y.shape[0])
        y_dim_2 = ctypes.c_int(y.shape[1])
        
        # Setting up "a_states"
        if ((not issubclass(type(a_states), numpy.ndarray)) or
            (not numpy.asarray(a_states).flags.f_contiguous) or
            (not (a_states.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'a_states' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            a_states = numpy.asarray(a_states, dtype=ctypes.c_float, order='F')
        a_states_dim_1 = ctypes.c_int(a_states.shape[0])
        a_states_dim_2 = ctypes.c_int(a_states.shape[1])
        a_states_dim_3 = ctypes.c_int(a_states.shape[2])
        
        # Setting up "m_states"
        if ((not issubclass(type(m_states), numpy.ndarray)) or
            (not numpy.asarray(m_states).flags.f_contiguous) or
            (not (m_states.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'm_states' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            m_states = numpy.asarray(m_states, dtype=ctypes.c_float, order='F')
        m_states_dim_1 = ctypes.c_int(m_states.shape[0])
        m_states_dim_2 = ctypes.c_int(m_states.shape[1])
        m_states_dim_3 = ctypes.c_int(m_states.shape[2])
        
        # Setting up "info"
        if (type(info) is not ctypes.c_int): info = ctypes.c_int(info)
    
        # Call C-accessible Fortran wrapper.
        clib.c_evaluate(ctypes.byref(config), ctypes.byref(model_dim_1), ctypes.c_void_p(model.ctypes.data), ctypes.byref(ax_dim_1), ctypes.byref(ax_dim_2), ctypes.c_void_p(ax.ctypes.data), ctypes.byref(ay_dim_1), ctypes.byref(ay_dim_2), ctypes.c_void_p(ay.ctypes.data), ctypes.byref(sizes_dim_1), ctypes.c_void_p(sizes.ctypes.data), ctypes.byref(x_dim_1), ctypes.byref(x_dim_2), ctypes.c_void_p(x.ctypes.data), ctypes.byref(y_dim_1), ctypes.byref(y_dim_2), ctypes.c_void_p(y.ctypes.data), ctypes.byref(a_states_dim_1), ctypes.byref(a_states_dim_2), ctypes.byref(a_states_dim_3), ctypes.c_void_p(a_states.ctypes.data), ctypes.byref(m_states_dim_1), ctypes.byref(m_states_dim_2), ctypes.byref(m_states_dim_3), ctypes.c_void_p(m_states.ctypes.data), ctypes.byref(info))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return ax, ay, x, y, a_states, m_states, info.value

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine BASIS_GRADIENT
    
    def basis_gradient(self, config, model, y, x, ax, sizes, m_states, a_states, ay, grad):
        '''! Given the values at all internal states in the model and an output
!  gradient, propogate the output gradient through the model and
!  return the gradient of all basis functions.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
        
        # Setting up "model"
        if ((not issubclass(type(model), numpy.ndarray)) or
            (not numpy.asarray(model).flags.f_contiguous) or
            (not (model.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'model' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            model = numpy.asarray(model, dtype=ctypes.c_float, order='F')
        model_dim_1 = ctypes.c_int(model.shape[0])
        
        # Setting up "y"
        if ((not issubclass(type(y), numpy.ndarray)) or
            (not numpy.asarray(y).flags.f_contiguous) or
            (not (y.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'y' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            y = numpy.asarray(y, dtype=ctypes.c_float, order='F')
        y_dim_1 = ctypes.c_int(y.shape[0])
        y_dim_2 = ctypes.c_int(y.shape[1])
        
        # Setting up "x"
        if ((not issubclass(type(x), numpy.ndarray)) or
            (not numpy.asarray(x).flags.f_contiguous) or
            (not (x.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'x' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            x = numpy.asarray(x, dtype=ctypes.c_float, order='F')
        x_dim_1 = ctypes.c_int(x.shape[0])
        x_dim_2 = ctypes.c_int(x.shape[1])
        
        # Setting up "ax"
        if ((not issubclass(type(ax), numpy.ndarray)) or
            (not numpy.asarray(ax).flags.f_contiguous) or
            (not (ax.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ax' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ax = numpy.asarray(ax, dtype=ctypes.c_float, order='F')
        ax_dim_1 = ctypes.c_int(ax.shape[0])
        ax_dim_2 = ctypes.c_int(ax.shape[1])
        
        # Setting up "sizes"
        if ((not issubclass(type(sizes), numpy.ndarray)) or
            (not numpy.asarray(sizes).flags.f_contiguous) or
            (not (sizes.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'sizes' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            sizes = numpy.asarray(sizes, dtype=ctypes.c_int, order='F')
        sizes_dim_1 = ctypes.c_int(sizes.shape[0])
        
        # Setting up "m_states"
        if ((not issubclass(type(m_states), numpy.ndarray)) or
            (not numpy.asarray(m_states).flags.f_contiguous) or
            (not (m_states.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'm_states' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            m_states = numpy.asarray(m_states, dtype=ctypes.c_float, order='F')
        m_states_dim_1 = ctypes.c_int(m_states.shape[0])
        m_states_dim_2 = ctypes.c_int(m_states.shape[1])
        m_states_dim_3 = ctypes.c_int(m_states.shape[2])
        
        # Setting up "a_states"
        if ((not issubclass(type(a_states), numpy.ndarray)) or
            (not numpy.asarray(a_states).flags.f_contiguous) or
            (not (a_states.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'a_states' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            a_states = numpy.asarray(a_states, dtype=ctypes.c_float, order='F')
        a_states_dim_1 = ctypes.c_int(a_states.shape[0])
        a_states_dim_2 = ctypes.c_int(a_states.shape[1])
        a_states_dim_3 = ctypes.c_int(a_states.shape[2])
        
        # Setting up "ay"
        if ((not issubclass(type(ay), numpy.ndarray)) or
            (not numpy.asarray(ay).flags.f_contiguous) or
            (not (ay.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ay' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ay = numpy.asarray(ay, dtype=ctypes.c_float, order='F')
        ay_dim_1 = ctypes.c_int(ay.shape[0])
        ay_dim_2 = ctypes.c_int(ay.shape[1])
        
        # Setting up "grad"
        if ((not issubclass(type(grad), numpy.ndarray)) or
            (not numpy.asarray(grad).flags.f_contiguous) or
            (not (grad.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'grad' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            grad = numpy.asarray(grad, dtype=ctypes.c_float, order='F')
        grad_dim_1 = ctypes.c_int(grad.shape[0])
    
        # Call C-accessible Fortran wrapper.
        clib.c_basis_gradient(ctypes.byref(config), ctypes.byref(model_dim_1), ctypes.c_void_p(model.ctypes.data), ctypes.byref(y_dim_1), ctypes.byref(y_dim_2), ctypes.c_void_p(y.ctypes.data), ctypes.byref(x_dim_1), ctypes.byref(x_dim_2), ctypes.c_void_p(x.ctypes.data), ctypes.byref(ax_dim_1), ctypes.byref(ax_dim_2), ctypes.c_void_p(ax.ctypes.data), ctypes.byref(sizes_dim_1), ctypes.c_void_p(sizes.ctypes.data), ctypes.byref(m_states_dim_1), ctypes.byref(m_states_dim_2), ctypes.byref(m_states_dim_3), ctypes.c_void_p(m_states.ctypes.data), ctypes.byref(a_states_dim_1), ctypes.byref(a_states_dim_2), ctypes.byref(a_states_dim_3), ctypes.c_void_p(a_states.ctypes.data), ctypes.byref(ay_dim_1), ctypes.byref(ay_dim_2), ctypes.c_void_p(ay.ctypes.data), ctypes.byref(grad_dim_1), ctypes.c_void_p(grad.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return x, ax, m_states, a_states, ay, grad

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine EMBEDDING_GRADIENT
    
    def embedding_gradient(self, mde, mne, int_inputs, grad, embedding_grad):
        '''! Compute the gradient with respect to embeddings given the input
!  gradient by aggregating over the repeated occurrences of the embedding.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "mde"
        if (type(mde) is not ctypes.c_int): mde = ctypes.c_int(mde)
        
        # Setting up "mne"
        if (type(mne) is not ctypes.c_int): mne = ctypes.c_int(mne)
        
        # Setting up "int_inputs"
        if ((not issubclass(type(int_inputs), numpy.ndarray)) or
            (not numpy.asarray(int_inputs).flags.f_contiguous) or
            (not (int_inputs.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'int_inputs' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            int_inputs = numpy.asarray(int_inputs, dtype=ctypes.c_int, order='F')
        int_inputs_dim_1 = ctypes.c_int(int_inputs.shape[0])
        int_inputs_dim_2 = ctypes.c_int(int_inputs.shape[1])
        
        # Setting up "grad"
        if ((not issubclass(type(grad), numpy.ndarray)) or
            (not numpy.asarray(grad).flags.f_contiguous) or
            (not (grad.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'grad' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            grad = numpy.asarray(grad, dtype=ctypes.c_float, order='F')
        grad_dim_1 = ctypes.c_int(grad.shape[0])
        grad_dim_2 = ctypes.c_int(grad.shape[1])
        
        # Setting up "embedding_grad"
        if ((not issubclass(type(embedding_grad), numpy.ndarray)) or
            (not numpy.asarray(embedding_grad).flags.f_contiguous) or
            (not (embedding_grad.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'embedding_grad' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            embedding_grad = numpy.asarray(embedding_grad, dtype=ctypes.c_float, order='F')
        embedding_grad_dim_1 = ctypes.c_int(embedding_grad.shape[0])
        embedding_grad_dim_2 = ctypes.c_int(embedding_grad.shape[1])
    
        # Call C-accessible Fortran wrapper.
        clib.c_embedding_gradient(ctypes.byref(mde), ctypes.byref(mne), ctypes.byref(int_inputs_dim_1), ctypes.byref(int_inputs_dim_2), ctypes.c_void_p(int_inputs.ctypes.data), ctypes.byref(grad_dim_1), ctypes.byref(grad_dim_2), ctypes.c_void_p(grad.ctypes.data), ctypes.byref(embedding_grad_dim_1), ctypes.byref(embedding_grad_dim_2), ctypes.c_void_p(embedding_grad.ctypes.data))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return embedding_grad

    
    # ----------------------------------------------
    # Wrapper for the Fortran subroutine MINIMIZE_MSE
    
    def minimize_mse(self, config, model, rwork, iwork, ax, axi, sizes, x, xi, y, steps, record=None):
        '''! Fit input / output pairs by minimizing mean squared error.'''
        MODEL_CONFIG = apos.MODEL_CONFIG
        
        # Setting up "config"
        if (type(config) is not MODEL_CONFIG): config = MODEL_CONFIG(config)
        
        # Setting up "model"
        if ((not issubclass(type(model), numpy.ndarray)) or
            (not numpy.asarray(model).flags.f_contiguous) or
            (not (model.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'model' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            model = numpy.asarray(model, dtype=ctypes.c_float, order='F')
        model_dim_1 = ctypes.c_int(model.shape[0])
        
        # Setting up "rwork"
        if ((not issubclass(type(rwork), numpy.ndarray)) or
            (not numpy.asarray(rwork).flags.f_contiguous) or
            (not (rwork.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'rwork' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            rwork = numpy.asarray(rwork, dtype=ctypes.c_float, order='F')
        rwork_dim_1 = ctypes.c_int(rwork.shape[0])
        
        # Setting up "iwork"
        if ((not issubclass(type(iwork), numpy.ndarray)) or
            (not numpy.asarray(iwork).flags.f_contiguous) or
            (not (iwork.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'iwork' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            iwork = numpy.asarray(iwork, dtype=ctypes.c_int, order='F')
        iwork_dim_1 = ctypes.c_int(iwork.shape[0])
        
        # Setting up "ax"
        if ((not issubclass(type(ax), numpy.ndarray)) or
            (not numpy.asarray(ax).flags.f_contiguous) or
            (not (ax.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'ax' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            ax = numpy.asarray(ax, dtype=ctypes.c_float, order='F')
        ax_dim_1 = ctypes.c_int(ax.shape[0])
        ax_dim_2 = ctypes.c_int(ax.shape[1])
        
        # Setting up "axi"
        if ((not issubclass(type(axi), numpy.ndarray)) or
            (not numpy.asarray(axi).flags.f_contiguous) or
            (not (axi.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'axi' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            axi = numpy.asarray(axi, dtype=ctypes.c_int, order='F')
        axi_dim_1 = ctypes.c_int(axi.shape[0])
        axi_dim_2 = ctypes.c_int(axi.shape[1])
        
        # Setting up "sizes"
        if ((not issubclass(type(sizes), numpy.ndarray)) or
            (not numpy.asarray(sizes).flags.f_contiguous) or
            (not (sizes.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'sizes' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            sizes = numpy.asarray(sizes, dtype=ctypes.c_int, order='F')
        sizes_dim_1 = ctypes.c_int(sizes.shape[0])
        
        # Setting up "x"
        if ((not issubclass(type(x), numpy.ndarray)) or
            (not numpy.asarray(x).flags.f_contiguous) or
            (not (x.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'x' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            x = numpy.asarray(x, dtype=ctypes.c_float, order='F')
        x_dim_1 = ctypes.c_int(x.shape[0])
        x_dim_2 = ctypes.c_int(x.shape[1])
        
        # Setting up "xi"
        if ((not issubclass(type(xi), numpy.ndarray)) or
            (not numpy.asarray(xi).flags.f_contiguous) or
            (not (xi.dtype == numpy.dtype(ctypes.c_int)))):
            import warnings
            warnings.warn("The provided argument 'xi' was not an f_contiguous NumPy array of type 'ctypes.c_int' (or equivalent). Automatically converting (probably creating a full copy).")
            xi = numpy.asarray(xi, dtype=ctypes.c_int, order='F')
        xi_dim_1 = ctypes.c_int(xi.shape[0])
        xi_dim_2 = ctypes.c_int(xi.shape[1])
        
        # Setting up "y"
        if ((not issubclass(type(y), numpy.ndarray)) or
            (not numpy.asarray(y).flags.f_contiguous) or
            (not (y.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'y' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            y = numpy.asarray(y, dtype=ctypes.c_float, order='F')
        y_dim_1 = ctypes.c_int(y.shape[0])
        y_dim_2 = ctypes.c_int(y.shape[1])
        
        # Setting up "steps"
        if (type(steps) is not ctypes.c_int): steps = ctypes.c_int(steps)
        
        # Setting up "record"
        record_present = ctypes.c_bool(True)
        if (record is None):
            record_present = ctypes.c_bool(False)
            record = numpy.zeros(shape=(1,1), dtype=ctypes.c_float, order='F')
        elif (type(record) == bool) and (record):
            record = numpy.zeros(shape=(6, steps), dtype=ctypes.c_float, order='F')
        elif ((not issubclass(type(record), numpy.ndarray)) or
              (not numpy.asarray(record).flags.f_contiguous) or
              (not (record.dtype == numpy.dtype(ctypes.c_float)))):
            import warnings
            warnings.warn("The provided argument 'record' was not an f_contiguous NumPy array of type 'ctypes.c_float' (or equivalent). Automatically converting (probably creating a full copy).")
            record = numpy.asarray(record, dtype=ctypes.c_float, order='F')
        if (record_present):
            record_dim_1 = ctypes.c_int(record.shape[0])
            record_dim_2 = ctypes.c_int(record.shape[1])
        else:
            record_dim_1 = ctypes.c_int()
            record_dim_2 = ctypes.c_int()
        
        # Setting up "sum_squared_error"
        sum_squared_error = ctypes.c_float()
        
        # Setting up "info"
        info = ctypes.c_int()
    
        # Call C-accessible Fortran wrapper.
        clib.c_minimize_mse(ctypes.byref(config), ctypes.byref(model_dim_1), ctypes.c_void_p(model.ctypes.data), ctypes.byref(rwork_dim_1), ctypes.c_void_p(rwork.ctypes.data), ctypes.byref(iwork_dim_1), ctypes.c_void_p(iwork.ctypes.data), ctypes.byref(ax_dim_1), ctypes.byref(ax_dim_2), ctypes.c_void_p(ax.ctypes.data), ctypes.byref(axi_dim_1), ctypes.byref(axi_dim_2), ctypes.c_void_p(axi.ctypes.data), ctypes.byref(sizes_dim_1), ctypes.c_void_p(sizes.ctypes.data), ctypes.byref(x_dim_1), ctypes.byref(x_dim_2), ctypes.c_void_p(x.ctypes.data), ctypes.byref(xi_dim_1), ctypes.byref(xi_dim_2), ctypes.c_void_p(xi.ctypes.data), ctypes.byref(y_dim_1), ctypes.byref(y_dim_2), ctypes.c_void_p(y.ctypes.data), ctypes.byref(steps), ctypes.byref(record_present), ctypes.byref(record_dim_1), ctypes.byref(record_dim_2), ctypes.c_void_p(record.ctypes.data), ctypes.byref(sum_squared_error), ctypes.byref(info))
    
        # Return final results, 'INTENT(OUT)' arguments only.
        return config, model, rwork, iwork, ax, x, y, (record if record_present else None), sum_squared_error.value, info.value

apos = apos()

