# Configure Jupyter so figures appear in the notebook
%matplotlib inline

# Configure Jupyter to display the assigned value after an assignment
%config InteractiveShell.ast_node_interactivity='last_expr_or_assign'

# import functions from the modsim.py module
from modsim import *

"""
Explanation: Modeling and Simulation in Python
Copyright 2017 Allen Downey
License: Creative Commons Attribution 4.0 International
End of explanation
"""


with units_off():
    for i, name in enumerate(dir(UNITS)):
        unit = getattr(UNITS, name)
        try:
            res = 1*unit - 1
            if res == 0:
                print(name, 1*unit - 1)
        except TypeError:
            pass
        if i > 10000:
            break

with units_off():
    print(2 * UNITS.farad - 1)

with units_off():
    print(2 * UNITS.volt - 1)

with units_off():
    print(2 * UNITS.newton - 1)

mN = UNITS.gram * UNITS.meter / UNITS.second**2

with units_off():
    print(2 * mN - 1)

"""
Explanation: Low pass filter
End of explanation
"""


params = Params(
    R1 = 1e6, # ohm
    C1 = 1e-9, # farad
    A = 5, # volt
    f = 1000, # Hz 
)

"""
Explanation: Now I'll create a Params object to contain the quantities we need.  Using a Params object is convenient for grouping the system parameters in a way that's easy to read (and double-check).
End of explanation
"""


def make_system(params):
    """Makes a System object for the given conditions.
    
    params: Params object
    
    returns: System object
    """
    unpack(params)
    
    init = State(V_out = 0)
    omega = 2 * np.pi * f
    tau = R1 * C1
    cutoff = 1 / R1 / C1
    t_end = 3 / f
    
    return System(params, init=init, t_end=t_end,
                  omega=omega, cutoff=cutoff)

"""
Explanation: Now we can pass the Params object make_system which computes some additional parameters and defines init.
make_system uses the given radius to compute area and the given v_term to compute the drag coefficient C_d.
End of explanation
"""


system = make_system(params)

"""
Explanation: Let's make a System
End of explanation
"""


def slope_func(state, t, system):
    """Compute derivatives of the state.
    
    state: position, velocity
    t: time
    system: System object
    
    returns: derivatives of y and v
    """
    V_out, = state
    unpack(system)
    
    V_in = A * np.cos(omega * t)
    
    V_R1 = V_in - V_out
    
    I_R1 = V_R1 / R1    
    I_C1 = I_R1

    dV_out = I_C1 / C1
    
    return dV_out

"""
Explanation: Here's the slope function,
End of explanation
"""


slope_func(system.init, 0, system)

"""
Explanation: As always, let's test the slope function with the initial conditions.
End of explanation
"""


ts = linspace(0, system.t_end, 301)
results, details = run_ode_solver(system, slope_func, t_eval=ts)
details

"""
Explanation: And then run the simulation.
End of explanation
"""


# results

"""
Explanation: Here are the results.
End of explanation
"""


def plot_results(results):
    xs = results.V_out.index
    ys = results.V_out.values

    t_end = get_last_label(results)
    if t_end < 10:
        xs *= 1000
        xlabel = 'Time (ms)'
    else:
        xlabel = 'Time (s)'
        
    plot(xs, ys)
    decorate(xlabel=xlabel,
             ylabel='$V_{out}$ (volt)',
             legend=False)
    
plot_results(results)

"""
Explanation: Here's the plot of position as a function of time.
End of explanation
"""


fs = [1, 10, 100, 1000, 10000, 100000]
for i, f in enumerate(fs):
    system = make_system(Params(params, f=f))
    ts = linspace(0, system.t_end, 301)
    results, details = run_ode_solver(system, slope_func, t_eval=ts)
    subplot(3, 2, i+1)
    plot_results(results)

"""
Explanation: And velocity as a function of time:
End of explanation
"""


