import numpy as np
from rombus.model import RombusModel
from typing import NamedTuple
import math

# This sample code provides a starting point for building your own Reduced Order Models
# Edit it to suit your needs.  In its initial form, it provides a model for building
# a reduced order model (ROM) of the function sinc(x)=sin(x)/x

# The following class is the essential element needed to define a model for RombusModel
# It must inherit from the RombusModel Class (which needs to be imported above).  The class
# must include one-or-more calls to 'params.add_param()' to define the names and limits of
# the parameters of the model and define the following methods:
#
#   1) set_domain(): sets the domain over-and-on which the ROM will be defined
#
#   2) compute(): returns a model for a given parameter set for each value of domain
#
class model(RombusModel):
    """Class for creating a ROM for the function sinc(x)=sin(x)/x"""

    # This sets the datatype generated by the model (default: 'float64')
    model_dtype = np.dtype("float64")

    # Add as many calls to params.add() as your model has parameters.
    # Samples will be sent to the 'compute()' method (see below) as
    # named tuples and will be addressable using the names you give them.
    #
    # Note that you may need to silence linting errors by adding a '# noqa F821'
    # directive at the end.  params is initialised, it's just done in a way that
    # IDEs and linters have troubles following.
    #
    # Syntax: params.add(name, min_value_allowed,max_value_allowed)
    params.add("A", 0, 10)  # noqa F821

    # Anything added as a class member here will just be computed once and will
    # be accessible in the 'compute()' and 'set-domain()' methods below.
    def cache(self):
        self.x_min = 0
        self.x_max = 100
        self.n_x = 1024

    # Set the domain over-and-on which the ROM will be defined
    def set_domain(self) -> np.array:
        return np.linspace(self.x_min, self.x_max, self.n_x)

    # Compute the model.  This function should accept a named tuple with
    # member names given by the params.add() calls above and iter should
    # return a numpy array of type given my 'model_dtype' given above
    def compute(self, params: NamedTuple, x) -> np.array:
        """Compute the model for a given parameter set."""

        return sinc_vectorized(params.A * x)


# Create the function that will compute our model
def sinc_scalar(x):
    """A scalar version of our model function, which takes a
    value x and returns a value x."""

    if x == 0.0:
        return 1.0
    else:
        return math.sin(x) / x


# Use np.vectorize() to create a function that accepts a
# numpy array and returns a numpy array
sinc_vectorized = np.vectorize(sinc_scalar)
