Skip to content

Commit dce9bbc

Browse files
authored
Merge pull request #501 from ICB-DCM/release_v0.8.2
Release v0.8.2 Features / improvements: * Speedup symbolic processing for ODE generation in python Bugfixes: * Fix(python) Add missing deepcopy (introduced in 6847ba6) * Fix(core) Prevent parameter scaling length mismatch (#488) * Fix(python) Set distutils dependency to current version to fix </usr/lib/python3.6/distutils/dist.py:261: UserWarning: Unknown distribution option: 'long_description_content_type'> * fix(python) add symlink to version.txt to be included in package Backwards-compatibility: * Replace 'newline' by literal to restore <R2016b compatibility (Fixes #493) Maintenance: * Remove obsolete swig library build via cmake and related file copying * Provide issue template for bug reports * Providing valid SBML document to import is not optional anymore * Update documentation and tests * Add python version check and raise required version to 3.6 to prevent cryptic error messages when encountering f-strings
2 parents c6a3345 + a4d703d commit dce9bbc

32 files changed

+225
-364
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: new
6+
assignees: ''
7+
8+
---
9+
10+
**What did you expect to happen?**
11+
12+
**What has happened instead?**
13+
14+
**To Reproduce**
15+
*Steps to reproduce the behavior*
16+
*Ideally include minimal code examples here*
17+
18+
**AMICI version and system environment**
19+
- OS and version: [e.g. Ubuntu, iOS, Windows]
20+
- AMICI interface: [e.g. Python, Matlab, C++]
21+
- AMICI version:
22+
- Additional information: Compiler name and version used, Python/Matlab version, ...
23+
24+
**How to fix**
25+
Do you know how to resolve the problem?
26+
Can you submit a pull request?

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ sudo: required
44
branches:
55
only:
66
- master
7-
- staging
7+
- develop
88

99
matrix:
1010
fast_finish: true

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@ endif()
172172

173173
# build interfaces for other languages
174174
option(ENABLE_SWIG "Build AMICI swig library?" ON)
175-
#if(ENABLE_SWIG)
176-
# add_subdirectory(swig)
177-
#endif()
175+
if(ENABLE_SWIG)
176+
add_subdirectory(swig)
177+
endif()
178178

179179
option(ENABLE_PYTHON "Create Python module?" ON)
180180
if(ENABLE_PYTHON)

CONTRIBUTING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ We are happy about contributions to AMICI in any form (new functionality, docume
77
When making code changes:
88

99
* Check if you agree to release your contribution under the conditions provided in `LICENSE`
10-
* Start a new branch from `master`
10+
* Start a new branch from `develop`
1111
* Implement your changes
12-
* Submit a pull request
12+
* Submit a pull request to the `develop` branch
1313
* Make sure your code is documented appropriately
1414
* Run `mtoc/makeDocumentation.m` to check completeness of your documentation
1515
* Make sure your code is compatible with C++11, `gcc` and `clang`
1616
* when adding new functionality, please also provide test cases (see `tests/cpputest/`)
1717
* Write meaningful commit messages
18-
* Run all tests to ensure nothing got broken
18+
* Run all tests to ensure nothing was broken
1919
* Run `tests/cpputest/wrapTestModels.m` followed by CI tests `scripts/buildAll.sh && scripts/run-cpputest.sh`
2020
* Run `tests/testModels.m`
21+
* Run `make python-tests` in `build`
2122
* When all tests are passing and you think your code is ready to merge, request a code review
2223

2324
## Adding/Updating tests

matlab/@amimodel/compileAndLinkModel.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ function updateFileHash(fileFolder,hashFolder,filename)
331331

332332
function versionstring = getCompilerVersionString()
333333
[~,systemreturn] = system([mex.getCompilerConfigurations('c++').Details.CompilerExecutable ' --version']);
334-
newlinePos = strfind(systemreturn,newline);
334+
newlinePos = strfind(systemreturn, sprintf('\n'));
335335
str = systemreturn(1:(newlinePos(1)-1));
336336
str = regexprep(str,'[\(\)]','');
337337
str = regexprep(str,'[\s\.\-]','_');

python/CMakeLists.txt

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,9 @@
11
find_package(PythonInterp 3.6 REQUIRED)
22

3-
# Configure setup.py and copy to output directory
4-
set(AMICI_VERSION "@AMICI_VERSION@") # to be replaced later
5-
configure_file(${PROJECT_SOURCE_DIR}/python/amici/setup.template.py ${CMAKE_CURRENT_BINARY_DIR}/setup.template.py)
6-
7-
# Copy further amici-python-module files to binary_dir
8-
add_custom_target(python-module-files
9-
DEPENDS always_rebuild
10-
COMMENT "Preparing python module directory"
11-
COMMAND ${CMAKE_COMMAND} -D SRC=${CMAKE_CURRENT_BINARY_DIR}/setup.py
12-
-D DST=${CMAKE_CURRENT_BINARY_DIR}/setup.py
13-
-P ${PROJECT_SOURCE_DIR}/cmake/configureVersion.cmake
14-
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/amici
15-
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/amici
16-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/python/amici ${CMAKE_CURRENT_BINARY_DIR}/amici
17-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/amici/src
18-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/amici/include
19-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/swig ${CMAKE_CURRENT_BINARY_DIR}/amici/swig
20-
COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/ThirdParty ${CMAKE_CURRENT_BINARY_DIR}/amici/ThirdParty
21-
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/setup.template.py ${CMAKE_CURRENT_BINARY_DIR}/amici/setup.template.py
22-
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_BINARY_DIR}/
23-
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
24-
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libamici.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
25-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/sundials/build/lib/libsundials_nvecserial.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
26-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/sundials/build/lib/libsundials_cvodes.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
27-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/sundials/build/lib/libsundials_idas.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
28-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/SuiteSparse/KLU/Lib/libklu.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
29-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/SuiteSparse/COLAMD/Lib/libcolamd.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
30-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/SuiteSparse/BTF/Lib/libbtf.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
31-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/SuiteSparse/AMD/Lib/libamd.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
32-
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/ThirdParty/SuiteSparse/SuiteSparse_config/libsuitesparseconfig.a" ${CMAKE_CURRENT_BINARY_DIR}/amici/libs/
33-
)
34-
353
add_custom_target(install-python
364
COMMENT "Installing AMICI base python package"
37-
DEPENDS python-module-files
38-
COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY_OUT} install --prefix= --user)
5+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/sdist
6+
COMMAND ${PYTHON_EXECUTABLE} setup.py install --prefix= --user)
397

408

419
# Create python wheel
@@ -44,19 +12,20 @@ add_custom_target(install-python
4412
# build_py stage
4513
add_custom_target(python-wheel
4614
COMMENT "Creating wheel for AMICI base python package"
47-
DEPENDS python-module-files
15+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/sdist
4816
COMMAND ${PYTHON_EXECUTABLE} setup.py build_ext
4917
COMMAND ${PYTHON_EXECUTABLE} setup.py bdist_wheel --dist-dir=${CMAKE_CURRENT_BINARY_DIR}
5018
)
5119

20+
5221
add_custom_target(python-sdist
5322
COMMENT "Creating sdist for AMICI base python package"
54-
COMMAND ${PYTHON_EXECUTABLE} setup.py sdist --dist-dir=${CMAKE_CURRENT_BINARY_DIR}
5523
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/sdist
24+
COMMAND ${PYTHON_EXECUTABLE} setup.py sdist --dist-dir=${CMAKE_CURRENT_BINARY_DIR}
5625
)
5726

27+
5828
add_custom_command(
5929
OUTPUT always_rebuild
6030
COMMAND cmake -E echo
6131
)
62-

python/amici/ode_export.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ def _computeEquation(self, name):
11091109
dx0_fixedParametersdx = \
11101110
self.eq('x0_fixedParameters').jacobian(self.sym('x'))
11111111

1112-
if not dx0_fixedParametersdx.is_zero:
1112+
if dx0_fixedParametersdx.is_zero is not True:
11131113
for ip in range(self._eqs[name].shape[1]):
11141114
self._eqs[name][:,ip] += \
11151115
dx0_fixedParametersdx \
@@ -1206,8 +1206,11 @@ def _derivative(self, eq, var, name=None):
12061206
else:
12071207
eq = self.eq(eq)
12081208

1209-
if min(eq.shape) and min(self.sym(var).shape):
1210-
self._eqs[name] = eq.jacobian(self.sym(var))
1209+
sym_var = self.sym(var)
1210+
1211+
if min(eq.shape) and min(sym_var.shape) \
1212+
and eq.is_zero is not True and sym_var.is_zero is not True:
1213+
self._eqs[name] = eq.jacobian(sym_var)
12111214
else:
12121215
self._eqs[name] = sp.zeros(eq.shape[0], self.sym(var).shape[0])
12131216

@@ -1264,10 +1267,14 @@ def _totalDerivative(self, name, eq, chainvar, var,
12641267
variables[var]['sym'] = self.eq(varname)
12651268

12661269
self._eqs[name] = \
1267-
variables['dydz']['sym'] \
1268-
+ variables['dydx']['sym'] * variables['dxdz']['sym']
1269-
1270+
copy.deepcopy(variables['dydz']['sym'])
12701271

1272+
# Save time for for large models if one multiplicand is zero,
1273+
# which is not checked for by sympy
1274+
if variables['dydx']['sym'].is_zero is not True \
1275+
and variables['dxdz']['sym'].is_zero is not True:
1276+
self._eqs[name] += variables['dydx']['sym'] * \
1277+
variables['dxdz']['sym']
12711278

12721279

12731280
def _multiplication(self, name, x, y,
@@ -1617,6 +1624,10 @@ def _writeFunctionFile(self, function):
16171624
if 'sparse' in self.functions[function] and \
16181625
self.functions[function]['sparse']:
16191626
symbol = self.model.sparseeq(function)
1627+
elif not self.allow_reinit_fixpar_initcond \
1628+
and function == 'sx0_fixedParameters':
1629+
# Not required. Will create empty function body.
1630+
symbol = sp.Matrix()
16201631
else:
16211632
symbol = self.model.eq(function)
16221633

python/amici/sbml_import.py

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
#!/usr/bin/env python3
12
""" @package amici.sbml_import The python sbml import module for python
23
"""
3-
#!/usr/bin/env python3
44

55
import sympy as sp
66
import libsbml as sbml
@@ -31,15 +31,15 @@ class SbmlImporter:
3131
3232
Attributes:
3333
34-
check_validity: indicates whether the validity of the SBML document
35-
should be checked @type bool
34+
show_sbml_warnings: indicates whether libSBML warnings should be
35+
displayed @type bool
3636
3737
symbols: dict carrying symbolic definitions @type dict
3838
3939
SBMLreader: the libSBML sbml reader [!not storing this will result
4040
in a segfault!]
4141
42-
sbml_doc: document carrying the sbml defintion [!not storing this
42+
sbml_doc: document carrying the sbml definition [!not storing this
4343
will result in a segfault!]
4444
4545
sbml: sbml definition [!not storing this will result in a segfault!]
@@ -62,25 +62,25 @@ class SbmlImporter:
6262
6363
compartmentSymbols: compartment ids @type sympy.Matrix
6464
65-
compartmentVolume: numeric/symbnolic compartment volumes @type
65+
compartmentVolume: numeric/symbolic compartment volumes @type
6666
sympy.Matrix
6767
68-
stoichiometricMatrix: stoichiometrix matrix of the model @type
68+
stoichiometricMatrix: stoichiometric matrix of the model @type
6969
sympy.Matrix
7070
7171
fluxVector: reaction kinetic laws @type sympy.Matrix
7272
7373
"""
7474

75-
def __init__(self, SBMLFile, check_validity=True):
75+
def __init__(self, SBMLFile, show_sbml_warnings=False):
7676
"""Create a new Model instance.
7777
7878
Arguments:
7979
8080
SBMLFile: Path to SBML file where the model is specified @type string
8181
82-
check_validity: Flag indicating whether the validity of the SBML
83-
document should be checked @type bool
82+
show_sbml_warnings: indicates whether libSBML warnings should be
83+
displayed @type bool
8484
8585
Returns:
8686
SbmlImporter instance with attached SBML document
@@ -89,7 +89,7 @@ def __init__(self, SBMLFile, check_validity=True):
8989
9090
"""
9191

92-
self.check_validity = check_validity
92+
self.show_sbml_warnings = show_sbml_warnings
9393

9494
self.loadSBMLFile(SBMLFile)
9595

@@ -123,10 +123,11 @@ def loadSBMLFile(self, SBMLFile):
123123

124124
self.SBMLreader = sbml.SBMLReader()
125125
self.sbml_doc = self.SBMLreader.readSBML(SBMLFile)
126-
self.checkLibSBMLErrors()
127-
# If any of the above calls produces an error, this will be added to
128-
# the SBMLError log in the sbml document. Thus, it is sufficient to
129-
# check the error log just once after all conversion/validation calls.
126+
127+
# Ensure we got a valid SBML model, otherwise further processing
128+
# might lead to undefined results
129+
self.sbml_doc.validateSBML()
130+
checkLibSBMLErrors(self.sbml_doc, self.show_sbml_warnings)
130131

131132
# apply several model simplifications that make our life substantially
132133
# easier
@@ -139,45 +140,13 @@ def loadSBMLFile(self, SBMLFile):
139140
getDefaultProperties()
140141
self.sbml_doc.convert(convertConfig)
141142

142-
if self.check_validity:
143-
self.sbml_doc.validateSBML()
144-
145143
# If any of the above calls produces an error, this will be added to
146144
# the SBMLError log in the sbml document. Thus, it is sufficient to
147145
# check the error log just once after all conversion/validation calls.
148-
self.checkLibSBMLErrors()
146+
checkLibSBMLErrors(self.sbml_doc, self.show_sbml_warnings)
149147

150148
self.sbml = self.sbml_doc.getModel()
151149

152-
def checkLibSBMLErrors(self):
153-
"""Checks the error log in the current self.sbml_doc
154-
155-
Arguments:
156-
157-
Returns:
158-
159-
Raises:
160-
raises SBMLException if errors with severity ERROR or FATAL have
161-
occured
162-
163-
"""
164-
num_warning = self.sbml_doc.getNumErrors(sbml.LIBSBML_SEV_WARNING)
165-
num_error = self.sbml_doc.getNumErrors(sbml.LIBSBML_SEV_ERROR)
166-
num_fatal = self.sbml_doc.getNumErrors(sbml.LIBSBML_SEV_FATAL)
167-
if num_warning + num_error + num_fatal:
168-
for iError in range(0, self.sbml_doc.getNumErrors()):
169-
error = self.sbml_doc.getError(iError)
170-
# we ignore any info messages for now
171-
if error.getSeverity() >= sbml.LIBSBML_SEV_WARNING:
172-
category = error.getCategoryAsString()
173-
severity = error.getSeverityAsString()
174-
error_message = error.getMessage()
175-
print(f'libSBML {severity} ({category}): {error_message}')
176-
if num_error + num_fatal:
177-
raise SBMLException(
178-
'SBML Document failed to load (see error messages above)'
179-
)
180-
181150
def sbml2amici(self,
182151
modelName,
183152
output_dir=None,
@@ -766,11 +735,13 @@ def processObservables(self, observables, sigmas):
766735

767736
if sigmas is None:
768737
sigmas = {}
769-
elif len(set(sigmas.keys()) - set(observables.keys())):
770-
# Ensure no non-existing observableIds have been specified (no problem here, but usually an upstream bug)
771-
raise ValueError(f'Sigma provided for an unknown observableId: {set(sigmas.keys()) - set(observables.keys())}')
772-
773-
738+
else:
739+
# Ensure no non-existing observableIds have been specified
740+
# (no problem here, but usually an upstream bug)
741+
unknown_observables = set(sigmas.keys()) - set(observables.keys())
742+
if unknown_observables:
743+
raise ValueError('Sigma provided for an unknown observableId: '
744+
+ str(unknown_observables))
774745

775746
speciesSyms = self.symbols['species']['identifier']
776747

@@ -1101,3 +1072,38 @@ def l2s(inputs):
11011072
11021073
"""
11031074
return [str(inp) for inp in inputs]
1075+
1076+
1077+
def checkLibSBMLErrors(sbml_doc, show_warnings=False):
1078+
"""Checks the error log in the current self.sbml_doc
1079+
1080+
Arguments:
1081+
sbml_doc: SBML document @type libsbml.SBMLDocument
1082+
show_warnings: display SBML warnings @type bool
1083+
1084+
Returns:
1085+
1086+
Raises:
1087+
raises SBMLException if errors with severity ERROR or FATAL have
1088+
occurred
1089+
"""
1090+
num_warning = sbml_doc.getNumErrors(sbml.LIBSBML_SEV_WARNING)
1091+
num_error = sbml_doc.getNumErrors(sbml.LIBSBML_SEV_ERROR)
1092+
num_fatal = sbml_doc.getNumErrors(sbml.LIBSBML_SEV_FATAL)
1093+
1094+
if num_warning + num_error + num_fatal:
1095+
for iError in range(0, sbml_doc.getNumErrors()):
1096+
error = sbml_doc.getError(iError)
1097+
# we ignore any info messages for now
1098+
if error.getSeverity() >= sbml.LIBSBML_SEV_ERROR \
1099+
or (show_warnings and
1100+
error.getSeverity() >= sbml.LIBSBML_SEV_WARNING):
1101+
category = error.getCategoryAsString()
1102+
severity = error.getSeverityAsString()
1103+
error_message = error.getMessage()
1104+
print(f'libSBML {severity} ({category}): {error_message}')
1105+
1106+
if num_error + num_fatal:
1107+
raise SBMLException(
1108+
'SBML Document failed to load (see error messages above)'
1109+
)

0 commit comments

Comments
 (0)