<jupyter_start><jupyter_text>Scientific Programming in Python <NAME>-Rhein-Sieg University of Applied SciencesSankt Augustin, Germany  User-defined Fuctions Sources1. <NAME>, Python Essential Reference, Developer's Library, Third Edition, Indianapolis, IN, 2006.  User-defined functions... ...are the modular brains for your scientific programming.1. First line: '**def function_name():**'    - declares a function that is name 'function_name'    - typically, passed parameters are given with the ()    2. Second line and to the end    - indented body of the code3. Then, simply call the function when you want to use it (i.e. function calls)<jupyter_code>def hello():
    print('hello')
    print('hi')
    print('hey')
    print('hi-ya')
    print('greetings')
    print('good day')
    print('good morning')
    print("what's happening")
    print("what's up")
    print('how are you')
    print('how goes it')
    print('howdy-do')
    print('bonjour')
    print('buenas noches')
    print('buenos dias')
    print('shalom')
    print("howdy y'all")
hello() # function call
## Define the function new
## pass parameter of name

def hello(name):
    '''A simple print user-defined function.
        Input: Name (str)
    '''

    print(f'Howdy-do {name}')
hello(name='Isadora')<jupyter_output><empty_output><jupyter_text>After each function call, the passed variable values are forgotten since they are local variables within the function.<jupyter_code>hello()
def hello(name):
    '''A simple print user-defined function.
       An internal check on the passed variable is now done.

       Input
           Name (str)
    '''

    if not isinstance(name, str):
        raise TypeError('You did not specify a string for the name.')
    else:
        print(f'Howdy-do {name}')
hello(name='Isadora')
hello()<jupyter_output><empty_output><jupyter_text>What happens now if we don't pass the correct type of a variable to the function?- we now can customize the error that is reported due to the `isinstance`Example, if we pass an `int` instead of a `str`:<jupyter_code>hello(name=42)<jupyter_output><empty_output><jupyter_text>Global versus Local Variables- What happens when using **local variables** within a function that have the **same name** as a **global variable**?<jupyter_code>## global variables
age = 23
name = 'Jane'

def hello_two_local(age, name):
    '''A simple print user-defined function, with two local variables (age, name)
       An internal check on the passed variable is now done.

       Input
           Name (str)
    '''

    if not isinstance(age, int):
        raise TypeError('You did not specify an integer for the age.')
    elif not isinstance(name, str):
        raise TypeError('You did not specify a string for the name.')
    else:
        print(f'Howdy-do {name}, who is {age} years old.')
hello_two_local()
hello_two_local(age=age, name=name)<jupyter_output><empty_output><jupyter_text>- What happens when you have a **global variable** that **is not a local variable** within a function?<jupyter_code>## global variables
age = 23

def hello_one_local_one_global(name):
    '''A simple print user-defined function, with two local variables (age, name)
       An internal check on the passed variable is now done.

       Input
           Name (str)
    '''

    if not isinstance(name, str):
        raise TypeError('You did not specify a string for the name.')
    else:
        print(f'Howdy-do {name}, who is {age} years old.')
hello_one_local_one_global(name=name)<jupyter_output><empty_output><jupyter_text>Returning an object from a function(Recall that SciPy has a large collection of physical constants.)<jupyter_code>from scipy.constants import c

def mass2energy(mass, speedoflight):
    ''' Converts mass to energy using Einstein's equation.

        Input
            mass: mass of an object (units = kg since 1 J = 1 kg m^2/s^2)
            speedoflight: speed of light (unit = m/s)

        Return
            energy: energy associated for a given mass (units = J)
    '''

    energy = mass*(speedoflight**2)

    return energy
my_mass = 0.100

energy = mass2energy(mass=my_mass, speedoflight=c)

print(f'Energy = {energy} Joules')<jupyter_output><empty_output><jupyter_text>Perhaps we can make things a bit more logical and informative...<jupyter_code>def mass2energy(mass, speedoflight):
    ''' Converts mass to energy using Einstein's equation.

        Input
            mass: mass of an object (units = kg since 1 J = 1 kg m^2/s^2)
            speedoflight: speed of light (unit = m/s)

        Return
            energy: energy associated for a given mass (units = J)
    '''

    if not isinstance(mass, float):
        raise TypeError(f'The value for the mass (i.e. {mass}) must be a float type')
    elif not isinstance(speedoflight, float):
        raise TypeError(f'The value for the speed-of-light (i.e. {speedoflight}) must be a float type')
    else:
        energy = mass*(c**2)

        return energy
energy = mass2energy(mass=0.100, speedoflight=c)
print(f'Energy = {energy:0.2e} Joules')<jupyter_output><empty_output><jupyter_text>Now, make sure our internal checks are working:<jupyter_code>energy = mass2energy(mass='one_hundred', speedoflight=c)<jupyter_output><empty_output><jupyter_text>Required versus Optional ParametersAll of the above user-defined functions have had **required** parameters.To define **optional parameters**, one can assign those parameters a **default value**.**Once a parameter** is assigned a default value, then all of the **subsequent** (i.e. the remaining) variables must also be optional parameters.<jupyter_code>def mass2energy(mass, speedoflight, fun_comment=None):
    ''' Converts mass to energy using Einstein's equation.

        Input
            mass (float): units in kg since 1 J = 1 kg m^2/s^2
            speedoflight: speed of light

        Return
            energy (float): units in Joules
    '''

    if fun_comment is not None:
        print(fun_comment)

    if not isinstance(mass, float):
        raise TypeError(f'The value for the mass (i.e. {mass}) must be a float type')
    elif not isinstance(speedoflight, float):
        raise TypeError(f'The value for the speed-of-light (i.e. {speedoflight}) must be a float type')
    else:
        energy = mass*(c**2)

        return energy
energy = mass2energy(mass=0.100, speedoflight=c)

print(f'Energy = {energy:0.2e} Joules')<jupyter_output><empty_output><jupyter_text>**Note**: the `fun_comment` wasn't used and thus its corresponding `if statement` was passed.<jupyter_code>energy = mass2energy(mass=0.100, speedoflight=c, fun_comment='Hi, are you Einstein?')

print(f'Energy = {energy:0.2e} Joules')<jupyter_output><empty_output><jupyter_text><!-- Including a None default value for all user-function variable. Arguements for might include:- Allows you to later do some internal code checking.    - E.g.: might be helpful for optional vairables- Easier for nonexperts to understand the code's flow.- Good practice? (e.g. accidently using a global variable when you -- or someone else -- didn't mean to)Why it might be a bad idea:- Lose the required versus default parameter idea. In this course: We will create functions that specify a default value of `None` for optional variables. --> Additional Remarks1. One can pass multiple additional unnamed variables to a function using `*args`.    - `args` = **arg**ument**s**    - the `*` indicates that args is going to be passed as an iterable.<jupyter_code>def my_args_func(*args):
    '''Add a series of numbers together.'''

    answer = 0

    for number in args:
        answer += number

    return answer
my_args_func(1, 2, 3, 4, 5)<jupyter_output><empty_output><jupyter_text>2. One can also pass additional keyword-based arguments (e.g. weighting_factor=0.85) using `**kwargs`.    - `kwargs` = **k**ey**w**ord **arg**ument**s**    - the `**` indicates that kwargs are going to be passed as a dictionary.Important: the sequence of must go as `def my_function(required, *args, **kwargs)`Dictionaries: https://docs.python.org/3/tutorial/datastructures.htmldictionariesAccess a value wihtin a dictionary by its key:`my_dictionary = {'a_key': its_value}`<jupyter_code>def my_kwargs_func(**kwargs):
    '''Print the features of a molecule.

        kwargs: dictionary that contains features and values for a given molecule
    '''

    for key, value in kwargs.items():
        print(f'{key}: {value}')
my_kwargs_func(name='octane', number_carbons=8, molec_weight=114.23, density=703)
my_kwargs_func(name='nonane', number_carbons=9, molec_weight=128.2)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10829947765525247, 'fraction_numerical': 0.009866511897852582, 'mean_word_length': 3.824188129899216, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 52, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '595759', 'license': 'MIT', 'n_tokens_mistral': 2656, 'n_tokens_neox': 2537, 'n_words': 920, 'path': 'user_functions.ipynb', 'repo_name': 'karlkirschner/2020_Scientific_Programming', 'size': '8639'}