Skip to content

Commit 0527b5d

Browse files
authored
Merge pull request #651 from ICB-DCM/develop
0.10.3 Release
2 parents b7095b5 + 9c73221 commit 0527b5d

20 files changed

+602
-460
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ install:
9191
- if [[ "$CI_MODE" == "test" ]]; then ./scripts/buildSundials.sh; fi
9292
- if [[ "$CI_MODE" == "test" ]]; then ./scripts/buildCpputest.sh; fi
9393
- if [[ "$CI_MODE" == "test" ]]; then ./scripts/buildBNGL.sh; fi
94-
- if [[ "$CI_MODE" == "test" ]]; then export BNGPATH=$(pwd)/ThirdParty/BioNetGen-2.3.2; fi
9594
- if [[ "$CI_MODE" == "test" ]]; then ./scripts/buildAmici.sh; fi
9695
- if [[ "$CI_MODE" == "test" ]]; then ./scripts/installAmiciArchive.sh; fi
9796
- if [[ "$CI_MODE" == "test" ]]; then ./scripts/installAmiciSource.sh; fi

include/amici/amici.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ std::unique_ptr<ReturnData> runAmiciSimulation(Solver &solver, const ExpData *ed
3838
* @param solver Solver instance
3939
* @param edatas experimental data objects
4040
* @param model model specification object
41+
* @param failfast flag to allow early termination
4142
* @param num_threads number of threads for parallel execution
4243
* @return vector of pointers to return data objects
4344
*/
4445
std::vector<std::unique_ptr<ReturnData>> runAmiciSimulations(Solver const& solver,
4546
const std::vector<ExpData *> &edatas,
4647
Model const& model,
48+
const bool failfast,
4749
int num_threads);
4850

4951
} // namespace amici

include/amici/serialization.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ void serialize(Archive &ar, amici::Model &u, const unsigned int version) {
8787
ar &const_cast<int &>(u.nw);
8888
ar &const_cast<int &>(u.ndwdx);
8989
ar &const_cast<int &>(u.ndwdp);
90+
ar &const_cast<int &>(u.ndxdotdw);
9091
ar &const_cast<int &>(u.nnz);
9192
ar &const_cast<int &>(u.nJ);
9293
ar &const_cast<int &>(u.ubw);

include/amici/sundials_matrix_wrapper.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,12 @@ class SUNMatrixWrapper {
159159
void multiply(realtype *c, const realtype *b) const;
160160

161161
private:
162+
void update_ptrs();
163+
162164
SUNMatrix matrix = nullptr;
165+
realtype *data_ptr = nullptr;
166+
sunindextype *indexptrs_ptr = nullptr;
167+
sunindextype *indexvals_ptr = nullptr;
163168
};
164169

165170
} // namespace amici

python/amici/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,15 @@ def ExpData(*args):
153153
return amici.ExpData(*args)
154154

155155

156-
def runAmiciSimulations(model, solver, edata_list, num_threads=1):
156+
def runAmiciSimulations(model, solver, edata_list, failfast=True,
157+
num_threads=1):
157158
""" Convenience wrapper for loops of amici.runAmiciSimulation
158159
159160
Arguments:
160161
model: Model instance
161162
solver: Solver instance, must be generated from Model.getSolver()
162163
edata_list: list of ExpData instances
164+
failfast: returns as soon as an integration failure is encountered
163165
num_threads: number of threads to use
164166
(only used if compiled with openmp)
165167
@@ -174,5 +176,6 @@ def runAmiciSimulations(model, solver, edata_list, num_threads=1):
174176
rdata_ptr_list = amici.runAmiciSimulations(solver.get(),
175177
edata_ptr_vector,
176178
model.get(),
179+
failfast,
177180
num_threads)
178181
return [numpy.rdataToNumPyArrays(r) for r in rdata_ptr_list]

python/amici/gradient_check.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from . import (runAmiciSimulation, SensitivityOrder_none,
2+
SensitivityMethod_forward)
3+
import numpy as np
4+
import copy
5+
6+
7+
def check_finite_difference(x0, model, solver, edata, ip, fields,
8+
assert_fun, atol=1e-4, rtol=1e-4, epsilon=1e-3):
9+
old_sensitivity_order = solver.getSensitivityOrder()
10+
old_parameters = model.getParameters()
11+
old_plist = model.getParameterList()
12+
13+
# sensitivity
14+
p = copy.deepcopy(x0)
15+
plist = [ip]
16+
17+
model.setParameters(p)
18+
model.setParameterList(plist)
19+
rdata = runAmiciSimulation(model, solver, edata)
20+
21+
# finite difference
22+
solver.setSensitivityOrder(SensitivityOrder_none)
23+
24+
# forward:
25+
p = copy.deepcopy(x0)
26+
p[ip] += epsilon/2
27+
model.setParameters(p)
28+
rdataf = runAmiciSimulation(model, solver, edata)
29+
30+
# backward:
31+
p = copy.deepcopy(x0)
32+
p[ip] -= epsilon/2
33+
model.setParameters(p)
34+
rdatab = runAmiciSimulation(model, solver, edata)
35+
36+
for field in fields:
37+
sensi_raw = rdata[f's{field}']
38+
fd = (rdataf[field]-rdatab[field])/epsilon
39+
if len(sensi_raw.shape) == 1:
40+
sensi = sensi_raw[0]
41+
elif len(sensi_raw.shape) == 2:
42+
sensi = sensi_raw[:, 0]
43+
elif len(sensi_raw.shape) == 3:
44+
sensi = sensi_raw[:, 0, :]
45+
else:
46+
assert_fun(False) # not implemented
47+
48+
check_close(sensi, fd, assert_fun, atol, rtol, field, ip=ip)
49+
50+
solver.setSensitivityOrder(old_sensitivity_order)
51+
model.setParameters(old_parameters)
52+
model.setParameterList(old_plist)
53+
54+
55+
def check_derivatives(model, solver, edata, assert_fun,
56+
atol=1e-4, rtol=1e-4, epsilon=1e-3):
57+
"""Finite differences check for likelihood gradient
58+
59+
Arguments:
60+
model: amici model
61+
solver: amici solver
62+
edata: exp data
63+
atol: absolute tolerance
64+
rtol: relative tolerance
65+
epsilon: finite difference step-size
66+
"""
67+
from scipy.optimize import check_grad
68+
69+
p = np.array(model.getParameters())
70+
71+
rdata = runAmiciSimulation(model, solver, edata)
72+
73+
fields = ['llh']
74+
75+
leastsquares_applicable = \
76+
solver.getSensitivityMethod() == SensitivityMethod_forward
77+
78+
if 'ssigmay' in rdata.keys():
79+
if rdata['ssigmay'] is not None:
80+
if rdata['ssigmay'].any():
81+
leastsquares_applicable = False
82+
83+
if leastsquares_applicable:
84+
fields += ['res', 'x', 'y']
85+
86+
check_results(rdata, 'FIM',
87+
np.dot(rdata['sres'].transpose(), rdata['sres']),
88+
assert_fun,
89+
1e-8, 1e-4)
90+
check_results(rdata, 'sllh',
91+
-np.dot(rdata['res'].transpose(), rdata['sres']),
92+
assert_fun,
93+
1e-8, 1e-4)
94+
for ip in range(len(p)):
95+
check_finite_difference(p, model, solver, edata, ip, fields,
96+
assert_fun, atol=atol, rtol=rtol,
97+
epsilon=epsilon)
98+
99+
100+
def check_close(result, expected, assert_fun, atol, rtol, field, ip=None):
101+
close = np.isclose(result, expected, atol=atol, rtol=rtol, equal_nan=True)
102+
103+
if not close.all():
104+
if ip is None:
105+
index_str = ''
106+
check_type = 'Regression check '
107+
else:
108+
index_str = f'at index ip={ip} '
109+
check_type = 'FD check '
110+
print(f'{check_type} failed for {field} {index_str}for '
111+
f'{close.sum()} indices:')
112+
adev = np.abs(result - expected)
113+
rdev = np.abs((result - expected)/(expected + atol))
114+
print(f'max(adev): {adev.max()}, max(rdev): {rdev.max()}')
115+
116+
assert_fun(close.all())
117+
118+
119+
def check_results(rdata, field, expected, assert_fun, atol, rtol):
120+
"""
121+
checks whether rdata[field] agrees with expected according to provided
122+
tolerances
123+
124+
Arguments:
125+
rdata: simulation results as returned by amici.runAmiciSimulation
126+
field: name of the field to check
127+
expected: expected test results
128+
atol: absolute tolerance
129+
rtol: relative tolerance
130+
"""
131+
132+
result = rdata[field]
133+
if type(result) is float:
134+
result = np.array(result)
135+
136+
check_close(result, expected, assert_fun, atol, rtol, field)
137+

python/amici/ode_export.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,9 @@ class ODEModel:
691691
_syms: carries symbolic identifiers of the symbolic variables of the
692692
model @type dict
693693
694+
_strippedsyms: carries symbolic identifiers that were stripped of
695+
additional class information @type dict
696+
694697
_sparsesyms: carries linear list of all symbolic identifiers for
695698
sparsified variables @type dict
696699
@@ -759,6 +762,7 @@ def __init__(self):
759762
self._vals = dict()
760763
self._names = dict()
761764
self._syms = dict()
765+
self._strippedsyms = dict()
762766
self._sparsesyms = dict()
763767
self._colptrs = dict()
764768
self._rowvals = dict()
@@ -913,7 +917,7 @@ def add_conservation_law(self, state, total_abundance, state_expr,
913917
state_id = self._states[ix].get_id()
914918

915919
self.add_component(
916-
Expression(state_id, f'cl_{state_id}', state_expr)
920+
Expression(state_id, str(state_id), state_expr)
917921
)
918922

919923
self.add_component(
@@ -1004,22 +1008,31 @@ def np(self):
10041008
"""
10051009
return len(self.sym('p'))
10061010

1007-
def sym(self, name):
1011+
def sym(self, name, stripped=False):
10081012
"""Returns (and constructs if necessary) the identifiers for a symbolic
10091013
entity.
10101014
10111015
Arguments:
10121016
name: name of the symbolic variable @type str
1017+
stripped: (optional) should additional class information be
1018+
stripped from the symbolic variables? @type bool
10131019
10141020
Returns:
10151021
containing the symbolic identifiers @type symengine.DenseMatrix
10161022
10171023
Raises:
1018-
1024+
ValueError if stripped symbols not available
10191025
"""
10201026
if name not in self._syms:
10211027
self._generateSymbol(name)
1022-
return self._syms[name]
1028+
1029+
if stripped:
1030+
if name not in self._variable_prototype:
1031+
raise ValueError('Stripped symbols only available for '
1032+
'variables from variable prototypes')
1033+
return self._strippedsyms[name]
1034+
else:
1035+
return self._syms[name]
10231036

10241037
def sparsesym(self, name):
10251038
"""Returns (and constructs if necessary) the sparsified identifiers for
@@ -1166,6 +1179,10 @@ def _generateSymbol(self, name):
11661179
comp.get_id()
11671180
for comp in getattr(self, component)
11681181
])
1182+
self._strippedsyms[name] = sp.Matrix([
1183+
sp.Symbol(comp.get_name())
1184+
for comp in getattr(self, component)
1185+
])
11691186
if name == 'y':
11701187
self._syms['my'] = sp.Matrix([
11711188
sp.Symbol(f'm{strip_pysb(comp.get_id())}')
@@ -1486,13 +1503,26 @@ def _derivative(self, eq, var, name=None):
14861503
self._lock_total_derivative = False
14871504
return
14881505

1506+
# this is the basic requirement check
1507+
needs_stripped_symbols = eq == 'xdot' and var != 'x'
1508+
14891509
# partial derivative
14901510
if eq == 'Jy':
14911511
eq = self.eq(eq).transpose()
14921512
else:
14931513
eq = self.eq(eq)
14941514

1495-
sym_var = self.sym(var)
1515+
if pysb is not None and needs_stripped_symbols:
1516+
needs_stripped_symbols = not any(
1517+
isinstance(sym, pysb.Component)
1518+
for sym in eq.free_symbols
1519+
)
1520+
1521+
# now check whether we are working with energy_modeling branch
1522+
# where pysb class info is already stripped
1523+
# TODO: fixme as soon as energy_modeling made it to the main pysb
1524+
# branch
1525+
sym_var = self.sym(var, needs_stripped_symbols)
14961526

14971527
if min(eq.shape) and min(sym_var.shape) \
14981528
and eq.is_zero is not True and sym_var.is_zero is not True:

python/amici/pysb_import.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,6 @@ def _greedy_target_index_update(cl_prototypes):
793793
# with the highest diff_fillin (note that the target index counts
794794
# are recomputed on the fly)
795795

796-
797796
if prototype['diff_fillin'] > -1 \
798797
and (
799798
get_target_indices(cl_prototypes).count(

python/sdist/MANIFEST.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ include amici/*
66
include amici/src/*template*
77
include amici/swig/CMakeLists_model.txt
88
include setup_clibs.py
9-
include version.txt
9+
include custom_commands.py
10+
include version.txt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../amici/gradient_check.py

0 commit comments

Comments
 (0)