#NEW_032417
#This version of FunctionLibraryIan does not embed LocalFunctions. Therefore, to use it you must make sure the LocalFunctions.py file
# already exists with the correct function in it. To use it, save the current FunctionLibraryIan.py under a new filename and rename this file FunctionLibraryIan.py. We are preserving this in case we need it for legacy stuff (D-R work, NASA JPL Phase I, etc)
import EqnSolver
import UnitsConversion
import Utils
import pint
import random
#import myfuncs
from scipy.special import *
from scipy.integrate import *
#PM_042619
#from FunctionTools import RetUnits_,InpUnits_ #PM_082620
from Utils import ureg
import time #PM_090220
from PetesCache import cacheme, memory #PM_090220
memory.clear() #PM_101420 -- This makes the memory clear every time we import/reload
#
print('In FunctionLibrary ------------------------------------------------------------- memory cleared')
# iklh 4.1.19 -- this will cause problems if "_custom_units.txt" isn't in the available namespace
# ureg = pint.UnitRegistry()
# with open(Globals.schome + '/_custom_units.txt') as f:
    # ureg.load_definitions(f)
# pint.set_application_registry(ureg) # Register custom registry for pickling / unpickling during multiprocessing operations

#pint won't accept t=1.0*ureg('degC') unless you do this
ureg.autoconvert_offset_to_baseunit=True

import numpy
import TabularInterpolation
#
count = 0
print_out_of_bounds = True
import Globals

schome,userhome,exhome,pubhome = Globals.gethome()



# We use the following 'decorator' syntax to tell solvercad what the input and return units are, so this function can 
# be used in a solvercad equation and processed correctly with the necessary input/return units. The decorator syntax is used
# because it is familiar and consistent with the way it is done in LocalFunctions. However, the decorators themselves do nothing.
# They do NOT perform the unit conversion because that is done interally in Solvercad. 
#
# Note that decorators are only necessary on user-written functions called by Solvercad equations. For auto-generated functions, 
# the ones that encapsulate entire problems, solvercad has its own way of introspecting the function to come up with the input and
# return units.
#
# A couple of things: order is important. The return unit order has to be the same as the order of the return list. Ditto
# for input units. Input units are not strictly necessary. You can omit them for most cases. They are only important if the function
# is absolutely expecting certain fixed units. Return units are necessary in every case, however they are really only for the purpose
# of making sure dimensions are consistent. Therefore users can simply use dimensions instead. If you want to indicate the units, you
# would normally use the calculation units of SolverCAD, ie SI. Only in the case when you know you're function is returning a specific
# unit would you specify a unit other than the default calculation unit.
#
#All returns from user-written functions (used by solvercad equations) have to be in the form of a list.
#
#Note that you can decorate these functions, especially the auto-generated ones, to provide caching by using the decorator @cacheme
# from PetesCache.py.

#decorator that does nothing
def InpUnits_(*decargs):
    def convert_units_decorator(func):
        def inner(*args,**kwargs):
            result = func(*args, **kwargs)
            return result
        return inner
    return convert_units_decorator

#decorator that does nothing 
def RetUnits_(*decargs):
    def convert_units_decorator(func):
        def inner(*args,**kwargs):
            result = func(*args, **kwargs)
            return result
        return inner
    return convert_units_decorator

#example of a user-written function that can be called by a SolverCAD equation. 
# This is an example of needing to specify both input and return units. 
#  Why? Because we know that the function only works when feet and inches are provided as input units. 
#   We also know that it returns feet and inches. 
@RetUnits_('foot','inch')
@InpUnits_('foot','inch')
@cacheme
def AddMyFootAndHandToTheKings(myfoot,myhand):
    print('In FunctionLibrary, memory.specialnumber = ',memory.specialnumber)
    time.sleep(0.3)
    kingsfoot = 1.0 #foot
    kingshand = 5.0 #inches
    return [myfoot + kingsfoot,myhand+kingshand]

#Another example of a user-written function. Here we can omit the
# input units altogether and specify only area for the return units. 
# Why? Because the function does not depend on any particular input unit. It squares a number and will do it correctly
# no matter what the input unit is. In this case we presume that the  input is a length and thus the output will be a length**2,
# or an area. But the enforcement of the output unit is done outside of this function, in SolverCAD proper.
@RetUnits_('area')
def junk1_2001(x=2.0):
    return [x**2]
    


#Example of an auto-generated function. Here we don't need to decorate or anything.
@cacheme
def junk1_1889(x=2.0,ret=['$all$','$nounits$'],eqns='default',guesses='default',randomguesses='default',errCrit='default',constraints='default',functionunits='default',localfunctions='default',multiproc='default'):
    global count
    time_insidefunction_start = time.time()
    if(ret[1]=='$nounits$'):
        x_str='x='+repr(x)+ ' dimensionless'
    else:
        x_str='x='+repr(x)
    knowns = [x_str]
    if(eqns == 'default'):
        eqns=[]
        eqns.append('y=x**2')
        eqns.append('z=y*2.0')
    if(guesses == 'default'):
        guesses = []
        guesses.append('y,1.0,10.0,1,dimensionless')
        guesses.append('z,1.0,10.0,1,dimensionless')
    if(randomguesses == 'default'):
        randomguesses = []
        randomguesses.append('UseRandomGuesses: False')
        randomguesses.append('NumRandomGuesses: 10')
        randomguesses.append('NumPreGuesses: 10')
        randomguesses.append('RandomGuessMethod: LatinHypercube')
    if(errCrit=='default'):
        errCrit = ['ErrorCriteria: 1e-07', 'SolveMethod: Numerical', 'StraightSolveWhenPossible: False', 'AllowSymbolicSolveInStraightSolve: True', 'CacheLocalFunctions: True', 'CheckInput: True', 'AdjustGuessesBasedOnSolution: False', 'SolutionToUseForAdjustment: 1', 'PercentSpreadAroundSolution: 10.0', 'InputFileToModify: none', 'GuessMethod: all', 'ApplyToDefaultOrAll: Default', 'SolutionToFind: 1', 'PercentSpread: 10.0', 'FilenameBaseToNarrowSearch: new', 'SolveForGuessesInSteps: True', 'HardcodedRange: 0.0,1000.0']
    if(constraints=='default'):
        constraints = []
    if(functionunits=='default'):
        functionunits = []
    if(localfunctions=='default'):
        localfunctions = []
        localfunctions.append(r"")
    if(multiproc=='default'):
        multiproc = ['useMultiprocessing: False','numProc: 1']
    S = EqnSolver.TEqnSolver()
    count = count + 1
    time_insidefunction_end = time.time()
    time_insidefunction_delta = time_insidefunction_end - time_insidefunction_start
    print('time_insidefunction_delta = ', time_insidefunction_delta)
    converted_listOfSolutionDictionaries = S.Launcher_FunctionsPy([eqns,knowns,guesses,randomguesses,errCrit,constraints,functionunits,[],[],[],localfunctions,[],multiproc])
    constrained_listOfSolutionDictionaries = S.ConstrainSolution([eqns,knowns,guesses,randomguesses,errCrit,constraints,functionunits],converted_listOfSolutionDictionaries)
    converted_listOfSolutionDictionaries = constrained_listOfSolutionDictionaries
    #Here is the problem. We are returning some random single solution in the list. Is there not a better way?
    num_solns = len(converted_listOfSolutionDictionaries)
    soln_to_return = random.randint(0,num_solns-1)
    dict_to_return = converted_listOfSolutionDictionaries[soln_to_return]
    list_to_return = Utils.ConvertSolutionDictToList(dict_to_return,guesses)
    retval = 0
    varname = ret[0].strip('$')
    nameofthisfunction = 'junk1_1889'
    if(nameofthisfunction.endswith('_doe') or nameofthisfunction.endswith('_opt')):
        if(varname == 'all'):
            retval = dict_to_return
        else:
            retval = dict_to_return[varname].magnitude
        return retval
    if(ret[1] == '$units$'):
        if(varname == 'all'):
            retval = dict_to_return
        else:
            retval = dict_to_return[varname]
    else:
        if(varname == 'all'):
            retval = list_to_return
        else:
            retval = dict_to_return[varname].magnitude
    return retval

#########################UserFunctionsBelow#######################################
