# Continuous-Time Models Some models of computation treat time as continuous. These kinds of models often define "equations of motion" that describe the dynamics of the system, and often consist of one or more differential equations. These kinds of systems can be described within the Netomaton framework, and they can thus be thought of as Network Automata. Because Network Automata are discrete-time systems, a continuous-time model must first be discretized. Consider the following automaton with a single node that behaves like a single spring. In this single-node Network Automaton, the node's state consists of two continuous values, which represent position and velocity. The state changes according to the following equations of motion: ``` dx/dt = v dv/dt = −k/m x − b/m v ``` where `x` is position, `v` is velocity, `k` is stiffness, `b` is damping, `m` is mass, `t` is time. This makes the node's state change as if it had the mechanics of a single spring. Since a Network Automaton exists in discrete time, the state will be updated with difference equations, using the Euler method. For `k = 3.0`, `m = 0.5`, `b = 0.0`, the equations of motion reduce to: ``` dx/dt = v dv/dt = −6 x ``` and the difference equations are: ``` x[n+1] = x[n] + Δt*v[n] v[n+1] = v[n] + Δt*(−6x[n]) ``` The following code snippet demonstrates this automaton: ```python import netomaton as ntm network = ntm.topology.from_adjacency_matrix([[1]]) dt = 0.025000 def activity_rule(ctx): x_n, v_n = ctx.current_activity x_new = x_n + (dt * v_n) v_new = v_n + (dt * (-6 * x_new)) return x_new, v_new initial_conditions = [(-2.00000, 0.00000)] trajectory = ntm.evolve(initial_conditions=initial_conditions, network=network, activity_rule=activity_rule, timesteps=1000) activities = ntm.get_activities_over_time_as_list(trajectory) # plot the position and velocity as a function of time positions = [a[0][0] for a in activities] velocities = [a[0][1] for a in activities] ntm.plot1D(x=[i for i in range(1000)], y=[positions, velocities], color=["blue", "orange"], xlabel="t", ylabel=["x(t)", "v(t)"], ylim=[(-6, 6), (-6, 6)], twinx=True) ``` The full source code for this example can be found [here](single_spring_netomaton_demo.py). A partial differential equation can also be modelled. The following automaton models the Diffusion (or Heat) Equation: ∂u/∂t = α ∂²u/∂x². In this Network Automaton, each of the 120 nodes represents a body that can contain some amount of heat. The following code snippet demonstrates this automaton: ```python import numpy as np import netomaton as ntm space = np.linspace(25, -25, 120) initial_conditions = [np.exp(-x ** 2) for x in space] network = ntm.topology.cellular_automaton(120) a = 0.25 dt = .5 dx = .5 F = a * dt / dx ** 2 def activity_rule(ctx): current = ctx.current_activity left = ctx.neighbourhood_activities[0] right = ctx.neighbourhood_activities[2] return current + F * (right - 2 * current + left) trajectory = ntm.evolve(initial_conditions=initial_conditions, network=network, activity_rule=activity_rule, timesteps=75) ntm.plot_activities(trajectory) ``` The full source code for this example can be found [here](simple_diffusion_demo.py). Note that in the automaton above, a node's neighbourhood influences the state of the node, and the system can be thought of as a 1D Cellular Automaton with continuous state values evolving in continuous time. (The automaton above also closely resembles the plot at the top of Wolfram's NKS, page 163.) The examples above involve working with the first derivative of a variable with respect to time. However, it is also possible to implement models with higher-order derivatives of a variable with respect to time. The following example demonstrates an implementation of the 1D Wave Equation, ∂²u/∂t² = ∂²u/∂x²: ```python import netomaton as ntm import numpy as np nx = 401 # the number of nodes (i.e. the number of points in the grid) nt = 255 # the number of timesteps dx = 0.1 # the distance between any pair of adjacent points dt = .05 # the amount of time each timestep covers space = np.linspace(20, -20, nx) initial_conditions = [np.exp(-x ** 2) for x in space] network = ntm.topology.cellular_automaton(nx) def activity_rule(ctx): un_i = ctx.current_activity left_label = (ctx.node_label - 1) % nx un_i_m1 = ctx.activity_of(left_label) right_label = (ctx.node_label + 1) % nx un_i_p1 = ctx.activity_of(right_label) # the activity not at the previous timestep, but the timestep before that un_m1_i = ctx.past_activity_of(ctx.node_label) return ((dt ** 2 * (un_i_p1 - 2 * un_i + un_i_m1)) / dx ** 2) + (2 * un_i - un_m1_i) trajectory = ntm.evolve(initial_conditions=initial_conditions, network=network, activity_rule=activity_rule, timesteps=nt, past_conditions=[initial_conditions]) ntm.plot_activities(trajectory) ``` The full source code for this example can be found [here](wave_equation_demo.py). Note the use of the `past_conditions` parameter of the `evolve` function. Setting the past conditions makes part of the history of the evolution available in the `NodeContext`. (The automaton above also closely resembles the plot in the middle of Wolfram's NKS, page 163.) The [Gray-Scott Reaction-Diffusion model](../reaction_diffusion/README.md) and the [Hopfield-Tank Neural Network](../hopfield_tank_tsp/README.md) are also examples of continuous-time models that have been implemented in this project as Network Automata. Finally, a good resource for learning more about working with partial differential equations computationally is [CFD Python](https://github.com/barbagroup/CFDPython/blob/master/README.md). Several examples from that resource are implemented with Netomaton here: * [1D Linear Convection](linear_convection_demo.py) * [1D Non-linear Convection](nonlinear_convection_demo.py) * [1D Diffusion](diffusion_demo.py) * [Burger's Equation](burgers_equation_demo.py) See the following for more information: > https://www.myphysicslab.com/explain/numerical-solution-en.html > http://hplgit.github.io/num-methods-for-PDEs/doc/pub/diffu/sphinx/._main_diffu001.html > https://www.wolframscience.com/nks/p163--partial-differential-equations/