
#       parametricStandard.py
#
#
#       Copyright 2010 alex arsenovic <arsenovic@virginia.edu>
#
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later versionpy.
#
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
'''
Provides Parametric Standard class, and some specific instances. The
specific instances are named as follows
        StandardType_UnknownQuantity
'''
import numpy as npy
from copy import copy,deepcopy


class ParameterBoundsError(Exception):
    pass

class ParametricStandard(object):
    global INF
    INF=1e12
    '''
    A parametric standard represents a calibration standard which
    has uncertainty in its response. This uncertainty is functionally
    known, and      represented by a parametric function, where the
    uknown quantity is the adjustable parameter.

    This class presents an abstract interface to a general Parametric
    Standard. Its main purpose is to allow the self calibration routine
    to be independent of calibration set.

    See initializer for more
    details.
    '''
    def __init__(self, function=None, parameters={}, parameter_bounds={},**kwargs):
        '''
        takes:
                function: a function which will be called to generate
                        a Network type, to be used as a ideal response.

                parameters: an dictionary holding an list of parameters,
                        which will be the dependent variables to optimize.
                        these are passed to the network creating function.

                **kwargs: keyword arguments passed to the function, but
                        not used in parametric optimization.
        '''
        self.kwargs = kwargs
        self.parameters = parameters
        self.function = function
        self.parameter_bounds = parameter_bounds

    @property
    def parameter_keys(self):
        '''
        returns a list of parameter dictionary keys in alphabetical order
        '''
        keys = self.parameters.keys()
        keys.sort()
        return keys

    @property
    def parameter_array(self):
        '''
        This property provides a 1D-array interface to the parameters
        dictionary. This is needed to intereface teh optimizing function
        because it only takes a 1D-array. Therefore, order must be
        preserved with accessing and updating the parameters through this
        array. To handle this I make it return and update in alphebetical
        order of the parameters dictionary keys.
        '''
        return npy.array([self.parameters[k] for  k in self.parameter_keys])

    @parameter_array.setter
    def parameter_array(self,input_array):
        counter = 0
        for k in self.parameter_keys:
            self.parameters[k]= input_array[counter]
            counter+=1

    @property
    def parameter_bounds_array(self):
        '''
        This property provides a 1D-array interface to the parameters
        bounds dictionary. if key doesnt exist, then i presume the
        parameter has no bounds. this then returns a tuple of -INF,INF
        where INF is a global variable in this class.
        '''
        return ([self.parameter_bounds.get(k,(-INF,INF)) for  k in self.parameter_keys])


    @property
    def number_of_parameters(self):
        '''
        the number of parameters this standard has
        '''
        return len(self.parameter_keys)

    @property
    def s(self):
        '''
        a direct access to the calulated networks' s-matrix
        '''
        return self.network.s

    @property
    def network(self):
        '''
         a Networks instance generated by calling self.function(), for
        the current set of parameters (and kwargs)
        '''
        tmp_args = copy(self.kwargs)
        tmp_args.update(self.parameters)

        #for k in range( self.number_of_parameters):
            #if not(self.parameter_bounds_array[k][0] < self.parameter_array[k] \
            #       <self.parameter_bounds_array[k][1]):
            #       raise ParameterBoundsError('a parameter is out of bounds')

        return self.function(**tmp_args)







## multi-standards
class SlidingLoad_UnknownTermination(ParametricStandard):
    '''
    A set of parametersized standards representing a set of Delayed
     Terminations of known length, but unknown termination
    '''

    def __init__(self, media,d_list,Gamma0,**kwargs):
        '''
        takes:
                media: a Media type, with a RectangularWaveguide object
                        for its tline property.
                d_list: list of distances to termination [m]
                Gamma0: guess for reflection coefficient off termination at
                        termination
                **kwargs: passed to self.function
        '''
        raise (NotImplementedError())
        kwargs.update({'Gamma0':Gamma0,})

        #def tempfunc(Gamma0,**kwargs):
        #
            #return [media.delay_load(


        parameters = {}
        #[parameters['d'+str(k)] = d_list[k] for k in range(len(d_list))]
        ParametricStandard.__init__(self, \
                function = media.delay_load,\
                parameters = parameters,\
                **kwargs\
                )
