diff --git a/.github/issue_template.md b/.github/issue_template.md index 79269c9e..3d7e716c 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,12 +1,12 @@ ### Description of the issue: -*Try to be as clear as possible, e.g.: + #### Reproducing this issue: -*If applicable, please attach the problematic code. PLEASE DELETE THIS LINE.* + ```matlab INSERT CODE HERE ``` @@ -17,8 +17,7 @@ INSERT CODE HERE 2. Operating system (Windows/Mac/Linux; include version) **I hereby confirm that I have:** + - [ ] Followed the [guidelines](https://github.com/SysBioChalmers/RAVEN/wiki/Installation) to install RAVEN. - [ ] Checked that a similar issue does not [already exist](https://github.com/SysBioChalmers/RAVEN/issues?utf8=%E2%9C%93&q=is%3Aissue) - [ ] If suitable, needed, asked first in the [Gitter chat room](https://gitter.im/SysBioChalmers/RAVEN) about the issue - -*Note: replace [ ] with [X] to check the box. PLEASE DELETE THIS LINE* diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a114340e..ce928797 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,12 @@ ### Main improvements in this PR: -*Pointwise mention what changes were made in what function. Examples: + **I hereby confirm that I have:** + - [ ] Tested my code on my own machine - [ ] Followed the [development guidelines](https://github.com/SysBioChalmers/RAVEN/wiki/DevGuidelines). - [ ] Selected `devel` as a target branch - [ ] If needed, asked first in the [Gitter chat room](https://gitter.im/SysBioChalmers/RAVEN) about this PR - -*Note: replace [ ] with [X] to check the box. PLEASE DELETE THIS LINE* diff --git a/.github/workflows/testing-comment.md b/.github/workflows/testing-comment.md new file mode 100644 index 00000000..c0c38f90 --- /dev/null +++ b/.github/workflows/testing-comment.md @@ -0,0 +1,7 @@ +This PR has been [automatically tested with GH Actions](https://github.com/SysBioChalmers/RAVEN/actions/runs/{GH_ACTION_RUN}). Here is the output of the tests: + +``` +{TEST_RESULTS} +``` + +> _Note: In the case of multiple test runs, this post will be edited._ \ No newline at end of file diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 00000000..888c2aba --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,28 @@ +name: Testing + +on: [pull_request] + +jobs: + matlab-tests: + runs-on: self-hosted + + steps: + + - name: Checkout + uses: actions/checkout@v2 + + - name: Run tests + id: matlab-test + run: | + TEST_RESULTS=$(/usr/local/bin/matlab -nodisplay -nosplash -nodesktop -r "addpath(genpath('.')); cd('testing/unit_tests'); runtests(struct2table(dir('*.m')).name); exit;") + PARSED_RESULTS=$(echo $TEST_RESULTS | awk -F'.com.' '{ n = split($2, v, "__________"); for (i = 0; ++i <= n;) { print v[i] } }') + echo ::set-output name=results::$PARSED_RESULTS + + - name: Post comment + uses: NejcZdovc/comment-pr@v1 + with: + file: "testing-comment.md" + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + TEST_RESULTS: ${{steps.matlab-test.outputs.results}} + GH_ACTION_RUN: ${{github.run_id}} \ No newline at end of file diff --git a/core/checkModelStruct.m b/core/checkModelStruct.m index fd20ab87..381278ae 100755 --- a/core/checkModelStruct.m +++ b/core/checkModelStruct.m @@ -246,6 +246,18 @@ function checkModelStruct(model,throwErrors,trimWarnings) EM='The following reactions have bounds contradicting their reversibility:'; dispEM(EM,throwErrors,model.rxns(model.lb<0 & model.rev==0),trimWarnings); +%Multiple or no objective functions not allowed in SBML L3V1 FBCv2 +if numel(find(model.c))>1 + EM='Multiple objective functions found. This might be intended, but exportModel will fail due to SBML FBCv2 non-compliance:'; + dispEM(EM,false,model.rxns(find(model.c)),trimWarnings); +elseif ~any(model.c) + EM='No objective function found. This might be intended, but exportModel will fail due to SBML FBCv2 non-compliance'; + dispEM(EM,false); +end + +EM='The following reactions have contradicting bounds:'; +dispEM(EM,throwErrors,model.rxns(model.lb>model.ub),trimWarnings); + %Mapping of compartments if isfield(model,'compOutside') EM='The following compartments are in "compOutside" but not in "comps":'; diff --git a/core/compareModels.m b/core/compareRxnsGenesMetsComps.m old mode 100755 new mode 100644 similarity index 97% rename from core/compareModels.m rename to core/compareRxnsGenesMetsComps.m index 7af16a6d..37d1f9e7 --- a/core/compareModels.m +++ b/core/compareRxnsGenesMetsComps.m @@ -1,5 +1,5 @@ -function compStruct=compareModels(models,printResults) -% compareModels +function compStruct=compareRxnsGenesMetsComps(models,printResults) +% compareRxnsGenesMetsComps % Compares two or more models with respect to overlap in terms of genes, % reactions, metabolites and compartments. % @@ -23,7 +23,7 @@ % nElements vector with the number of elements for each % comparison % -% Usage: compStruct=compareModels(models,printResults) +% Usage: compStruct=compareRxnsGenesMetsComps(models,printResults) if nargin<2 printResults=true; diff --git a/core/convertToIrrev.m b/core/convertToIrrev.m index ecac9d0b..aae1e62f 100755 --- a/core/convertToIrrev.m +++ b/core/convertToIrrev.m @@ -14,13 +14,13 @@ % Usage: irrevModel=convertToIrrev(model,rxns) if nargin<2 - rxns=model.rxns; + I=true(numel(model.rxns),1); +else + I=getIndexes(model,rxns,'rxns',true); end irrevModel=model; -I=getIndexes(model,rxns,'rxns',true); - revIndexesBool=model.rev~=0 & I; revIndexes=find(revIndexesBool); if any(revIndexesBool) diff --git a/core/dispEM.m b/core/dispEM.m index 325ba758..9947f76f 100755 --- a/core/dispEM.m +++ b/core/dispEM.m @@ -42,6 +42,8 @@ function dispEM(string,throwErrors,toList,trimWarnings) end end if throwErrors==false + %Escape special characters, required for fprintf + errorText=regexprep(errorText,'(\\|\%|'')','\\$0'); fprintf([errorText '\n']); else throw(MException('',errorText)); diff --git a/core/permuteModel.m b/core/permuteModel.m index 15765f83..23a06cea 100755 --- a/core/permuteModel.m +++ b/core/permuteModel.m @@ -2,111 +2,155 @@ % permuteModel % Changes the order of the reactions or metabolites in a model % +% Input: % model a model structure -% indexes a vector with the same length as the number of reactions in the -% model which gives the new order of reactions -% type 'rxns' for reactions and 'mets' for metabolites +% indexes a vector with the same length as the number of items in the +% model, which gives the new order of items +% type 'rxns' for reactions, 'mets' for metabolites, 'genes' for +% genes, 'comps' for compartments % -% newModel an updated model structure +% Output: +% newModel an updated model structure % % Usage: newModel=permuteModel(model, indexes, type) newModel=model; indexes=indexes(:); -if strcmp(type,'rxns') - if isfield(newModel,'rxns') - newModel.rxns=newModel.rxns(indexes); - end - if isfield(newModel,'lb') - newModel.lb=newModel.lb(indexes); - end - if isfield(newModel,'ub') - newModel.ub=newModel.ub(indexes); - end - if isfield(newModel,'rev') - newModel.rev=newModel.rev(indexes); - end - if isfield(newModel,'c') - newModel.c=newModel.c(indexes); - end - if isfield(newModel,'S') - newModel.S=newModel.S(:,indexes); - end - if isfield(newModel,'rxnNames') - newModel.rxnNames=newModel.rxnNames(indexes); - end - if isfield(newModel,'rxnGeneMat') - newModel.rxnGeneMat=newModel.rxnGeneMat(indexes,:); - end - if isfield(newModel,'grRules') - newModel.grRules=newModel.grRules(indexes); - end - if isfield(newModel,'subSystems') - newModel.subSystems=newModel.subSystems(indexes); - end - if isfield(newModel,'eccodes') - newModel.eccodes=newModel.eccodes(indexes); - end - if isfield(newModel,'equations') - newModel.equations=newModel.equations(indexes); - end - if isfield(newModel,'rxnMiriams') - newModel.rxnMiriams=newModel.rxnMiriams(indexes); - end - if isfield(newModel,'rxnComps') - newModel.rxnComps=newModel.rxnComps(indexes); - end - if isfield(newModel,'rxnFrom') - newModel.rxnFrom=newModel.rxnFrom(indexes); - end - if isfield(newModel,'rxnScores') - newModel.rxnScores=newModel.rxnScores(indexes); - end - if isfield(newModel,'rxnNotes') - newModel.rxnNotes=newModel.rxnNotes(indexes); - end - if isfield(newModel,'rxnReferences') - newModel.rxnReferences=newModel.rxnReferences(indexes); - end - if isfield(newModel,'rxnConfidenceScores') - newModel.rxnConfidenceScores=newModel.rxnConfidenceScores(indexes); - end -end - -if strcmp(type,'mets') - if isfield(newModel,'mets') - newModel.mets=newModel.mets(indexes); - end - if isfield(newModel,'metNames') - newModel.metNames=newModel.metNames(indexes); - end - if isfield(newModel,'b') - newModel.b=newModel.b(indexes,:); - end - if isfield(newModel,'metComps') - newModel.metComps=newModel.metComps(indexes); - end - if isfield(newModel,'S') - newModel.S=newModel.S(indexes,:); - end - if isfield(newModel,'unconstrained') - newModel.unconstrained=newModel.unconstrained(indexes); - end - if isfield(newModel,'metMiriams') - newModel.metMiriams=newModel.metMiriams(indexes,:); - end - if isfield(newModel,'inchis') - newModel.inchis=newModel.inchis(indexes); - end - if isfield(newModel,'metFormulas') - newModel.metFormulas=newModel.metFormulas(indexes); - end - if isfield(newModel,'metFrom') - newModel.metFrom=newModel.metFrom(indexes); - end - if isfield(newModel,'metCharges') - newModel.metCharges=newModel.metCharges(indexes); - end +switch type + case 'rxns' + if isfield(newModel,'rxns') + newModel.rxns=newModel.rxns(indexes); + end + if isfield(newModel,'lb') + newModel.lb=newModel.lb(indexes); + end + if isfield(newModel,'ub') + newModel.ub=newModel.ub(indexes); + end + if isfield(newModel,'rev') + newModel.rev=newModel.rev(indexes); + end + if isfield(newModel,'c') + newModel.c=newModel.c(indexes); + end + if isfield(newModel,'S') + newModel.S=newModel.S(:,indexes); + end + if isfield(newModel,'rxnNames') + newModel.rxnNames=newModel.rxnNames(indexes); + end + if isfield(newModel,'rxnGeneMat') + newModel.rxnGeneMat=newModel.rxnGeneMat(indexes,:); + end + if isfield(newModel,'grRules') + newModel.grRules=newModel.grRules(indexes); + end + if isfield(newModel,'subSystems') + newModel.subSystems=newModel.subSystems(indexes); + end + if isfield(newModel,'eccodes') + newModel.eccodes=newModel.eccodes(indexes); + end + if isfield(newModel,'equations') + newModel.equations=newModel.equations(indexes); + end + if isfield(newModel,'rxnMiriams') + newModel.rxnMiriams=newModel.rxnMiriams(indexes); + end + if isfield(newModel,'rxnComps') + newModel.rxnComps=newModel.rxnComps(indexes); + end + if isfield(newModel,'rxnFrom') + newModel.rxnFrom=newModel.rxnFrom(indexes); + end + if isfield(newModel,'rxnScores') + newModel.rxnScores=newModel.rxnScores(indexes); + end + if isfield(newModel,'rxnNotes') + newModel.rxnNotes=newModel.rxnNotes(indexes); + end + if isfield(newModel,'rxnReferences') + newModel.rxnReferences=newModel.rxnReferences(indexes); + end + if isfield(newModel,'rxnConfidenceScores') + newModel.rxnConfidenceScores=newModel.rxnConfidenceScores(indexes); + end + case 'mets' + if isfield(newModel,'mets') + newModel.mets=newModel.mets(indexes); + end + if isfield(newModel,'metNames') + newModel.metNames=newModel.metNames(indexes); + end + if isfield(newModel,'b') + newModel.b=newModel.b(indexes,:); + end + if isfield(newModel,'metComps') + newModel.metComps=newModel.metComps(indexes); + end + if isfield(newModel,'S') + newModel.S=newModel.S(indexes,:); + end + if isfield(newModel,'unconstrained') + newModel.unconstrained=newModel.unconstrained(indexes); + end + if isfield(newModel,'metMiriams') + newModel.metMiriams=newModel.metMiriams(indexes,:); + end + if isfield(newModel,'inchis') + newModel.inchis=newModel.inchis(indexes); + end + if isfield(newModel,'metFormulas') + newModel.metFormulas=newModel.metFormulas(indexes); + end + if isfield(newModel,'metFrom') + newModel.metFrom=newModel.metFrom(indexes); + end + if isfield(newModel,'metCharges') + newModel.metCharges=newModel.metCharges(indexes); + end + case 'genes' + if isfield(newModel,'genes') + newModel.genes=newModel.genes(indexes); + end + if isfield(newModel,'geneComps') + newModel.geneComps=newModel.geneComps(indexes); + end + if isfield(newModel,'geneMiriams') + newModel.geneMiriams=newModel.geneMiriams(indexes); + end + if isfield(newModel,'geneShortNames') + newModel.geneShortNames=newModel.geneShortNames(indexes); + end + if isfield(newModel,'rxnGeneMat') + newModel.rxnGeneMat=newModel.rxnGeneMat(:,indexes); + end + case 'comps' + if isfield(newModel,'comps') + newModel.comps=newModel.comps(indexes); + end + if isfield(newModel,'compNames') + newModel.compNames=newModel.compNames(indexes); + end + if isfield(newModel,'compOutside') + newModel.compOutside=newModel.compOutside(indexes); + end + if isfield(newModel,'compMiriams') + newModel.compMiriams=newModel.compMiriams(indexes); + end + [~,J]=sort(indexes); % The *index* of compartment is used in next fields + if isfield(newModel,'metComps') + [toreplace, bywhat] = ismember(newModel.metComps,1:length(J)); + newModel.metComps(toreplace) = J(bywhat(toreplace)); + end + if isfield(model,'rxnComps') + [toreplace, bywhat] = ismember(model.rxnComps,1:length(J)); + model.rxnComps(toreplace) = J(bywhat(toreplace)); + end + if isfield(model,'geneComps') + [toreplace, bywhat] = ismember(model.geneComps,1:length(J)); + model.geneComps(toreplace) = J(bywhat(toreplace)); + end end end diff --git a/doc/core/checkModelStruct.html b/doc/core/checkModelStruct.html index 90aa5bf6..92e3e48f 100644 --- a/doc/core/checkModelStruct.html +++ b/doc/core/checkModelStruct.html @@ -305,116 +305,128 @@