<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Modeling and Simulation in Python
Chapter 15
Copyright 2017 Allen Downey
License
Step1:  The coffee cooling problem
I'll use a State object to store the initial temperature.
Step2:  And a System object to contain the system parameters.
Step4:  The update function implements Newton's law of cooling.
Step5:  Here's how it works.
Step7:  Here's a version of run_simulation that uses linrange to make an array of time steps.
Step8:  And here's how it works.
Step9:  Here's what the results look like.
Step10:  And here's the final temperature
Step12:  Encapsulation
Before we go on, let's define a function to initialize System objects with relevant parameters
Step13:  Here's how we use it
Step14:  Exercises
Exercise
<ASSISTANT_TASK:>
Python Code:
# 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
Chapter 15
Copyright 2017 Allen Downey
License: Creative Commons Attribution 4.0 International
End of explanation
init = State(T=90)
Explanation: The coffee cooling problem
I'll use a State object to store the initial temperature.
End of explanation
coffee = System(init=init,
                volume=300,
                r=0.01,
                T_env=22,
                t_0=0,
                t_end=30,
                dt=1)
Explanation: And a System object to contain the system parameters.
End of explanation
def update_func(state, t, system):
    Update the thermal transfer model.
    
    state: State (temp)
    t: time
    system: System object
    
    returns: State (temp)
    
    r, T_env, dt = system.r, system.T_env, system.dt
    
    T = state.T
    T += -r * (T - T_env) * dt
    
    return State(T=T)
Explanation: The update function implements Newton's law of cooling.
End of explanation
update_func(init, 0, coffee)
Explanation: Here's how it works.
End of explanation
def run_simulation(system, update_func):
    Runs a simulation of the system.
    
    Add a TimeFrame to the System: results
    
    system: System object
    update_func: function that updates state
    
    init = system.init
    t_0, t_end, dt = system.t_0, system.t_end, system.dt
    
    frame = TimeFrame(columns=init.index)
    frame.row[t_0] = init
    ts = linrange(t_0, t_end, dt)
    
    for t in ts:
        frame.row[t+dt] = update_func(frame.row[t], t, system)
    
    return frame
Explanation: Here's a version of run_simulation that uses linrange to make an array of time steps.
End of explanation
results = run_simulation(coffee, update_func)
Explanation: And here's how it works.
End of explanation
plot(results.T, label='coffee')
decorate(xlabel='Time (minutes)',
         ylabel='Temperature (C)')
Explanation: Here's what the results look like.
End of explanation
coffee.T_final = get_last_value(results.T)
T_final = get_last_value(results.T)
Explanation: And here's the final temperature:
End of explanation
def make_system(T_init, r, volume, t_end):
    Makes a System object with the given parameters.
    T_init: initial temperature in degC
    r: heat transfer rate, in 1/min
    volume: volume of liquid in mL
    t_end: end time of simulation
    
    returns: System object
    
    init = State(T=T_init)
                   
    return System(init=init,
                  r=r, 
                  volume=volume,
                  temp=T_init,
                  t_0=0, 
                  t_end=t_end, 
                  dt=1,
                  T_env=22)
Explanation: Encapsulation
Before we go on, let's define a function to initialize System objects with relevant parameters:
End of explanation
coffee = make_system(T_init=90, r=0.01, volume=300, t_end=30)
results = run_simulation(coffee, update_func)
T_final = get_last_value(results.T)
Explanation: Here's how we use it:
End of explanation
# Solution
milk = make_system(T_init=5, t_end=15, r=0.133, volume=50)
results = run_simulation(milk, update_func)
T_final = get_last_value(results.T)
plot(results.T, label='milk')
decorate(xlabel='Time (minutes)',
         ylabel='Temperature (C)')
Explanation: Exercises
Exercise:  Simulate the temperature of 50 mL of milk with a starting temperature of 5 degC, in a vessel with the same insulation, for 15 minutes, and plot the results.
By trial and error, find a value for r that makes the final temperature close to 20 C.
End of explanation
<END_TASK>