An example on how to use PyDDA from command line: Gradiend descent

This example is a go-throught of how using PyDDA from the Unix (Linux or Mac OS X) command line. We use the Rosenbrock function as an example for Gradient descent.

The file rosenbrock.dda is given with the following content:

# Gradient descent on Rosenbrock problem with continous-time analog computing
# f(x,y) = (a-x)**2 + b*(y - x**2)**2

a = const(1)
b = const(100)

# Initial conditions are starting points for optimization

ic_x  = const(2)  # x(t0)
ic_my = const(2)  # -y(t0)

# timestep size is more for the numerical integrator
dt = const(0.01)

# next lines are the gradient
# D[f,x] = -2 (a - x) - 4 b x (-x^2 + y)
# D[f,y] = 2 b (-x^2 + y)
# written in DDA notation.
# Keep the negating sums and integrals in mind! Sum[x,y] == neg(sum(x,y))

x = neg(neg(int(mult(2,sum(a,neg(x))), mult(4,mult(b,mult(x,sum(neg(mult(x,x)),y)))), dt, ic_x)))
y = neg(int(mult(2,mult(b,sum(neg(mult(x,x)),y))), dt, ic_my))

It can now be run on the shell by running the following commands:

python -m dda rosenbrock.dda c > rosenbrock.cpp
g++ -std=c++17 -o rosenbrock.o rosenbrock.cpp
./rosenbrock.o --dt:int_1=0.0001 --dt:int_2=0.0001  --max_iterations=100000 x y | tee output.txt

You can now plot the output for instance with gnuplot:

set key top left autotitle columnhead
set logscale x
plot "output.txt" using 0:1 w l
replot "output.txt" using 0:2 w l

Or do anything else on the CSV data.

Inspecting the command line interface of the C++ ODE solver

Run the executable with --help to see all possible options:

Usage: ./rosenbrock.o [arguments] <variables_to_print>
This is an ODE integrator generated by PyDDA.

* Boolean arguments (Usage --foo or --foo=1 or --foo=0)
  always_compute_aux_before_printing (default value: 1)
  binary_output (default value: 0)
  debug (default value: 0)
  list_all_variables (default value: 0)
  skip_header (default value: 0)
  write_initial_conditions (default value: 0)
* Numeric arguments: (Usage --foo=123)
  max_iterations (default value: 100)
  modulo_progress (default value: -1)
  modulo_write (default value: 1)
  number_precision (default value: 5)
  rk_order (default value: 1)
* Overwrite initial conditions (initial data): (Usage --initial:foo=1.23)
  int_1 (default value: 2)
  int_2 (default value: 2)
* Overwrite (per-variable) time step size: (Usage --dt:foo=0.001 or even --dt:foo=1e-6)
  int_1 (default value: 0.01)
  int_2 (default value: 0.01)
* Overwrite constants: Not possible since compiled as compile time constants.
* Query fields: (if none given, all are dumped)
  a, b, dt, ic_my, ic_x,
  int_1, int_2, mult_1, mult_2, mult_3,
  mult_4, mult_5, mult_6, mult_7, mult_8,
  neg_1, neg_2, neg_3, neg_4, sum_1,
  sum_2, sum_3, x, y

Exemplaric usage:
  ./rosenbrock.o --foo=1 --bar=0 --baz=7 --ic:var1=0.5 --dt:var2=0.01 --const:something=42 var1 var2 var3

For more options and help, see the PyDDA code documentation at https://pyanalog.readthedocs.io/

But I want a better ODE Solver!

The C++ based solver is really very basic. It allows to introspect all the quantities in a dense output, but for instance does not have adaptive time step size. Time to use something more mature, such as the mature ODE/IVP solvers by scipy (which used to be a frontend for ODEPACK). There is a DDA interface to Scipy (see package dda.scipy) and it can also be used from command line:

python -m dda.scipy  -t 10 rosenbrock.dda | tee output2.txt

Note that in this example, output.txt and output2.txt hold similar data, since in the C++ solver case, the solver was run until t_final = dt * N = 0.0001 * 100000 = 10, the same as in the scipy solver case. However, by convention, the C++ solver does not include a time column (you have to generate it by yourself with t=int(const(1), dt, 0) if you need one).