This guide provides instructions for submitting and formatting new code in OTP.
Changes to OTP should be proposed as a pull request and undergo a review process before being merged. New code must be free of warnings and errors and adhere to the style guidelines.
Each problem defines a package under +otp
that contains all files used by the problem. When creating a new problem we recommend duplicating an existing problem package, e.g., Lorenz63 or Brusselator, then renaming and editing the contents as needed.
To add a new test problem from scratch follow these steps:
- Check out the latest version of OTP:
git clone https://github.com/ComputationalScienceLaboratory/ODE-Test-Problems.git
cd ODE-Test-Problems/
- Create a new folder in the
toolbox/+opt/
directory with a name that starts with+
to indicate it is a package. Also create a subfolder named+presets
in the new problem folder:
mkdir toolbox/+opt/+example
cd toolbox/+opt/+example
mkdir +presets
- The minimal set of files needed inside the problem folder to set up a new example problem are:
- The right-hand side (RHS) function named as
f.m
- The problem class to specify properties, override plotting and solver options, and convert parameters into arguments for RHS functions
- The parameters class
- A
Canonical.m
preset inside the+presets
subfolder to set standard initial condition and parameters.
touch f.m ExampleProblem.m ExampleParameters.m +presets/Canonical.m
The RHS function f.m
, which is the time-derivative of the state y
, is defined as a function with at least two arguments. If parameters are needed, they can be added as arguments.
function dy = f(t, y, param1, ...)
dy = ...
end
Other functions associated with an otp.RHS
like the Jacobian and mass matrix should be implemented in separate .m
files with the same function signature as f.m
.
A problem package must contain a class named <Name>Problem.m
that is a subclass of otp.Problem
. There are two methods that must be implemented: the constructor and onSettingsChanged
. Optionally, one can override functions such as internalPlot
and internalSolve
to provide problem-specific defaults. Partitioned problems can add custom RHS functions as class properties with private write access. The property name should start with RHS
, e.g., RHSStiff
.
A basic template for a new class of problems called Example
looks like
classdef ExampleProblem < otp.Problem
methods
function obj = ExampleProblem(timeSpan, y0, parameters)
[email protected]('Example', [], timeSpan, y0, parameters);
% The second argument specifies the number of variables in the problem is arbitrary
end
end
methods (Access=protected)
function onSettingsChanged(obj)
% Parameters are stored in the obj.Parameters structure. We can assign them to individual variables to be
% used in function calls
param1 = obj.Parameters.Param1;
% set up the RHS function wrapper
obj.RHS = otp.RHS(@(t, y) otp.example.f(t, y, param1));
end
% set up internal plot function
function fig = internalPlot(obj, t, y, varargin)
fig = [email protected](obj, t, y, 'xscale', 'log', 'yscale', 'log', varargin{:});
end
% set up internal movie function
function mov = internalMovie(obj, t, y, varargin)
mov = [email protected](obj, t, y,, 'xscale', 'log', 'yscale', 'log', varargin{:});
end
% set up internal solver
function sol = internalSolve(obj, varargin)
% Set tolerances due to the very small scales
sol = [email protected](obj, 'AbsTol', 1e-50, varargin{:});
end
end
end
A problem package must also contain a class named <Name>Parameters.m
that is a subclass of otp.Parameters
. It needs to provide public properties for each of the problem parameters and a constructor which forwards arguments to the superclass constructor; no methods are needed. Note that property validation is currently not supported in Octave. Therefore, we use a custom comment syntax that is parsed by the installer to optionally include validation. The following is an example of a parameter class with property validation:
classdef ExampleParameters
properties
Param1 %MATLAB ONLY: (1,1) {mustBeReal, mustBeNonnegative}
end
methods
function obj = ExampleParameters(varargin)
obj = [email protected](varargin{:});
end
end
end
Within a problem package, there should be a subpackage named +presets
. This contains subclasses of <Name>Problem
that specify the time span, initial conditions, and parameters. Typically, only the constructor needs to be implemented in a preset class.
In our example, we add a Canonical.m
preset inside the +presets
subfolder.
classdef Canonical < otp.example.ExampleProblem
methods
function obj = Canonical(varargin)
y0 = ...
tspan = ...
% Specify a default value for Param1 which can be overridden by a name-value pair passed to this constructor
params = otp.example.ExampleParameters('Param1', pi, varargin{:});
obj = [email protected](tspan, y0, params);
end
end
end
In order for this project to maintain a consistent coding style, the following conventions should be used. These standards largely match those most commonly used in MATLAB's code and documentation.
Four spaces are used for indentation. A line should be kept to 120 characters or less.
Variable names should be written in camel case.
% Examples
data = 4;
maxEigenvalue = eigs(rand(4), 1);
fun = @(t, y) y + sin(t);
Functions should be completely alphanumeric and written in camel case. No special character is used to distinguish between words.
% Example
function r = depthFirstSearch(tree)
...
end
Structures should have camel case property names.
% Example
car = struct('make', 'Ford', 'modelYear', 2020);
Package names should be completely lowercase and start with a plus symbol. No capitalization or special character is used to distinguish between words.
% Example
% Path: +otp/+utils/PhysicalConstants.m
help otp.utils.PhysicalConstants
Class names and properties should be written in Pascal case. When the name contains an acronym, all letters should be capitalized. Methods should be written in camel case.
% Examples
classdef Employee
properties
FirstName
LastName
Salary
end
methods
function p = calculatePay(hours)
...
end
end
end
classdef ODETestProblems
...
end