Skip Symbolic Differentiation

Although symbolic differentiation is convenient for most problems, you may want to skip it for the following reasons:

  • The problem cannot be simply described as symbolic expressions
  • There are other more suitable automatic differentiation methods
  • Functions need to call pre-compiled external libraries
  • Want to directly call compiled functions for better performance

Setup Method

If you need to skip symbolic differentiation for certain functions, the method is similar to using the cache feature (see Compilation Is Very Slow). Simply place the manually edited function files in a specified folder, then set the cache parameter to that folder path in the set_* functions. Pockit will directly load these functions, thereby skipping symbolic differentiation.

For example, if you need to skip symbolic differentiation of the dynamics equation for the first state variable, you can prepare a cache/dynamics_0.py file, then set cache="cache" in the set_dynamics function. Regardless of the symbolic dynamics equation passed in (can be set to 0), pockit will directly load the cache/dynamics_0.py file. The filenames for different functions are shown in the following table:

Function Filename
Phase.set_dynamics dynamics_{i}.py
Phase.set_integral integral_{i}.py
Phase.set_phase_constraint phase_constraint_{i}.py
Phase.set_boundary_condition boundary_condition_0_{i}.py,
boundary_condition_f_{i}.py,
boundary_condition_t_0.py,
boundary_condition_t_f.py
System.set_objective objective.py
System.set_system_constraint system_constraint_{i}.py

File Format

The provided Python files should contain vector functions F, G, H and variables G_index, H_index_row, H_index_col. These functions and variables are defined as follows (also see the API documentation):

  • Functions F, G, H take two parameters: a one-dimensional NumPy array x and a scalar l. The functions should return values at l different points. In x, every consecutive l elements represent the values of one parameter at different points. For example, if the function is \(f(x_0, x_1, \dots, x_{m - 1})\), the vectorized function should compute values at points \([f(x_0, x_1,\)\( \dots, x_{l - 1}), \)\(f(x_l, x_{l + 1}, \)\(\dots, x_{2l - 1}), \)\(\dots, \)\(f(x_{(m - 1) l}, x_{(m - 1) l + 1}, \)\(\dots, x_{-1})\)\(]\).
  • Function F returns a one-dimensional NumPy array of length l, representing the values of function \(f\) at l points. Function G returns a two-dimensional NumPy array of shape (len(G_index), l), representing the gradient values of function \(f\) at l points. Function H returns a two-dimensional NumPy array of shape (len(H_index_row), l), representing the Hessian matrix values of function \(f\) at l points.
  • Variables G_index, H_index_row, H_index_col must correspond to the outputs of G and H. These variables should be integer NumPy arrays, representing the indices of non-zero elements in the gradient and Hessian matrix of function \(f\). H_index_row and H_index_col have equal length and only contain indices of the lower triangular part. (The Hessian matrix is symmetric, so only the lower triangular part needs to be stored.) G_index and H_index_row should be strictly increasing, and H_index_col should also be strictly increasing within the same row.

Example files can be found in the generated cache files.

Given a system with \(n_x\) state variables, \(n_u\) control variables, \(n_i\) integrals, and \(n_s\) system parameters, the input parameters and dimensions for different functions are shown in the following table:

Function Input Parameters Input Dimension \(m\)
Phase.set_dynamics \(x\), \(u\), \(t\), \(s\) \(n_x + n_u + 1 + n_s\)
Phase.set_integral \(x\), \(u\), \(t\), \(s\) \(n_x + n_u + 1 + n_s\)
Phase.set_phase_constraint \(x\), \(u\), \(t\), \(s\) \(n_x + n_u + 1 + n_s\)
Phase.set_boundary_condition \(s\) \(n_s\)
System.set_objective \(I\), \(s\) \(n_i + n_s\)
System.set_system_constraint \(I\), \(s\) \(n_i + n_s\)