How to contribute — The COBRA Toolbox (original) (raw)

How to contribute

thumbsup tada First off, thanks for taking the time to contribute to The COBRA Toolbox! tada thumbsup

devTools

You can install theMATLAB.devTools from within MATLAB by typing:

bulb Check out the MATLAB.devTools - and contribute the smart way! The official documentation is here.

thumbsup Contribute to the opencobra/cobratoolbox repository by following these instructions:

contribute('opencobra/cobratoolbox');

thumbsup Contribute to the opencobra/COBRA.tutorials repository by following these instructions:

contribute('opencobra/COBRA.tutorials');

If you want to use git via the command line interface and need help, thisguideor the official GitHub guidecome in handy.

Guide for writing a test

Before starting to write a test on your own, it might be instructive to follow common test practices in /test/verifiedTests. A style guide on how to write tests is givenhere.

Prepare the test (define requirements)

There are functions that need a specific solver or that can only be run if a certain toolbox is installed on a system. To address these, you should specify the respective requirements by using

solvers = prepareTest(requirements)

If successfull and all requirements are fulfilled, prepareTest will return a struct with one field for each problem type (solvers.LP, solvers.MILP etc.). Each field will be a cell array of solver names (if any are available). If the test does not ask for multiple solvers (via the requiredSolvers or theuseSolversIfAvailable arguments), the returned cell array will only contain at most one solver.

Here are a few examples:

Example A: Require Windows for the test

The same works with needsMac, needsLinux and needsUnixinstead of needsWindows.

solvers = prepareTest('needsWindows', true);

Example B: Require an LP solver (needsLP)

The same works with needsNLP, needsMILP, needsQP orneedsMIQP. solvers.LP, solvers.MILP etc. will be cell arrays of string with the respective available solver for the problem type. If the 'useSolversIfAvailable' parameter is non empty, all installed solvers requested will be in the cell array. Otherwise, there will be at most one solver (if no solver for the problem is installed, the cell array is empty).

solvers = prepareTest('needsLP', true);

Example C: Use multiple solvers if present

If multiple solvers are requested. solvers.LP, solvers.MILP etc will contain all those requested solvers that can solve the respective problem type and that are installed.

solvers = prepareTest('needsLP', true, 'useSolversIfAvailable', {'ibm_cplex', 'gurobi'});

Example D: Require one of a set of solvers

Some functionalities do only work properly with a limited set of solvers. with the keyword requireOneSolverOf you can specify a set of solvers of which the test requires at least one to be present. This option, will make the test never be run with any but the solvers specified in the supplied list. E.g. if your test only works with gurobi or mosekyou would call prepareTest as

solvers = prepareTest('requireOneSolverOf', {'ibm_cplex', 'gurobi'})

Example E: Exclude a solver

If for some reason, a function is known not to work with a few specific solvers (e.g. precision is insufficient) but works with all others, it might be advantageous to explicitly exclude that one solver instead of defining the list of possible solvers. This can be done with theexcludeSolvers parameter. Eg. to exclude matlab and lp_solveyou would use the following command:

solvers = prepareTest('excludeSolvers', {'matlab', 'lp_solve'})

Example F: Require multiple solvers

Some tests require more than one solver to be run, and otherwise fail. To require multiple solvers for a test use the requiredSolvers parameters. E.g. if your function requires ibm_cplex and gurobi use the following call:

solvers = prepareTest('requiredSolvers', {'ibm_cplex', 'gurobi'})

Example G: Require a specific MATLAB toolbox

The toolbox IDs are specified as those used in license('test', 'toolboxName'). The following example requires the statistics toolbox to be present.

solvers = prepareTest('requiredToolboxes', {'statistics_toolbox'})

Example H: Multiple requirements

If the test requires multiple different properties to be met, you should test them all in the same call. To keep the code readable, first define the requirements and then pass them in.

% define required toolboxes requiredToolboxes = {'bioinformatics_toolbox', 'optimization_toolbox'};

% define the required solvers (in this case matlab and dqqMinos) requiredSolvers = {'dqqMinos', 'matlab'};

% check if the specified requirements are fullfilled (toolboxes, solvers in thhis example, a unix OS). solversPkgs = prepareTest('requiredSolvers', requiredSolvers, 'requiredToolboxes', requiredToolboxes, 'needsUnix', true);

Test if an output is correct

If you want to test if the output of a function[output1, output2] = function1(input1, input2) is correct, you should call this function at least 4 times in your test. The argumentìnput2 might be an optional input argument.

% Case 1: test with 1 input and 1 output argument output1 = function1(input1)

% Case 2: test with 1 input and 2 output arguments [output1, output2] = function1(input1)

% Case 3: test with 1 output and 2 input arguments output1 = function1(input1, input2)

% Case 4: test with 2 input and 2 output arguments [output1, output2] = function1(input1, input2)

Each of the 4 test scenarios should be followed by a test on output1and output2. For instance, for Case 4:

% Case 4: test with 2 input and 2 output arguments [output1, output2] = function1(input1, input2)

% test on output1 assert(output1 < tol); % tol must be defined previously, e.g. tol = 1e-6;

% test on output2 assert(abs(output2 - refData_output2) < tol); % refData_output2 can be loaded from a file

The test succeeds if the argument of assert() yields a truelogical condition.

Test if a function throws an error or warning message

If you want to test whether your function1 correctly throws anerror message, you can test as follows:

% Case 5: test with 2 input and 1 output arguments (2nd input argument is of wrong dimension) % There are two options. If a particular error message is to be tested (here, 'Input2 has the wrong dimension'): assert(verifyCobraFunctionError('function1', 'inputs', {input1, input2'}, 'testMessage', 'Input2 has the wrong dimension'));

% If the aim is to test, that the function throws an error at all assert(verifyCobraFunctionError('function1', 'inputs', {input1, input2'}));

If you want to test whether your function1 correctly throws awarning message, you can test as follows:

warning('off', 'all') output1 = function1(input1, input2'); assert(length(lastwarn()) > 0) warning('on', 'all')

Note that this allows the error message to be thrown without failing the test.

Test template

A test template is readily availablehere. The following sections shall be included in a test file:

1. Header

% The COBRAToolbox: .m % % Purpose: % - <provide a short description of the purpose of the test % % Authors: % - : %

2. Test initialization

global CBTDIR

% save the current path and switch to the test path currentDir = cd(fileparts(which('fileName')));

% get the path of the test folder testPath = pwd;

3. Define the solver packages to be tested and the tolerance

% set the tolerance tol = 1e-8;

% define the solver packages to be used to run this test solvers = prepareTest('needsLP',true);

4. Load a model and/or reference data

% load a model distributed by the toolbox getDistributedModel('testModel.mat'); % load a particular model for this test: readCbModel([testPath filesep 'SpecificModel.mat']) % load reference data load([testPath filesep 'testData_functionToBeTested.mat']);

Please only load small models, i.e. less than 100 reactions. If you want to use a non-standard test model that is already available online, please make a pull request with the URL entry to theCOBRA.models repository.

warning: In order to guarantee compatibility across platforms, please use the full path to the model. For instance:

5. Create a parallel pool

This is only necessary for tests that test a function that runs in parallel.

% create a parallel pool poolobj = gcp('nocreate'); % if no pool, do not create new one. if isempty(poolobj) parpool(2); % launch 2 workers end

warning: Please only launch a pool of 2 workers - more workers

should not be needed to test a parallel function efficiently.

6. Body of test

The test itself. If the solvers are essential for the functionality tested in this test use:

for k = 1:length(solvers.LP) fprintf(' -- Running using the solver interface: %s ... ', solvers.LP{k});

solverLPOK = changeCobraSolver(solvers.LP{k}, 'LP', 0);
% <your test goes here>

% output a success message
fprintf('Done.\n');

end

This is important, as the continuous integration system will run other solvers on the test in its nightly build. That way, we can determine solvers that work with a specific method, and those that do not (potentially due to precision problems or other issues). If the solvers are only used to test the outputs of a function for correctness, use:

solverLPOK = changeCobraSolver(solvers.LP{1}, 'LP', 0); %

% output a success message fprintf('Done.\n');

7. Return to the original directory

% change the directory cd(currentDir)

Run the test locally on your machine

Please make sure that your test runs individually by typing after a fresh start:

initCobraToolbox

Please then verify that the test runs in the test suite by running:

Alternatively, you can run the test suite in the background by typing:

$ matlab -nodesktop -nosplash < test/testAll.m

Verify that your test passed

Once your pull request (PR) has been submitted, you will notice an orange mark next to your latest commit. Once the continuous integration (CI) server succeeded, you will see a green check mark. If the CI failed, you will see a red cross.

What should I do in case my PR failed?

You can check why your PR failed by clicking on the mark and following the respective links. Alternatively, you can see the output of the CI for your PRhere. You can then click on the build number. Under Console Output, you can see the output of test/testAll.m with your integrated PR.

Once you understood why the build for your proposed PR failed, you can add more commits that aim at fixing the error, and the CI will be re-triggered.

Common errors include:

Can I find out how many tests have failed?

The logical conditions, when tested using assert(), will throw an error when not satisfied. It is bad practice to test the sum of tests passed and failed. Please only test using assert(logicalCondition). Even though a test may fail using assert(), a summary table with comprehensive information is provided at the end of the test run.

For instance, the following test script do not do this - bad practice!:

% do not do this: bad practice! testPassed = 0; testFailed = 0;

% test on logical condition 1 - do not do this: bad practice! if logicalCondition1 testPassed = testPassed + 1; else testFailed = testFailed + 1; end

% test on logical condition 2 - do not do this: bad practice! if logicalCondition2 testPassed = testPassed + 1; else testFailed = testFailed + 1; end

assert(testPassed == 2 && testFailed == 0); % do not do this: bad practice!

shall be rewritten as follows:

% good practice assert(logicalCondition1); assert(logicalCondition2);

Documentation guide

In order to enable the automatic documentation generation, the header of a MATLAB function has to be formatted properly. The automatic documentation extracts the commented header of the function (commented lines between the function’s signature and the first line of code) based on keywords and blocks.

Rule 1: There should be 1 free space between the percentage sign %and the text. For instance, a correctly formatted line in the header reads:

A line in the header that is formatted as follows is ignored:

%this is not correct (note the space)

Rule 2: After the last line of the header, leave one empty line before the body of the function.

% This is the end of the header of the function

x = 5; % the body of the function begins after one empty line

Importantly, do not put a comment above the first line of code, but include the comment inline:

% This is the end of the header of the function

% the body of the function begins after one empty line x = 5;

Rule 3: A line in the header that includes .. after the percentage sign will be ignored in the documentation:

% .. this line will be ignored

Function signature

The function signature must be correctly formatted. Leave a space after every comma and before and after the equal sign =. A correctly formatted function signature reads:

function [output1, output2, output3] = someFunction(input1, input2, input3) % good practice

A function signature that is not formatted properly throws an errorduring the automatic documentation generation:

function [ output1,output2,output3 ]=someFunction( input1,input2,input3 ) % bad practice

Function description

The description of the function is a brief explanation of the purpose of the function. The description of the function may extend over several lines. However, try to keep the explanations as brief as possible.

function [output1, output2, output3] = someFunction(input1, input2, input3) % This is a description of the function that helps to understand how the function works % Here the description continues, then we leave an empty comment line %

Keywords

The automatic documentation software relies on keywords to properly format the documented function. A keyword also defines the start of a block with the header of a function. Main keywords include:

Each of them must be followed by non-empty lines and should be separated from the next block by an empty line.

All keywords are optional. For instance, if a function does not have any input arguments, the keyword INPUTS: can be omitted.

Any line of the block must be indented by 4 spaces after the comment sign %:

% INPUTS: % input1: Description of input1 % input2: Description of input2 % input3: Description <-- this is bad practice % % OUTPUTS: % output1: Description of output1

If the indentation differs, there will be an error.

Keyword USAGE:

In the block starting with the keyword USAGE:, the function’s signature must be given in order to show how the function should be used. It is important to leave one empty line before the keywordUSAGE:, after the keyword, and after the function’s signature.

% the end of the description % % USAGE: % % [output1, output2, output3] = someFunction(input1, input2, input3) % % here the other section can begin

Keyword INPUT: and OUTPUT:

The arguments declared in the blocks: INPUT:, INPUTS:,OUTPUT: and OUTPUTS: must be followed by a colon : before the argument description is provided.

The indentation between the argument (with colon) and the description should be at least 4 spaces, so that all argument descriptions are aligned.

% INPUTS: % input1: Description of input1 <-- good practice % input2 No colon <-- bad practice % input3: Not enough distance (4+ spaces) <-- bad practice % % OUTPUTS: % longerNameOutput: Description of longerNameOutput after 4 spaces % output1: Description begins at the same place as the longest argument <-- good practice % output2: Description begins too soon <-- bad practice

For a structure argument, it is possible to list its fields. An empty line is added after the structure argument. Then, in the next line, the field is written aligned with the description of thestructure plus 2 extra spaces.

The field is listed beginning as * .field - description (note the space between * and .). It is not necessary to leave an empty line after listing fields and writing the next argument. The following illustrates how to list a structure with its fields:

% OUTPUT: % output: output argument with fields: % % * .field1 - first field of the structure. % * .field2 - no indent <-- bad practice % * .field3 - multi-line comment must begin always % where the text of the first line begins <-- good practice % * .field4 - multi-line comment where % the text in line 2 begins too soon <-- bad practice % next: next argument can be added without empty line

It is also possible to replace * with a numbered list. You can use numbers followed by a dot (e.g., 1.) instead of * ..

% OPTIONAL INPUT: % input: optional input argument with fields: % % 1. first element of a numbered list % 2. second element of a numbered list

Keyword EXAMPLE:

A common usage example can be included in the EXAMPLE: block. Code included in this block will be formatted as MATLAB formatted code. Leave one empty line before the keyword EXAMPLE:, after the keyword, and after the properly indented (4 spaces) code snippet.

% the previous block ends here % % EXAMPLE: % % result = someFunction(input1, input2) % %additional comment if necessary % % another block begins here

Keyword NOTE:

Important information, such as common errors, can be included in the block that starts with the keyword NOTE:. A NOTE: block is formatted in a dedicated and highlighted box in the documentation.

Leave one empty line before the keyword NOTE:, after the keyword, and after the properly indented text (indent of 4 spaces).

Normally formatted text can be left at the with one space after the comment sign. An example of a NOTE: block reads:

% % NOTE: % % This is a note that contains a important information. % It will be clearly visible in the documentation online. % % This is an additional final comment that can be added and that is % only relevant to the code itself

Keyword Author: or Author(s):

In the Author(s) block, the author(s) that have written or contributed to the function are listed. Authors are not shown in the documentation itself, so the keyword is preceded by ... List 1 author as follows:

% % .. Author: - Name, date, additional information if needed

x = 5; % here the body of the function begins

If there are 2 or more authors, format as follows:

% % .. Authors: % - Name1, date, additional information if needed % - Name2, date, additional information if needed

x = 5; % here the body of the function begins

Example

A complete example of a function is provided here. Please remember that colons, indentations, and keywords are important to guarantee pretty formatting.

function [output1, output2] = someFunction(input1, input2, input3, input4) % This is a description of the function that helps understand how the function works % Here the description continues, then we leave an empty comment line % % USAGE: % % [output1, output2] = someFunction(input1, input2, input3, input4) % % INPUTS: % input1: Description of input1 % input2: Description of input2 % % OPTIONAL INPUTS: % input3: Structure with fields: % % * First field - description % * Second field - description % input4: Description of input4 % % OUTPUT: % output1: Description of output1 % % OPTIONAL OUTPUT: % output2: Description of output2 % % EXAMPLE: % % % this could be an example that can be copied from the documentation to MATLAB % [output1, output2] = someFunction(11, '22', structure, [1;2]) % % without optional values % output1 = someFunction(11, '22') % % NOTE: % This is a very important information to be highlighted % % This is a final comment that cannot be in the description but can be useful % % .. Author: - Name, date, some information

x = 5; % here the body of the function begins

Style guide

A comprehensive MATLAB style guide written by Richard Jonson can be found [here](http://www.datatool.com/downloads/MatlabStyle2%20book.pdf).

Code

  1. Spacing
  1. Variable names
  1. Miscellaneous

function [parameter1, parameter2, parameter3, parameter4] = functionManyParameters... (InputParameter1, InputParameter2, InputParameter3, InputParameter3, ... InputParameter4, InputParameter5)

  1. Platform independent code

Tests

Git commit messages

Issues/enhancements guide

What should I do before opening an issue?

Following these guidelines helps maintainers and the community understand your report pencil, reproduce the behavior computer, and fix it.

If you provide snippets in the issue/pull request, use Markdown code blocks.

How can I report an issue (or enhancement)?

Explain the problem and include additional details to help maintainers reproduce the problem:

Provide details on the nature of the issue:

Include details about your configuration and environment:

Provide more context by answering these questions: