# ode_flow
A small Python library for solving and sampling first-order initial
value problems. The solver returns a symbolic solution, a helper
evaluates that solution at a list of sample times, and a verifier
checks that a candidate expression satisfies the equation.
## Tutorial
We are going to solve the exponential-growth problem
\(y'(t) = y(t)\) with \(y(0) = 1\). The exact solution is
\(y(t) = e^t\). We start by importing the library and declaring the
symbols:
```python
>>> import sympy as sym
>>> import ode_flow
>>> t = sym.Symbol("t")
>>> y = sym.Function("y")
```
The right-hand side of the ODE is the expression `y(t)`:
```python
>>> solution = ode_flow.solve_initial_value_problem(y(t), t, y, 0, 1)
>>> solution
exp(t)
```
We can sample the solution at a list of times:
```python
>>> ode_flow.trajectory(solution, t, [0, 1, 2])
[1.0, 2.718281828459045, 7.38905609893065]
```
We can also check that the candidate solution satisfies the ODE:
```python
>>> ode_flow.verify_solution(solution, y(t), t, y)
True
```
## How to guide
### How to solve a first-order initial value problem
The right-hand side is a `sympy` expression in the time variable and
the unknown function. The initial time and value can be either
symbolic or numeric.
```python
>>> import sympy as sym
>>> import ode_flow
>>> t = sym.Symbol("t")
>>> y = sym.Function("y")
>>> ode_flow.solve_initial_value_problem(2 * t, t, y, 0, 0)
t**2
```
### How to sample a symbolic solution at a list of times
```python
>>> import sympy as sym
>>> import ode_flow
>>> t = sym.Symbol("t")
>>> ode_flow.trajectory(t**2, t, [0, 1, 2, 3])
[0.0, 1.0, 4.0, 9.0]
```
### How to verify that an expression satisfies an ODE
```python
>>> import sympy as sym
>>> import ode_flow
>>> t = sym.Symbol("t")
>>> y = sym.Function("y")
>>> ode_flow.verify_solution(sym.exp(t), y(t), t, y)
True
>>> ode_flow.verify_solution(t, y(t), t, y)
False
```
## Discussion
A first-order initial value problem has the form
\(y'(t) = f(t, y(t))\) with \(y(t_0) = y_0\). For many right-hand
sides \(f\), the solution can be written down in closed form by
recognising one of the standard cases (separable, linear, exact). The
solver `sympy.dsolve` already implements these cases, and a great deal
more, so we treat it as the engine and focus on the interface.
The `trajectory` helper exists because the natural next step after
finding a symbolic solution is to plot it. The `verify_solution`
helper exists for tests: given a candidate symbolic solution, it
substitutes the candidate into the right-hand side and compares
against the derivative of the candidate. The general theory of ODEs
is covered in [2].
### How we built this
We pass the right-hand side and the initial condition through
`sym.Eq` and the `ics=` keyword of `sym.dsolve`. The return value of
`sym.dsolve` is an `Eq` of the form \(y(t) = \text{expression}\); we
take `.rhs` to return the expression itself, which is easier to
substitute into. Inside `trajectory` we convert each substitution to
`float` so that the output is small and plottable; the symbolic
solution remains available if exactness is needed.
We follow the differential equations chapter of *Python for
Mathematics* [1]: we use `sym.Function` for the unknown, `sym.Eq`
for the equation, and `sym.dsolve` with the `ics` keyword for the
initial condition.
## Reference
### List of functionality
A list of functionality in this library is:
- `solve_initial_value_problem(right_hand_side, time_variable,
function, initial_time, initial_value)`: return the symbolic
solution to a first-order initial value problem.
- `trajectory(solution_expression, time_variable, sample_times)`:
evaluate the solution at a list of times.
- `verify_solution(solution_expression, right_hand_side,
time_variable, function)`: check that a candidate solves the ODE.
### Tests
The tests live in `test_ode_flow.py` and can be run with:
```sh
python test_ode_flow.py
```
They check the solver on the exponential-growth problem
\(y' = y\), the constant-growth problem \(y' = 1\), the explicit
time-dependent problem \(y' = 2t\), and the same exponential-growth
problem with a non-zero initial time. They also check that
`trajectory` returns the correct length and known values, and that
`verify_solution` accepts a correct candidate and rejects an
incorrect one.
### Bibliography
[1] Knight, V. *Python for Mathematics*, vknight.org/pfm. Accessed 2026.
[2] Boyce, W. E. and DiPrima, R. C. *Elementary Differential Equations
and Boundary Value Problems*. Wiley, 11th edition, 2017.