From e75f30017de960ef534576179d87994ee7dd2866 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 28 Jul 2025 22:43:20 +0200 Subject: [PATCH 1/2] Drop MATLAB-import Drop MATLAB model import code, MATLAB interface, MATLAB examples, MATLAB interface, documentation, mtocpp, ... Related to #2727. --- .github/workflows/test_matlab.yml | 28 - CMakeLists.txt | 29 - LICENSE.md | 6 - {matlab/mtoc/config => doc}/Doxyfile.template | 15 +- doc/MATLAB.rst | 11 - doc/MATLAB_.md | 304 ---- doc/availability.rst | 1 - doc/conf.py | 96 -- doc/cpp_installation.rst | 5 +- doc/cpp_interface.rst | 10 +- doc/index.rst | 1 - doc/matlab_faq.rst | 55 - doc/matlab_installation.rst | 30 - doc/matlab_interface.rst | 510 ------ doc/rtd_requirements.txt | 1 - include/amici/returndata_matlab.h | 145 -- matlab/@amidata/amidata.m | 262 --- matlab/@amievent/amievent.m | 78 - matlab/@amievent/setHflag.m | 24 - matlab/@amifun/amifun.m | 74 - matlab/@amifun/gccode.m | 161 -- matlab/@amifun/getArgs.m | 122 -- matlab/@amifun/getCVar.m | 19 - matlab/@amifun/getDeps.m | 242 --- matlab/@amifun/getNVecs.m | 26 - matlab/@amifun/getSensiFlag.m | 74 - matlab/@amifun/getSyms.m | 824 --------- matlab/@amifun/writeCcode.m | 97 -- matlab/@amifun/writeCcode_sensi.m | 91 - matlab/@amifun/writeMcode.m | 27 - matlab/@amimodel/amimodel.m | 272 --- matlab/@amimodel/augmento2.m | 160 -- matlab/@amimodel/augmento2vec.m | 134 -- matlab/@amimodel/checkDeps.m | 50 - matlab/@amimodel/compileAndLinkModel.m | 384 ----- matlab/@amimodel/compileC.m | 8 - matlab/@amimodel/generateC.m | 322 ---- matlab/@amimodel/generateM.m | 25 - matlab/@amimodel/generateMatlabWrapper.m | 464 ----- matlab/@amimodel/generateRebuildM.m | 25 - matlab/@amimodel/getFun.m | 46 - matlab/@amimodel/loadOldHashes.m | 67 - matlab/@amimodel/makeEvents.m | 288 ---- matlab/@amimodel/makeSyms.m | 143 -- matlab/@amimodel/parseModel.m | 264 --- matlab/@amioption/amioption.m | 266 --- matlab/@amised/amised.m | 124 -- matlab/@optsym/optsym.m | 35 - matlab/AMICI2D2D.m | 39 - matlab/SBML2AMICI.m | 29 - matlab/SBMLimporter/.gitrepo | 11 - matlab/SBMLimporter/@SBMLode/SBMLode.m | 75 - matlab/SBMLimporter/@SBMLode/checkODE.m | 16 - matlab/SBMLimporter/@SBMLode/importSBML.m | 773 --------- matlab/SBMLimporter/@SBMLode/writeAMICI.m | 102 -- matlab/SBMLimporter/LICENSE | 29 - matlab/SBMLimporter/README.md | 2 - matlab/SBMLimporter/computeBracketLevel.m | 28 - matlab/amiwrap.m | 201 --- matlab/auxiliary/CalcMD5/CalcMD5.c | 650 ------- matlab/auxiliary/CalcMD5/CalcMD5.m | 85 - matlab/auxiliary/CalcMD5/TestCalcMD5.m | 191 --- matlab/auxiliary/CalcMD5/license.txt | 24 - matlab/auxiliary/am_setdefault.m | 15 - matlab/auxiliary/ami_mfun.m | 225 --- matlab/auxiliary/betterSym.m | 8 - matlab/auxiliary/compileAMICIDependencies.m | 362 ---- matlab/auxiliary/getCommitHash.m | 40 - matlab/auxiliary/struct2xml/license.txt | 24 - matlab/auxiliary/struct2xml/struct2xml.m | 198 --- matlab/auxiliary/structToHDF5Attribute.m | 42 - matlab/auxiliary/template.m | 44 - matlab/auxiliary/xml2struct/license.txt | 24 - matlab/auxiliary/xml2struct/xml2struct.m | 183 -- matlab/examples/amiExamples.m | 17 - .../example_adjoint/example_adjoint.m | 130 -- .../example_adjoint/model_adjoint_syms.m | 51 - .../example_adjoint_hessian.m | 94 - .../model_adjoint_hessian_syms.m | 51 - .../example_calvetti/example_calvetti.m | 101 -- .../example_calvetti/model_calvetti_syms.m | 74 - matlab/examples/example_dirac/example_dirac.m | 179 -- .../examples/example_dirac/model_dirac_syms.m | 52 - .../example_dirac_adjoint.m | 85 - .../example_model_5_paper.m | 243 --- .../model_dirac_adjoint_syms.m | 54 - .../example_dirac_adjoint_hessVecProd.m | 58 - .../model_dirac_adjoint_hessVecProd_syms.m | 54 - .../example_dirac_secondorder.m | 94 - .../model_dirac_secondorder_syms.m | 52 - .../example_dirac_secondorder_vectmult.m | 113 -- .../model_dirac_secondorder_vectmult_syms.m | 52 - .../examples/example_events/example_events.m | 195 --- .../example_events/model_events_syms.m | 82 - .../example_jakstat_adjoint.m | 117 -- .../model_jakstat_adjoint_syms.m | 69 - .../pnas_data_original.xls | Bin 21504 -> 0 bytes .../example_jakstat_adjoint_hvp.m | 108 -- .../model_jakstat_adjoint_hvp_syms.m | 69 - .../pnas_data_original.xls | Bin 21504 -> 0 bytes .../example_nested_events.m | 129 -- .../model_nested_events_syms.m | 64 - .../examples/example_neuron/example_neuron.m | 304 ---- .../example_neuron/model_neuron_syms.m | 40 - .../example_robertson/example_robertson.m | 131 -- .../example_robertson/model_robertson_syms.m | 68 - .../example_steadystate/example_steadystate.m | 236 --- .../model_steadystate_syms.m | 66 - matlab/installAMICI.m | 6 - matlab/mtoc/MatlabDocMaker.m | 682 -------- matlab/mtoc/config/customdoxygen.css | 1524 ----------------- matlab/mtoc/config/latexextras.template | 15 - matlab/mtoc/config/mtocpp | Bin 1446116 -> 0 bytes matlab/mtoc/config/mtocpp.conf | 107 -- matlab/mtoc/config/mtocpp_post | Bin 90916 -> 0 bytes matlab/mtoc/makeDocumentation.m | 5 - matlab/symbolic/am_and.m | 11 - matlab/symbolic/am_eq.m | 10 - matlab/symbolic/am_ge.m | 17 - matlab/symbolic/am_gt.m | 17 - matlab/symbolic/am_if.m | 24 - matlab/symbolic/am_le.m | 17 - matlab/symbolic/am_lt.m | 17 - matlab/symbolic/am_max.m | 11 - matlab/symbolic/am_min.m | 11 - matlab/symbolic/am_or.m | 11 - matlab/symbolic/am_piecewise.m | 12 - matlab/symbolic/am_spline.m | 14 - matlab/symbolic/am_spline_pos.m | 33 - matlab/symbolic/am_stepfun.m | 14 - matlab/symbolic/am_xor.m | 12 - matlab/tests/SBMLTest.m | 109 -- matlab/tests/testModels.m | 214 --- scripts/downloadAndBuildMtocpp.sh | 73 - scripts/run-doxygen.sh | 24 +- src/returndata_matlab.cpp | 471 ----- 136 files changed, 16 insertions(+), 16542 deletions(-) delete mode 100644 .github/workflows/test_matlab.yml rename {matlab/mtoc/config => doc}/Doxyfile.template (99%) delete mode 100644 doc/MATLAB.rst delete mode 100644 doc/MATLAB_.md delete mode 100644 doc/matlab_faq.rst delete mode 100644 doc/matlab_installation.rst delete mode 100644 doc/matlab_interface.rst delete mode 100644 include/amici/returndata_matlab.h delete mode 100644 matlab/@amidata/amidata.m delete mode 100644 matlab/@amievent/amievent.m delete mode 100644 matlab/@amievent/setHflag.m delete mode 100644 matlab/@amifun/amifun.m delete mode 100644 matlab/@amifun/gccode.m delete mode 100644 matlab/@amifun/getArgs.m delete mode 100644 matlab/@amifun/getCVar.m delete mode 100644 matlab/@amifun/getDeps.m delete mode 100644 matlab/@amifun/getNVecs.m delete mode 100644 matlab/@amifun/getSensiFlag.m delete mode 100644 matlab/@amifun/getSyms.m delete mode 100644 matlab/@amifun/writeCcode.m delete mode 100644 matlab/@amifun/writeCcode_sensi.m delete mode 100644 matlab/@amifun/writeMcode.m delete mode 100644 matlab/@amimodel/amimodel.m delete mode 100644 matlab/@amimodel/augmento2.m delete mode 100644 matlab/@amimodel/augmento2vec.m delete mode 100644 matlab/@amimodel/checkDeps.m delete mode 100644 matlab/@amimodel/compileAndLinkModel.m delete mode 100644 matlab/@amimodel/compileC.m delete mode 100644 matlab/@amimodel/generateC.m delete mode 100644 matlab/@amimodel/generateM.m delete mode 100644 matlab/@amimodel/generateMatlabWrapper.m delete mode 100644 matlab/@amimodel/generateRebuildM.m delete mode 100644 matlab/@amimodel/getFun.m delete mode 100644 matlab/@amimodel/loadOldHashes.m delete mode 100644 matlab/@amimodel/makeEvents.m delete mode 100644 matlab/@amimodel/makeSyms.m delete mode 100644 matlab/@amimodel/parseModel.m delete mode 100644 matlab/@amioption/amioption.m delete mode 100644 matlab/@amised/amised.m delete mode 100644 matlab/@optsym/optsym.m delete mode 100644 matlab/AMICI2D2D.m delete mode 100644 matlab/SBML2AMICI.m delete mode 100644 matlab/SBMLimporter/.gitrepo delete mode 100644 matlab/SBMLimporter/@SBMLode/SBMLode.m delete mode 100644 matlab/SBMLimporter/@SBMLode/checkODE.m delete mode 100644 matlab/SBMLimporter/@SBMLode/importSBML.m delete mode 100644 matlab/SBMLimporter/@SBMLode/writeAMICI.m delete mode 100644 matlab/SBMLimporter/LICENSE delete mode 100644 matlab/SBMLimporter/README.md delete mode 100644 matlab/SBMLimporter/computeBracketLevel.m delete mode 100644 matlab/amiwrap.m delete mode 100644 matlab/auxiliary/CalcMD5/CalcMD5.c delete mode 100644 matlab/auxiliary/CalcMD5/CalcMD5.m delete mode 100644 matlab/auxiliary/CalcMD5/TestCalcMD5.m delete mode 100644 matlab/auxiliary/CalcMD5/license.txt delete mode 100755 matlab/auxiliary/am_setdefault.m delete mode 100644 matlab/auxiliary/ami_mfun.m delete mode 100644 matlab/auxiliary/betterSym.m delete mode 100644 matlab/auxiliary/compileAMICIDependencies.m delete mode 100644 matlab/auxiliary/getCommitHash.m delete mode 100644 matlab/auxiliary/struct2xml/license.txt delete mode 100644 matlab/auxiliary/struct2xml/struct2xml.m delete mode 100644 matlab/auxiliary/structToHDF5Attribute.m delete mode 100644 matlab/auxiliary/template.m delete mode 100644 matlab/auxiliary/xml2struct/license.txt delete mode 100644 matlab/auxiliary/xml2struct/xml2struct.m delete mode 100644 matlab/examples/amiExamples.m delete mode 100644 matlab/examples/example_adjoint/example_adjoint.m delete mode 100644 matlab/examples/example_adjoint/model_adjoint_syms.m delete mode 100644 matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m delete mode 100644 matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m delete mode 100755 matlab/examples/example_calvetti/example_calvetti.m delete mode 100755 matlab/examples/example_calvetti/model_calvetti_syms.m delete mode 100644 matlab/examples/example_dirac/example_dirac.m delete mode 100644 matlab/examples/example_dirac/model_dirac_syms.m delete mode 100644 matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m delete mode 100644 matlab/examples/example_dirac_adjoint/example_model_5_paper.m delete mode 100644 matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m delete mode 100644 matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m delete mode 100644 matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m delete mode 100644 matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m delete mode 100644 matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m delete mode 100644 matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m delete mode 100644 matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m delete mode 100644 matlab/examples/example_events/example_events.m delete mode 100644 matlab/examples/example_events/model_events_syms.m delete mode 100644 matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m delete mode 100644 matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m delete mode 100644 matlab/examples/example_jakstat_adjoint/pnas_data_original.xls delete mode 100644 matlab/examples/example_jakstat_adjoint_hvp/example_jakstat_adjoint_hvp.m delete mode 100644 matlab/examples/example_jakstat_adjoint_hvp/model_jakstat_adjoint_hvp_syms.m delete mode 100644 matlab/examples/example_jakstat_adjoint_hvp/pnas_data_original.xls delete mode 100644 matlab/examples/example_nested_events/example_nested_events.m delete mode 100644 matlab/examples/example_nested_events/model_nested_events_syms.m delete mode 100644 matlab/examples/example_neuron/example_neuron.m delete mode 100644 matlab/examples/example_neuron/model_neuron_syms.m delete mode 100644 matlab/examples/example_robertson/example_robertson.m delete mode 100644 matlab/examples/example_robertson/model_robertson_syms.m delete mode 100644 matlab/examples/example_steadystate/example_steadystate.m delete mode 100644 matlab/examples/example_steadystate/model_steadystate_syms.m delete mode 100644 matlab/installAMICI.m delete mode 100644 matlab/mtoc/MatlabDocMaker.m delete mode 100644 matlab/mtoc/config/customdoxygen.css delete mode 100644 matlab/mtoc/config/latexextras.template delete mode 100755 matlab/mtoc/config/mtocpp delete mode 100644 matlab/mtoc/config/mtocpp.conf delete mode 100755 matlab/mtoc/config/mtocpp_post delete mode 100644 matlab/mtoc/makeDocumentation.m delete mode 100644 matlab/symbolic/am_and.m delete mode 100644 matlab/symbolic/am_eq.m delete mode 100644 matlab/symbolic/am_ge.m delete mode 100644 matlab/symbolic/am_gt.m delete mode 100644 matlab/symbolic/am_if.m delete mode 100644 matlab/symbolic/am_le.m delete mode 100644 matlab/symbolic/am_lt.m delete mode 100644 matlab/symbolic/am_max.m delete mode 100644 matlab/symbolic/am_min.m delete mode 100644 matlab/symbolic/am_or.m delete mode 100644 matlab/symbolic/am_piecewise.m delete mode 100644 matlab/symbolic/am_spline.m delete mode 100644 matlab/symbolic/am_spline_pos.m delete mode 100644 matlab/symbolic/am_stepfun.m delete mode 100644 matlab/symbolic/am_xor.m delete mode 100644 matlab/tests/SBMLTest.m delete mode 100644 matlab/tests/testModels.m delete mode 100755 scripts/downloadAndBuildMtocpp.sh delete mode 100644 src/returndata_matlab.cpp diff --git a/.github/workflows/test_matlab.yml b/.github/workflows/test_matlab.yml deleted file mode 100644 index 252113d57a..0000000000 --- a/.github/workflows/test_matlab.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Matlab -on: - push: - merge_group: - workflow_dispatch: - pull_request: - branches: - - main - - -jobs: - matlab: - name: Matlab - - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v4 - - run: git fetch --prune --unshallow - - - run: echo "AMICI_DIR=$(pwd)" >> $GITHUB_ENV - - - name: Install MATLAB - uses: matlab-actions/setup-matlab@v2 - - name: Run script - uses: matlab-actions/run-command@v2 - with: - command: cd matlab; installAMICI; addpath tests; testModels diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c4018ed5b..63a775c708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,35 +308,6 @@ if(AMICI_PYTHON_BUILD_EXT_ONLY) endif() # Create targets to make the sources show up in IDEs for convenience - -# For matlab interface -if(NOT AMICI_PYTHON_BUILD_EXT_ONLY) - - set(MATLAB_SOURCES - src/interface_matlab.cpp src/returndata_matlab.cpp - include/amici/interface_matlab.h include/amici/returndata_matlab.h) - find_package(Matlab) - # In case we can find Matlab, we create a respective library to compile the - # extension from cmake. Otherwise we just create a dummy target for the files - # to show up inside IDEs. (Set the Matlab_ROOT_DIR cmake variable if CMake - # cannot find your Matlab installation) - if(${Matlab_FOUND}) - add_library(matlabInterface ${MATLAB_SOURCES}) - set_target_properties(matlabInterface PROPERTIES INCLUDE_DIRECTORIES - "${Matlab_INCLUDE_DIRS}") - target_link_libraries(matlabInterface PUBLIC amici) - else() - add_custom_target( - matlabInterface - SOURCES ${MATLAB_SOURCES} - COMMENT "Dummy target for MATLAB interface files") - endif() - set_property( - TARGET matlabInterface - APPEND - PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include/") -endif() - # For template files add_custom_target( fileTemplates diff --git a/LICENSE.md b/LICENSE.md index a086d45955..ec918a1fd8 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -32,9 +32,3 @@ The AMICI logo is released under the Creative Commons CC0 1.0 Universal terms given in `ThirdParty/SuiteSparse/LICENSE.txt` * *gsl-lite* is redistributed under the MIT License (MIT) with the terms given in `ThirdParty/gsl/gsl/gsl-lite.hpp` -* *xml2struct* and *struct2xml* are redistributed under the BSD 2-Clause - License (BSD-2-Clause) with terms given in - `matlab/auxiliary/xml2struct/license.txt` and - `matlab/auxiliary/struct2xml/license.txt` -* *CalcMD5* is redistributed under the BSD 2-Clause License (BSD-2-Clause) - with terms given in `matlab/auxiliary/CalcMD5/license.txt` diff --git a/matlab/mtoc/config/Doxyfile.template b/doc/Doxyfile.template similarity index 99% rename from matlab/mtoc/config/Doxyfile.template rename to doc/Doxyfile.template index b59cff3cdf..850e70c630 100644 --- a/matlab/mtoc/config/Doxyfile.template +++ b/doc/Doxyfile.template @@ -947,10 +947,8 @@ WARN_LOGFILE = INPUT = "_SourceDir_/README.md" \ "_SourceDir_/LICENSE.md" \ - "_SourceDir_/doc/MATLAB_.md" \ "_SourceDir_/doc/CPP_.md" \ - "_SourceDir_/include" \ - "_SourceDir_/matlab" + "_SourceDir_/include" # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1040,11 +1038,6 @@ EXCLUDE_PATTERNS = "_SourceDir_/models/*" \ "_SourceDir_/tests/*" \ "_SourceDir_/scripts/*" \ "_SourceDir_/ThirdParty/*" \ - "_SourceDir_/matlab/examples/*" \ - "_SourceDir_/matlab/mtoc/*" \ - "_SourceDir_/matlab/auxiliary/*" \ - "_SourceDir_/matlab/SBMLimporter/*" \ - "_SourceDir_/matlab/tests/*" \ "_SourceDir_/python/tests/*" \ "_SourceDir_/python/sdist/*" \ "_SourceDir_/python/examples/example_*/model_*" \ @@ -1127,7 +1120,7 @@ INPUT_FILTER = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. -FILTER_PATTERNS = "*.m=_MTOCFILTER_" +# FILTER_PATTERNS = "*.m=_MTOCFILTER_" # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for @@ -1330,7 +1323,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = _ConfDir_/customdoxygen.css +# HTML_STYLESHEET = _ConfDir_/customdoxygen.css # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1971,7 +1964,7 @@ PAPER_TYPE = a4 # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = "_LatexExtras_" +# EXTRA_PACKAGES = "_LatexExtras_" # The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for # the generated LaTeX document. The header should contain everything until the diff --git a/doc/MATLAB.rst b/doc/MATLAB.rst deleted file mode 100644 index f5eccdcd43..0000000000 --- a/doc/MATLAB.rst +++ /dev/null @@ -1,11 +0,0 @@ -Matlab interface -================ - -.. toctree:: - :maxdepth: 2 - :caption: MATLAB - - Installation - Usage - FAQ - API reference <_exhale_matlab_api/library_root> diff --git a/doc/MATLAB_.md b/doc/MATLAB_.md deleted file mode 100644 index 7573b30930..0000000000 --- a/doc/MATLAB_.md +++ /dev/null @@ -1,304 +0,0 @@ -# MATLAB Interface {#matlab_interface} - -In the following we will give a detailed overview how to specify models in MATLAB and how to call the generated simulation files. - -## Model Definition - -This guide will guide the user on how to specify models in MATLAB. For example implementations see the examples in the matlab/examples directory. - -### Header - -The model definition needs to be defined as a function which returns a struct with all symbolic definitions and options. - - function [model] = example_model_syms() - -### Options - -Set the options by specifying the respective field of the modelstruct - - model.(fieldname) = value - -The options specify default options for simulation, parametrisation and compilation. All of these options are optional. - -| field | description | default -|--------------|-----------------------------------------------|--------- -| .param | default parametrisation 'log'/'log10'/'lin' | 'lin' -| .debug | flag to compile with debug symbols | false -| .forward | flag to activate forward sensitivities | true -| .adjoint | flag to activate adjoint sensitivities | true - -When set to false, the fields 'forward' and 'adjoint' will speed up the time required to compile the model but also disable the respective sensitivity computation. - -### States - -Create the respective symbolic variables. The name of the symbolic variable can be chosen arbitrarily. - - syms state1 state2 state3 - -Create the state vector containing all states: - - model.sym.x = [ state1 state2 state3 ]; - -### Parameters - -Create the respective symbolic variables. The name of the symbolic variable can be chosen arbitrarily. -Sensitivities __will be derived__ for all paramaters. - - syms param1 param2 param3 param4 param5 param6 - -Create the parameters vector - - model.sym.p = [ param1 param2 param3 param4 param5 param6 ]; - -### Constants - -Create the respective symbolic variables. The name of the symbolic variable can be chosen arbitrarily. -Sensitivities with respect to constants __will not be derived__. - - syms const1 const2 - -Create the parameters vector - - model.sym.k = [ const1 const2 ]; - -### Differential Equation - -For time-dependent differential equations you can specify a symbolic variable for time. This __needs__ to be denoted by `t`. - - syms t - -Specify the right hand side of the differential equation `f` or `xdot` - - model.sym.xdot(1) = [ const1 - param1*state1 ]; - model.sym.xdot(2) = [ +param2*state1 + dirac(t-param3) - const2*state2 ]; - model.sym.xdot(3) = [ param4*state2 ]; - -or - - model.sym.f(1) = [ const1 - param1*state1 ]; - model.sym.f(2) = [ +param2*state1 + dirac(t-param3) - const2*state2 ]; - model.sym.f(3) = [ param4*state2 ]; - -The specification of `f` or `xdot` may depend on states, parameters and constants. - -For DAEs also specify the mass matrix. - - model.sym.M = [1, 0, 0;... - 0, 1, 0;... - 0, 0, 0]; - -The specification of M may depend on parameters and constants. - -For ODEs the integrator will solve the equation \f$ \dot{x} = f \f$ and for DAEs the equations \f$ M \cdot \dot{x} = f \f$. -AMICI will decide whether to use CVODES (for ODEs) or IDAS (for DAEs) based on whether the mass matrix is defined or not. - -In the definition of the differential equation you can use certain symbolic functions. For a full list of available functions see `src/symbolic_functions.cpp`. - -Dirac functions can be used to cause a jump in the respective states at the specified time-point. This is typically used to model injections, or other external stimuli. Spline functions can be used to model time/state dependent response with unkown time/state dependence. - -### Initial Conditions - -Specify the initial conditions. These may depend on parameters on constants and must have the same size as `x`. - - model.sym.x0 = [ param4, 0, 0 ]; - -### Observables - -Specify the observables. These may depend on parameters and constants. - - model.sym.y(1) = state1 + state2; - model.sym.y(2) = state3 - state2; - -In the definition of the observable you can use certain symbolic functions. For a full list of available functions see `src/symbolic_functions.cpp`. -Dirac functions in observables will have no effect. - -### Events - -Specifying events is optional. Events are specified in terms of a trigger function, a bolus fuction and an output function. The roots of the trigger function defines the occurences of the event. The bolus function defines the change in the state on event occurences. The output function defines the expression which is evaluated and reported by the simulation routine on every event occurence. The user can create events by constructing a vector of objects of the class @ref amievent. - - model.sym.event(1) = amievent(state1 - state2,0,[]); - -Events may depend on states, parameters and constants but __not__ on observables. - -For more details about event support see https://doi.org/10.1093/bioinformatics/btw764 - -### Standard Deviation - -Specifying standard deviations is optional. It only has an effect when computing adjoint sensitivities. It allows the user to specify standard deviations of experimental data for observables and events. - -Standard deviaton for observable data is denoted by sigma_y - - model.sym.sigma_y(1) = param5; - -Standard deviaton for event data is denoted by sigma_t - - model.sym.sigma_t(1) = param6; - -Both `sigma_y` and `sigma_t` can either be a scalar or of the same dimension as the observables / events function. -They can depend on time and parameters but must not depend on the states or observables. The values provided in `sigma_y` and `sigma_t` will only be used if the value in `D.Sigma_Y` or `D.Sigma_T` in the user-provided data struct is `NaN`. See simulation for details. - -### Objective Function - -By default, AMICI assumes a normal noise model and uses the corresponding negative log-likelihood - - J = 1/2*sum(((y_i(t)-my_ti)/sigma_y_i)^2 + log(2*pi*sigma_y^2) - -as objective function. A user provided objective function can be specified in - - model.sym.Jy - -As reference see the default specification of `this.sym.Jy` in `amimodel.makeSyms`. - -### SBML - -AMICI can also import SBML models using the command `SBML2AMICI`. This will generate a model specification as described above, which may be edited by the user to apply further changes. - -## Model Compilation - -The model can then be compiled by calling `amiwrap.m`: - - amiwrap(modelname,'example_model_syms',dir,o2flag) - -Here `modelname` should be a string defining the name of the model, `dir` should be a string containing the path to the directory in which simulation files should be placed and `o2flag` is a flag indicating whether second order sensitivities should also be compiled. -The user should make sure that the previously defined function `'example_model_syms'` is in the user path. Alternatively, the user can also call the function `'example_model_syms'` - - [model] = example_model_syms() - -and subsequently provide the generated struct to `amiwrap(...)`, instead of providing the symbolic function: - - amiwrap(modelname,model,dir,o2flag) - -In a similar fashion, the user could also generate multiple models and pass them directly to `amiwrap(...)` without generating respective model definition scripts. - - -### Compiling a Python-generated model - -Due to better performance or to avoid the Symbolic Toolbox requirement, -it might be desirable to import a model in Python and compile the -resulting code into a mex file. For Python model import, consult the -respective section of the Python documentation. Once the imported -succeeded, there will be a `compileMexFile.m` script inside the newly -created model directory which can be invoked to compile the mex file. -This mex file and `simulate_*.m` can be used as if fully created by -matlab. - - -#### Using Python-AMICI model import from Matlab - -With recent matlab versions it is possible to use the AMICI python package -from within Matlab. This not quite comfortable yet, but it is possible. - -Here for proof of concept: - -* Install the python package as described in the documentation -* Ensure `pyversion` shows the correct python version (>= 3.11) -* Then, from within the AMICI `matlab/` directory: - - ``` - sbml_importer = py.amici.SbmlImporter('../doc/examples/getting_started/model_steadystate_scaled.xml') - sbml_importer.sbml2amici('steadystate', 'steadystate_example_from_python') - model = py.steadystate.getModel() - solver = model.getSolver() - model.setTimepoints(linspace(0, 50, 51)) - rdata = py.amici.runAmiciSimulation(model, solver) - result = struct(py.dict(rdata.items())) - t = double(py.array.array('d', result.ts)) - x = double(py.array.array('d', result.x.flatten())) - x = reshape(x, flip(double(py.array.array('d', result.x.shape)))) - plot(t, x) - ``` - - -## Model Simulation - -After the call to `amiwrap(...)` two files will be placed in the specified directory. One is a _modelname_.mex and the other is simulate_ _modelname_.m. The mex file should never be called directly. Instead the MATLAB script, which acts as a wrapper around the .mex simulation file should be used. - -The simulate_ _modelname_.m itself carries extensive documentation on how to call the function, what it returns and what additional options can be specified. In the following we will give a short overview of possible function calls. - -### Integration - -Define a time vector: - - t = linspace(0,10,100) - - -Generate a parameter vector: - - theta = ones(6,1); - - -Generate a constants vector: - - kappa = ones(2,1); - - -Integrate: - - sol = simulate_modelname(t,theta,kappa,[],options) - - -The integration status will be indicated by the `sol.status` flag. Negative values indicated failed integration. The states will then be available as sol.x. The observables will then be available as `sol.y`. The event outputs will then be available as `sol.z`. If no event occured there will be an event at the end of the considered interval with the final value of the root function is stored in `sol.rz`. - -Alternatively the integration can also be called via - - [status,t,x,y] = simulate_modelname(t,theta,kappa,[],options) - -The integration status will be indicated by the flag `status` . Negative values indicated failed integration. The states will then be available as `x`. The observables will then be available as `y`. No event output will be given. - -### Forward Sensitivities - -Set the sensitivity computation to forward sensitivities and integrate: - - options.sensi = 1; - options.sensi_meth = 'forward; - sol = simulate_modelname(t,theta,kappa,[],options) - -The integration status will be indicated by the `sol.status` flag. Negative values indicate failed integration. The states will be available as `sol.x`, with the derivative with respect to the parameters in `sol.sx`. The observables will be available as `sol.y`, with the derivative with respect to the parameters in `sol.sy`. The event outputs will be available as `sol.z`, with the derivative with respect to the parameters in `sol.sz`. If no event occured there will be an event at the end of the considered interval with the final value of the root function stored in `sol.rz`, with the derivative with respect to the parameters in `sol.srz`. - -Alternatively the integration can also be called via - - [status,t,x,y,sx,sy] = simulate_modelname(t,theta,kappa,[],options) - -The integration status will be indicated by the status flag. Negative values indicate failed integration. The states will then be available as `x`, with derivative with respect to the parameters in `sx`. The observables will then be available as `y`, with derivative with respect to the parameters in `sy`. No event output will be given. - -### Adjoint Sensitivities - -Set the sensitivity computation to adjoint sensitivities: - - options.sensi = 1; - options.sensi_meth = 'adjoint'; - -Define Experimental Data: - - D.Y = [NaN(1,2)],ones(length(t)-1,2)]; - D.Sigma_Y = [0.1*ones(length(t)-1,2),NaN(1,2)]; - D.T = ones(1,1); - D.Sigma_T = NaN; - -The `NaN` values in `Sigma_Y` and `Sigma_T` will be replaced by the specification in `model.sym.sigma_y` and `model.sym.sigma_t`. Data points with `NaN` value will be completely ignored. - -Integrate: - - sol = simulate_modelname(t,theta,kappa,D,options) - -The integration status will be indicated by the sol.status flag. Negative values indicate failed integration. The log-likelihood will then be available as `sol.llh` and the derivative with respect to the parameters in `sol.sllh`. Notice that for adjoint sensitivities no state, observable and event sensitivities will be available. Yet this approach can be expected to be significantly faster for systems with a large number of parameters. - -### Steady State Sensitivities - -This will compute state sensitivities according to the formula \f$ s_k^x = -\left(\frac{\partial f}{\partial x} \right)^{-1}\frac{\partial f}{\partial \theta_k} \f$ - -In the current implementation this formulation does not allow for conservation laws as this would result in a singular Jacobian. - -Set the final timepoint as infinity, this will indicate the solver to compute the steadystate: - - t = Inf; - -Set the sensitivity computation to steady state sensitivities: - - options.sensi = 1; - -Integrate: - - sol = simulate_modelname(t,theta,kappa,D,options) - -The states will be available as `sol.x`, with the derivative with respect to the parameters in `sol.sx`. The observables will be available as `sol.y`, with the derivative with respect to the parameters in `sol.sy`. Notice that for steady state sensitivities no event sensitivities will be available. For the accuracy of the computed derivatives it is essential that the system is sufficiently close to a steady state. This can be checked by examining the right hand side of the system at the final time-point via `sol.diagnosis.xdot`. diff --git a/doc/availability.rst b/doc/availability.rst index ef8f3e2650..97224b0cbe 100644 --- a/doc/availability.rst +++ b/doc/availability.rst @@ -41,4 +41,3 @@ Installation instructions are available for * :ref:`Python ` * :ref:`C++ ` -* :ref:`Matlab ` diff --git a/doc/conf.py b/doc/conf.py index 7af9f93ae6..11c15dc949 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,10 +9,8 @@ import subprocess import sys from enum import EnumType -import exhale.deploy from unittest import mock import sphinx -from exhale import configs as exhale_configs from sphinx.transforms.post_transforms import ReferencesResolver try: @@ -37,71 +35,6 @@ import sympy as sp # noqa: F401 -# BEGIN Monkeypatch exhale -from exhale.deploy import _generate_doxygen as exhale_generate_doxygen - - -def my_exhale_generate_doxygen(doxygen_input): - """Monkey-patch exhale for post-processing doxygen output""" - - # run mtocpp_post - doxy_xml_dir = exhale_configs._doxygen_xml_output_directory - if "matlab" in doxy_xml_dir: - print("Running mtocpp_post on ", doxy_xml_dir) - mtocpp_post = os.path.join( - amici_dir, "ThirdParty", "mtocpp-master", "build", "mtocpp_post" - ) - subprocess.run([mtocpp_post, doxy_xml_dir]) - - # let exhale do its job - exhale_generate_doxygen(doxygen_input) - - -exhale.deploy._generate_doxygen = my_exhale_generate_doxygen -# END Monkeypatch exhale - - -# BEGIN Monkeypatch breathe -from breathe.renderer.sphinxrenderer import ( - DomainDirectiveFactory as breathe_DomainDirectiveFactory, -) - -old_breathe_DomainDirectiveFactory_create = ( - breathe_DomainDirectiveFactory.create -) - - -def my_breathe_DomainDirectiveFactory_create(domain: str, args): - if domain != "mat": - return old_breathe_DomainDirectiveFactory_create(domain, args) - - from sphinxcontrib.matlab import MatClassmember, MATLABDomain - - matlab_classes = {k: (v, k) for k, v in MATLABDomain.directives.items()} - matlab_classes["variable"] = (MatClassmember, "attribute") - cls, name = matlab_classes[args[0]] - return cls(domain + ":" + name, *args[1:]) - - -breathe_DomainDirectiveFactory.create = ( - my_breathe_DomainDirectiveFactory_create -) - - -# END Monkeypatch breathe - - -def install_mtocpp(): - """Install mtocpp (Matlab doxygen filter)""" - cmd = os.path.join(amici_dir, "scripts", "downloadAndBuildMtocpp.sh") - ret = subprocess.run( - cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) - if ret.returncode != 0: - print(ret.stdout.decode("utf-8")) - raise RuntimeError("downloadAndBuildMtocpp.sh failed") - - def install_doxygen(): """Get a more recent doxygen""" version = "1.14.0" @@ -143,9 +76,6 @@ def install_doxygen(): if "READTHEDOCS" in os.environ and os.environ["READTHEDOCS"]: install_doxygen() -# Required for matlab doxygen processing -install_mtocpp() - # -- Project information ----------------------------------------------------- # The short X.Y version @@ -186,7 +116,6 @@ def install_doxygen(): "sphinx.ext.autosummary", "sphinx.ext.viewcode", "sphinx.ext.mathjax", - "sphinxcontrib.matlab", "nbsphinx", "IPython.sphinxext.ipython_console_highlighting", "sphinx_autodoc_typehints", @@ -259,7 +188,6 @@ def install_doxygen(): "**.ipynb_checkpoints", "numpy.py", "INSTALL.md", - "MATLAB_.md", "CPP_.md", "gfx", "AGENTS.md", @@ -285,7 +213,6 @@ def install_doxygen(): # breathe settings breathe_projects = { - "AMICI_Matlab": "./_doxyoutput_amici_matlab/xml", "AMICI_CPP": "./_doxyoutput_amici_cpp/xml", } @@ -307,9 +234,6 @@ def install_doxygen(): "verboseBuild": True, } -mtocpp_filter = os.path.join( - amici_dir, "matlab", "mtoc", "config", "mtocpp_filter.sh" -) exhale_projects_args = { "AMICI_CPP": { "exhaleDoxygenStdin": "\n".join( @@ -331,26 +255,6 @@ def install_doxygen(): "rootFileTitle": "AMICI C++ API", "afterTitleDescription": "AMICI C++ library functions", }, - # Third Party Project Includes - "AMICI_Matlab": { - "exhaleDoxygenStdin": "\n".join( - [ - "INPUT = ../matlab", - "EXTENSION_MAPPING = .m=C++", - f"FILTER_PATTERNS = *.m={mtocpp_filter}", - "EXCLUDE += ../matlab/examples", - "EXCLUDE += ../matlab/mtoc", - "EXCLUDE += ../matlab/SBMLimporter", - "EXCLUDE += ../matlab/auxiliary", - "EXCLUDE += ../matlab/tests", - "PREDEFINED += EXHALE_DOXYGEN_SHOULD_SKIP_THIS", - ] - ), - "containmentFolder": "_exhale_matlab_api", - "rootFileTitle": "AMICI Matlab API", - "afterTitleDescription": "AMICI Matlab library functions", - "lexerMapping": {r".*\.m$": "matlab"}, - }, } # -- Options for HTML output ------------------------------------------------- diff --git a/doc/cpp_installation.rst b/doc/cpp_installation.rst index 00d896394f..fb16035f76 100644 --- a/doc/cpp_installation.rst +++ b/doc/cpp_installation.rst @@ -8,9 +8,8 @@ The following section describes building the AMICI C++ library: .. note:: The AMICI C++ interface only supports simulation of models imported using - the :ref:`Python interface ` and - :ref:`Matlab interface `. It cannot be used for model - import itself. + the :ref:`Python interface `. + It cannot be used for model import itself. Prerequisites: diff --git a/doc/cpp_interface.rst b/doc/cpp_interface.rst index 751ee493a5..9c01b00dd9 100644 --- a/doc/cpp_interface.rst +++ b/doc/cpp_interface.rst @@ -5,8 +5,7 @@ Using AMICI's C++ interface =========================== The various import functions in of the -:ref:`Python interface ` and -:ref:`Matlab interface ` translate models defined in +:ref:`Python interface ` translate models defined in different formats into C++ code. These generated model libraries, together with the AMICI base library can be used in any C++ application for model simulation and sensitivity analysis. This section will give a short overview over the @@ -17,10 +16,9 @@ included in other applications. Further details are available in the AMICI-generated C++ model files =============================== -After importing a model using either the -:ref:`Python interface ` or the -:ref:`Matlab interface `, the specified output directory -contains (among others) C++ code for the various model functions. +After importing a model using the :ref:`Python interface `, +the specified output directory contains (among others) C++ code for the various +model functions. The content of a model source directory looks something like this (given `MODEL_NAME=model_steadystate`): diff --git a/doc/index.rst b/doc/index.rst index 4d5d2b681f..595c6c9892 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -33,7 +33,6 @@ Welcome to AMICI's documentation! .. include:: PYTHON.rst .. include:: CPP.rst -.. include:: MATLAB.rst .. toctree:: :maxdepth: 2 diff --git a/doc/matlab_faq.rst b/doc/matlab_faq.rst deleted file mode 100644 index 309d44ae10..0000000000 --- a/doc/matlab_faq.rst +++ /dev/null @@ -1,55 +0,0 @@ -FAQ -=== - -**Q**: My model fails to build. - -**A**: Remove the corresponding model directory located in -AMICI/models/\ *yourmodelname* and compile again. - --------------- - -**Q**: It still does not compile. - -**A**: Remove the directory AMICI/models/\ ``mexext`` and compile again. - --------------- - -**Q**: It still does not compile. - -**A**: Make an `issue `__ and -we will have a look. - --------------- - -**Q**: My Python-generated model does not compile from MATLAB. - -**A**: Try building any of the available examples before. If this -succeeds, retry building the original model. Some dependencies might not -be built correctly when using only the ``compileMexFile.m`` script. - --------------- - -**Q**: I get an out of memory error while compiling my model on a -Windows machine. - -**A**: This may be due to an old compiler version. See `issue -#161 `__ for instructions -on how to install a new compiler. - --------------- - -**Q**: How are events interpreted in a DAE context? - -**A**: Currently we only support impulse free events. Also sensitivities -have never been tested. Proceed with care and create an -`issue `__ if any problems -arise! - --------------- - -**Q**: The simulation/sensitivities I get are incorrect. - -**A**: There are some known issues, especially with adjoint -sensitivities, events and DAEs. If your particular problem is not -featured in the `issues `__ -list, please add it! diff --git a/doc/matlab_installation.rst b/doc/matlab_installation.rst deleted file mode 100644 index 19dabf1c28..0000000000 --- a/doc/matlab_installation.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. _amici_matlab_installation: - -Installing the AMICI MATLAB toolbox -=================================== - -To use AMICI from MATLAB, start MATLAB and add the ``AMICI/matlab`` -directory to the MATLAB path. To add all toolbox directories to the -MATLAB path, execute the matlab script: - -.. code-block:: matlab - - installAMICI.m - -To store the installation for further MATLAB session, the path can be -saved via: - -.. code-block:: matlab - - savepath - -For the compilation of ``.mex`` files, MATLAB needs to be configured with a -working C++ compiler. The C++ compiler needs to be installed and -configured via: - -.. code-block:: matlab - - mex -setup c++ - -For a list of supported compilers we refer to the respective MathWorks -`documentation `_. diff --git a/doc/matlab_interface.rst b/doc/matlab_interface.rst deleted file mode 100644 index 8c47229ebf..0000000000 --- a/doc/matlab_interface.rst +++ /dev/null @@ -1,510 +0,0 @@ -.. _matlab_interface: - -Using AMICI's MATLAB interface -============================== - -In the following we will give a detailed overview how to specify models in -MATLAB and how to call the generated simulation files. - -.. note:: - - The MATLAB interface requires the MathWorks - `Symbolic Math Toolbox `_ - for model import (but not for model simulation). - - The Symbolic Math Toolbox requirement can be circumvented by performing model - import using the Python interface. The resulting code can then be used from - Matlab (see :ref:`matlab_compile_python_imported_model`). - -.. warning:: - - Due to changes in the Symbolic Math Toolbox, the last MATLAB release - with working AMICI model import is R2017b - (see `https://github.com/AMICI-dev/AMICI/issues/307 `__). - - -Specifying models in Matlab -+++++++++++++++++++++++++++ - -This guide will guide the user on how to specify models in MATLAB. -For example implementations see the examples in the ``matlab/examples`` -directory. - -Header ------- - -The model definition needs to be defined as a function which returns a -``struct`` with all symbolic definitions and options. - -.. code-block:: matlab - - function [model] = example_model_syms() - -Options -------- - -Set the options by specifying the respective field of the model struct - -.. code-block:: matlab - - model.(fieldname) = value - -The options specify default options for simulation, parametrisation and -compilation. All of these options are optional. - -+--------------+-----------------------------------------------+---------+ -| field | description | default | -+==============+===============================================+=========+ -| .param | default parametrisation 'log'/'log10'/'lin' | 'lin' | -+--------------+-----------------------------------------------+---------+ -| .debug | flag to compile with debug symbols | false | -+--------------+-----------------------------------------------+---------+ -| .forward | flag to activate forward sensitivities | true | -+--------------+-----------------------------------------------+---------+ -| .adjoint | flag to activate adjoint sensitivities | true | -+--------------+-----------------------------------------------+---------+ - -When set to ``false``, the fields ``forward`` and ``adjoint`` will speed up the -time required to compile the model but also disable the respective sensitivity -computation. - -States ------- - -Create the respective symbolic variables. The name of the symbolic variable -can be chosen arbitrarily. - -.. code-block:: matlab - - syms state1 state2 state3 - -Create the state vector containing all states: - -.. code-block:: matlab - - model.sym.x = [ state1 state2 state3 ]; - -Parameters ----------- - -Create the respective symbolic variables. The name of the symbolic variable can -be chosen arbitrarily. Sensitivities will be derived for all *parameters*. - -.. code-block:: matlab - - syms param1 param2 param3 param4 param5 param6 - -Create the parameters vector - -.. code-block:: matlab - - model.sym.p = [ param1 param2 param3 param4 param5 param6 ]; - -Constants ---------- - -Create the respective symbolic variables. The name of the symbolic variable -can be chosen arbitrarily. Sensitivities with respect to *constants* will not -be derived. - -.. code-block:: matlab - - syms const1 const2 - -Create the constants vector - -.. code-block:: matlab - - model.sym.k = [ const1 const2 ]; - -Differential equations ----------------------- - -For time-dependent differential equations you can specify a symbolic variable -for time. This **needs** to be denoted by ``t``. - -.. code-block:: matlab - - syms t - -Specify the right hand side of the differential equation ``f`` or ``xdot`` - -.. code-block:: matlab - - model.sym.xdot(1) = [ const1 - param1*state1 ]; - model.sym.xdot(2) = [ +param2*state1 + dirac(t-param3) - const2*state2 ]; - model.sym.xdot(3) = [ param4*state2 ]; - -or - -.. code-block:: matlab - - model.sym.f(1) = [ const1 - param1*state1 ]; - model.sym.f(2) = [ +param2*state1 + dirac(t-param3) - const2*state2 ]; - model.sym.f(3) = [ param4*state2 ]; - -The specification of ``f`` or ``xdot`` may depend on states, parameters and -constants. - -For DAEs also specify the mass matrix. - -.. code-block:: matlab - - model.sym.M = [1, 0, 0;... - 0, 1, 0;... - 0, 0, 0]; - -The specification of ``M`` may depend on parameters and constants. - -For ODEs the integrator will solve the equation :math:`\dot{x} = f` and for -DAEs the equations :math:`M \cdot \dot{x} = f`. -AMICI will decide whether to use CVODES (for ODEs) or IDAS (for DAEs) based on -whether the mass matrix is defined or not. - -In the definition of the differential equation you can use certain symbolic -functions. For a full list of available functions see -``src/symbolic_functions.cpp``. - -Dirac functions can be used to cause a jump in the respective states at the -specified time-point. This is typically used to model injections, or other -external stimuli. Spline functions can be used to model time/state dependent -response with unknown time/state dependence. - -Initial Conditions ------------------- - -Specify the initial conditions. These may depend on parameters on constants -and must have the same size as ``x``. - -.. code-block:: matlab - - model.sym.x0 = [ param4, 0, 0 ]; - -Observables ------------ - -Specify the observables. These may depend on parameters and constants. - -.. code-block:: matlab - - model.sym.y(1) = state1 + state2; - model.sym.y(2) = state3 - state2; - -In the definition of the observable you can use certain symbolic functions. -For a full list of available functions see ``src/symbolic_functions.cpp``. -Dirac functions in observables will have no effect. - -Events ------- - -Specifying events is optional. Events are specified in terms of a trigger -function, a bolus function and an output function. The roots of the trigger -function defines the occurrences of the event. The bolus function defines the -change in the state on event occurrences. The output function defines the -expression which is evaluated and reported by the simulation routine on every -event occurrence. The user can create events by constructing a vector of -objects of the class :mat:class:`amievent`. - -.. code-block:: matlab - - model.sym.event(1) = amievent(state1 - state2,0,[]); - -Events may depend on states, parameters and constants but *not* on observables. - -For more details about event support see: - - Fröhlich, F., Theis, F. J., Rädler, J. O., & Hasenauer, J. (2017). - Parameter estimation for dynamical systems with discrete events and logical - operations. Bioinformatics, 33(7), 1049-1056. - doi:`10.1093/bioinformatics/btw764 `_. - - -Standard deviation ------------------- - -Specifying standard deviations is optional. It only has an effect when -computing adjoint sensitivities. It allows the user to specify standard -deviations of experimental data for observables and events. - -Standard deviation for observable data is denoted by ``sigma_y`` - -.. code-block:: matlab - - model.sym.sigma_y(1) = param5; - -Standard deviation for event data is denoted by ``sigma_t`` - -.. code-block:: matlab - - model.sym.sigma_t(1) = param6; - -Both ``sigma_y`` and ``sigma_t`` can either be a scalar or of the same dimension -as the observables / events function. -They can depend on time and parameters but must not depend on the states or -observables. The values provided in ``sigma_y`` and ``sigma_t`` will only be used -if the value in ``D.Sigma_Y`` or ``D.Sigma_T`` in the user-provided data struct is -``NaN``. See simulation for details. - -Objective Function ------------------- - -By default, AMICI assumes a normal noise model and uses the corresponding -negative log-likelihood - -.. math:: - - J = 1/2*sum(((y_i(t)-my_ti)/sigma_y_i)^2 + log(2*pi*sigma_y^2) - -as objective function. A user provided objective function can be specified in - -.. code-block:: matlab - - model.sym.Jy - -As reference see the default specification of ``this.sym.Jy`` in ``amimodel.makeSyms``. - -SBML -++++ - -AMICI can also import SBML models using the command ``SBML2AMICI``. -This will generate a model specification as described above, which may be -edited by the user to apply further changes. - -Model Compilation -+++++++++++++++++ - -The model can then be compiled by calling ``amiwrap.m``: - -.. code-block:: matlab - - amiwrap(modelname,'example_model_syms',dir,o2flag) - -Here ``modelname`` should be a string defining the name of the model, ``dir`` -should be a string containing the path to the directory in which simulation -files should be placed and ``o2flag`` is a flag indicating whether second order -sensitivities should also be compiled. -The user should make sure that the previously defined function -``example_model_syms`` is in the user path. Alternatively, the user can also -call the function ``example_model_syms`` - -.. code-block:: matlab - - [model] = example_model_syms() - -and subsequently provide the generated struct to ``amiwrap(...)``, instead of -providing the symbolic function: - -.. code-block:: matlab - - amiwrap(modelname,model,dir,o2flag) - -In a similar fashion, the user could also generate multiple models and pass -them directly to ``amiwrap(...)`` without generating respective model -definition scripts. - - -.. _matlab_compile_python_imported_model: - -Compiling a Python-generated model ----------------------------------- - -For better performance or to avoid the Symbolic Math Toolbox requirement, -it might be desirable to import a model in Python and compile the -resulting code into a mex file. For Python model import, consult the -respective section of the Python documentation. Once the imported -succeeded, there will be a ``compileMexFile.m`` script inside the newly -created model directory which can be invoked to compile the mex file. -This mex file and ``simulate_*.m`` can be used as if fully created by -matlab. - - -Using Python-AMICI model import from Matlab -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With recent matlab versions it is possible to use the AMICI python package -from within Matlab. This not quite comfortable yet, but it is possible. - -Here for proof of concept: - -* Install the python package as described in the documentation -* Ensure ``pyversion`` shows the correct python version (3.6 or 3.7) -* Then, from within the AMICI ``matlab/`` directory: - -.. code-block:: matlab - - sbml_importer = py.amici.SbmlImporter('../python/examples/example_steadystate/model_steadystate_scaled.xml') - sbml_importer.sbml2amici('steadystate', 'steadystate_example_from_python') - model = py.steadystate.getModel() - solver = model.getSolver() - model.setTimepoints(linspace(0, 50, 51)) - rdata = py.amici.runAmiciSimulation(model, solver) - result = struct(py.dict(rdata.items())) - t = double(py.array.array('d', result.ts)) - x = double(py.array.array('d', result.x.flatten())) - x = reshape(x, flip(double(py.array.array('d', result.x.shape)))) - plot(t, x) - -Model simulation -++++++++++++++++ - -After the call to ``amiwrap(...)`` two files will be placed in the specified -directory. One is a ``_modelname_.mex`` and the other is ``simulate_*modelname*.m``. -The mex file should never be called directly. Instead the MATLAB script, which -acts as a wrapper around the .mex simulation file should be used. - -The ``simulate_ _modelname_.m`` itself carries extensive documentation on how to -call the function, what it returns and what additional options can be -specified. In the following we will give a short overview of possible function -calls. - -Integration ------------ -Define a time vector: - -.. code-block:: matlab - - t = linspace(0,10,100) - -Generate a parameter vector: - -.. code-block:: matlab - - theta = ones(6,1); - -Generate a constants vector: - -.. code-block:: matlab - - kappa = ones(2,1); - -Integrate: - -.. code-block:: matlab - - sol = simulate_modelname(t,theta,kappa,[],options) - - -The integration status will be indicated by the ``sol.status`` flag. Negative -values indicated failed integration. The states will then be available as ``sol.x``. -The observables will then be available as ``sol.y``. The event outputs will then -be available as ``sol.z``. If no event occurred there will be an event at the end -of the considered interval with the final value of the root function is stored -in ``sol.rz``. - -Alternatively the integration can also be called via - -.. code-block:: matlab - - [status,t,x,y] = simulate_modelname(t,theta,kappa,[],options) - -The integration status will be indicated by the flag ``status`` . Negative -values indicated failed integration. The states will then be available as ``x``. -The observables will then be available as ``y``. No event output will be given. - -Forward Sensitivities ---------------------- - -Set the sensitivity computation to forward sensitivities and integrate: - -.. code-block:: matlab - - options.sensi = 1; - options.sensi_meth = 'forward'; - sol = simulate_modelname(t,theta,kappa,[],options) - -The integration status will be indicated by the ``sol.status`` flag. Negative -values indicate failed integration. The states will be available as ``sol.x``, -with the derivative with respect to the parameters in ``sol.sx``. -The observables will be available as ``sol.y``, with the derivative with respect -to the parameters in ``sol.sy``. The event outputs will be available as ``sol.z``, -with the derivative with respect to the parameters in ``sol.sz``. If no event -occured there will be an event at the end of the considered interval with the -final value of the root function stored in ``sol.rz``, with the derivative with -respect to the parameters in ``sol.srz``. - -Alternatively the integration can also be called via - -.. code-block:: matlab - - [status,t,x,y,sx,sy] = simulate_modelname(t,theta,kappa,[],options) - -The integration status will be indicated by the status flag. Negative values -indicate failed integration. The states will then be available as ``x``, with -derivative with respect to the parameters in ``sx``. The observables will then -be available as ``y``, with derivative with respect to the parameters in ``sy``. -No event output will be given. - -Adjoint sensitivities ---------------------- - -Set the sensitivity computation to adjoint sensitivities: - -.. code-block:: matlab - - options.sensi = 1; - options.sensi_meth = 'adjoint'; - -Define Experimental Data: - -.. code-block:: matlab - - D.Y = [NaN(1,2)],ones(length(t)-1,2)]; - D.Sigma_Y = [0.1*ones(length(t)-1,2),NaN(1,2)]; - D.T = ones(1,1); - D.Sigma_T = NaN; - -The ``NaN`` values in ``Sigma_Y`` and ``Sigma_T`` will be replaced by the -specification in ``model.sym.sigma_y`` and ``model.sym.sigma_t``. Data points -with ``NaN`` value will be completely ignored. - -Integrate: - -.. code-block:: matlab - - sol = simulate_modelname(t,theta,kappa,D,options) - -The integration status will be indicated by the sol.status flag. Negative -values indicate failed integration. The log-likelihood will then be available -as ``sol.llh`` and the derivative with respect to the parameters in -``sol.sllh``. Note that for adjoint sensitivities no state, observable and -event sensitivities will be available. Yet this approach can be expected to be -significantly faster for systems with a large number of parameters. - -Steady-state sensitivities --------------------------- - -This will compute state sensitivities according to the formula - -.. math:: - - s_k^x = -\left(\frac{\partial f}{\partial x} \right)^{-1}\frac{\partial f}{\partial \theta_k} - -In the current implementation this formulation does not allow for conservation -laws as this would result in a singular Jacobian. - -Set the final timepoint as infinity, this will indicate the solver to compute -the steadystate: - -.. code-block:: matlab - - t = Inf; - -Set the sensitivity computation to steady state sensitivities: - -.. code-block:: matlab - - options.sensi = 1; - -Integrate: - -.. code-block:: matlab - - sol = simulate_modelname(t,theta,kappa,D,options) - -The states will be available as ``sol.x``, with the derivative with respect -to the parameters in ``sol.sx``. The observables will be available as ``sol.y``, -with the derivative with respect to the parameters in ``sol.sy``. Notice that -for steady state sensitivities no event sensitivities will be available. For -the accuracy of the computed derivatives it is essential that the system is -sufficiently close to a steady state. This can be checked by examining the -right hand side of the system at the final time-point via ``sol.diagnosis.xdot``. diff --git a/doc/rtd_requirements.txt b/doc/rtd_requirements.txt index 3dbc195569..cbb21058c2 100644 --- a/doc/rtd_requirements.txt +++ b/doc/rtd_requirements.txt @@ -19,7 +19,6 @@ ipython>=8.13.2 breathe>=4.35.0 exhale>=0.3.7 -e git+https://github.com/mithro/sphinx-contrib-mithro#egg=sphinx-contrib-exhale-multiproject&subdirectory=sphinx-contrib-exhale-multiproject -sphinxcontrib-matlabdomain>=0.20.0 sphinxcontrib-napoleon>=0.7 pygments>=2.15.1 Jinja2>=3.1.6 diff --git a/include/amici/returndata_matlab.h b/include/amici/returndata_matlab.h deleted file mode 100644 index 26b7d55d9e..0000000000 --- a/include/amici/returndata_matlab.h +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef RETURNDATA_MATLAB_H -#define RETURNDATA_MATLAB_H - -#include "amici/rdata.h" - -#include - -#include - -namespace amici { - -/** - * @brief generates matlab mxArray from a ReturnData object - * @param rdata ReturnDataObject - * @return rdatamatlab ReturnDataObject stored as matlab compatible data - */ -mxArray* getReturnDataMatlabFromAmiciCall(ReturnData const* rdata); - -/** - * @brief allocates and initializes solution mxArray with the corresponding - * fields - * @param rdata ReturnDataObject - * @return Solution mxArray - */ -mxArray* initMatlabReturnFields(ReturnData const* rdata); - -/** - * @brief allocates and initializes diagnosis mxArray with the corresponding - * fields - * @param rdata ReturnDataObject - * @return Diagnosis mxArray - */ -mxArray* initMatlabDiagnosisFields(ReturnData const* rdata); - -/** - * @brief initialize vector and attach to the field - * @param matlabStruct pointer of the field to which the vector will be - * attached - * @param fieldName Name of the field to which the vector will be attached - * @param fieldData Data which will be stored in the field - */ -template -void writeMatlabField0( - mxArray* matlabStruct, char const* fieldName, T fieldData -); - -/** - * @brief initialize vector and attach to the field - * @param matlabStruct pointer of the field to which the vector will be - * attached - * @param fieldName Name of the field to which the vector will be attached - * @param fieldData Data which will be stored in the field - * @param dim0 Number of elements in the vector - */ -template -void writeMatlabField1( - mxArray* matlabStruct, char const* fieldName, - gsl::span const& fieldData, mwSize const dim0 -); - -/** - * @brief initialize matrix, attach to the field and write data - * @param matlabStruct Pointer to the matlab structure - * @param fieldName Name of the field to which the tensor will be attached - * @param fieldData Data which will be stored in the field - * @param dim0 Number of rows in the tensor - * @param dim1 Number of columns in the tensor - * @param perm reordering of dimensions (i.e., transposition) - */ -template -void writeMatlabField2( - mxArray* matlabStruct, char const* fieldName, - std::vector const& fieldData, mwSize dim0, mwSize dim1, - std::vector perm -); - -/** - * @brief initialize 3D tensor, attach to the field and write data - * @param matlabStruct Pointer to the matlab structure - * @param fieldName Name of the field to which the tensor will be attached - * @param fieldData Data which will be stored in the field - * @param dim0 number of rows in the tensor - * @param dim1 number of columns in the tensor - * @param dim2 number of elements in the third dimension of the tensor - * @param perm reordering of dimensions - */ -template -void writeMatlabField3( - mxArray* matlabStruct, char const* fieldName, - std::vector const& fieldData, mwSize dim0, mwSize dim1, mwSize dim2, - std::vector perm -); - -/** - * @brief initialize 4D tensor, attach to the field and write data - * @param matlabStruct Pointer to the matlab structure - * @param fieldName Name of the field to which the tensor will be attached - * @param fieldData Data which will be stored in the field - * @param dim0 number of rows in the tensor - * @param dim1 number of columns in the tensor - * @param dim2 number of elements in the third dimension of the tensor - * @param dim3 number of elements in the fourth dimension of the tensor - * @param perm reordering of dimensions - */ -template -void writeMatlabField4( - mxArray* matlabStruct, char const* fieldName, - std::vector const& fieldData, mwSize dim0, mwSize dim1, mwSize dim2, - mwSize dim3, std::vector perm -); - -/** - * @brief initializes the field fieldName in matlabStruct with dimension dim - * @param matlabStruct Pointer to the matlab structure - * @param fieldName Name of the field to which the tensor will be attached - * @param dim vector of field dimensions - * - * @return pointer to field data - */ -double* initAndAttachArray( - mxArray* matlabStruct, char const* fieldName, std::vector dim -); - -/** - * @brief checks whether fieldNames was properly allocated - * @param fieldNames array of field names - * @param fieldCount expected number of fields in fieldNames - */ -void checkFieldNames(char const** fieldNames, int const fieldCount); - -/** - * @brief template function that reorders elements in a std::vector - * - * @param input unordered vector - * @param order dimension permutation - * - * @return Reordered vector - */ -template -std::vector -reorder(std::vector const& input, std::vector const& order); - -} // namespace amici - -#endif // RETURNDATA_MATLAB_H diff --git a/matlab/@amidata/amidata.m b/matlab/@amidata/amidata.m deleted file mode 100644 index 85ff897fed..0000000000 --- a/matlab/@amidata/amidata.m +++ /dev/null @@ -1,262 +0,0 @@ -% -% @file amidata -% @brief definition of amidata class -% -classdef amidata < handle - % AMIDATA provides a data container to pass experimental data to the - % simulation routine for likelihood computation. - % when any of the properties are updated, the class automatically - % checks consistency of dimension and updates related properties and - % initialises them with NaNs - - properties - % number of timepoints - nt=0; - % number of observables - ny=0; - % number of event observables - nz=0; - % number of events - ne=0; - % number of conditions/constants - nk=0; - % timepoints of observations - t = double.empty(); - % observations - Y = double.empty(); - % standard deviation of observations - Sigma_Y = double.empty(); - % event observations - Z = double.empty(); - % standard deviation of event observations - Sigma_Z = double.empty(); - % experimental condition - condition = double.empty(); - % experimental condition for preequilibration - conditionPreequilibration = double.empty(); - % reinitialize states based on fixed parameters after preeq.? - reinitializeStates = false; - end - - methods - function D = amidata(varargin) - % amidata creates an amidata container for experimental data - % with specified dimensions amidata. - % - % AMIDATA(amidata) creates a copy of the input container - % - % AMIDATA(struct) tries to creates an amidata container from the - % input struct. the struct should have the following - % fields: - % t [nt,1] - % Y [nt,ny] - % Sigma_Y [nt,ny] - % Z [ne,nz] - % Sigma_Z [ne,nz] - % condition [nk,1] - % conditionPreequilibration [nk,1] - % if some fields are missing the function will try - % to initialise them with NaNs with consistent - % dimensions - % - % AMIDATA(nt,ny,nz,ne,nk) constructs an empty data container with - % in the provided dimensions intialised with NaNs - % - % - % - % Parameters: - % varargin: - % - % Return values: - % - - - % initialisation via struct - if isa(varargin{1},'amidata') - if strcmp(class(varargin{1}),class(D)) - D = varargin{1}; - else - thisProps = properties(D); - for i = 1:length(thisProps) - try %#ok - % Try to set one of the properties of the - % old object in the new one. - D.(thisProps{i}) = varargin{1}.(thisProps{i}); - end - end - end - elseif(isstruct(varargin{1})) - if(isfield(varargin{1},'t')) - D.nt = numel(varargin{1}.t); - D.t = varargin{1}.t(:); - elseif(isfield(varargin{1},'Y')) - D.nt = size(varargin{1}.Y,1); - else - error('Cannot construct valid amidata object from input struct. There is no field D.t or D.Y in the input struct!'); - end - if(isfield(varargin{1},'Y')) - D.ny = size(varargin{1}.Y,2); - D.Y = varargin{1}.Y; - else - D.ny = 0; - end - if(isfield(varargin{1},'Z')) - D.nz = size(varargin{1}.Z,2); - D.ne = size(varargin{1}.Z,1); - D.Z = varargin{1}.Z; - else - D.nz = 0; - end - if(isfield(varargin{1},'Sigma_Y')) - D.Sigma_Y = varargin{1}.Sigma_Y; - end - if(isfield(varargin{1},'Sigma_Z')) - D.Sigma_Z = varargin{1}.Sigma_Z; - end - if(isfield(varargin{1},'condition')) - D.nk = numel(varargin{1}.condition); - D.condition = varargin{1}.condition; - else - D.nk = 0; - end - if(isfield(varargin{1},'conditionPreequilibration')) - assert(D.nk == numel(varargin{1}.conditionPreequilibration)); - D.conditionPreequilibration = varargin{1}.conditionPreequilibration; - end - if(isfield(varargin{1},'reinitializeStates')) - if islogical(varargin{1}.reinitializeStates) - D.reinitializeStates = varargin{1}.reinitializeStates; - elseif isnumeric(varargin{1}.reinitializeStates) - D.reinitializeStates = logical(varargin{1}.reinitializeStates); - else - error('Assignment error: Value for field reinitializeStates must be logical.'); - end - end - elseif(nargin == 5) - D.nt = varargin{1}; - D.ny = varargin{2}; - D.nz = varargin{3}; - D.ne = varargin{4}; - D.nk = varargin{5}; - end - - end - - function set.nt(this,nt) - this.nt = nt; - this.t = 1:nt; - this.Y = NaN; - this.Sigma_Y = NaN; - end - - function set.ny(this,ny) - this.ny = ny; - this.Y = NaN; - this.Sigma_Y = NaN; - end - - function set.nz(this,nz) - this.nz = nz; - this.Z = NaN; - this.Sigma_Z = NaN; - end - - function set.ne(this,ne) - this.ne = ne; - this.Z = NaN; - this.Sigma_Z = NaN; - end - - function set.nk(this,nk) - this.nk = nk; - this.condition = NaN(nk,1); - end - - function set.t(this,value) - assert(isnumeric(value),'AMICI:amimodel:t:numeric','t must have a numeric value!') - assert(ismatrix(value),'AMICI:amimodel:t:ndims','t must be a two dimensional matrix!') - assert(numel(value)==this.nt,'AMICI:amimodel:t:ndims',['t must have ' num2str(this.nt) ' (D.nt) elements!']) - this.t = double(value(:)); - end - - function set.condition(this,value) - assert(isnumeric(value),'AMICI:amimodel:condition:numeric','condition must have a numeric value!') - assert(ismatrix(value),'AMICI:amimodel:condition:ndims','condition must be a two dimensional matrix!') - assert(numel(value)==this.nk,'AMICI:amimodel:condition:ndims',['condition must have ' num2str(this.nk) ' (D.nk) elements!']) - this.condition = double(value(:)); - end - - function set.conditionPreequilibration(this,value) - assert(isnumeric(value),'AMICI:amimodel:condition:numeric','condition must have a numeric value!') - assert(ismatrix(value),'AMICI:amimodel:condition:ndims','condition must be a two dimensional matrix!') - assert(numel(value)==this.nk,'AMICI:amimodel:condition:ndims',['condition must have ' num2str(this.nk) ' (D.nk) elements!']) - this.conditionPreequilibration = double(value(:)); - end - - function set.Y(this,value) - assert(ismatrix(value),'AMICI:amimodel:Y:ndims','Y must be a two dimensional matrix!') - assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Y:numeric','Y must have a numeric value!') - - if(all(size(value)==[this.nt this.ny])) - this.Y = double(value); - elseif(all(size(value)==[this.nt 1])) - this.Y = repmat(double(value),[1,this.ny]); - elseif(all(size(value)==[1 this.ny])) - this.Y = repmat(double(value),[this.nt,1]); - elseif(all(size(value)==[1 1])) - this.Y = repmat(double(value),[this.nt,this.ny]); - else - error('AMICI:amimodel:Y:size',['Y must have size [' num2str(this.nt) ',' num2str(this.ny) '] ([D.nt,D.ny])!']) - end - end - - function set.Sigma_Y(this,value) - assert(ismatrix(value),'AMICI:amimodel:Sigma_Y:ndims','Sigma_Y must be a two dimensional matrix!') - assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Sigma_Y:numeric','Sigma_Y must have a numeric value!') - if(all(size(value)==[this.nt this.ny])) - this.Sigma_Y = double(value); - elseif(all(size(value)==[this.nt 1])) - this.Sigma_Y = repmat(double(value),[1,this.ny]); - elseif(all(size(value)==[1 this.ny])) - this.Sigma_Y = repmat(double(value),[this.nt,1]); - elseif(all(size(value)==[1 1])) - this.Sigma_Y = repmat(double(value),[this.nt,this.ny]); - else - error('AMICI:amimodel:Sigma_Y:size',['Sigma_Y must have size [' num2str(this.nt) ',' num2str(this.ny) '] ([D.nt,D.ny])!']) - end - end - - function set.Z(this,value) - assert(ismatrix(value),'AMICI:amimodel:Z:ndims','Z must be a two dimensional matrix!') - assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Z:numeric','Z must have a numeric value!') - if(all(size(value)==[this.ne this.nz])) - this.Z = double(value); - elseif(all(size(value)==[this.ne 1])) - this.Z = repmat(double(value),[1,this.nz]); - elseif(all(size(value)==[1 this.nz])) - this.Z = repmat(double(value),[this.ne,1]); - elseif(all(size(value)==[1 1])) - this.Z = repmat(double(value),[this.ne,this.nz]); - else - error('AMICI:amimodel:Z:size',['Z must have size [' num2str(this.ne) ',' num2str(this.nz) '] ([D.ne,D.nz])!']) - end - end - - function set.Sigma_Z(this,value) - assert(ismatrix(value),'AMICI:amimodel:Sigma_Z:ndims','Sigma_Z must be a two dimensional matrix!') - assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Sigma_Z:numeric','Sigma_Z must have a numeric value!') - if(all(size(value)==[this.ne this.nz])) - this.Sigma_Z = double(value); - elseif(all(size(value)==[this.ne 1])) - this.Sigma_Z = repmat(double(value),[1,this.nz]); - elseif(all(size(value)==[1 this.nz])) - this.Sigma_Z = repmat(double(value),[this.ne,1]); - elseif(all(size(value)==[1 1])) - this.Sigma_Z = repmat(double(value),[this.ne,this.nz]); - else - error('AMICI:amimodel:Sigma_Z:size',['Sigma_Z must have size [' num2str(this.ne) ',' num2str(this.nz) '] ([D.ne,D.nz])!']) - end - end - end - -end diff --git a/matlab/@amievent/amievent.m b/matlab/@amievent/amievent.m deleted file mode 100644 index 1ce5d7f2cd..0000000000 --- a/matlab/@amievent/amievent.m +++ /dev/null @@ -1,78 +0,0 @@ -% -% @file amievent -% @brief definition of amievent class -% -classdef amievent - % AMIEVENT defines events which later on will be transformed into appropriate - % C code - - properties ( GetAccess = 'public', SetAccess = 'private' ) - % the trigger function activates the event on every zero crossing @type symbolic - trigger = sym.empty(); - % the bolus function defines the change in states that is applied on every event occurence @type symbolic - bolus = sym.empty(); - % output function for the event @type symbolic - z = sym.empty(); - % flag indicating that a heaviside function is present, this helps - % to speed up symbolic computations - hflag = logical.empty(); - end - - methods - function AE = amievent(trigger,bolus,z) - % amievent constructs an amievent object from the provided input. - % - % Parameters: - % trigger: trigger function, the event will be triggered on - % at all roots of this function - % bolus: the bolus that will be added to all states on every - % occurence of the event - % z: the event output that will be reported on every occurence - % of the event - % - % Return values: - % AE: amievent object - % - if(~isa(trigger,'sym')) - if(isa(trigger,'double')) - AE.trigger = sym(trigger); - warning('Constant trigger function will never trigger. Please check the event definition.') - else - error('trigger function must be a symbolic expression') - end - else - AE.trigger = trigger; - end - if(numel(AE.trigger)>1) - error('The trigger function must be scalar.') - end - - if(~isa(bolus,'sym')) - if(isa(bolus,'double')) - AE.bolus = sym(bolus(:)); - else - error('bolus function must be a symbolic expression') - end - else - AE.bolus = bolus; - end - - if(~isa(z,'sym')) - if(isa(z,'double')) - if(~isempty(z)) - AE.z = sym(z); - warning('Constant outputs are not informative. Please check the event definition.') - else - AE.z = sym(zeros(0,1)); - end - else - error('output function must be a symbolic expression') - end - else - AE.z = z; - end - end - - this = setHflag(this,hflag); - end -end diff --git a/matlab/@amievent/setHflag.m b/matlab/@amievent/setHflag.m deleted file mode 100644 index 2dd5f1b73c..0000000000 --- a/matlab/@amievent/setHflag.m +++ /dev/null @@ -1,24 +0,0 @@ -function this = setHflag(this,hflag) - % setHflag sets the hflag property. - % - % Parameters: - % hflag: value for the hflag property, type double - % - % Return values: - % this: updated event definition object @type amievent - - try - if(all(size(this.bolus) == size(hflag))) - if(isa(hflag,'double')) - this.hflag = hflag~=0; - elseif(islogical(hflag)) - this.hflag = hflag; - else - error('provided hflag is not a double/logical value!'); - end - else - error('provided hflag does not match the bolus dimension!'); - end - catch - error('provided hflag does not match the bolus dimension!'); - end diff --git a/matlab/@amifun/amifun.m b/matlab/@amifun/amifun.m deleted file mode 100644 index e51c43b311..0000000000 --- a/matlab/@amifun/amifun.m +++ /dev/null @@ -1,74 +0,0 @@ -% -% @file amifun -% @brief definition of amifun class -% -classdef amifun - % AMIFUN defines functions which later on will be transformed into - % appropriate C code - - properties ( GetAccess = 'public', SetAccess = 'public' ) - % symbolic definition struct @type symbolic - sym = sym([]); - % symbolic definition which was not optimized (no dependencies on w) @type symbolic - sym_noopt = sym([]); - % short symbolic string which can be used for the reuse of precomputed values @type symbolic - strsym = sym([]); - % short symbolic string which can be used for the reuse of old values @type symbolic - strsym_old = sym([]); - % name of the model @type char - funstr = char.empty(); - % name of the c variable @type char - cvar = char.empty(); - % argument string (solver specific) @type char - argstr = char.empty(); - % dependencies on other functions @type cell - deps = cell.empty(); - % nvec dependencies - nvecs = cell.empty(); - % indicates whether the function is a sensitivity or derivative - % with respect to parameters - sensiflag = logical.empty(); - end - - methods - function AF = amifun(funstr,model) - % amievent constructs an amifun object from the provided input. - % - % Parameters: - % funstr: name of the requested function - % model: amimodel object which carries all symbolic - % definitions to construct the function - % - % - % Return values: - % AF: amifun object - % - AF.funstr = funstr; - AF = AF.getDeps(model); - AF = AF.getArgs(model); - AF = AF.getNVecs(); - AF = AF.getCVar(); - AF = AF.getSensiFlag(); - end - - writeCcode_sensi(this,model,fid) - - writeCcode(this,model,fid) - - writeMcode(this,model) - - gccode(this,model,fid) - - [ this ] = getDeps(this,model) - - [ this ] = getArgs(this,model) - - [ this ] = getNVecs(this) - - [ this ] = getCVar(this) - - [ this ] = getSensiFlag(this) - - [ this, model ] = getSyms(this,model) - end -end diff --git a/matlab/@amifun/gccode.m b/matlab/@amifun/gccode.m deleted file mode 100644 index 5670f26886..0000000000 --- a/matlab/@amifun/gccode.m +++ /dev/null @@ -1,161 +0,0 @@ -function this = gccode(this,model,fid) - % gccode transforms symbolic expressions into c code and writes the - % respective expression into a specified file - % - % Parameters: - % model: model definition object @type amimodel - % fid: file id in which the expression should be written @type fileid - % - % Return values: - % this: function definition object @type amifun - - - if(any(any(any(this.sym~=0)))) - - % replace unknown partial derivatives - if(model.maxflag) - this.sym = subs(this.sym,sym('D([1], am_max)'),sym('D1max')); - this.sym = subs(this.sym,sym('D([2], am_max)'),sym('D2max')); - this.sym = subs(this.sym,sym('am_max'),sym('max')); - end - - % If we have spline, we need to parse them to get derivatives - if (model.splineflag) - symstr = char(this.sym); - if (strfind(symstr, 'spline')) - tokens = regexp(symstr, 't\,\s(\w+\.\w+)\,', 'tokens'); - nNodes = round(str2double(tokens{1})); - end - if (regexp(symstr, 'D\(\[(\w+|\w+\,\w+)\]\,.spline')) - isDSpline = true; - else - isDSpline = false; - end - - if (isDSpline) - [~, nCol] = size(this.sym); - for iCol = 1 : nCol - for iNode = 1 : nNodes - if (model.o2flag) - for jNode = 1:nNodes - this.sym(:,iCol) = subs(this.sym(:,iCol),sym(['D([' num2str(iNode*2+2) ', ' num2str(jNode*2+2) '], spline_pos)']),sym(['D' num2str(iNode*2+2) 'D' num2str(jNode*2+2) 'spline_pos'])); - this.sym(:,iCol) = subs(this.sym(:,iCol),sym(['D([' num2str(iNode*2+2) ', ' num2str(jNode*2+2) '], spline)']),sym(['D' num2str(iNode*2+2) 'D' num2str(jNode*2+2) 'spline'])); - end - end - this.sym(:,iCol) = subs(this.sym(:,iCol),sym(['D([' num2str(iNode*2+2) '], spline_pos)']),sym(['D' num2str(iNode*2+2) 'spline_pos'])); - this.sym(:,iCol) = subs(this.sym(:,iCol),sym(['D([' num2str(iNode*2+2) '], spline)']),sym(['D' num2str(iNode*2+2) 'spline'])); - end - end - end - end - - cstr = ccode(this.sym); - if(~strcmp(cstr(3:4),'t0')) - if(any(strcmp(this.funstr,{'J','JB','JDiag','dJydsigma','dJydy','dJzdsigma','dJzdz','dJrzdsigma','dJrzdz','dydx','dzdx','drzdx','M','dfdx'}) )) - cstr = regexprep(cstr,'T\[([0-9]*)\]\[([0-9]*)\]',[this.cvar '[$1+$2*' num2str(size(this.sym,1)) ']']); - else - cstr = regexprep(cstr,'T\[([0-9]*)\]\[0\]',[this.cvar '_$1']); - cstr = regexprep(cstr,'T\[0\]\[([0-9]*)\]',[this.cvar '_$1']); - end - else - cstr = strrep(cstr,'t0',[this.cvar '_0']); - end - - cstr = strrep(cstr,'log','amici::log'); - % fix derivatives again (we cant do this before as this would yield - % incorrect symbolic expressions - cstr = regexprep(regexprep(cstr,'D([0-9]*)([\w]*)\(','D$2\($1,'),'DD([0-9]*)([\w]*)\(','DD$2\($1,'); - cstr = strrep(strrep(cstr, 'DDspline', 'DDspline'), 'Dspline', 'Dspline'); - - if (model.splineflag) - if (strfind(symstr, 'spline')) - % The floating numbers after 't' must be converted to integers - cstr = regexprep(cstr, '([D]*(spline|spline_pos))\(t\,\w+\.\w+\,', ['amici::$1\(t\,', num2str(nNodes), '\,']); - cstr = regexprep(cstr, '([D]*(spline|spline_pos))\((\w+)\,t\,\w+\.\w+\,', ['amici::$1\($2\,t\,', num2str(nNodes), '\,']); - cstr = regexprep(cstr, '([D]*(spline|spline_pos))\((\w+)\,(\w+)\,t\,\w+\.\w+\,', ['amici::$1\($2\,$3\,t\,', num2str(nNodes), '\,']); - end - end - - if(numel(cstr)>1) - - % fix various function specific variable names/indexes - - cstr = regexprep(cstr,'var_x_([0-9]+)','x[$1]'); - cstr = regexprep(cstr,'var_dx_([0-9]+)','dx[$1]'); - cstr = regexprep(cstr,'var_sx_([0-9]+)','sx[$1]'); - cstr = regexprep(cstr,'var_sdx_([0-9]+)','sdx[$1]'); - % sxdot needs to preces xdot - cstr = regexprep(cstr,'var_sxdot_([0-9]+)','sxdot[$1]'); - cstr = regexprep(cstr,'var_xdot_([0-9]+)','xdot[$1]'); - cstr = regexprep(cstr,'var_xBdot_([0-9]+)','xBdot[$1]'); - cstr = regexprep(cstr,'xdot_old_([0-9]+)','xdot_old[$1]'); - cstr = regexprep(cstr,'xdot_([0-9]+)','xdot[$1]'); - cstr = regexprep(cstr,'var_xB_([0-9]+)','xB[$1]'); - cstr = regexprep(cstr,'var_dxB_([0-9]+)','dxB[$1]'); - cstr = regexprep(cstr,'var_qBdot_([0-9]+)','qBdot[$1]'); - cstr = regexprep(cstr,'var_v_([0-9]+)', 'v[$1]'); - cstr = regexprep(cstr,'var_vB_([0-9]+)', 'vB[$1]'); - cstr = regexprep(cstr,'var_JSparse_([0-9]+)', 'JSparse->data[$1]'); - cstr = regexprep(cstr,'var_x0_([0-9]+)','x0[$1]'); - cstr = regexprep(cstr,'var_dx0_([0-9]+)','dx0[$1]'); - cstr = regexprep(cstr,'var_sx0_([0-9]+)','sx0[$1]'); - cstr = regexprep(cstr,'var_sdx0_([0-9]+)','sdx0[$1]'); - cstr = regexprep(cstr,'var_root_([0-9]+)', 'root[$1]'); - - cstr = regexprep(cstr,'var_p_([0-9]+)','p[$1]'); - cstr = regexprep(cstr,'var_k_([0-9]+)','k[$1]'); - cstr = regexprep(cstr,'h_([0-9]+)','h[$1]'); - cstr = regexprep(cstr,'var_w_([0-9]+)','w[$1]'); - cstr = regexprep(cstr,'var_dxdotdp_([0-9]+)','dxdotdp[$1]'); - cstr = regexprep(cstr,'var_stau_([0-9]+)','stau[0]'); - cstr = regexprep(cstr,'dfdx_([0-9]+)','dfdx[$1]'); - cstr = regexprep(cstr,'M_([0-9]+)','M[$1]'); - cstr = regexprep(cstr,'var_dwdx_([0-9]+)','dwdx[$1]'); - cstr = regexprep(cstr,'var_dwdp_([0-9]+)','dwdp[$1]'); - cstr = regexprep(cstr,'tmp_J_([0-9]+)','J->data[$1]'); - cstr = regexprep(cstr,'tmp_dxdotdp_([0-9]+)','dxdotdp[$1]'); - - cstr = regexprep(cstr,'var_y_([0-9]+)','y[$1]'); - cstr = regexprep(cstr,'my_([0-9]+)','my[$1]'); - cstr = regexprep(cstr,'var_z_([0-9]+)','z[$1]'); - cstr = regexprep(cstr,'mz_([0-9]+)','mz[$1]'); - cstr = regexprep(cstr,'var_rz_([0-9]+)','rz[$1]'); - cstr = regexprep(cstr,'var_srz_([0-9]+)','srz[$1]'); - cstr = regexprep(cstr,'var_sy_([0-9]+)','sy[$1]'); - cstr = regexprep(cstr,'var_sz_([0-9]+)','sz[$1]'); - - cstr = regexprep(cstr,'var_dydx[_\[]*([0-9\+\*]+)[\]]*','dydx[$1]'); % matches both _... and [...] - cstr = regexprep(cstr,'var_dzdx[_\[]*([0-9\+\*]+)[\]]*','dzdx[$1]'); - cstr = regexprep(cstr,'var_drzdx[_\[]*([0-9\+\*]+)[\]]*','drzdx[$1]'); - cstr = regexprep(cstr,'var_dydp_([0-9]+)',['dydp[$1]']); - cstr = regexprep(cstr,'var_dzdp_([0-9]+)',['dzdp[$1]']); - cstr = regexprep(cstr,'var_drzdp_([0-9]+)',['drzdp[$1]']); - cstr = regexprep(cstr,'var_drootdp_([0-9]+)',['drzdp[$1]']); - cstr = regexprep(cstr,'var_deltax_([0-9]+)','deltax[$1]'); - cstr = regexprep(cstr,'var_deltaxB_([0-9]+)','deltaxB[$1]'); - cstr = regexprep(cstr,'var_deltasx_([0-9]+)','deltasx[$1]'); - cstr = regexprep(cstr,'var_deltaqB_([0-9]+)','deltaqB[$1]'); - cstr = regexprep(cstr,'var_sigma_y_([0-9]+)','sigmay[$1]'); - cstr = regexprep(cstr,'var_sigma_z_([0-9]+)','sigmaz[$1]'); - cstr = regexprep(cstr,'var_dsigma_zdp_([0-9]+)',['dsigmazdp[$1]']); - cstr = regexprep(cstr,'var_dsigma_ydp_([0-9]+)',['dsigmaydp[$1]']); - - cstr = regexprep(cstr,'var_dsdydp_([0-9]+)',['dsigmaydp[ip*' num2str(model.ny) ' + $1]']); - cstr = regexprep(cstr,'var_dsdzdp_([0-9]+)',['dsigmazdp[ip*' num2str(model.nz) ' + $1]']); - cstr = regexprep(cstr,'var_Jy_([0-9]+)','nllh[$1]'); - cstr = regexprep(cstr,'var_Jz_([0-9]+)','nllh[$1]'); - cstr = regexprep(cstr,'var_Jrz_([0-9]+)','nllh[$1]'); % not a typo; we dont want to creat an additional variable that we end the end add to Jz anyways. - cstr = regexprep(cstr,'var_dJydy[_\[]*([0-9\+\*]+)[\]]*','dJydy[$1]'); % matches both _... and [...] - cstr = regexprep(cstr,'var_dJzdz[_\[]*([0-9\+\*]+)[\]]*','dJzdz[$1]'); - cstr = regexprep(cstr,'var_dJrzdz[_\[]*([0-9\+\*]+)[\]]*','dJrzdz[$1]'); - cstr = regexprep(cstr,'var_dJydsigma[_\[]*([0-9\+\*]+)[\]]*','dJydsigma[$1]'); - cstr = regexprep(cstr,'var_dJzdsigma[_\[]*([0-9\+\*]+)[\]]*','dJzdsigma[$1]'); - cstr = regexprep(cstr,'var_dJrzdsigma[_\[]*([0-9\+\*]+)[\]]*','dJrzdsigma[$1]'); - cstr = regexprep(cstr,'var_JDiag[_\[]*([0-9\+\*]+)[\]]*','JDiag[$1]'); - end - - %% - % print to file - fprintf(fid,[cstr '\n']); - end -end diff --git a/matlab/@amifun/getArgs.m b/matlab/@amifun/getArgs.m deleted file mode 100644 index a34c658ee5..0000000000 --- a/matlab/@amifun/getArgs.m +++ /dev/null @@ -1,122 +0,0 @@ -function this = getArgs(this,model) - % getFArgs populates the fargstr property with the argument string of - % the respective model function (if applicable). model functions are not - % wrapped versions of functions which have a model specific name and - % for which the call is solver specific. - % - % Parameters: - % model: model definition object @type amimodel - % - % Return values: - % this: updated function definition object @type amifun - % - - if(strcmp(model.wtype,'iw')) - dx = ', const realtype *dx'; - sdx = ', const realtype *sdx'; - dxB = ', const realtype *dxB'; - M = ', const realtype *M'; - cj = ', const realtype cj'; - else - dx = ''; - sdx = ''; - dxB = ''; - M = ''; - cj = ''; - end - - switch(this.funstr) - case 'xdot' - this.argstr = ['(realtype *xdot, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h' dx ', const realtype *w)']; - case 'xBdot' - this.argstr = ['(realtype *xBdot, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *xB' dx dxB ', const realtype *w, const realtype *dwdx)']; - case 'qBdot' - this.argstr = ['(realtype *qBdot, const int ip, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *xB' dx dxB ', const realtype *w, const realtype *dwdp)']; - case 'x0' - this.argstr = '(realtype *x0, const realtype t, const realtype *p, const realtype *k)'; - case 'dx0' - case 'JSparse' - this.argstr = ['(SUNMatrixContent_Sparse JSparse, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h' cj dx ', const realtype *w, const realtype *dwdx)']; - case 'sxdot' - this.argstr = ['(realtype *sxdot, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ip' dx ', const realtype *sx' sdx ', const realtype *w, const realtype *dwdx, const realtype *J' M ', const realtype *dxdotdp)']; - case 'sx0' - this.argstr = '(realtype *sx0, const realtype t,const realtype *x0, const realtype *p, const realtype *k, const int ip)'; - case 'root' - if(strcmp(model.wtype,'iw')) - this.argstr = ['(realtype *root, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h' dx ')']; - else - this.argstr = ['(realtype *root, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *tcl' dx ')']; - end - case 'y' - this.argstr = '(double *y, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w)'; - case 'z' - this.argstr = '(double *z, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h)'; - case 'rz' - this.argstr = '(double *rz, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h)'; - case 'sz' - this.argstr = '(double *sz, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *sx, const int ip)'; - case 'srz' - this.argstr = '(double *srz, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *sx, const int ip)'; - case 'dydp' - this.argstr = '(double *dydp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ip, const realtype *w, const realtype *dwdp)'; - case 'dydx' - this.argstr = '(double *dydx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *dwdx)'; - case 'dzdp' - this.argstr = '(double *dzdp, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ip)'; - case 'dzdx' - this.argstr = '(double *dzdx, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h)'; - case 'drzdp' - this.argstr = '(double *drzdp, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ip)'; - case 'drzdx' - this.argstr = '(double *drzdx, const int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h)'; - case 'deltax' - this.argstr = '(double *deltax, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ie, const realtype *xdot, const realtype *xdot_old)'; - case 'deltaxB' - this.argstr = '(double *deltaxB, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ie, const realtype *xdot, const realtype *xdot_old, const realtype *xB)'; - case 'deltaqB' - this.argstr = '(double *deltaqB, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ip, const int ie, const realtype *xdot, const realtype *xdot_old, const realtype *xB)'; - case 'deltasx' - this.argstr = '(double *deltasx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const int ip, const int ie, const realtype *xdot, const realtype *xdot_old, const realtype *sx, const realtype *stau, const realtype *tcl)'; - case 'dxdotdp' - this.argstr = ['(realtype *dxdotdp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const int ip' dx ', const realtype *w, const realtype *dwdp)']; - case 'sigma_y' - this.argstr = '(double *sigmay, const realtype t, const realtype *p, const realtype *k, const realtype *y)'; - case 'dsigma_ydp' - this.argstr = '(double *dsigmaydp, const realtype t, const realtype *p, const realtype *k, const realtype *y, const int ip)'; - case 'sigma_z' - this.argstr = '(double *sigmaz, const realtype t, const realtype *p, const realtype *k)'; - case 'dsigma_zdp' - this.argstr = '(double *dsigmazdp, const realtype t, const realtype *p, const realtype *k, const int ip)'; - case 'stau' - this.argstr = '(double *stau, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *tcl, const realtype *sx, const int ip, const int ie)'; - case 'Jy' - this.argstr = '(double *nllh, const int iy, const realtype *p, const realtype *k, const double *y, const double *sigmay, const double *my)'; - case 'dJydy' - this.argstr = '(double *dJydy, const int iy, const realtype *p, const realtype *k, const double *y, const double *sigmay, const double *my)'; - case 'dJydsigma' - this.argstr = '(double *dJydsigma, const int iy, const realtype *p, const realtype *k, const double *y, const double *sigmay, const double *my)'; - case 'Jz' - this.argstr = '(double *nllh, const int iz, const realtype *p, const realtype *k, const double *z, const double *sigmaz, const double *mz)'; - case 'Jrz' - this.argstr = '(double *nllh, const int iz, const realtype *p, const realtype *k, const double *rz, const double *sigmaz)'; - case 'dJzdz' - this.argstr = '(double *dJzdz, const int iz, const realtype *p, const realtype *k, const double *z, const double *sigmaz, const double *mz)'; - case 'dJzdsigma' - this.argstr = '(double *dJzdsigma, const int iz, const realtype *p, const realtype *k, const double *z, const double *sigmaz, const double *mz)'; - case 'dJrzdz' - this.argstr = '(double *dJrzdz, const int iz, const realtype *p, const realtype *k, const double *rz, const double *sigmaz)'; - case 'dJrzdsigma' - this.argstr = '(double *dJrzdsigma, const int iz, const realtype *p, const realtype *k, const double *rz, const double *sigmaz)'; - case 'w' - this.argstr = '(realtype *w, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *tcl, const realtype *spl, bool include_static)'; - case 'dwdp' - this.argstr = '(realtype *dwdp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *tcl, const realtype *stcl, const realtype *spl, const realtype *sspl, bool include_static)'; - case 'dwdx' - this.argstr = '(realtype *dwdx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *tcl, const realtype *spl, bool include_static)'; - case 'M' - this.argstr = '(realtype *M, const realtype t, const realtype *x, const realtype *p, const realtype *k)'; - otherwise - %nothing - end - -end diff --git a/matlab/@amifun/getCVar.m b/matlab/@amifun/getCVar.m deleted file mode 100644 index 43d5ecc2ed..0000000000 --- a/matlab/@amifun/getCVar.m +++ /dev/null @@ -1,19 +0,0 @@ -function [ this ] = getCVar(this) - % getCVar populates the cvar property - % - % Parameters: - % - % Return values: - % this: updated function definition object @type amifun - - switch(this.funstr) - case 'JSparse' - this.cvar = 'var_JSparse'; - case 'M' - this.cvar = 'M'; - case 'dfdx' - this.cvar = 'dfdx'; - otherwise - this.cvar = ['var_' this.funstr]; - end -end diff --git a/matlab/@amifun/getDeps.m b/matlab/@amifun/getDeps.m deleted file mode 100644 index 5249fde319..0000000000 --- a/matlab/@amifun/getDeps.m +++ /dev/null @@ -1,242 +0,0 @@ -function [ this ] = getDeps(this, model) - % getDeps populates the sensiflag for the requested function - % - % Parameters: - % model: model definition object @type amimodel - % - % Return values: - % this: updated function definition object @type amifun - - switch(this.funstr) - case 'xdot' - if(strcmp(model.wtype,'iw')) - this.deps = {'M','p','x','k','dx'}; - else - this.deps = {'p','x','k'}; - end - - case 'dfdx' - this.deps = {'xdot','x','dwdx'}; - - case 'J' - if(strcmp(model.wtype,'iw')) - this.deps = {'dfdx','M','x','xdot'}; - else - this.deps = {'xdot','x','dwdx'}; - end - - case 'dxdotdp' - this.deps = {'xdot','p','dwdp'}; - - case 'sx0' - this.deps = {'x0','p'}; - - case 'sdx0' - this.deps = {'dx0','p'}; - - case 'sxdot' - if(strcmp(model.wtype,'iw')) - this.deps = {'dfdx','M','dxdotdp','sdx','sx'}; - else - this.deps = {'J','dxdotdp','sx'}; - end - - case 'dydx' - this.deps = {'y','x'}; - - case 'dydp' - this.deps = {'y','p'}; - - case 'sy' - this.deps = {'dydp','dydx','sx'}; - - case 'Jv' - this.deps = {'J'}; - - case 'JvB' - this.deps = {'J'}; - - case 'xBdot' - if(strcmp(model.wtype,'iw')) - this.deps = {'J','M','xB','dxB'}; - else - this.deps = {'J','xB'}; - end - - case 'qBdot' - this.deps = {'dxdotdp','xB'}; - - case 'dsigma_ydp' - this.deps = {'sigma_y','p'}; - - case 'dsigma_zdp' - this.deps = {'sigma_z','p'}; - - case 'root' - this.deps = {'x','k','p'}; - - case 'drootdp' - this.deps = {'root','p','drootdx','sx'}; - - case 'drzdp' - this.deps = {'rz','p',}; - - case 'drootdx' - this.deps = {'root','x'}; - - case 'drzdx' - this.deps = {'rz','x',}; - - case 'drootdt' - % w is necessary for xdot_noopt - this.deps = {'root','drootdx','xdot','w'}; - - case 'deltax' - this.deps = {'x','k','p'}; - - case 'ddeltaxdp' - this.deps = {'deltax','p'}; - - case 'ddeltaxdx' - this.deps = {'deltax','x'}; - - case 'ddeltaxdt' - this.deps = {'deltax'}; - - case 'deltasx' - this.deps = {'deltax','deltaxdot','ddeltaxdx','ddeltaxdp','ddeltaxdt','dtaudp','xdot','sx','stau'}; - - case 'deltaqB' - this.deps = {'ddeltaxdp','xB'}; - - case 'deltaxB' - this.deps = {'deltax','dtaudp','xdot','xB','ddeltaxdx'}; - - case 'z' - this.deps = {'x','k','p'}; - - case 'rz' - this.deps = {'z','root'}; - - case 'srz' - this.deps = {'rz','root','drootdx','drootdp','sx'}; - - case 'dzdp' - this.deps = {'z','p','dtaudp'}; - - case 'dzdx' - this.deps = {'z','x','dtaudx'}; - - case 'dzdt' - % w is necessary for xdot_noopt - this.deps = {'z','x','xdot','w'}; - - case 'sz' - this.deps = {'dzdp','dzdx','dzdt','sx','dtaudp','stau'}; - - case 'sz_tf' - this.deps = {'dzdp','dzdx','sx'}; - - case 'dtaudp' - this.deps = {'drootdp','drootdt'}; - - case 'dtaudx' - this.deps = {'drootdx','drootdt'}; - - case 'stau' - this.deps = {'sroot','drootdt'}; - - case 'sroot' - this.deps = {'drootdp','drootdx','sx'}; - - case 'x0' - this.deps = {'p','k','x'}; - - case 'JBand' - this.deps = {'J'}; - - case 'JBandB' - this.deps = {'JB'}; - - case 'JSparse' - this.deps = {'J'}; - - case 'y' - this.deps = {'x','p','k'}; - - case 'sigma_y' - this.deps = {'p','k'}; - - case 'sigma_z' - this.deps = {'p','k'}; - - case 'rhs' - this.deps = {'xdot'}; - - case 'dx0' - this.deps = {'x','p','k'}; - - case 'M' - this.deps = {'x','p','k'}; - - case 'x' - this.deps = {}; - - case 'dx' - this.deps = {}; - - case 'xB' - this.deps = {}; - - case 'dxB' - this.deps = {}; - - case 'k' - this.deps = {}; - - case 'p' - this.deps = {}; - - case 'sx' - this.deps = {}; - - case 'sdx' - this.deps = {}; - - case 'deltaxdot' - this.deps = {'xdot'}; - - case 'Jy' - this.deps = {'y','sigma_y'}; - case 'dJydy' - this.deps = {'Jy','y'}; - case 'dJydsigma' - this.deps = {'Jy','sigma_y'}; - - case 'Jz' - this.deps = {'z','sigma_z'}; - case 'dJzdz' - this.deps = {'Jz','x'}; - case 'dJzdsigma' - this.deps = {'Jz','sigma_z'}; - case 'Jrz' - this.deps = {'rz','sigma_z'}; - case 'dJrzdz' - this.deps = {'Jrz','x'}; - case 'dJrzdsigma' - this.deps = {'Jrz','sigma_z'}; - - case 'w' - this.deps = {'xdot'}; - case 'dwdp' - this.deps = {'w','p'}; - case 'dwdx' - this.deps = {'w','x'}; - - case 's2root' - this.deps = {'sroot'}; - - otherwise - error(['unknown function string: ' this.funstr ]) - end -end diff --git a/matlab/@amifun/getNVecs.m b/matlab/@amifun/getNVecs.m deleted file mode 100644 index 1eb6670c57..0000000000 --- a/matlab/@amifun/getNVecs.m +++ /dev/null @@ -1,26 +0,0 @@ -function this = getNVecs(this) - % getfunargs populates the nvecs property with the names of the - % N_Vector elements which are required in the execution of the function - % (if applicable). the information is directly extracted from the - % argument string - % - % Parameters: - % - % Return values: - % this: updated function definition object @type amifun - % - - vecs = {'x,','dx,','sx,','*sx,','sdx,','xB,','dxB,',... - '*sxdot,','sxdot,','xdot,','xBdot,','qBdot,',... - 'x0,','dx0,','*sx0,','*sdx0,',... - 'v,','vB,','JDiag,','Jv,','JvB,',... - 'xdot_old,'}; - - this.nvecs = {}; - for iv = 1:length(vecs) - if strfind(this.argstr,['N_Vector ' vecs{iv}]) - this.nvecs = [this.nvecs,vecs{iv}(1:(end-1))]; - end - end - -end diff --git a/matlab/@amifun/getSensiFlag.m b/matlab/@amifun/getSensiFlag.m deleted file mode 100644 index 195f119184..0000000000 --- a/matlab/@amifun/getSensiFlag.m +++ /dev/null @@ -1,74 +0,0 @@ -function [ this ] = getSensiFlag(this) - % getSensiFlag populates the sensiflag property - % - % Parameters: - % - % Return values: - % this: updated function definition object @type amifun - - switch(this.funstr) - case 'dxdotdp' - this.sensiflag = true; - - case 'sx0' - this.sensiflag = true; - - case 'sdx0' - this.sensiflag = true; - - - case 'dydp' - this.sensiflag = true; - - case 'sy' - this.sensiflag = true; - - case 'qBdot' - this.sensiflag = true; - - case 'dsigma_ydp' - this.sensiflag = true; - - case 'dsigma_zdp' - this.sensiflag = true; - - case 'drzdp' - this.sensiflag = true; - - case 'ddeltaxdp' - this.sensiflag = true; - - case 'deltasx' - this.sensiflag = true; - - case 'deltaqB' - this.sensiflag = true; - - case 'dzdp' - this.sensiflag = true; - - case 'sz' - this.sensiflag = true; - - case 'dtaudp' - this.sensiflag = true; - - case 'stau' - this.sensiflag = true; - - case 'sroot' - this.sensiflag = true; - - case 'srz' - this.sensiflag = true; - - case 'sx' - this.sensiflag = true; - - case 'sdx' - this.sensiflag = true; - - otherwise - this.sensiflag = false; - end -end diff --git a/matlab/@amifun/getSyms.m b/matlab/@amifun/getSyms.m deleted file mode 100644 index 77c23f75af..0000000000 --- a/matlab/@amifun/getSyms.m +++ /dev/null @@ -1,824 +0,0 @@ -function [this,model] = getSyms(this,model) - % getSyms computes the symbolic expression for the requested function - % - % Parameters: - % model: model definition object @type amimodel - % - % Return values: - % this: updated function definition object @type amifun - % model: updated model definition object @type amimodel - - % store often used variables for ease of notation, dependencies should - % ensure that these variables are defined - - persistent x p sx w ndw jacw - - nx = model.nx; - nevent = model.nevent; - np = model.np; - nk = model.nk; - nz = model.nz; - ny = model.ny; - - fprintf([this.funstr ' | ']) - switch(this.funstr) - case 'x' - % create cell array of same size - xs = cell(nx,1); - % fill cell array - for j=1:nx - xs{j} = sprintf('var_x_%i',j-1); - end - % transform into symbolic expression - this.sym = sym(xs); - x = this.sym; - - case 'dx' - % create cell array of same size - dxs = cell(nx,1); - % fill cell array - for j=1:nx - dxs{j} = sprintf('var_dx_%i',j-1); - end - % transform into symbolic expression - this.sym = sym(dxs); - - case 'p' - % create cell array of same size - ps = cell(np,1); - % fill cell array - for j=1:np - ps{j} = sprintf('var_p_%i',j-1); - end - % transform into symbolic expression - this.sym = sym(ps); - p = this.sym; - - case 'k' - % create cell array of same size - ks = cell(nk,1); - % fill cell array - for j=1:nk - ks{j} = sprintf('var_k_%i',j-1); - end - % transform into symbolic expression - this.sym = sym(ks); - - case 'sx' - % create cell array of same size - sxs = cell(nx,1); - % fill cell array - for j = 1:nx - sxs{j} = sprintf('var_sx_%i', j-1); - end - % transform into symbolic expression - this.sym = repmat(sym(sxs),[1,np]); - sx = this.sym; - - case 'sdx' - % create cell array of same size - sdx = cell(nx,np); - % fill cell array - for j = 1:nx - for i = 1:np - sdx{j,i} = sprintf('var_sdx_%i', j-1); - end - end - % transform into symbolic expression - this.sym = sym(sdx); - - case 'xB' - % create cell array of same size - xBs = cell(nx,1); - % fill cell array - for j = 1:nx - xBs{j} = sprintf('var_xB_%i', j-1); - end - % transform into symbolic expression - this.sym = sym(xBs); - - case 'dxB' - % create cell array of same size - dxBs = cell(nx,1); - % fill cell array - for j = 1:nx - dxBs{j} = sprintf('var_dxB_%i', j-1); - end - % transform into symbolic expression - this.sym = sym(dxBs); - - case 'y' - this.sym = model.sym.y; - % replace unify symbolic expression - this = unifySyms(this,model); - this = makeStrSymsFull(this); - - - % activate splines - for iy = 1:ny - if(not(all([model.splineflag,model.minflag,model.maxflag]))) - str = char(this.sym(iy)); - if(strfind(str,'spline')) - model.splineflag = true; - end - if(strfind(str,'max')) - model.maxflag = true; - end - if(strfind(str,'min')) - model.minflag = true; - end - end - end - - case 'x0' - this.sym = model.sym.x0; - % replace unify symbolic expression - this = unifySyms(this,model); - - case 'dx0' - this.sym = model.sym.dx0; - % replace unify symbolic expression - this = unifySyms(this,model); - - case 'sigma_y' - this.sym = model.sym.sigma_y; - this = makeStrSymsFull(this); - % replace unify symbolic expression - this = unifySyms(this,model); - - case 'sigma_z' - this.sym = model.sym.sigma_z; - this = makeStrSymsFull(this); - % replace unify symbolic expression - this = unifySyms(this,model); - - case 'M' - this.sym = sym(model.sym.M); - % replace unify symbolic expression - this = unifySyms(this,model); - this = makeStrSyms(this); - - case 'xdot' - this.sym = model.sym.xdot; - % replace unify symbolic expression - this = unifySyms(this,model); - - if(strcmp(model.wtype,'iw')) - if(size(this.sym,2)>size(this.sym,1)) - this.sym = -transpose(model.fun.M.sym*model.fun.dx.sym)+this.sym; - else - this.sym = -model.fun.M.sym*model.fun.dx.sym+this.sym; - end - end - - % create cell array of same size - xdots = cell(nx,1); - xdot_olds = cell(nx,1); - % fill cell array - for j=1:nx - xdots{j} = sprintf('xdot_%i',j-1); - xdot_olds{j} = sprintf('xdot_old_%i',j-1); - end - this.strsym = sym(xdots); - this.strsym_old = sym(xdot_olds); - - % activate splines - for ix = 1:nx - if(not(all([model.splineflag,model.minflag,model.maxflag]))) - str = char(this.sym(ix)); - if(strfind(str,'spline')) - model.splineflag = true; - end - if(strfind(str,'max')) - model.maxflag = true; - end - if(strfind(str,'min')) - model.minflag = true; - end - end - end - - case 'w' - optimize = getoptimized(optsym(model.fun.xdot.sym)); - tmpxdot = sym(char(optimize(end))); - nw = (length(optimize)-1); - model.nw = nw; - if(nw>0) - exprs = arrayfun(@(x) children(x),optimize(1:(end-1)),'UniformOutput',false); % extract symbolic variable - S.subs = {2}; - S.type='()'; - C = cellfun(@(x) subsref(x,S),exprs,'UniformOutput',false); % get second element - this.sym = [C{:}]; % transform cell to matrix - S.subs = {1}; - C = cellfun(@(x) subsref(x,S),exprs,'UniformOutput',false); - temps = [C{:}]; - end -% model.nw = 0; -% nw = 0; -% this.sym = sym(zeros(0,1)); - - - - ws = cell(nw,1); - ts = cell(nw,1); - % fill cell array - for iw = 1:nw - ws{iw} = sprintf('var_w_%i', iw-1); - end - % transform into symbolic expression - this.strsym = sym(ws); - ndw = 0; - if(nw>0) - tmpxdot = mysubs(tmpxdot,temps,this.strsym); % replace common expressions - this.sym = mysubs(this.sym,temps,this.strsym); - model.updateRHS(tmpxdot); % update rhs - ndw = 1; - jacw = jacobian(this.sym,this.strsym); - vv = sym('v',[length(this.strsym),1]); - while(sum(jacw^ndw*vv)~=0) - ndw = ndw+1; - end - ndw = ndw - 1; - else - model.updateRHS(tmpxdot); % update RHS anyways to generate sym_noopt for fun.xdot - end - w = this.strsym; - - case 'dwdx' - if(length(model.fun.w.sym)>0) - jacx = jacobian(model.fun.w.sym,x); - this.sym = jacx; - for idw = 1:ndw - this.sym = this.sym + (jacw^idw)*jacx; % this part is only to get the right nonzero entries - end - % fill cell array - idx_w = find(this.sym); - this.strsym = sym(zeros(size(jacx))); - if(numel(idx_w)>0) - for iw = 1:length(idx_w) - this.strsym(idx_w(iw)) = sym(sprintf('var_dwdx_%i', iw-1)); - end - model.ndwdx = length(idx_w); - % update dwdx with simplified expressions, here we can exploit - % the proper ordering of w to ensure correctness of expressions - tmp = jacx + jacw*this.strsym; - this.sym = tmp(idx_w); - else - this.strsym = sym(zeros(size(jacx))); - end - else - this.sym = sym(zeros(0,nx)); - this.strsym = sym(zeros(0,nx)); - end - - case 'dwdp' - if(length(model.fun.w.sym)>0) - jacp = jacobian(model.fun.w.sym,p); - this.sym = jacp; - for idw = 1:ndw - this.sym = this.sym + (jacw^idw)*jacp; % this part is only to get the right nonzero entries - end - % fill cell array - idx_w = find(this.sym); - this.strsym = sym(zeros(size(jacp))); - if(numel(idx_w)>0) - for iw = 1:length(idx_w) - this.strsym(idx_w(iw)) = sym(sprintf('var_dwdp_%i', iw-1)); - end - model.ndwdp = length(idx_w); - % update dwdx with simplified expressions, here we can exploit - % the proper ordering of w to ensure correctness of expressions - tmp = jacp + jacw*this.strsym; - this.sym = tmp(idx_w); - end - else - this.sym = sym(zeros(0,nx)); - this.strsym = sym(zeros(0,nx)); - end - - case 'dfdx' - this.sym=jacobian(model.fun.xdot.sym,x) + jacobian(model.fun.xdot.sym,w)*model.fun.dwdx.strsym; - this = makeStrSyms(this); - - case 'J' - if(strcmp(model.wtype,'iw')) - syms cj - this.sym = model.fun.dfdx.sym - cj*model.fun.M.sym; - else - if(~isempty(w)) - this.sym = jacobian(model.fun.xdot.sym,x) + jacobian(model.fun.xdot.sym,w)*model.fun.dwdx.strsym; - this.sym_noopt = jacobian(model.fun.xdot.sym_noopt,x); - else - this.sym = jacobian(model.fun.xdot.sym,x); - this.sym_noopt = this.sym; - end - end - - this = makeStrSymsSparse(this); - - - - case 'JDiag' - this.sym = diag(model.fun.J.sym); - this = makeStrSyms(this); - - case 'dxdotdp' - if(~isempty(w)) - this.sym=jacobian(model.fun.xdot.sym,p) + jacobian(model.fun.xdot.sym,w)*model.fun.dwdp.strsym; - this.sym_noopt = jacobian(model.fun.xdot.sym_noopt,p); - else - this.sym=jacobian(model.fun.xdot.sym,p); - this.sym_noopt = this.sym; - end - - %% - % build short strings for reuse of dxdotdp - % create cell array of same size - dxdotdps = cell(nx,1); - % fill cells with strings - for ix = 1:nx - dxdotdps{ix} = sprintf('tmp_dxdotdp_%i',ix-1); - end - % create full symbolic array - this.strsym = sym(dxdotdps); - - case 'sx0' - this.sym=jacobian(model.fun.x0.sym,p); - - case 'sdx0' - this.sym=jacobian(model.fun.dx0.sym,p); - - case 'sxdot' - if(np>0) - if(strcmp(model.wtype,'iw')) - this.sym=model.fun.J.strsym*sx(:,1)-model.fun.M.strsym*model.fun.sdx.sym(:,1)+model.fun.dxdotdp.strsym; - else - this.sym=model.fun.J.strsym*sx(:,1)+model.fun.dxdotdp.strsym; - end - else - this.sym = sym(zeros(size(sx,1),0)); - end - - case 'dydx' - this.sym=jacobian(model.fun.y.sym,x); - % create cell array of same sizex - this.strsym = sym(zeros(ny,nx)); - % fill cell array - this = makeStrSyms(this); - - case 'dydp' - this.sym=jacobian(model.fun.y.sym,p); - % create cell array of same size - this = makeStrSyms(this); - - case 'xBdot' - if(strcmp(model.wtype,'iw')) - syms t - this.sym = diff(transpose(model.fun.M.sym),t)*model.fun.xB.sym + transpose(model.fun.M.sym)*model.fun.dxB.sym - transpose(model.fun.dfdx.sym)*model.fun.xB.sym; - else - this.sym = model.fun.JB.sym * model.fun.xB.sym; - end - - case 'qBdot' - % If we do second order adjoints, we have to augment - if (model.nxtrue < nx) - this.sym = sym(zeros(model.ng, model.np)); - this.sym(1,:) = -transpose(model.fun.xB.sym(1:model.nxtrue)) * model.fun.dxdotdp.sym(1:model.nxtrue, :); - for ig = 2 : model.ng - this.sym(ig,:) = ... - -transpose(model.fun.xB.sym(1:model.nxtrue)) * model.fun.dxdotdp.sym((ig-1)*model.nxtrue+1 : ig*model.nxtrue, :) ... - -transpose(model.fun.xB.sym((ig-1)*model.nxtrue+1 : ig*model.nxtrue)) * model.fun.dxdotdp.sym(1:model.nxtrue, :); - end - else - this.sym = -transpose(model.fun.xB.sym)*model.fun.dxdotdp.sym; - end - - case 'dsigma_ydp' - this.sym = jacobian(model.fun.sigma_y.sym,p); - this = makeStrSyms(this); - - case 'dsigma_zdp' - if(nz>0) - this.sym = jacobian(model.fun.sigma_z.sym,p); - else - this.sym = sym(zeros(model.nz,np)); - end - this = makeStrSyms(this); - - case 'root' - if(nevent>0) - this.sym = transpose([model.event.trigger]); - this = unifySyms(this,model); - else - this.sym = sym(zeros(0,1)); - end - for ir = 1:nevent - if(not(all([model.splineflag,model.minflag,model.maxflag]))) - str = char(this.sym(ir)); - if(strfind(str,'spline')) - model.splineflag = true; - end - if(strfind(str,'max')) - model.maxflag = true; - end - if(strfind(str,'min')) - model.minflag = true; - end - end - end - - case 'drootdp' - this.sym = jacobian(model.fun.root.sym,p); - - case 'drootdx' - this.sym = jacobian(model.fun.root.sym,x); - - case 'drootdt' - % noopt is important here to get derivatives right - this.sym = diff(model.fun.root.sym,'t') + model.fun.drootdx.sym*model.fun.xdot.sym_noopt; - - case 'sroot' - this.sym = model.fun.drootdp.sym + model.fun.drootdx.sym*sx; - - case 'srz' - if(isfield(model.sym,'rz')) % user defined input or from augmentation - this.sym = jacobian(model.fun.rz.sym,p) + jacobian(model.fun.rz.sym,x)*sx; - else - for iz = 1:length(model.z2event) - this.sym(iz,:) = model.fun.sroot.sym(model.z2event(iz),:); - end - end - - case 's2root' - switch(model.o2flag) - case 1 - s2x = reshape(sx((model.nxtrue+1):end,:),[model.nxtrue,np,np]); - vec = sym(eye(np)); - case 2 - s2x = reshape(sx((model.nxtrue+1):end,:),[model.nxtrue,np,1]); - vec = model.sym.k((end-np+1):end); - end - for ievent = 1:nevent - - this.sym(ievent,:,:) = (jacobian(model.fun.sroot.sym(ievent,:),p) + jacobian(model.fun.sroot.sym(ievent,:),x(1:model.nxtrue))*sx(1:model.nxtrue,:) + jacobian(model.fun.sroot.sym(ievent,:),x(1:model.nxtrue))*sx(1:model.nxtrue,:))*vec; - for ix = 1:model.nxtrue - this.sym(ievent,:,:) = this.sym(ievent,:,:) + model.fun.drootdx.sym(ievent,ix)*s2x(ix,:,:); - end - end - - case 'dtaudp' - this.sym = sym(zeros(nevent,np)); - for ievent = 1:nevent - this.sym(ievent,:) = - model.fun.drootdp.sym(ievent,:)/model.fun.drootdt.sym(ievent); - end - - case 'dtaudx' - this.sym = sym(zeros(nevent,nx)); - for ievent = 1:nevent - this.sym(ievent,:) = - model.fun.drootdx.sym(ievent,:)/model.fun.drootdt.sym(ievent); - end - - case 'stau' - this.sym = sym(zeros(nevent,np)); - for ievent = 1:nevent - this.sym(ievent,:) = - model.fun.sroot.sym(ievent,:)/model.fun.drootdt.sym(ievent); - end - % create cell array of same size - staus = cell(1,np); - % fill cells - for j=1:np - staus{j} = sprintf('var_stau_%i',j-1); - end - % transform to symbolic variable - staus = sym(staus); - % multiply - this.strsym = staus; - - case 'deltax' - if(nevent>0) - this.sym = [model.event.bolus]; - this = unifySyms(this,model); - else - this.sym = sym(zeros(0,1)); - end - - case 'deltaxdot' - this.sym = model.fun.xdot.strsym-model.fun.xdot.strsym_old; - - case 'ddeltaxdp' - this.sym = sym(zeros(nx,nevent,np)); - for ievent = 1:nevent - this.sym(:,ievent,:) = jacobian(model.fun.deltax.sym(:,ievent),p); - end - - case 'ddeltaxdx' - this.sym = sym(zeros(nx,nevent,nx)); - if(nx>0) - for ievent = 1:nevent - this.sym(:,ievent,:) = jacobian(model.fun.deltax.sym(:,ievent),x); - end - end - - case 'ddeltaxdt' - this.sym = diff(model.fun.deltax.sym,'t'); - - case 'deltasx' - - if(nevent>0) - for ievent = 1:nevent - - % dtdp = (1/drdt)*drdp - dtdp = model.fun.stau.strsym; % this 1 here is correct, we explicitely do not want ievent here as the actual stau_tmp will only have dimension np - - % if we are just non-differentiable and but not - % discontinuous we can ignore some of the terms! - if(any(logical(model.fun.deltax.sym(:,ievent)~=0))) - % dxdp = dx/dt*dt/dp + dx/dp - dxdp = sym(zeros(nx,np)); - for ix = 1:nx - dxdp(ix,:) = model.fun.xdot.sym(ix)*dtdp + sx(ix,:); - end - - this.sym(:,:,ievent) = ... - + permute(model.fun.ddeltaxdx.sym(:,ievent,:),[1 3 2])*dxdp ... - + model.fun.ddeltaxdt.sym(:,ievent)*dtdp ... - + permute(model.fun.ddeltaxdp.sym(:,ievent,:),[1 3 2]); - else - this.sym(:,:,ievent) = sym(zeros([nx,np])); - end - if(any(model.event(ievent).hflag)) - this.sym(model.event(ievent).hflag,:,ievent) = ... - this.sym(model.event(ievent).hflag,:,ievent) ... - - model.fun.deltaxdot.sym(model.event(ievent).hflag)*dtdp; - end - end - end - - case 'deltaqB' - if (model.nxtrue < nx) - ng_tmp = round(nx / model.nxtrue); - this.sym = sym(zeros(np*ng_tmp,nevent)); - else - this.sym = sym(zeros(np,nevent)); - end - - for ievent = 1:nevent - this.sym(1:np,ievent) = -transpose(model.fun.xB.sym)*squeeze(model.fun.ddeltaxdp.sym(:,ievent,:)); - % This is just a very quick fix. Events in adjoint systems - % have to be implemented in a way more rigorous way later - % on... Some day... - end - - case 'deltaxB' - this.sym = sym(zeros(nx,nevent)); - for ievent = 1:nevent - this.sym(:,ievent) = -transpose(squeeze(model.fun.ddeltaxdx.sym(:,ievent,:)))*model.fun.xB.sym; - end - - - case 'z' - if(nevent>0) - this.sym = transpose([model.event.z]); - this = unifySyms(this,model); - else - this.sym = sym(zeros(0,1)); - end - % construct the event identifyer, this is a vector which maps - % events to outputs z - model.z2event = zeros(length(this.sym),1); - iz = 0; - for ievent = 1:nevent - for jz = 1:length(model.event(ievent).z) - iz = iz+1; - model.z2event(iz) = ievent; - end - end - this = makeStrSymsFull(this); - - case 'rz' - this.sym = sym(zeros(size(model.fun.z.sym))); - if(isfield(model.sym,'rz')) - this.sym = model.sym.rz; - else - for iz = 1:length(model.z2event) - this.sym(iz) = model.fun.root.sym(model.z2event(iz)); - end - end - this = unifySyms(this,model); - this = makeStrSymsFull(this); - - case 'dzdp' - this.sym = jacobian(model.fun.z.sym,p); - - for iz = 1:nz - this.sym(iz,:) = this.sym(iz,:) + diff(model.fun.z.sym(iz),sym('t'))*model.fun.dtaudp.sym(model.z2event(iz),:); - end - % create cell array of same size - this = makeStrSyms(this); - - case 'drzdp' - this.sym = jacobian(model.fun.rz.sym,p); - this = makeStrSyms(this); - - case 'dzdx' - this.sym = jacobian(model.fun.z.sym,x); - for iz = 1:nz - this.sym(iz,:) = this.sym(iz,:)+ diff(model.fun.z.sym(iz),sym('t'))*model.fun.dtaudx.sym(model.z2event(iz),:); - end - this = makeStrSyms(this); - - case 'drzdx' - this.sym = jacobian(model.fun.rz.sym,x); - this = makeStrSyms(this); - - case 'dzdt' - if(nz>0) - this.sym = diff(model.fun.z.sym,'t')+jacobian(model.fun.z.sym,x(1:model.nxtrue))*model.fun.xdot.sym_noopt(1:model.nxtrue); - else - this.sym = sym.empty(); - end - - case 'sz' - this.sym = sym(zeros(nz,np)); - tmpsym = sym(zeros(nz-model.nztrue,np)); - for iz = 1:nz - if iz <= model.nztrue - this.sym(iz,:) = ... - + jacobian(model.fun.z.sym(iz),p) ... - + jacobian(model.fun.z.sym(iz),x(1:model.nxtrue))*sx(1:model.nxtrue,:) ... - + model.fun.dzdt.sym(iz)*model.fun.stau.sym(model.z2event(iz),:); - else - % I have discovered a truly marvelous proof of these equations, which this margin is too small to - % contain. - % - % we need (dtau/dt)\(ddzdpdt*dtdp + dtdpddzdtdp + ddzdpdp + dtdp*ddzdtdt*dtdp here - % term above: jacx*sx+jacp dzdt*st jacx*sx+jacp dzdt*st - % term in aug z: drootdt dzdp dzdp drootdt - % augmented is always drootdt\dzdp or linear combinations - % we cannot correctly compute ddzdpdt, but only ddzdtdp so we omit the drootdt part of jacx*sx-jacp - % symmetrise it and add it later on. - % also the dzdp part contains second order sensitivities and the drootdt part does not (this leads - % to 1:model.nxtrue indexing) - - - if(model.o2flag==1) - tmpsym(iz-model.nztrue,:) = jacobian(1/model.fun.drootdt.sym(model.z2event(iz)),p)*model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)) ... - + jacobian(1/model.fun.drootdt.sym(model.z2event(iz)),x(1:model.nxtrue))*sx(1:model.nxtrue,:)*model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)); - else - error('sz for directional second order sensis was never implemented and I do not know how to, you are on your own here.'); - end - - this.sym(iz,:) = ... - + jacobian(model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)),p)/model.fun.drootdt.sym(model.z2event(iz)) ... - + jacobian(model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)),x)*sx/model.fun.drootdt.sym(model.z2event(iz)) ... - + model.fun.dzdt.sym(iz)*model.fun.stau.sym(model.z2event(iz),:); - end - end - if(model.nz>0) - if(model.o2flag==1) - % THIS IS WHAT YOU NEED TO FIX FOR model.o2flag == 2, transpose does not do the deal :( - % we need to pay attention here, some sensitivities are encoded as sx and some as augmented x. - % the sx ones are not transposable (as the parameter is encoded in the matrix column) so we need to - % convert all of them to augmented x. - for ip = 1:np - tmpsym(:,ip) = subs(tmpsym(:,ip),sx(1:model.nxtrue,1),x((model.nxtrue*ip+1):(model.nxtrue*(ip+1)))); - end - this.sym(model.nztrue+1:end,:) = this.sym(model.nztrue+1:end,:) + tmpsym + transpose(tmpsym); - % you might not believe this, but this matrix should (and hopefully will) actually be symmetric ;) - end - end - - % create cell array of same size - szs = cell(nz,np); - % fill cell array - for j = 1:nz - for i = 1:np - szs{j,i} = sprintf('sz_%i', j-1); - end - end - % transform into symbolic expression - this.strsym = sym(szs); - - case 'JBand' - %do nothing - case 'JBandB' - %do nothing - case 'JSparse' - %do nothing - - case 'Jy' - this.sym = model.sym.Jy; - % replace unify symbolic expression - this = unifySyms(this,model); - tmp = arrayfun(@(iy)sym(sprintf('y_%i',iy-1)),1:ny,'UniformOutput',false); - this.sym = mysubs(this.sym,[tmp{:}],model.fun.y.strsym); - tmp = arrayfun(@(iy)sym(sprintf('sigma_y_%i',iy-1)),1:ny,'UniformOutput',false); - this.sym = mysubs(this.sym,[tmp{:}],model.fun.sigma_y.strsym); - case 'dJydy' - this.sym = sym(zeros(model.nytrue, model.ng, model.ny)); - for iyt = 1 : model.nytrue - this.sym(iyt,:,:) = jacobian(model.fun.Jy.sym(iyt,:),model.fun.y.strsym); - end - case 'dJydsigma' - this.sym = sym(zeros(model.nytrue, model.ng, model.ny)); - for iyt = 1 : model.nytrue - this.sym(iyt,:,:) = jacobian(model.fun.Jy.sym(iyt,:),model.fun.sigma_y.strsym); - end - case 'Jz' - this.sym = model.sym.Jz; - this = unifySyms(this,model); - z = arrayfun(@(iz)sym(sprintf('z_%i',iz-1)),1:nz,'UniformOutput',false); - this.sym = mysubs(this.sym,[z{:}],model.fun.z.strsym); - tmp = arrayfun(@(iz)sym(sprintf('sigma_z_%i',iz-1)),1:nz,'UniformOutput',false); - this.sym = mysubs(this.sym,[tmp{:}],model.fun.sigma_z.strsym); - case 'Jrz' - this.sym = model.sym.Jrz; - this = unifySyms(this,model); - tmp = arrayfun(@(iz)sym(sprintf('rz_%i',iz-1)),1:nz,'UniformOutput',false); - this.sym = mysubs(this.sym,[tmp{:}],model.fun.z.strsym); - tmp = arrayfun(@(iz)sym(sprintf('sigma_z_%i',iz-1)),1:nz,'UniformOutput',false); - this.sym = mysubs(this.sym,[tmp{:}],model.fun.sigma_z.strsym); - case 'dJzdz' - this.sym = sym(zeros(model.nztrue, model.ng, model.nz)); - for iz = 1 : model.nztrue - this.sym(iz,:,:) = jacobian(model.fun.Jz.sym(iz,:),model.fun.z.strsym); - end - this = makeStrSyms(this); - case 'dJrzdz' - this.sym = sym(zeros(model.nztrue, model.ng, model.nz)); - for iz = 1 : model.nztrue - this.sym(iz,:,:) = jacobian(model.fun.Jrz.sym(iz,:),model.fun.rz.strsym); - end - this = makeStrSyms(this); - case 'dJzdsigma' - this.sym = sym(zeros(model.nztrue, model.ng, model.nz)); - for iz = 1 : model.nztrue - this.sym(iz,:,:) = jacobian(model.fun.Jz.sym(iz,:),model.fun.sigma_z.strsym); - end - case 'dJrzdsigma' - this.sym = sym(zeros(model.nztrue, model.ng, model.nz)); - for iz = 1 : model.nztrue - this.sym(iz,:,:) = jacobian(model.fun.Jrz.sym(iz,:),model.fun.sigma_z.strsym); - end - - otherwise - error('unknown function name') - end -end - -function this = unifySyms(this,model) - % unifySyms replaces model specific variable names with general - % ones which conforms with C naming schemes - % - % Parameters: - % model: model definition object @type amimodel - this.sym = mysubs(this.sym,model.sym.x,model.fun.x.sym); - this.sym = mysubs(this.sym,model.sym.p,model.fun.p.sym); - this.sym = mysubs(this.sym,model.sym.k,model.fun.k.sym); -end - -function this = makeStrSymsSparse(this) - this.strsym = sym(zeros(size(this.sym))); - idx = find(this.sym); - for isym = 1:length(idx) - this.strsym(idx(isym)) = sym(sprintf([this.cvar '_%i'], isym-1)); - end -end - -function this = makeStrSyms(this) - this.strsym = sym(zeros(size(this.sym))); - idx = find(this.sym); - idx = transpose(idx(:)); - for isym = idx - this.strsym(isym) = sym(sprintf([this.cvar '_%i'], isym-1)); - end -end - -function this = makeStrSymsFull(this) - this.strsym = sym(zeros(size(this.sym))); - for isym = 1:numel(this.strsym) - this.strsym(isym) = sym(sprintf([this.cvar '_%i'], isym-1)); - end -end - -function out = mysubs(in, old, new) - % mysubs is a wrapper for ther subs matlab function - % - % Parameters: - % in: symbolic expression in which to replace @type symbolic - % old: expression to be replaced @type symbolic - % new: replacement expression @type symbolic - % - % Return values: - % out: symbolic expression with replacement @type symbolic - if(~isnumeric(in) && ~isempty(old) && ~isempty(symvar(in))) - matVer = ver('MATLAB'); - if(str2double(matVer.Version)>=8.1) - out = subs(in, old(:), new(:)); - else - out = subs(in, old(:), new(:), 0); - end - else - out = in; - end -end diff --git a/matlab/@amifun/writeCcode.m b/matlab/@amifun/writeCcode.m deleted file mode 100644 index b03232cd95..0000000000 --- a/matlab/@amifun/writeCcode.m +++ /dev/null @@ -1,97 +0,0 @@ -function writeCcode(this,model,fid) -% writeCcode is a wrapper for gccode which initialises data and reduces -% overhead by check nonzero values -% -% Parameters: -% model: model defintion object @type amimodel -% fid: file id in which the final expression is written @type fileid -% -% Return values: -% void - -nonzero_idx = find(this.sym); -nonzero = zeros(size(this.sym)); -nonzero(nonzero_idx) = 1; - -nevent = model.nevent; -if(strcmp(this.funstr,'JSparse')) - tmpfun = this; - tmpfun.sym = model.fun.J.sym(model.sparseidx); - tmpfun.gccode(model,fid); -elseif(ismember(this.funstr,{'rz','z','sz','srz'})) - if(any(nonzero)) - fprintf(fid,' switch(ie) { \n'); - for ievent=1:nevent - tmpfun = this; - % set all z that do not belong to this event to zero - % dont shorten the vector as we need the indices - fprintf(fid,[' case ' num2str(ievent-1) ': {\n']); - tmpfun.sym(model.z2event~=ievent) = 0; - tmpfun.gccode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - fprintf(fid,' } \n'); - end -elseif(strcmp(this.funstr,'stau') ) - if(any(nonzero)) - fprintf(fid,' switch(ie) { \n'); - for ievent=1:nevent - tmpfun = this; - % set all z that do not belong to this event to zero - % dont shorten the vector as we need the indices - fprintf(fid,[' case ' num2str(ievent-1) ': {\n']); - tmpfun.sym = tmpfun.sym(ievent); - tmpfun.gccode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - fprintf(fid,' } \n'); - end -elseif(strcmp(this.funstr,'deltax') || strcmp(this.funstr,'deltasx') || strcmp(this.funstr,'deltaxB') || strcmp(this.funstr,'deltaqB')) - if(any(any(nonzero))) - fprintf(fid,' switch(ie) { \n'); - tmpfun = this; - for ievent=1:nevent - if(any(nonzero(:,ievent))) - fprintf(fid,[' case ' num2str(ievent-1) ': {\n']); - tmpfun.sym = this.sym(:,ievent); - tmpfun.gccode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - end - fprintf(fid,' } \n'); - end -elseif(any(strcmp(this.funstr,{'Jy','dJydsigma','dJydy'}))) - tmpfun = this; - if(any(any(any(nonzero)))) - fprintf(fid,['switch(iy){\n']); - for iy = 1:model.nytrue - fprintf(fid,[' case ' num2str(iy-1) ':\n']); - tmpfun.sym = permute(this.sym(iy,:,:),[2,3,1]); - tmpfun.gccode(model,fid); - fprintf(fid,' break;\n'); - end - fprintf(fid,['}\n']); - end -elseif(any(strcmp(this.funstr,{'Jz','dJzdsigma','dJzdz','Jrz','dJrzdsigma','dJrzdz'}))) - tmpfun = this; - if(any(any(any(nonzero)))) - fprintf(fid,['switch(iz){\n']); - for iz = 1:model.nztrue - fprintf(fid,[' case ' num2str(iz-1) ':\n']); - tmpfun.sym = permute(this.sym(iz,:,:),[2,3,1]); - tmpfun.gccode(model,fid); - fprintf(fid,' break;\n'); - end - fprintf(fid,['}\n']); - end -else - if(any(any(any(nonzero)))) - this.gccode(model,fid); - end -end - - -end diff --git a/matlab/@amifun/writeCcode_sensi.m b/matlab/@amifun/writeCcode_sensi.m deleted file mode 100644 index c660a5e43b..0000000000 --- a/matlab/@amifun/writeCcode_sensi.m +++ /dev/null @@ -1,91 +0,0 @@ -function writeCcode_sensi(this,model,fid) -% writeCcode_sensi is a wrapper for writeCcode which loops over parameters and reduces -% overhead by check nonzero values -% -% Parameters: -% model: model defintion object @type amimodel -% fid: file id in which the final expression is written @type fileid -% -% Return values: -% void - -np = model.np; -ng = model.ng; - -nonzero_idx = find(this.sym); -nonzero = zeros(size(this.sym)); -nonzero(nonzero_idx) = 1; - -if(strcmp(this.funstr,'deltaqB')) - if(any(nonzero)) - tmpfun = this; - for ip=1:np*ng - if(nonzero(ip)) - fprintf(fid,[' case ' num2str(ip-1) ': {\n']); - tmpfun.sym = this.sym(ip,:); - tmpfun.writeCcode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - end - end -elseif(strcmp(this.funstr,'deltasx')) - if(any(any(any(nonzero)))) - tmpfun = this; - for ip=1:np - if(any(any(any(nonzero(:,ip,:))))) - fprintf(fid,[' case ' num2str(ip-1) ': {\n']); - tmpfun.sym = permute(this.sym(:,ip,:),[1,3,2]); - tmpfun.writeCcode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - end - end -elseif(strcmp(this.funstr,'s2root')) - if(any(any(any(nonzero)))) - tmpfun = this; - for ip=1:np - if(any(any(any(nonzero(:,ip,:))))) - fprintf(fid,[' case ' num2str(ip-1) ': {\n']); - tmpfun.sym = squeeze(this.sym(model.z2event(1:model.nztrue),ip,:)); - tmpfun.writeCcode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - end - end -elseif(strcmp(this.funstr,'qBdot')) - nonzero = this.sym ~=0; - if(any(any(nonzero))) - tmpfun = this; - for ip=1:np - if(any(nonzero(:,ip))) - fprintf(fid,[' case ' num2str(ip-1) ': {\n']); - tmpfun.sym = this.sym(:,ip); - tmpfun.writeCcode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - end - end -else - nonzero = this.sym ~=0; - if(any(any(nonzero))) - tmpfun = this; - for ip=1:np - if(any(nonzero(:,ip))) - fprintf(fid,[' case ' num2str(ip-1) ': {\n']); - if(strcmp(this.funstr,'sroot')) - tmpfun.sym = this.sym(model.z2event,ip); - else - tmpfun.sym = this.sym(:,ip); - end - tmpfun.writeCcode(model,fid); - fprintf(fid,'\n'); - fprintf(fid,' } break;\n\n'); - end - end - end -end -end diff --git a/matlab/@amifun/writeMcode.m b/matlab/@amifun/writeMcode.m deleted file mode 100644 index 4b5fb126c9..0000000000 --- a/matlab/@amifun/writeMcode.m +++ /dev/null @@ -1,27 +0,0 @@ -function writeMcode(this,model) -% writeMcode generates matlab evaluable code for specific model functions -% -% Parameters: -% model: model defintion object @type amimodel -% -% Return values: -% void - - -% replace heaviside placeholder vars -if(~isempty(model.event)) - h_vars = [sym('h_0');sym('h_',[length(model.event)-1,1])]; - h_rep= sym(zeros(size(h_vars))); - if(~isfield(model.fun,'root')) - model.getFun([],'root'); - end - for ievent = 1:length(model.event) - h_rep(ievent) = heaviside(model.fun.root.sym(ievent)); - end - this.sym_noopt = subs(this.sym_noopt,h_vars,h_rep); -end - -ami_mfun(this.sym_noopt, 'file', fullfile(model.wrap_path,'models',... - model.modelname,[ this.funstr '_',model.modelname,'.m']), ... - 'vars', {'t',model.fun.x.sym,model.fun.p.sym,model.fun.k.sym},'varnames',{'t','x','p','k'}); -end diff --git a/matlab/@amimodel/amimodel.m b/matlab/@amimodel/amimodel.m deleted file mode 100644 index 1aed263556..0000000000 --- a/matlab/@amimodel/amimodel.m +++ /dev/null @@ -1,272 +0,0 @@ -% -% @file amimodel -% @brief definition of amimodel class -% -classdef amimodel < handle - % AMIMODEL carries all model definitions including functions and events - - properties ( GetAccess = 'public', SetAccess = 'private' ) - % symbolic definition struct @type struct - sym = struct.empty(); - % struct which stores information for which functions c code needs to be generated @type struct - fun = struct.empty(); - % struct which stores information for which functions c code needs - % to be generated @type amievent - event = amievent.empty(); - % name of the model @type string - modelname = char.empty(); - % struct that contains hash values for the symbolic model definitions @type struct - HTable = struct.empty(); - % flag indicating whether debugging symbols should be compiled @type bool - debug = false; - % flag indicating whether adjoint sensitivities should be enabled @type bool - adjoint = true; - % flag indicating whether forward sensitivities should be enabled @type bool - forward = true; - % default initial time @type double - t0 = 0; - % type of wrapper (cvodes/idas) @type string - wtype = char.empty(); - % number of states @type int - nx = double.empty(); - % number of original states for second order sensitivities @type int - nxtrue = double.empty(); - % number of observables @type int - ny = double.empty(); - % number of original observables for second order sensitivities @type int - nytrue = double.empty(); - % number of parameters @type int - np = double.empty(); - % number of constants @type int - nk = double.empty(); - % number of objective functions @type int - ng = double.empty(); - % number of events @type int - nevent = double.empty(); - % number of event outputs @type int - nz = double.empty(); - % number of original event outputs for second order sensitivities @type int - nztrue = double.empty(); - % flag for DAEs @type *int - id = double.empty(); - % upper Jacobian bandwidth @type int - ubw = double.empty(); - % lower Jacobian bandwidth @type int - lbw = double.empty(); - % number of nonzero entries in Jacobian @type int - nnz = double.empty(); - % dataindexes of sparse Jacobian @type *int - sparseidx = double.empty(); - % rowindexes of sparse Jacobian @type *int - rowvals = double.empty(); - % columnindexes of sparse Jacobian @type *int - colptrs = double.empty(); - % dataindexes of sparse Jacobian @type *int - sparseidxB = double.empty(); - % rowindexes of sparse Jacobian @type *int - rowvalsB = double.empty(); - % columnindexes of sparse Jacobian @type *int - colptrsB = double.empty(); - % cell array of functions to be compiled @type *cell - funs = cell.empty(); - % cell array of matlab functions to be compiled @type *cell - mfuns = cell.empty(); - % optimisation flag for compilation @type string - coptim = '-O3'; - % default parametrisation @type string - param = 'lin'; - % path to wrapper - wrap_path = char.empty(); - % flag to enforce recompilation of the model - recompile = false; - % storage for flags determining recompilation of individual - % functions - cfun = struct.empty(); - % flag which identifies augmented models - % 0 indicates no augmentation - % 1 indicates augmentation by first order sensitivities (yields - % second order sensitivities) - % 2 indicates augmentation by one linear combination of first - % order sensitivities (yields hessian-vector product) - o2flag = 0; - end - - properties ( GetAccess = 'public', SetAccess = 'public' ) - % vector that maps outputs to events - z2event = double.empty(); - % flag indicating whether the model contains spline functions - splineflag = false; - % flag indicating whether the model contains min functions - minflag = false; - % flag indicating whether the model contains max functions - maxflag = false; - % number of derived variables w, w is used for code optimization to reduce the number of frequently occuring - % expressions @type int - nw = 0; - % number of derivatives of derived variables w, dwdx @type int - ndwdx = 0; - % number of derivatives of derived variables w, dwdp @type int - ndwdp = 0; - end - - methods - function AM = amimodel(symfun,modelname) - % amimodel initializes the model object based on the provided - % symfun and modelname - % - % Parameters: - % symfun: this is the string to the function which generates - % the modelstruct. You can also directly pass the struct here @type string - % modelname: name of the model @type string - % - % Return values: - % AM: model definition object - if(isa(symfun,'amimodel')) - AM = symfun; - else - if(isa(symfun,'char')) - if(exist(symfun,'file')==2) - fun = str2func(symfun); - model = fun(); - else - error(['"' symfun '" must be the name of a matlab function in a matlab file, with the corresponding name, in the matlab path. Please check whether the folder containing "' symfun '" is in the matlab path. AMICI currently does not support absolute or relative paths in its input arguments.']) - end - elseif(isa(symfun,'struct')) - model = symfun; - elseif(isa(symfun,'function_handle')) - model = symfun(); - else - error('invalid input symfun') - end - - if(isfield(model,'sym')) - AM.sym = model.sym; - else - error('symbolic definitions missing in struct returned by symfun') - end - - - - props = fields(model); - - for j = 1:length(props) - if(~strcmp(props{j},'sym')) % we already checked for the sym field - if(isfield(model,props{j})) - try - AM.(props{j}) = model.(props{j}); - catch - error(['The provided model struct or the struct created by the provided model function has the field ' props{j} ' which is not a valid property of ' ... - 'the amimodel class. Please check your model definition.']); - end - end - else - AM.makeSyms(); - AM.nx = length(AM.sym.x); - if(isempty(AM.nxtrue)) % if its not empty we are dealing with an augmented model - AM.nxtrue = AM.nx; - end - AM.ng = size(AM.sym.Jy,2); - AM.np = length(AM.sym.p); - AM.nk = length(AM.sym.k); - AM.ny = length(AM.sym.y); - if(isempty(AM.nytrue)) % if its not empty we are dealing with an augmented model - AM.nytrue = AM.ny; - end - end - end - - AM.modelname = modelname; - % set path and create folder - AM.wrap_path=fileparts(fileparts(fileparts(mfilename('fullpath')))); - if(~exist(fullfile(AM.wrap_path,'models'),'dir')) - mkdir(fullfile(AM.wrap_path,'models')); - mkdir(fullfile(AM.wrap_path,'models',AM.modelname)); - else - if(~exist(fullfile(AM.wrap_path,'models',AM.modelname),'dir')) - mkdir(fullfile(AM.wrap_path,'models',AM.modelname)) - end - end - AM.makeEvents(); - AM.nz = length([AM.event.z]); - if(isempty(AM.nztrue)) - AM.nztrue = AM.nz; - end - AM.nevent = length(AM.event); - - % check whether we have a DAE or ODE - if(isfield(AM.sym,'M')) - AM.wtype = 'iw'; % DAE - else - AM.wtype = 'cw'; % ODE - end - end - end - - function updateRHS(this,xdot) - % updateRHS updates the private fun property .fun.xdot.sym - % (right hand side of the differential equation) - % - % Parameters: - % xdot: new right hand side of the differential equation - % - % Return values: - % void - this.fun.xdot.sym_noopt = this.fun.xdot.sym; - this.fun.xdot.sym = xdot; - end - - function updateModelName(this,modelname) - % updateModelName updates the modelname - % - % Parameters: - % modelname: new modelname - % - % Return values: - % void - this.modelname = modelname; - end - - function updateWrapPath(this,wrap_path) - % updateModelName updates the modelname - % - % Parameters: - % wrap_path: new wrap_path - % - % Return values: - % void - this.wrap_path = wrap_path; - end - - parseModel(this) - - generateC(this) - - generateRebuildM(this) - - compileC(this) - - generateM(this,amimodelo2) - - getFun(this,HTable,funstr) - - makeEvents(this) - - makeSyms(this) - - cflag = checkDeps(this,HTable,deps) - - HTable = loadOldHashes(this) - - modelo2 = augmento2(this) - - modelo2vec = augmento2vec(this) - - end - - methods(Static) - compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, cfun) - - generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFilename, modelname, pscale, forward, adjoint) - end - -end diff --git a/matlab/@amimodel/augmento2.m b/matlab/@amimodel/augmento2.m deleted file mode 100644 index e2a0852ebe..0000000000 --- a/matlab/@amimodel/augmento2.m +++ /dev/null @@ -1,160 +0,0 @@ -function [modelo2] = augmento2(this) - % augmento2 augments the system equation to also include equations for - % sensitivity equation. This will enable us to compute second order - % sensitivities in a forward-adjoint or forward-forward apporach later on. - % - % Parameters: - % - % Return values: - % this: augmented system which contains symbolic definition of the - % original system and its sensitivities @type amimodel - - syms Sx Sdot Sy S0 - - augmodel.nxtrue = length(this.sym.x); % number of states - augmodel.nytrue = length(this.sym.y); % number of observables - if(this.nevent>0) - augmodel.nztrue = length([this.event.z]); % number of observables - else - augmodel.nztrue = 0; - end - np = this.np; - - % augment states - Sx = sym(zeros(length(this.sym.x),np)); - for j = 1:length(this.sym.x) - for k = 1:np - eval(['syms S' num2str(j) '_' num2str(k)]); - eval(['Sx(j,k) = S' num2str(j) '_' num2str(k) ';']); - end - end - Sdot = jacobian(this.sym.xdot,this.sym.x)*Sx+jacobian(this.sym.xdot,this.sym.p); - - % augment output - Sy = jacobian(this.sym.y,this.sym.x)*Sx+jacobian(this.sym.y,this.sym.p); - - % generate deltasx - this.getFun([],'deltasx'); - this.getFun([],'sz'); - this.getFun([],'srz'); - for ievent = 1:this.nevent; - if(numel(this.event(ievent).z)>0) - Sz = this.fun.sz.sym(this.z2event==ievent,:); - for ip = 1:np - Sz(:,ip) = subs(Sz(:,ip),this.fun.sx.sym(:,ip),Sx(:,ip)); - end - Srz = this.fun.srz.sym(this.z2event==ievent,:); - for ip = 1:np - Srz(:,ip) = subs(Srz(:,ip),this.fun.sx.sym(:,ip),Sx(:,ip)); - end - znew = [this.event(ievent).z,reshape(Sz,[sum(this.z2event==ievent),np])]; - else - znew = this.event(ievent).z; - end - tmp=subs(this.fun.deltasx.sym(:,:,ievent),this.fun.xdot.strsym_old,this.fun.xdot.sym); - tmp=subs(tmp,this.fun.xdot.strsym,subs(this.fun.xdot.sym,this.fun.x.sym,this.fun.x.sym+this.event(ievent).bolus)); - tmp=subs(tmp,this.fun.stau.strsym,this.fun.stau.sym(ievent,:)); - for ip = 1:np - tmp(:,ip)=subs(tmp(:,ip),this.fun.sx.sym(:,ip), Sx(:,ip)); - end - bolusnew = [this.event(ievent).bolus;tmp(:)]; - % replace sx by augmented x - for ip = 1:np - bolusnew(this.nxtrue*ip+(1:this.nxtrue)) = mysubs(bolusnew(this.nxtrue*ip+(1:this.nxtrue)), this.fun.sx.sym(:,ip),Sx(:,ip)); - end - augmodel.event(ievent) = amievent(this.event(ievent).trigger,bolusnew,znew); - end - - % augment likelihood - this.getFun([],'dsigma_ydp'); - this.getFun([],'y'); - this.getFun([],'dydp'); - this.getFun([],'Jy'); - tmp = arrayfun(@(x) sym(['var_y_' num2str(x)]),0:(augmodel.nytrue*(1+np)-1),'UniformOutput',false); - tmp = transpose([tmp{:}]); - aug_y_strsym = reshape(tmp((augmodel.nytrue+1):end),[augmodel.nytrue,np]);% the update Jy must not contain any - % references to x (otherwise we have to deal with sx in sJy), thus - % we replace sy with the corresponding augmented y that we are about to - % create - SJy = jacobian(this.fun.Jy.sym,this.sym.p) ... - + jacobian(this.fun.Jy.sym,this.fun.sigma_y.strsym)*this.fun.dsigma_ydp.sym ... - + jacobian(this.fun.Jy.sym,this.fun.y.strsym)*aug_y_strsym; - - this.getFun([],'dsigma_zdp'); - this.getFun([],'rz'); - this.getFun([],'Jz'); - tmp = arrayfun(@(x) sym(['var_z_' num2str(x)]),0:(augmodel.nztrue*(1+np)-1),'UniformOutput',false); - tmp = transpose([tmp{:}]); - aug_z_strsym = reshape(tmp((augmodel.nztrue+1):end),[augmodel.nztrue,np]);% the update Jz must not contain any - % references to x (otherwise we have to deal with sx in sJy), thus - % we replace sy with the corresponding augmented y that we are about to - % create - SJz = jacobian(this.fun.Jz.sym,this.sym.p); - if(~isempty(this.fun.sigma_z.strsym)) - SJz = SJz + jacobian(this.fun.Jz.sym,this.fun.sigma_z.strsym)*this.fun.dsigma_zdp.sym ... - + jacobian(this.fun.Jz.sym,this.fun.z.strsym)*aug_z_strsym; - end - this.getFun([],'Jrz'); - tmp = arrayfun(@(x) sym(['var_rz_' num2str(x)]),0:(augmodel.nztrue*(1+np)-1),'UniformOutput',false); - tmp = transpose([tmp{:}]); - aug_rz_strsym = reshape(tmp((augmodel.nztrue+1):end),[augmodel.nztrue,np]);% the update Jz must not contain any - % references to x (otherwise we have to deal with sx in sJy), thus - % we replace sy with the corresponding augmented y that we are about to - % create - SJrz = jacobian(this.fun.Jrz.sym,this.sym.p); - if(~isempty(this.fun.sigma_z.strsym)) - SJrz = SJrz + jacobian(this.fun.Jrz.sym,this.fun.sigma_z.strsym)*this.fun.dsigma_zdp.sym ... - + jacobian(this.fun.Jrz.sym,this.fun.rz.strsym)*aug_rz_strsym; - end - - % augment sigmas - this.getFun([],'sigma_y'); - this.getFun([],'sigma_z'); - S0 = jacobian(this.sym.x0,this.sym.p); - - augmodel.sym.x = [this.sym.x;Sx(:)]; - augmodel.sym.xdot = [this.sym.xdot;Sdot(:)]; - augmodel.sym.f = augmodel.sym.xdot; - augmodel.sym.y = [this.sym.y;Sy(:)]; - augmodel.sym.x0 = [this.sym.x0;S0(:)]; - augmodel.sym.Jy = [this.sym.Jy,SJy]; - augmodel.sym.Jz = [this.sym.Jz,SJz]; - augmodel.sym.Jrz = [this.sym.Jrz,SJrz]; - if(numel([this.event.z])) - augmodel.sym.rz = [this.fun.rz.sym,Srz]; - end - augmodel.sym.p = this.sym.p; - augmodel.sym.k = this.sym.k; - augmodel.sym.sigma_y = [transpose(this.sym.sigma_y(:)), reshape(transpose(this.fun.dsigma_ydp.sym), [1,numel(this.fun.dsigma_ydp.sym)])]; - augmodel.sym.sigma_z = [transpose(this.sym.sigma_z(:)), reshape(transpose(this.fun.dsigma_zdp.sym), [1,numel(this.fun.dsigma_zdp.sym)])]; - - modelo2 = amimodel(augmodel,[this.modelname '_o2']); - modelo2.o2flag = 1; - modelo2.debug = this.debug; - modelo2.forward = this.forward; - modelo2.adjoint = this.adjoint; - modelo2.param = this.param; -end - - -function out = mysubs(in, old, new) - % mysubs is a wrapper for ther subs matlab function - % - % Parameters: - % in: symbolic expression in which to replace @type symbolic - % old: expression to be replaced @type symbolic - % new: replacement expression @type symbolic - % - % Return values: - % out: symbolic expression with replacement @type symbolic - if(~isnumeric(in) && ~isempty(old) && ~isempty(symvar(in))) - matVer = ver('MATLAB'); - if(str2double(matVer.Version)>=8.1) - out = subs(in, old(:), new(:)); - else - out = subs(in, old(:), new(:), 0); - end - else - out = in; - end -end diff --git a/matlab/@amimodel/augmento2vec.m b/matlab/@amimodel/augmento2vec.m deleted file mode 100644 index 09ba3000ad..0000000000 --- a/matlab/@amimodel/augmento2vec.m +++ /dev/null @@ -1,134 +0,0 @@ -function [modelo2vec] = augmento2vec(this) - % augmento2 augments the system equation to also include equations for - % sensitivity equation. This will enable us to compute second order - % sensitivities in a forward-adjoint or forward-forward apporach later on. - % - % Parameters: - % - % Return values: - % modelo2vec: augmented system which contains symbolic definition of the - % original system and its sensitivities @type amimodel - - syms Sx Sdot Sy S0 - - - - augmodel.nxtrue = length(this.sym.x); % number of states - augmodel.nytrue = length(this.sym.y); % number of observables - augmodel.nztrue = this.nz; - augmodel.coptim = this.coptim; - augmodel.debug = this.debug; - - % multiplication vector (extension of kappa - vecs = cell([length(this.sym.p),1]); - for ivec = 1:length(this.sym.p) - vecs{ivec} = sprintf('k_%i', length(this.sym.k) + ivec-1); - end - vec = sym(vecs); - - if(this.nevent>0) - augmodel.nztrue = length([this.event.z]); % number of observables - else - augmodel.nztrue = 0; - end - np = this.np; - - % augment states - sv = sym('sv',[length(this.sym.x),1]); - Sdot = jacobian(this.sym.xdot,this.sym.x)*sv+jacobian(this.sym.xdot,this.sym.p)*vec; - - % augment output - Sy = jacobian(this.sym.y,this.sym.x)*sv+jacobian(this.sym.y,this.sym.p)*vec; - - % generate deltasx - this.getFun([],'deltasx'); - for ievent = 1:this.nevent; - if(numel(this.event(ievent).z)>0) - Sz = jacobian(this.event(ievent).z,this.sym.x)*sv+jacobian(this.event(ievent).z,this.sym.p)*vec; - znew = [this.event(ievent).z,Sz]; - else - znew = this.event(ievent).z; - end - tmp=subs(this.fun.deltasx.sym(:,:,ievent),this.fun.xdot.strsym_old,this.fun.xdot.sym); - tmp=subs(tmp,this.fun.xdot.strsym,subs(this.fun.xdot.sym,this.fun.x.sym,this.fun.x.sym+this.event(ievent).bolus)); - tmp=subs(subs(tmp,this.fun.stau.strsym,this.fun.stau.sym(ievent,:)),this.fun.sx.sym*vec,sv); - bolusnew = [this.event(ievent).bolus;tmp*vec]; - % replace sx by augmented x - bolusnew(this.nxtrue+(1:this.nxtrue)) = mysubs(bolusnew(this.nxtrue+(1:this.nxtrue)), this.fun.sx.sym(:,1),sv); - - hflagold = this.event(ievent).hflag; - augmodel.event(ievent) = amievent(this.event(ievent).trigger,bolusnew,znew); - augmodel.event(ievent) = augmodel.event(ievent).setHflag([hflagold;zeros([numel(sv),1])]); - end - - % augment likelihood - this.getFun([],'dsigma_ydp'); - this.getFun([],'y'); - this.getFun([],'dydp'); - this.getFun([],'Jy'); - tmp = arrayfun(@(x) sym(['var_y_' num2str(x)]),0:(augmodel.nytrue*2-1),'UniformOutput',false); - aug_y_strsym = transpose([tmp{(augmodel.nytrue+1):end}]); % the update Jy must not contain any - % references to x (otherwise we have to deal with sx in sJy), thus - % we replace sy with the corresponding augmented y that we are about to - % create - SJy = (jacobian(this.fun.Jy.sym,this.sym.p) ... - + jacobian(this.fun.Jy.sym,this.fun.sigma_y.strsym)*this.fun.dsigma_ydp.sym) ... - * vec + jacobian(this.fun.Jy.sym,this.fun.y.strsym)*aug_y_strsym; - this.getFun([],'dsigma_zdp'); - - this.getFun([],'dzdp'); - this.getFun([],'Jz'); - SJz = jacobian(this.fun.Jz.sym,this.sym.p); - if(~isempty(this.fun.sigma_z.strsym)) - SJz = SJz + jacobian(this.fun.Jz.sym,this.fun.sigma_z.strsym)*this.fun.dsigma_zdp.sym ... - + jacobian(this.fun.Jz.sym,this.fun.z.strsym)*Sz; - end - - % augment sigmas - this.getFun([],'sigma_y'); - this.getFun([],'sigma_z'); - - S0 = jacobian(this.sym.x0,this.sym.p)*vec; - - augmodel.sym.x = [this.sym.x;sv]; - augmodel.sym.xdot = [this.sym.xdot;Sdot]; - augmodel.sym.f = augmodel.sym.xdot; - augmodel.sym.y = [this.sym.y;Sy]; - augmodel.sym.x0 = [this.sym.x0;S0]; - augmodel.sym.Jy = [this.sym.Jy,SJy]; - augmodel.sym.Jz = [this.sym.Jz,SJz*vec]; - augmodel.sym.k = [this.sym.k;vec]; - augmodel.sym.p = this.sym.p; - augmodel.sym.sigma_y = [this.sym.sigma_y, transpose(this.fun.dsigma_ydp.sym * vec)]; - augmodel.sym.sigma_z = [this.sym.sigma_z, transpose(this.fun.dsigma_zdp.sym * vec)]; - - modelo2vec = amimodel(augmodel,[this.modelname '_o2vec']); - modelo2vec.o2flag = 2; - modelo2vec.debug = this.debug; - modelo2vec.forward = this.forward; - modelo2vec.adjoint = this.adjoint; - modelo2vec.param = this.param; -end - - -function out = mysubs(in, old, new) - % mysubs is a wrapper for ther subs matlab function - % - % Parameters: - % in: symbolic expression in which to replace @type symbolic - % old: expression to be replaced @type symbolic - % new: replacement expression @type symbolic - % - % Return values: - % out: symbolic expression with replacement @type symbolic - if(~isnumeric(in) && ~isempty(old) && ~isempty(symvar(in))) - matVer = ver('MATLAB'); - if(str2double(matVer.Version)>=8.1) - out = subs(in, old(:), new(:)); - else - out = subs(in, old(:), new(:), 0); - end - else - out = in; - end -end diff --git a/matlab/@amimodel/checkDeps.m b/matlab/@amimodel/checkDeps.m deleted file mode 100644 index 77c40b7fb0..0000000000 --- a/matlab/@amimodel/checkDeps.m +++ /dev/null @@ -1,50 +0,0 @@ -function cflag = checkDeps(this,HTable,deps) - % checkDeps checks the dependencies of functions and populates sym - % fields if necessary - % - % Parameters: - % HTable: struct with reference hashes of functions in its fields - % @type struct - % deps: cell array with containing a list of dependencies @type cell - % - % Return values: - % cflag: boolean indicating whether any of the dependencies have - % changed with respect to the hashes stored in HTable @type - % bool - - if(~isempty(HTable)) - cflags = zeros(length(deps),1); - for id = 1:length(deps) - if(ismember(deps{id},{'w','dwdx','dwdp'})) - cflags(id) = 0; - else - if(~isfield(this.HTable,deps{id})) - % check subdependencies - fun = amifun(deps{id},this); - fun = fun.getDeps(this); - cflagdep = this.checkDeps(HTable,fun.deps); - cflags(id) = cflagdep; - else - if(~isfield(HTable,deps{id})) %newly added field in HTable, need to recompile - cflags(id) = 1; - else - cflags(id) = ~strcmp(this.HTable.(deps{id}),HTable.(deps{id})); - end - end - end - end - cflag = any(cflags); - else - cflag = 1; - end - if(cflag) - % if the function needs to be computed, we need to make sure that - % all symbolic definitions are generated. this is forced by passing - % an empty HTable to getFun - for id = 1:length(deps) - if(~isfield(this.fun,deps{id})) - this.getFun([],deps{id}); - end - end - end -end diff --git a/matlab/@amimodel/compileAndLinkModel.m b/matlab/@amimodel/compileAndLinkModel.m deleted file mode 100644 index 927479ecb3..0000000000 --- a/matlab/@amimodel/compileAndLinkModel.m +++ /dev/null @@ -1,384 +0,0 @@ -function compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, cfun) - % compileAndLinkModel compiles the mex simulation file. - % It does not check if the model files have changed since generating - % C++ code or whether all files are still present. - % Use only if you know what you are doing. The safer alternative is - % rerunning amiwrap(). - % - % Parameters: - % modelname: name of the model as specified for amiwrap() - % modelSourceFolder: path to model source directory - % coptim: optimization flags - % debug: enable debugging - % funs: array with names of the model functions, will be guessed - % from source files if left empty - % cfun: struct indicating which files should be recompiled - % - % Return values: - % void - - % if no list provided, try to determine relevant files from model - % folder - if(isempty(funs)) - ls = dir(fullfile(modelSourceFolder, '*.cpp')); - ls = {ls.name}; - % wrapfunctions is handled separately - ls = ls(cellfun(@(x) ~strcmp(x, "wrapfunctions.cpp"), ls)); - % extract funs from filename (strip of modelname_ and .cpp - funs = cellfun(@(x) x(1:(length(x)-4)), ls, 'UniformOutput', false); - end - - objectFileSuffix = '.o'; - if(ispc) - objectFileSuffix = '.obj'; - end - - % compile flags - COPT = ['COPTIMFLAGS=''' coptim ' -DNDEBUG'' CXXFLAGS=''$CXXFLAGS -std=c++20''']; - if(debug) - DEBUG = ' -g CXXFLAGS=''$CXXFLAGS -Wall -std=c++20 -Wno-unused-function -Wno-unused-variable'' '; - COPT = ''; % no optimization with debug flags! - else - DEBUG = ''; - end - - compilerVersion = getCompilerVersionString(); - amiciRootPath = fileparts(fileparts(fileparts(mfilename('fullpath')))); - baseObjectFolder = fullfile(amiciRootPath,'models',mexext,version('-release'),compilerVersion); - baseModelObjectFolder = fullfile(modelSourceFolder,version('-release'),compilerVersion); - if(debug) - objectFolder = fullfile(baseObjectFolder, 'debug'); - modelObjectFolder = fullfile(baseModelObjectFolder, 'debug'); - else - objectFolder = fullfile(baseObjectFolder, 'release'); - modelObjectFolder = fullfile(baseModelObjectFolder, 'release'); - end - % compile directory - if(~exist(objectFolder, 'dir')) - mkdir(objectFolder); - end - if(~exist(modelObjectFolder, 'dir')) - mkdir(modelObjectFolder); - end - - %% Third party libraries - dependencyPath = fullfile(amiciRootPath, 'ThirdParty'); - gslPath = fullfile(dependencyPath, 'gsl'); - [objectsstr, includesstr] = compileAMICIDependencies(dependencyPath, objectFolder, objectFileSuffix, COPT, DEBUG); - includesstr = strcat(includesstr, ' -I"', gslPath, '"'); - if (~isempty(modelSourceFolder)) - includesstr = strcat(includesstr,' -I"', modelSourceFolder, '"'); - end - - %% Recompile AMICI base files if necessary - [objectStrAmici] = compileAmiciBase(amiciRootPath, objectFolder, objectFileSuffix, includesstr, DEBUG, COPT); - objectsstr = [objectsstr, objectStrAmici]; - - %% Model-specific files - for j=1:length(funs) - baseFileName = strrep(funs{j}, 'sigma_', 'sigma'); - cfun(1).(funs{j}) = sourceNeedsRecompilation(modelSourceFolder, modelObjectFolder, baseFileName, objectFileSuffix); - end - - funsForRecompile = {}; - - % flag dependencies for recompilation - if(~isempty(cfun)) - if(isfield('J',cfun(1))) - if(cfun(1).J) - if(ismember('JBand',funs)) - cfun(1).JBand = 1; - end - end - end - - if(isfield('JB',cfun(1))) - if(cfun(1).JB) - if(ismember('JBandB',funs)) - cfun(1).JBandB = 1; - end - end - end - - if(isfield('JSparse',cfun(1))) - if(cfun(1).JSparse) - if(ismember('sxdot',funs)) - cfun(1).sxdot = 1; - end - end - end - if(isfield('dxdotdp',cfun(1))) - if(isfield(cfun(1),'dxdotdp')) - if(cfun(1).dxdotdp) - if(ismember('sxdot',funs)) - cfun(1).sxdot = 1; - end - if(ismember('qBdot',funs)) - cfun(1).qBdot = 1; - end - end - end - end - funsForRecompile = funs(structfun(@(x) logical(x), cfun(1))); - funsForRecompile = cellfun(@(x) strrep(x, 'sigma_', 'sigma'), funsForRecompile, 'UniformOutput', false); - end - - if(numel(funsForRecompile)) - fprintf('ffuns | '); - - sources = cellfun(@(x) ['"' fullfile(modelSourceFolder,[x '.cpp']) '"'],funsForRecompile,'UniformOutput',false); - sources = strjoin(sources,' '); - - cmd = ['mex ' DEBUG COPT ... - ' -c -outdir "' modelObjectFolder '" ' ... - sources ' ' includesstr]; - try - eval(cmd); - catch ME - disp(cmd); - rethrow(ME); - end - cellfun(@(x) updateFileHashSource(modelSourceFolder, modelObjectFolder, x),funsForRecompile,'UniformOutput',false); - end - - % append model object files - for j=1:length(funs) - filename = fullfile(modelObjectFolder, [strrep(funs{j}, 'sigma_', 'sigma') objectFileSuffix]); - if(exist(filename,'file')) - objectsstr = strcat(objectsstr, ' "',filename,'"'); - end - end - - % in case we compile python-generated code, there is a modelname.cpp - model_cpp = fullfile(modelSourceFolder, [modelname '.cpp']); - if(exist(model_cpp, 'file')) - model_cpp = ['"' model_cpp '" ']; - model_cpp_obj = [' "' fullfile(modelObjectFolder,[modelname objectFileSuffix]) '" ']; - else - model_cpp = ''; - model_cpp_obj = ''; - end - - - % compile the wrapfunctions object - fprintf('wrapfunctions | '); - cmd = ['mex ' DEBUG COPT ... - ' -c -outdir "' modelObjectFolder '" "' ... - fullfile(modelSourceFolder,'wrapfunctions.cpp') '" ' model_cpp ... - includesstr]; - try - eval(cmd); - catch ME - disp(cmd); - rethrow(ME); - end - - objectsstr = [objectsstr, ' "' fullfile(modelObjectFolder,['wrapfunctions' objectFileSuffix]) '" ' model_cpp_obj]; - - % now we have compiled everything model-specific, so we can replace hashes.mat to prevent recompilation - try - movefile(fullfile(modelSourceFolder,'hashes_new.mat'),... - fullfile(modelSourceFolder,'hashes.mat'),'f'); - end - - %% Linking - fprintf('linking | '); - - if(isunix) - if(~ismac) - CLIBS = 'CLIBS="-lrt -lmwblas -ldl"'; - else - CLIBS = 'CLIBS="-lmwblas"'; - end - else - if(strcmp(mex.getCompilerConfigurations('c++').Name,'MinGW64 Compiler (C++)')) - CLIBS = 'LD="g++"'; - else - CLIBS = '-lmwblas'; - end - end - - mexFilename = fullfile(modelSourceFolder,['ami_' modelname]); - cmd = ['mex ' DEBUG ' ' COPT ' ' CLIBS ... - ' -output "' mexFilename '" ' objectsstr]; - try - eval(cmd); - catch ME - disp(cmd); - rethrow(ME); - end - -end - -function [objectStrAmici] = compileAmiciBase(amiciRootPath, objectFolder, objectFileSuffix, includesstr, DEBUG, COPT) - % generate hash for file and append debug string if we have an md5 - % file, check this hash against the contained hash - cppsrc = {'amici', 'symbolic_functions','spline', ... - 'edata','rdata', 'exception', 'logging', ... - 'interface_matlab', 'misc', 'simulation_parameters', ... - 'solver', 'solver_cvodes', 'solver_idas', 'model_state', ... - 'model', 'model_ode', 'model_dae', 'returndata_matlab', ... - 'forwardproblem', 'steadystateproblem', 'backwardproblem', 'newton_solver', ... - 'abstract_model', 'sundials_matrix_wrapper', 'sundials_linsol_wrapper', ... - 'vector', 'splinefunctions', 'event' - }; - % to be safe, recompile everything if headers have changed. otherwise - % would need to check the full include hierarchy - amiciIncludePath = fullfile(amiciRootPath,'include','amici'); - amiciSourcePath = fullfile(amiciRootPath,'src'); - recompile = headersHaveChanged(amiciIncludePath,objectFolder); - objectArray = cellfun(@(x) [' "', fullfile(objectFolder, x), objectFileSuffix, '"'], cppsrc, 'UniformOutput', false); - objectStrAmici = strjoin(objectArray, ' '); - sourcesForRecompile = cppsrc(cellfun(@(x) recompile || sourceNeedsRecompilation(amiciSourcePath, objectFolder, x, objectFileSuffix), cppsrc)); - if(numel(sourcesForRecompile)) - fprintf('AMICI base files | '); - sourceStr = ''; - for j = 1:numel(sourcesForRecompile) - baseFilename = fullfile(amiciSourcePath, sourcesForRecompile{j}); - sourceStr = [sourceStr, ' "', baseFilename, '.cpp"']; - end - cmd = ['mex ' DEBUG COPT ' -c -outdir "' objectFolder '" ' ... - includesstr ' ' sourceStr]; - try - eval(cmd); - catch ME - disp(cmd); - rethrow(ME); - end - - cellfun(@(x) updateFileHashSource(amiciSourcePath, objectFolder, x), sourcesForRecompile); - updateHeaderFileHashes(amiciIncludePath, objectFolder); - end - -end - -function headersChanged = headersHaveChanged(includePath, objectFolder) - list = dir([includePath '/*.h']); - headersChanged = false; - for file = {list.name} - headersChanged = headerFileChanged(includePath, objectFolder, file{:}); - if(headersChanged) - break; - end - end -end - -function updateHeaderFileHashes(includePath, objectFolder) - list = dir([includePath '/*.h']); - for file = {list.name} - updateFileHash(includePath, objectFolder, file{:}); - end -end - - -function hash = getFileHash(file) - % getFileHash computed the md5hash of a given file - % - % Parameters: - % file: path of the file @type string - % - % Return values: - % hash: md5 hash of the provided file @type string - hash = CalcMD5(file,'File','hex'); -end - -function updateFileHashSource(sourceFolder,objectFolder,baseFilename) - fileName = [baseFilename '.cpp']; - if(exist(fullfile(sourceFolder,fileName), 'file')) - updateFileHash(sourceFolder,objectFolder,fileName); - end -end - - -function updateFileHash(fileFolder,hashFolder,filename) - hash = getFileHash(fullfile(fileFolder,filename)); - fid = fopen(fullfile(hashFolder,[filename '.md5']),'w'); - fprintf(fid,hash); - fclose(fid); -end - -function headerChanged = headerFileChanged(includePath, objectFolder, fileName) - % headerFileChanged checks whether fileName.h has changed since last compilation - % - % Parameters: - % * includePath: path to directory containing header file @type string - % * objectFolder: path to directory containing compiled md5 file @type string - % * fileName: name of header @type string - % - % Return values: - % headerChanged: flag indicating whether we need to recompile filestr.cpp - hashFileSufffix = ['.md5']; - headerFile = fullfile(includePath,fileName); - hashFile = fullfile(objectFolder,[fileName hashFileSufffix]); - if(~exist(headerFile , 'file') && exist(hashFile, 'file')) - % header file has been removed, recompile and remove stray - % hash file - headerChanged = true; - delete(hashFile); - elseif(~exist(hashFile, 'file')) - % there exists a header file but no corresponding hash file - headerChanged = true; - else - % hash file exist, did hash change? - headerChanged = hashHasChanged(headerFile, hashFile); - end -end - -function recompile = sourceNeedsRecompilation(amiciSourcePath, objectFolder, fileName, o_suffix) - % sourceNeedsRecompilation checks whether fileName.cpp has already been - % compiled as fileName.o and whether the md5 hash of fileName.cpp matches - % the one in fileName.md5 - % - % Parameters: - % * amiciSourcePath: path to directory containing source file @type string - % * objectFolder: path to directory containing compiled object and md5 file @type string - % * fileName: name of source @type string - % * o_suffix: OS specific suffix for compiled objects @type string - % - % Return values: - % recompile: flag indicating whether we need to recompile filestr.cpp - - sourceFile = fullfile(amiciSourcePath,[fileName '.cpp']); - - if(~exist(sourceFile,'file')) - % cpp does not exist, we don't need to compile :) - recompile = 0; - elseif(~exist(fullfile(objectFolder,[fileName o_suffix]),'file')) - % object file does not exist, we need to recompile - recompile = 1; - else - hashFile = fullfile(objectFolder,[fileName '.cpp.md5']); - if(~exist(hashFile, 'file')) - % source file hash does not exist, we need to recompile - recompile = 1; - else - % hash files exists, did they change? - recompile = hashHasChanged(sourceFile, hashFile); - end - end -end - -function hasChanged = hashHasChanged(sourceFilename, hashFilename) - % checkHash checks whether the given file matches the saved hash - % - % Parameters: - % * sourceFilename: the file to hash (has to exist) - % * hashFilename: the file where the hash is saved (has to exist) - % Return values: - % * hasChanged: true if file and saved hash do not match - - hash = getFileHash(sourceFilename); - fid = fopen(hashFilename); - tline = fgetl(fid); - fclose(fid); - hasChanged = ~strcmp(tline, hash); -end - -function versionstring = getCompilerVersionString() - [~,systemreturn] = system([mex.getCompilerConfigurations('c++').Details.CompilerExecutable ' --version']); - newlinePos = strfind(systemreturn, sprintf('\n')); - str = systemreturn(1:(newlinePos(1)-1)); - str = regexprep(str,'[\(\)]',''); - str = regexprep(str,'[\s\.\-]','_'); - versionstring = genvarname(str); % fix everything else we have missed -end diff --git a/matlab/@amimodel/compileC.m b/matlab/@amimodel/compileC.m deleted file mode 100644 index 815e4e065c..0000000000 --- a/matlab/@amimodel/compileC.m +++ /dev/null @@ -1,8 +0,0 @@ -function compileC(this) - % compileC compiles the mex simulation file - % - % Return values: - % void - - amimodel.compileAndLinkModel(this.modelname, fullfile(this.wrap_path,'models',this.modelname), this.coptim, this.debug, this.funs, this.cfun); -end diff --git a/matlab/@amimodel/generateC.m b/matlab/@amimodel/generateC.m deleted file mode 100644 index 2f09831b24..0000000000 --- a/matlab/@amimodel/generateC.m +++ /dev/null @@ -1,322 +0,0 @@ -function generateC(this) -% generateC generates the c files which will be used in the compilation. -% -% Return values: -% void - -% different signatures for cvodes / idas -if(strcmp(this.wtype,'iw')) - dxvec = 'dx,'; - rtcj = 'cj,'; -else - dxvec = 'NULL,'; - rtcj = '0.0,'; -end - - -% write fun ccode - -for ifun = this.funs - cppFunctionName = strrep(ifun{1}, 'sigma_', 'sigma'); - if(isfield(this.fun,ifun{1})) - bodyNotEmpty = any(this.fun.(ifun{1}).sym(:)~=0); - if(strcmp(ifun{1},'JSparse')) - bodyNotEmpty = any(this.fun.J.sym(:)~=0); - end - - if(bodyNotEmpty) - fprintf([ifun{1} ' | ']); - fid = fopen(fullfile(this.wrap_path,'models',this.modelname,[ cppFunctionName '.cpp']),'w'); - fprintf(fid,'\n'); - fprintf(fid,'#include "amici/symbolic_functions.h"\n'); - fprintf(fid,'#include "amici/defines.h" //realtype definition\n'); - - if(ismember(ifun{1},{'JSparse'})) - fprintf(fid,'#include //SUNMatrixContent_Sparse definition\n'); - end - - fprintf(fid,'typedef amici::realtype realtype;\n'); - fprintf(fid,'#include \n'); - fprintf(fid,'\n'); - fprintf(fid,'using namespace amici;\n'); - fprintf(fid,'\n'); - fprintf(fid,'namespace amici {\n\n'); - fprintf(fid,['namespace model_' this.modelname '{\n\n']); - - % function definition - fprintf(fid,['void ' cppFunctionName '_' this.modelname '' this.fun.(ifun{1}).argstr ' {\n']); - if(strcmp(ifun{1},'JSparse')) - for i = 1:length(this.rowvals) - fprintf(fid,[' JSparse->indexvals[' num2str(i-1) '] = ' num2str(this.rowvals(i)) ';\n']); - end - for i = 1:length(this.colptrs) - fprintf(fid,[' JSparse->indexptrs[' num2str(i-1) '] = ' num2str(this.colptrs(i)) ';\n']); - end - end - - if(strcmp(ifun{1},'JBand')) - fprintf(fid,['return(J_' this.modelname removeTypes(this.fun.J.argstr) ');']); - elseif(strcmp(ifun{1},'JBandB')) - fprintf(fid,['return(JB_' this.modelname removeTypes(this.fun.JB.argstr) ');']); - else - if( strcmp(ifun{1},'qBdot') ) - fprintf(fid,'switch (ip) {\n'); - this.fun.(ifun{1}).writeCcode_sensi(this,fid); - fprintf(fid,'}\n'); - elseif(this.fun.(ifun{1}).sensiflag) - fprintf(fid,'switch (ip) {\n'); - this.fun.(ifun{1}).writeCcode_sensi(this,fid); - fprintf(fid,'}\n'); - else - this.fun.(ifun{1}).writeCcode(this,fid); - end - end - fprintf(fid,'}\n'); - fprintf(fid,'\n'); - fprintf(fid,['} // namespace model_' this.modelname '\n\n']); - fprintf(fid,'} // namespace amici\n\n'); - - fclose(fid); - end - end -end - -% wrapfunctions.h - -fid = fopen(fullfile(this.wrap_path,'models',this.modelname,'wrapfunctions.h'),'w'); -fprintf(fid,'#ifndef _amici_wrapfunctions_h\n'); -fprintf(fid,'#define _amici_wrapfunctions_h\n'); -fprintf(fid,'\n'); -fprintf(fid,['#include "' this.modelname '.h"\n']); -fprintf(fid,'\n'); -fprintf(fid,'namespace amici {\n\n'); -fprintf(fid,'namespace generic_model {\n\n'); -fprintf(fid,'std::unique_ptr getModel();\n'); -fprintf(fid,'\n'); -fprintf(fid,'} // namespace generic_model\n\n'); -fprintf(fid,'} // namespace amici \n\n'); -fprintf(fid,'#endif /* _amici_wrapfunctions_h */\n'); -fclose(fid); - -% -%---------------------------------------------------------------- -% modelname.h -% model specific function declarations -%---------------------------------------------------------------- -% -matVer = ver('MATLAB'); -fid = fopen(fullfile(this.wrap_path,'models',this.modelname,[this.modelname '.h']),'w'); -fprintf(fid,['#ifndef _amici_' this.modelname '_h\n']); -fprintf(fid,['#define _amici_' this.modelname '_h\n']); -fprintf(fid,['/* Generated by amiwrap ' matVer.Release ' ' getCommitHash(fileparts(fileparts(mfilename('fullpath')))) ' */\n']); -fprintf(fid,'#include \n'); -fprintf(fid,'#include \n'); -fprintf(fid,'#include "amici/defines.h"\n'); -fprintf(fid,'#include //SUNMatrixContent_Sparse definition\n'); -if(~strcmp(this.wtype,'iw')) - fprintf(fid,'#include "amici/solver_cvodes.h"\n'); - fprintf(fid,'#include "amici/model_ode.h"\n'); -else - fprintf(fid,'#include "amici/solver_idas.h"\n'); - fprintf(fid,'#include "amici/model_dae.h"\n'); -end -fprintf(fid,'\n'); -fprintf(fid,'namespace amici {\n\n'); -fprintf(fid,'class Solver;\n\n'); -fprintf(fid,['namespace model_' this.modelname '{\n\n']); - -for ifun = this.funs - if(~isfield(this.fun,ifun{1})) - - this.fun(1).(ifun{1}) = amifun(ifun{1},this); % don't use getfun here - % as we do not want symbolics to be generated, we only want to be able - % access argstr - end - if(checkIfFunctionBodyIsNonEmpty(this,ifun{1})) - cppFunctionName = strrep(ifun{1}, 'sigma_', 'sigma'); - fprintf(fid,['extern void ' cppFunctionName '_' this.modelname this.fun.(ifun{1}).argstr ';\n']); - end -end - -% Subclass Model -fprintf(fid,'\n'); -if(strcmp(this.wtype,'iw')) - baseclass = 'Model_DAE'; -else - baseclass = 'Model_ODE'; -end - -fprintf(fid,['class Model_' this.modelname ' : public amici::' baseclass ' {\n']); -fprintf(fid,'public:\n'); -fprintf(fid,[' Model_' this.modelname '()\n']); -fprintf(fid,[' : amici::' baseclass '(\n']); -fprintf(fid,[' amici::ModelDimensions(\n']); -fprintf(fid,[' ' num2str(this.nx) ',\n']); -fprintf(fid,[' ' num2str(this.nxtrue) ',\n']); -fprintf(fid,[' ' num2str(this.nx) ',\n']); -fprintf(fid,[' ' num2str(this.nxtrue) ',\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' ' num2str(this.np) ',\n']); -fprintf(fid,[' ' num2str(this.nk) ',\n']); -fprintf(fid,[' ' num2str(this.ny) ',\n']); -fprintf(fid,[' ' num2str(this.nytrue) ',\n']); -fprintf(fid,[' ' num2str(this.nz) ',\n']); -fprintf(fid,[' ' num2str(this.nztrue) ',\n']); -fprintf(fid,[' ' num2str(this.nevent) ',\n']); -fprintf(fid,[' ' num2str(this.nevent) ',\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' ' num2str(this.ng) ',\n']); -fprintf(fid,[' ' num2str(this.nw) ',\n']); -fprintf(fid,[' ' num2str(this.ndwdx) ',\n']); -fprintf(fid,[' ' num2str(this.ndwdp) ',\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' {},\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' 0,\n']); -fprintf(fid,[' ' num2str(this.nnz) ',\n']); -fprintf(fid,[' ' num2str(this.ubw) ',\n']); -fprintf(fid,[' ' num2str(this.lbw) '\n']); -fprintf(fid,[' ),\n']); -fprintf(fid,[' amici::SimulationParameters(\n']); -fprintf(fid,[' std::vector(' num2str(this.nk) ', 1.0),\n']); -fprintf(fid,[' std::vector(' num2str(this.np) ', 1.0)\n']); -fprintf(fid,[' ),\n']); -switch(this.o2flag) - case 1 - fprintf(fid,' amici::SecondOrderMode::full,\n'); - case 2 - fprintf(fid,' amici::SecondOrderMode::directional,\n'); - otherwise - fprintf(fid,' amici::SecondOrderMode::none,\n'); -end -initstr = num2str(this.id, '%d, '); -fprintf(fid,[' std::vector{' initstr(1:end-1) '},\n']); -initstr = num2str(transpose(this.z2event), '%d, '); -fprintf(fid,[' std::vector{' initstr(1:end-1) '})\n']); -fprintf(fid,[' {};\n\n']); -fprintf(fid,[' amici::Model* clone() const override { return new Model_' this.modelname '(*this); };\n\n']); -fprintf(fid,[' std::string getAmiciCommit() const override { return "' getCommitHash(fileparts(fileparts(mfilename('fullpath')))) '"; };\n\n']); - -for ifun = this.funs - cppFunctionName = strrep(ifun{1}, 'sigma_', 'sigma'); - fprintf(fid,[' void f' cppFunctionName this.fun.(ifun{1}).argstr ' override {\n']); - if(checkIfFunctionBodyIsNonEmpty(this,ifun{1})) - fprintf(fid,[' ' cppFunctionName '_' this.modelname '' removeTypes(this.fun.(ifun{1}).argstr) ';\n']); - end - fprintf(fid,' }\n\n'); -end -fprintf(fid,'};\n\n'); -fprintf(fid,['} // namespace model_' this.modelname '\n\n']); -fprintf(fid,'} // namespace amici\n\n'); -fprintf(fid,['#endif /* _amici_' this.modelname '_h */\n']); -fclose(fid); - -% wrapfunctions.cpp -fid = fopen(fullfile(this.wrap_path,'models',this.modelname,'wrapfunctions.cpp'),'w'); -fprintf(fid,'#include "amici/model.h"\n'); -fprintf(fid,'#include "wrapfunctions.h"\n\n'); -fprintf(fid,'namespace amici {\n\n'); -fprintf(fid,'namespace generic_model {\n\n'); -fprintf(fid,'std::unique_ptr getModel() {\n'); -fprintf(fid,' return std::unique_ptr(\n'); -fprintf(fid,[' new amici::model_' this.modelname '::Model_' this.modelname '());\n']); -fprintf(fid,'}\n\n'); -fprintf(fid,'} // namespace generic_model\n\n'); -fprintf(fid,'} // namespace amici \n\n'); -fclose(fid); - -fprintf('CMakeLists | '); -generateCMakeFile(this); - -fprintf('swig | '); -generateSwigInterfaceFiles(this) - -fprintf('main | '); -generateMainC(this); - -fprintf('\r') -end - - -function argstr = removeTypes(argstr) -% removeTypes transforms an argument string from a string with variable -% types (for function definition) to a string without variable types -% (for function calling) -% -% Parameters: -% argstr: function definition argument string @type *char -% -% Return values: -% argstr: function call argument string @type *char - -argstr = strrep(argstr,'realtype',''); -argstr = strrep(argstr,'int',''); -argstr = strrep(argstr,'bool',''); -argstr = strrep(argstr,'const',''); -argstr = strrep(argstr,'double',''); -argstr = strrep(argstr,'SUNMatrixContent_Sparse',''); -argstr = strrep(argstr,'*',''); -argstr = strrep(argstr,' ',''); -argstr = strrep(argstr,',',', '); - -end - - -function generateCMakeFile(this) - sourceStr = ''; - for j=1:length(this.funs) - funcName = this.funs{j}; - if(checkIfFunctionBodyIsNonEmpty(this,funcName)) - cppFunctionName = strrep(funcName, 'sigma_', 'sigma'); - sourceStr = [ sourceStr, sprintf('${MODEL_DIR}/%s.cpp\n', cppFunctionName) ]; - end - end - - t = template(); - t.add('TPL_MODELNAME', this.modelname); - t.add('TPL_SOURCES', sourceStr); - t.add('TPL_AMICI_VERSION', ''); - CMakeFileName = fullfile(this.wrap_path,'models',this.modelname,'CMakeLists.txt'); - CMakeTemplateFileName = fullfile(fileparts(fileparts(fileparts(mfilename('fullpath')))), 'src' , 'CMakeLists.template.cmake'); - t.replace(CMakeTemplateFileName, CMakeFileName); -end - -function generateSwigInterfaceFiles(this) - - modelSwigDir = fullfile(this.wrap_path,'models',this.modelname,'swig'); - amiciSwigDir = fullfile(fileparts(fileparts(fileparts(mfilename('fullpath')))),'swig'); - if(~exist(modelSwigDir,'dir')) - mkdir(modelSwigDir) - end - - %interface file - t = template(); - t.add('TPL_MODELNAME', this.modelname); - SwigInterfaceFile = fullfile(modelSwigDir,[this.modelname '.i']); - SwigInterfaceTemplateFileName = fullfile(amiciSwigDir, 'modelname.template.i'); - t.replace(SwigInterfaceTemplateFileName, SwigInterfaceFile); - - %CMakeLists.txt - if(~exist(fullfile(this.wrap_path,'models',this.modelname,'swig'),'dir')) - mkdir(fullfile(this.wrap_path,'models',this.modelname),'swig'); - end - copyfile(fullfile(amiciSwigDir,'CMakeLists_model.cmake'),fullfile(modelSwigDir,'CMakeLists.txt')); - -end - - - -function generateMainC(this) - mainFileSource = fullfile(fileparts(fileparts(fileparts(mfilename('fullpath')))), 'src/main.template.cpp'); - mainFileDestination = fullfile(this.wrap_path,'models',this.modelname,'main.cpp'); - copyfile(mainFileSource, mainFileDestination); -end - -function nonempty = checkIfFunctionBodyIsNonEmpty(this,ifun) - % if we don't have symbolic variables, it might have been generated before and symbolic expressions were simply not - % regenerated. any() for empty (no generated) variables is always false. - cppFunctionName = strrep(ifun, 'sigma_', 'sigma'); - nonempty = or(exist(fullfile(this.wrap_path,'models',this.modelname,[cppFunctionName '.cpp']),'file'),any(this.fun.(ifun).sym(:)~=0)); -end diff --git a/matlab/@amimodel/generateM.m b/matlab/@amimodel/generateM.m deleted file mode 100644 index ea8c531f83..0000000000 --- a/matlab/@amimodel/generateM.m +++ /dev/null @@ -1,25 +0,0 @@ -function generateM(this, amimodelo2) -% generateM generates the matlab wrapper for the compiled C files. -% -% Parameters: -% amimodelo2: this struct must contain all necessary symbolic -% definitions for second order sensivities @type amimodel -% -% Return values: -% void -wrapperFilename = fullfile(this.wrap_path,'models',this.modelname,['simulate_',this.modelname,'.m']); -this.generateMatlabWrapper(this.nx, this.ny, this.np, this.nk, this.nz, ... - this.o2flag, amimodelo2, wrapperFilename, this.modelname, this.param, ... - this.forward, this.adjoint) - -%% Generation of the file which computes the Jacobian - -for fun = this.mfuns - if(isfield(this.fun,fun{1})) - fprintf([fun{1} ' | ']); - this.fun.(fun{1}).writeMcode(this); - end -end - - -end diff --git a/matlab/@amimodel/generateMatlabWrapper.m b/matlab/@amimodel/generateMatlabWrapper.m deleted file mode 100644 index ae89270c8a..0000000000 --- a/matlab/@amimodel/generateMatlabWrapper.m +++ /dev/null @@ -1,464 +0,0 @@ -function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFilename, modelname, ... - pscale, forward, adjoint) - % generateMatlabWrapper generates the matlab wrapper for the compiled C files. - % - % Parameters: - % nx: number of states - % ny: number of observables - % np: number of parameters - % nk: number of fixed parameters - % nz: number of events - % o2flag: o2flag - % amimodelo2: this struct must contain all necessary symbolic - % definitions for second order sensivities @type amimodel - % wrapperFilename: output filename - % modelname: name of the model - % pscale: default parameter scaling - % forward: has forward sensitivity equations - % adjoint: has adjoint sensitivity equations - % - % Return values: - % void - amiciRootDir = fileparts(fileparts(fileparts(mfilename('fullpath')))); - - if(~isempty(amimodelo2)) - nztrue = amimodelo2.nztrue; - nxtrue = amimodelo2.nxtrue; - nytrue = amimodelo2.nytrue; - o2flag = amimodelo2.o2flag; - else - nztrue = nz; - nxtrue = nx; - nytrue = ny; - o2flag = o2flag; - end - - [commit_hash,branch,url] = getCommitHash(amiciRootDir); - if(isempty(commit_hash)) - commit_hash = '#'; - end - - if(o2flag) - nxtrue = amimodelo2.nxtrue; - nytrue = amimodelo2.nytrue; - end - - - %% Generation of the simulation file - - fid = fopen(wrapperFilename,'w'); - fprintf(fid,['%% simulate_' modelname '.m is the matlab interface to the cvodes mex\n'... - '%% which simulates the ordinary differential equation and respective\n'... - '%% sensitivities according to user specifications.\n'... - '%% this routine was generated using AMICI commit ' commit_hash ' in branch ' branch ' in repo ' url '.\n'... - '%%\n'... - '%% USAGE:\n'... - '%% ======\n'... - '%% [...] = simulate_' modelname '(tout,theta)\n'... - '%% [...] = simulate_' modelname '(tout,theta,kappa,data,options)\n'... - '%% [status,tout,x,y,sx,sy] = simulate_' modelname '(...)\n'... - '%%\n'... - '%% INPUTS:\n'... - '%% =======\n'... - '%% tout ... 1 dimensional vector of timepoints at which a solution to the ODE is desired\n'... - '%% theta ... 1 dimensional parameter vector of parameters for which sensitivities are desired.\n'... - '%% this corresponds to the specification in model.sym.p\n'... - '%% kappa ... 1 dimensional parameter vector of parameters for which sensitivities are not desired.\n'... - '%% this corresponds to the specification in model.sym.k\n'... - '%% data ... struct containing the following fields:\n'... - '%% Y ... 2 dimensional matrix containing data.\n'... - '%% columns must correspond to observables and rows to time-points\n'... - '%% Sigma_Y ... 2 dimensional matrix containing standard deviation of data.\n'... - '%% columns must correspond to observables and rows to time-points\n'... - '%% T ... (optional) 2 dimensional matrix containing events.\n'... - '%% columns must correspond to event-types and rows to possible event-times\n'... - '%% Sigma_T ... (optional) 2 dimensional matrix containing standard deviation of events.\n'... - '%% columns must correspond to event-types and rows to possible event-times\n'... - '%% options ... additional options to pass to the cvodes solver. Refer to the cvodes guide for more documentation.\n'... - '%% .atol ... absolute tolerance for the solver. default is specified in the user-provided syms function.\n'... - '%% default value is 1e-16\n'... - '%% .rtol ... relative tolerance for the solver. default is specified in the user-provided syms function.\n'... - '%% default value is 1e-8\n'... - '%% .maxsteps ... maximal number of integration steps. default is specified in the user-provided syms function.\n'... - '%% default value is 1e4\n'... - '%% .tstart ... start of integration. for all timepoints before this, values will be set to initial value.\n'... - '%% default value is 0\n'... - '%% .sens_ind ... 1 dimensional vector of indexes for which sensitivities must be computed.\n'... - '%% default value is 1:length(theta).\n'... - '%% .x0 ... user-provided state initialisation. This should be a vector of dimension [#states, 1].\n'... - '%% default is state initialisation based on the model definition.\n'... - '%% .sx0 ... user-provided sensitivity initialisation. this should be a matrix of dimension [#states x #parameters].\n'... - '%% default is sensitivity initialisation based on the derivative of the state initialisation.\n'... - '%% .lmm ... linear multistep method for forward problem.\n'... - '%% 1: Adams-Bashford\n'... - '%% 2: BDF (DEFAULT)\n'... - '%% .iter ... iteration method for linear multistep.\n'... - '%% 1: Functional\n'... - '%% 2: Newton (DEFAULT)\n'... - '%% .linsol ... linear solver module.\n'... - '%% direct solvers:\n'... - '%% 1: Dense\n'... - '%% 2: Band (not implemented)\n'... - '%% 3: LAPACK Dense (not implemented)\n'... - '%% 4: LAPACK Band (not implemented)\n'... - '%% 5: Diag (not implemented)\n'... - '%% implicit krylov solvers:\n'... - '%% 6: SPGMR\n'... - '%% 7: SPBCG\n'... - '%% 8: SPTFQMR\n'... - '%% sparse solvers:\n'... - '%% 9: KLU (DEFAULT)\n'... - '%% .stldet ... flag for stability limit detection. this should be turned on for stiff problems.\n'... - '%% 0: OFF\n'... - '%% 1: ON (DEFAULT)\n'... - '%% .sensi ... sensitivity order.\n'... - '%% 0: OFF (DEFAULT)\n'... - '%% 1: first\n'... - '%% 2: second\n'... - '%% .sensi_meth ... method for sensitivity analysis.\n'... - '%% 0: no sensitivity analysis\n'... - '%% 1 or ''forward'': forward sensitivity analysis (DEFAULT)\n'... - '%% 2 or ''adjoint'': adjoint sensitivity analysis \n'... - '%% 3 or ''ss'': defined but not documented \n'... - '%% .adjoint ... flag for adjoint sensitivity analysis.\n'... - '%% true: on \n'... - '%% false: off (DEFAULT)\n'... - '%% NO LONGER USED: Replaced by sensi_meth\n'... - '%% .ism ... only available for sensi_meth == 1. Method for computation of forward sensitivities.\n'... - '%% 1: Simultaneous (DEFAULT)\n'... - '%% 2: Staggered\n'... - '%% 3: Staggered1\n'... - '%% .Nd ... only available for sensi_meth == 2. Number of Interpolation nodes for forward solution. \n'... - '%% default is 1000. \n'... - '%% .interpType ... only available for sensi_meth == 2. Interpolation method for forward solution.\n'... - '%% 1: Hermite (DEFAULT for problems without discontinuities)\n'... - '%% 2: Polynomial (DEFAULT for problems with discontinuities)\n'... - '%% .ordering ... online state reordering.\n'... - '%% 0: AMD reordering (default)\n'... - '%% 1: COLAMD reordering\n'... - '%% 2: natural reordering\n'... - '%% .newton_maxsteps ... maximum newton steps\n'... - '%% default value is 40\n'... - '%% a value of 0 will disable the newton solver\n'... - '%% .newton_maxlinsteps ... maximum linear steps\n'... - '%% default value is 100\n'... - '%% .pscale ... parameter scaling\n'... - '%% []: (DEFAULT) use value specified in the model (fallback: ''lin'')\n'... - '%% 0 or ''lin'': linear\n'... - '%% 1 or ''log'': natural log (base e)\n'... - '%% 2 or ''log10'': log to the base 10\n'... - '%%\n'... - '%% Outputs:\n'... - '%% ========\n'... - '%% sol.status ... flag for status of integration. generally status<0 for failed integration\n'... - '%% sol.t ... vector at which the solution was computed\n'... - '%% sol.llh ... likelihood value\n'... - '%% sol.chi2 ... chi2 value\n'... - '%% sol.sllh ... gradient of likelihood\n'... - '%% sol.s2llh ... hessian or hessian-vector-product of likelihood\n'... - '%% sol.x ... time-resolved state vector\n'... - '%% sol.y ... time-resolved output vector\n'... - '%% sol.sx ... time-resolved state sensitivity vector\n'... - '%% sol.sy ... time-resolved output sensitivity vector\n'... - '%% sol.z ... event output\n'... - '%% sol.sz ... sensitivity of event output\n'... - ]); - - - fprintf(fid,['function varargout = simulate_' modelname '(varargin)\n\n']); - fprintf(fid,'%% DO NOT CHANGE ANYTHING IN THIS FILE UNLESS YOU ARE VERY SURE ABOUT WHAT YOU ARE DOING\n'); - fprintf(fid,'%% MANUAL CHANGES TO THIS FILE CAN RESULT IN WRONG SOLUTIONS AND CRASHING OF MATLAB\n'); - fprintf(fid,'if(nargin<2)\n'); - fprintf(fid,' error(''Not enough input arguments.'');\n'); - fprintf(fid,'else\n'); - fprintf(fid,' tout=varargin{1};\n'); - fprintf(fid,' theta=varargin{2};\n'); - fprintf(fid,'end\n'); - - fprintf(fid,'if(nargin>=3)\n'); - fprintf(fid,' kappa=varargin{3};\n'); - fprintf(fid,'else\n'); - fprintf(fid,' kappa=[];\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - if(nk==0) - fprintf(fid,'if(nargin==2)\n'); - fprintf(fid,' kappa = [];\n'); - fprintf(fid,'end\n'); - end - - fprintf(fid,['if(length(theta)<' num2str(np) ')\n']); - fprintf(fid,' error(''provided parameter vector is too short'');\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - - fprintf(fid,'\n'); - fprintf(fid,'xscale = [];\n'); - fprintf(fid,'if(nargin>=5)\n'); - fprintf(fid,' if(isa(varargin{5},''amioption''))\n'); - fprintf(fid,' options_ami = varargin{5};\n'); - fprintf(fid,' else\n'); - fprintf(fid,' options_ami = amioption(varargin{5});\n'); - fprintf(fid,' end\n'); - fprintf(fid,'else\n'); - fprintf(fid,' options_ami = amioption();\n'); - fprintf(fid,'end\n'); - fprintf(fid,'if(isempty(options_ami.sens_ind))\n'); - fprintf(fid,[' options_ami.sens_ind = 1:' num2str(np) ';\n']); - fprintf(fid,['end\n']); - if(o2flag == 0) - fprintf(fid,'if(options_ami.sensi>1)\n'); - fprintf(fid,' error(''Second order sensitivities were requested but not computed'');\n'); - fprintf(fid,'end\n'); - end - fprintf(fid,'\n'); - - - fprintf(fid,'if(isempty(options_ami.pscale))\n'); - fprintf(fid,[' options_ami.pscale = ''' pscale ''' ;\n']); - fprintf(fid,'end\n'); - - if(o2flag == 2) - fprintf(fid,'if(nargin>=6)\n'); - fprintf(fid,' chainRuleFactor = getChainRuleFactors(options_ami.pscale, theta, options_ami.sens_ind);\n'); - fprintf(fid,' v = varargin{6};\n'); - fprintf(fid,' v = v(:).*chainRuleFactor(:);\n'); - fprintf(fid,'else\n'); - fprintf(fid,' if(options_ami.sensi==2)\n'); - fprintf(fid,' error(''6th argument (multiplication vector is missing'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - end - - if(o2flag) - fprintf(fid,'if(nargout>1)\n'); - fprintf(fid,' if(nargout>6)\n'); - fprintf(fid,' options_ami.sensi = 2;\n'); - fprintf(fid,' options_ami.sensi_meth = ''forward'';\n'); - fprintf(fid,' elseif(nargout>4)\n'); - fprintf(fid,' options_ami.sensi = 1;\n'); - fprintf(fid,' options_ami.sensi_meth = ''forward'';\n'); - fprintf(fid,' else\n'); - fprintf(fid,' options_ami.sensi = 0;\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - else - fprintf(fid,'if(nargout>1)\n'); - fprintf(fid,' if(nargout>4)\n'); - fprintf(fid,' options_ami.sensi = 1;\n'); - fprintf(fid,' options_ami.sensi_meth = ''forward'';\n'); - fprintf(fid,' else\n'); - fprintf(fid,' options_ami.sensi = 0;\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - end - if(~forward) - fprintf(fid,'if(options_ami.sensi>0)\n'); - fprintf(fid,' if(options_ami.sensi_meth == 1)\n'); - fprintf(fid,' error(''forward sensitivities are disabled as necessary routines were not compiled'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - end - if(~adjoint) - fprintf(fid,'if(options_ami.sensi>0)\n'); - fprintf(fid,' if(options_ami.sensi_meth == 2)\n'); - fprintf(fid,' error(''adjoint sensitivities are disabled as necessary routines were not compiled'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - end - fprintf(fid,['nplist = length(options_ami.sens_ind); %% MUST NOT CHANGE THIS VALUE\n']); - fprintf(fid,['if(nplist == 0)\n']); - fprintf(fid,[' options_ami.sensi = 0;\n']); - fprintf(fid,['end\n']); - if(o2flag) - fprintf(fid,['if(options_ami.sensi > 1)\n']); - fprintf(fid,[' nxfull = ' num2str(amimodelo2.nx) ';\n']); - fprintf(fid,['else\n']); - fprintf(fid,[' nxfull = ' num2str(nxtrue) ';\n']); - fprintf(fid,['end\n']); - else - fprintf(fid,['nxfull = ' num2str(nx) ';\n']); - end - fprintf(fid,'plist = options_ami.sens_ind-1;\n'); - fprintf(fid,['if(nargin>=4)\n']); - fprintf(fid,[' if(isempty(varargin{4}))\n']); - fprintf(fid,[' data=[];\n']); - fprintf(fid,[' else\n']); - fprintf(fid,[' if(isa(varargin{4},''amidata''))\n']); - fprintf(fid,[' data=varargin{4};\n']); - fprintf(fid,[' else\n']); - fprintf(fid,[' data=amidata(varargin{4});\n']); - fprintf(fid,[' end\n']); - fprintf(fid,[' if(data.ne>0)\n']); - fprintf(fid,[' options_ami.nmaxevent = data.ne;\n']); - fprintf(fid,[' else\n']); - fprintf(fid,[' data.ne = options_ami.nmaxevent;\n']); - fprintf(fid,[' end\n']); - fprintf(fid,[' if(isempty(kappa))\n']); - fprintf(fid,[' kappa = data.condition;\n']); - fprintf(fid,[' end\n']); - fprintf(fid,[' if(isempty(tout))\n']); - fprintf(fid,[' tout = data.t;\n']); - fprintf(fid,[' end\n']); - fprintf(fid,[' end\n']); - fprintf(fid,['else\n']); - fprintf(fid,[' data=[];\n']); - fprintf(fid,['end\n']); - fprintf(fid,['if(~all(tout==sort(tout)))\n']); - fprintf(fid,[' error(''Provided time vector is not monotonically increasing!'');\n']); - fprintf(fid,['end\n']); - fprintf(fid,['if(max(options_ami.sens_ind)>' num2str(np) ')\n']); - fprintf(fid,[' error(''Sensitivity index exceeds parameter dimension!'')\n']); - fprintf(fid,['end\n']); - fprintf(fid,['if(length(kappa)<' num2str(nk) ')\n']); - fprintf(fid,' error(''provided condition vector is too short'');\n'); - fprintf(fid,'end\n'); - - if(o2flag == 2) - fprintf(fid,'if(nargin>=6)\n'); - fprintf(fid,' kappa = [kappa(:);v(:)];\n'); - fprintf(fid,'end\n'); - end - - fprintf(fid,'init = struct();\n'); - fprintf(fid,'if(~isempty(options_ami.x0))\n'); - fprintf(fid,' if(size(options_ami.x0,2)~=1)\n'); - fprintf(fid,' error(''x0 field must be a column vector!'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,' if(size(options_ami.x0,1)~=nxfull)\n'); - fprintf(fid,' error(''Number of rows in x0 field does not agree with number of states!'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,' init.x0 = options_ami.x0;\n'); - fprintf(fid,'end\n'); - fprintf(fid,'if(~isempty(options_ami.sx0))\n'); - fprintf(fid,' if(size(options_ami.sx0,2)~=nplist)\n'); - fprintf(fid,' error(''Number of columns in sx0 field does not agree with number of model parameters!'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,' if(size(options_ami.sx0,1)~=nxfull)\n'); - fprintf(fid,' error(''Number of rows in sx0 field does not agree with number of states!'');\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - - - if(o2flag) - fprintf(fid,'if(options_ami.sensi<2)\n'); - fprintf(fid,[' sol = ami_' modelname '(tout,theta(1:' num2str(np) '),kappa(1:' num2str(nk) '),options_ami,plist,xscale,init,data);\n']); - fprintf(fid,'else\n'); - switch(o2flag) - case 1 - fprintf(fid,[' sol = ami_' modelname '_o2(tout,theta(1:' num2str(np) '),kappa(1:' num2str(nk) '),options_ami,plist,xscale,init,data);\n']); - case 2 - fprintf(fid,[' sol = ami_' modelname '_o2vec(tout,theta(1:' num2str(np) '),kappa(1:' num2str(amimodelo2.nk) '),options_ami,plist,xscale,init,data);\n']); - end - fprintf(fid,'end\n'); - else - fprintf(fid,['sol = ami_' modelname '(tout,theta(1:' num2str(np) '),kappa(1:' num2str(nk) '),options_ami,plist,xscale,init,data);\n']); - end - - if(o2flag) - fprintf(fid,'if(options_ami.sensi == 2)\n'); - fprintf(fid, ' if(~(options_ami.sensi_meth==2))\n'); - - fprintf(fid,[' sz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' ssigmaz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' srz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' for iz = 1:' num2str(nztrue) '\n']); - fprintf(fid,[' sz(:,iz,:) = sol.sz(:,2*iz-1,:);\n']); - fprintf(fid,[' ssigmaz(:,iz,:) = sol.ssigmaz(:,2*iz-1,:);\n']); - fprintf(fid,[' srz(:,iz,:) = sol.srz(:,2*iz-1,:);\n']); - fprintf(fid,[' end\n']); - switch(o2flag) - case 1 - fprintf(fid,[' sol.s2x = reshape(sol.sx(:,' num2str(nxtrue+1) ':end,:),length(tout),' num2str(nxtrue) ',' num2str(np) ',length(options_ami.sens_ind));\n']); - fprintf(fid,[' sol.s2y = reshape(sol.sy(:,' num2str(nytrue+1) ':end,:),length(tout),' num2str(nytrue) ',' num2str(np) ',length(options_ami.sens_ind));\n']); - fprintf(fid,[' sol.s2sigmay = reshape(sol.ssigmay(:,' num2str(nytrue+1) ':end,:),length(tout),' num2str(nytrue) ',' num2str(np) ',length(options_ami.sens_ind));\n']); - case 2 - fprintf(fid,[' sol.s2x = sol.sx(:,' num2str(nxtrue+1) ':end,:);\n']); - fprintf(fid,[' sol.s2y = sol.sy(:,' num2str(nytrue+1) ':end,:);\n']); - fprintf(fid,[' sol.s2sigmay = sol.ssigmay(:,' num2str(nytrue+1) ':end,:);\n']); - end - switch(o2flag) - case 1 - fprintf(fid,[' s2z = zeros(size(sol.z,1),' num2str(nztrue) ',' num2str(np) ',length(options_ami.sens_ind));\n']); - fprintf(fid,[' s2sigmaz = zeros(size(sol.z,1),' num2str(nztrue) ',' num2str(np) ',length(options_ami.sens_ind));\n']); - fprintf(fid,[' s2rz = zeros(size(sol.z,1),' num2str(nztrue) ',' num2str(np) ',length(options_ami.sens_ind));\n']); - case 2 - fprintf(fid,[' s2z = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' s2sigmaz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' s2rz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); - end - fprintf(fid,[' for iz = 1:' num2str(nztrue) '\n']); - switch(o2flag) - case 1 - fprintf(fid,[' sol.s2z(:,iz,:,:) = reshape(sol.sz(:,((iz-1)*(' num2str(np) '+1)+2):((iz-1)*(' num2str(np) '+1)+' num2str(np) '+1),:),options_ami.nmaxevent,1,' num2str(np) ',length(options_ami.sens_ind));\n']); - fprintf(fid,[' sol.s2sigmaz(:,iz,:,:) = reshape(sol.ssigmaz(:,((iz-1)*(' num2str(np) '+1)+2):((iz-1)*(' num2str(np) '+1)+' num2str(np) '+1),:),options_ami.nmaxevent,1,' num2str(np) ',length(options_ami.sens_ind));\n']); - fprintf(fid,[' sol.s2rz(:,iz,:,:) = reshape(sol.srz(:,((iz-1)*(' num2str(np) '+1)+2):((iz-1)*(' num2str(np) '+1)+' num2str(np) '+1),:),options_ami.nmaxevent,1,' num2str(np) ',length(options_ami.sens_ind));\n']); - case 2 - fprintf(fid,[' sol.s2z(:,iz,:) = reshape(sol.sz(:,2*(iz-1)+2,:),options_ami.nmaxevent,1,length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' sol.s2sigmaz(:,iz,:) = reshape(sol.ssigmaz(:,2*(iz-1)+2,:),options_ami.nmaxevent,1,length(theta(options_ami.sens_ind)));\n']); - fprintf(fid,[' sol.s2rz(:,iz,:) = reshape(sol.srz(:,2*(iz-1)+2,:),options_ami.nmaxevent,1,length(theta(options_ami.sens_ind)));\n']); - end - fprintf(fid,' end\n'); - fprintf(fid,[' sol.sx = sol.sx(:,1:' num2str(nxtrue) ',:);\n']); - fprintf(fid,[' sol.sy = sol.sy(:,1:' num2str(nytrue) ',:);\n']); - fprintf(fid,[' sol.ssigmay = sol.ssigmay(:,1:' num2str(nytrue) ',:);\n']); - fprintf(fid,[' if(iz>0)\n']); - fprintf(fid,[' sol.sz = sz;\n']); - fprintf(fid,[' sol.ssigmaz = ssigmaz;\n']); - fprintf(fid,[' sol.srz = srz;\n']); - fprintf(fid,' end\n'); - fprintf(fid,' end\n'); - fprintf(fid,[' sol.x = sol.x(:,1:' num2str(nxtrue) ');\n']); - fprintf(fid,[' sol.y = sol.y(:,1:' num2str(nytrue) ');\n']); - fprintf(fid,[' sol.sigmay = sol.sigmay(:,1:' num2str(nytrue) ');\n']); - fprintf(fid,[' sol.z = sol.z(:,1:' num2str(nztrue) ');\n']); - fprintf(fid,[' sol.rz = sol.rz(:,1:' num2str(nztrue) ');\n']); - fprintf(fid,[' sol.sigmaz = sol.sigmaz(:,1:' num2str(nztrue) ');\n']); - fprintf(fid,'end\n'); - end - - fprintf(fid,'if(nargout>1)\n'); - fprintf(fid,' varargout{1} = sol.status;\n'); - fprintf(fid,' varargout{2} = sol.t;\n'); - fprintf(fid,' varargout{3} = sol.x;\n'); - fprintf(fid,' varargout{4} = sol.y;\n'); - fprintf(fid,' if(nargout>4)\n'); - fprintf(fid,' varargout{5} = sol.sx;\n'); - fprintf(fid,' varargout{6} = sol.sy;\n'); - if(o2flag) - fprintf(fid,' if(nargout>6)\n'); - fprintf(fid,' varargout{7} = sol.s2x;\n'); - fprintf(fid,' varargout{8} = sol.s2y;\n'); - fprintf(fid,' end\n'); - end - fprintf(fid,' end\n'); - fprintf(fid,'else\n'); - fprintf(fid,' varargout{1} = sol;\n'); - fprintf(fid,'end\n'); - - fprintf(fid,'function chainRuleFactors = getChainRuleFactors(pscale, theta, sens_ind)\n'); - fprintf(fid,' if(length(pscale) == 1 && length(sens_ind) ~= length(pscale))\n'); - fprintf(fid,' chainRuleFactors = arrayfun(@(x, ip) getChainRuleFactor(x, theta(ip)), repmat(pscale, 1, length(sens_ind)), sens_ind);\n'); - fprintf(fid,' else\n'); - fprintf(fid,' chainRuleFactors = arrayfun(@(x, ip) getChainRuleFactor(x, theta(ip)), pscale, sens_ind);\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - - fprintf(fid,'function chainRuleFactor = getChainRuleFactor(pscale, parameterValue)\n'); - fprintf(fid,' switch (pscale)\n'); - fprintf(fid,' case 1\n'); - fprintf(fid,' chainRuleFactor = exp(parameterValue);\n'); - fprintf(fid,' case 2\n'); - fprintf(fid,' chainRuleFactor = 10.^parameterValue*log(10);\n'); - fprintf(fid,' otherwise\n'); - fprintf(fid,' chainRuleFactor = 1.0;\n'); - fprintf(fid,' end\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - - fprintf(fid,'end\n'); - - fclose(fid); - -end diff --git a/matlab/@amimodel/generateRebuildM.m b/matlab/@amimodel/generateRebuildM.m deleted file mode 100644 index 07bf5bf02a..0000000000 --- a/matlab/@amimodel/generateRebuildM.m +++ /dev/null @@ -1,25 +0,0 @@ -function generateRebuildM(this) -% generateRebuildM generates a Matlab script for recompilation of this -% model -% -% Return values: -% void - - -filename = fullfile(this.wrap_path,'models',this.modelname,['rebuild_',this.modelname,'.m']); -% would require struct to string conversion, skipping for now -amimodelo2 = '[]'; - -fid = fopen(filename, 'w'); - -fprintf(fid, ['function ' ['rebuild_', this.modelname] '()\n']); -fprintf(fid, ['modelName = ''' this.modelname ''';\n']); -fprintf(fid, 'amimodel.compileAndLinkModel(modelName, '''', [], [], [], []);\n'); -fprintf(fid, ['amimodel.generateMatlabWrapper(' num2str(this.nx) ', ' num2str(this.ny) ... - ', ' num2str(this.np) ', ' num2str(this.nk) ', ' num2str(this.nz) ', ' num2str(this.o2flag) ', ' ... - amimodelo2 ', [''simulate_'' modelName ''.m''], ''' ... - this.modelname ''', ''' this.param ''', ' num2str(this.forward) ', ' num2str(this.adjoint) ');\n']); -fprintf(fid, 'end\n'); - -fclose(fid); -end diff --git a/matlab/@amimodel/getFun.m b/matlab/@amimodel/getFun.m deleted file mode 100644 index 12f405b329..0000000000 --- a/matlab/@amimodel/getFun.m +++ /dev/null @@ -1,46 +0,0 @@ -function getFun(this,HTable,funstr) - % getFun generates symbolic expressions for the requested function. - % - % Parameters: - % HTable: struct with hashes of symbolic definition from the previous - % compilation @type struct - % funstr: function for which symbolic expressions should be computed @type string - % - % Return values: - % void - - [wrap_path,~,~]=fileparts(fileparts(which('amiwrap.m'))); - - fun = amifun(funstr,this); - - - if(~isfield(this.fun,funstr)) % check whether we already computed the respective fun - if(~all(strcmp(fun.deps,funstr))) % prevent infinite loops - if(this.recompile) - cflag = this.checkDeps([],fun.deps); - else - cflag = this.checkDeps(HTable,fun.deps); - end - else - if(~isempty(fun.deps)) - if(~isfield(this.strsym,[fun.deps{1} 's'])) - cflag = 1; - else - cflag = 0; - end - else - cflag = 1; - end - end - else - cflag = 0; - end - - - if(cflag) - fun = amifun(funstr,this); - [fun,this] = fun.getSyms(this); - this.fun(1).(funstr) = fun; - end - -end diff --git a/matlab/@amimodel/loadOldHashes.m b/matlab/@amimodel/loadOldHashes.m deleted file mode 100644 index fd56c3361b..0000000000 --- a/matlab/@amimodel/loadOldHashes.m +++ /dev/null @@ -1,67 +0,0 @@ -function HTable = loadOldHashes(this) - % loadOldHashes loads information from a previous compilation of the model. - % - % Return values: - % HTable: struct with hashes of symbolic definition from the previous - % compilation @type struct - - [wrap_path,~,~]=fileparts(fileparts(which('amiwrap.m'))); - try - load(fullfile(wrap_path,'models',this.modelname,['hashes.mat'])) - try - this.nw = nw; - this.ndwdp = ndwdp; - this.ndwdx = ndwdx; - this.z2event = z2event; - this.nnz = nnonzeros; - this.id = id; - this.ubw = ubw; - this.lbw = lbw; - this.colptrs = colptrs; - this.rowvals = rowvals; - this.sparseidx = sparseidx; - this.colptrsB = colptrsB; - this.rowvalsB = rowvalsB; - this.sparseidxB = sparseidxB; - catch err - end - - catch - HTable = struct(); - end - DHTable.x = ''; - DHTable.k = ''; - - DHTable.p = ''; - DHTable.x0 = ''; - DHTable.y = ''; - DHTable.sigma_y = ''; - DHTable.sigma_z = ''; - DHTable.z = ''; - DHTable.trigger = ''; - DHTable.deltax = ''; - DHTable.xdot = ''; - if(strcmp(this.wtype,'iw')) - DHTable.dx0 = ''; - DHTable.M = ''; - end - DHTable.Jy = ''; - DHTable.Jz = ''; - - DHTable.generateC = ''; - DHTable.makeSyms = ''; - DHTable.makeEvents = ''; - DHTable.gccode = ''; - DHTable.getArgs = ''; - DHTable.getCVar = ''; - DHTable.getFArgs = ''; - DHTable.getSensiFlag = ''; - DHTable.getSyms = ''; - DHTable.printLocalVars = ''; - DHTable.writeCcode = ''; - DHTable.writeCcode_sensi = ''; - DHTable.tdata = ''; - - - HTable = am_setdefault(HTable,DHTable); -end diff --git a/matlab/@amimodel/makeEvents.m b/matlab/@amimodel/makeEvents.m deleted file mode 100644 index db6025403b..0000000000 --- a/matlab/@amimodel/makeEvents.m +++ /dev/null @@ -1,288 +0,0 @@ -function makeEvents( this ) -% makeEvents extracts discontiniuties from the model right hand side -% and converts them into events -% -% Parameters: -% -% Return values: -% void - -nevent = length(this.event); -nx = length(this.sym.x); - -% extract trigger and bolus, also check dimensions here -for ievent = 1:nevent - trigger{ievent} = this.event(ievent).trigger; - if(length(this.event(ievent).bolus) == nx) - bolus{ievent} = this.event(ievent).bolus(:); - elseif(numel(this.event(ievent).bolus) == 1) - bolus{ievent} = this.event(ievent).bolus*ones(nx,1); - else - error(['Bolus of event ' num2str(ievent) ' is neither a scalar nor does it match the state dimension!']); - end - z{ievent} = transpose(this.event(ievent).z(:)); -end - -ievent = nevent; -nx = length(this.sym.x); -triggerstr = ''; -triggers = {}; - -% collect all the triggers -for ix = 1:nx - tmp_str = char(this.sym.xdot(ix)); - % find discontinuity inducing functions - idx_start = [strfind(tmp_str,'heaviside') + 9,strfind(tmp_str,'dirac') + 5]; % find - if(~isempty(idx_start)) - % compute bracket level - brl = cumsum(tmp_str == '(') - cumsum(tmp_str == ')'); - for iocc = 1:length(idx_start) % loop over occurances - % extract argument - idx_end = find(brl(idx_start(iocc):end)-brl(idx_start(iocc))==-1,1,'first'); - arg = tmp_str((idx_start(iocc)+1):(idx_start(iocc)+idx_end-2)); - triggers{end+1} = arg; - triggers{end+1} = ['-(' arg ')']; % for dirac we need both +to- and -to+ transitions - end - end -end - -% select the unique ones -utriggers = unique(arrayfun(@char,betterSym(triggers),'UniformOutput',false)); -for itrigger = 1:length(utriggers) - ievent = ievent + 1; - trigger{ievent} = betterSym(utriggers{itrigger}); - bolus{ievent} = sym(zeros(nx,1)); - z{ievent} = sym.empty([0,0]); -end - -% update number of events -if(exist('trigger','var')) - nevent = length(trigger); -else - nevent = 0; -end - -if(nevent>0) - % initialis tmp_bolus - tmp_bolus = cell([nevent,1]); - for ievent = 1:nevent - tmp_bolus{ievent} = sym(zeros([nx,1])); - end - syms polydirac - - % initialise hflag - hflags = zeros([nx,nevent]); - - % heaviside - event_dependency = zeros(nevent); - for ievent = 1:nevent - symchar = char(trigger{ievent}); - if(~isempty(strfind(symchar,'heaviside'))) - for jevent = 1:nevent - % remove the heaviside function and replace by h - % variable which is update on event occurrence in the - % solver - triggerchar = char(trigger{jevent}); - str_arg_h = ['heaviside(' triggerchar ')' ]; - event_dependency(ievent,jevent) = event_dependency(ievent,jevent) + ~isempty(strfind(symchar,str_arg_h)); - mtriggerchar = char(-trigger{jevent}); - str_arg_h = ['heaviside(' mtriggerchar ')' ]; - event_dependency(ievent,jevent) = event_dependency(ievent,jevent) + ~isempty(strfind(symchar,str_arg_h)); - end - end - end - - % check for loops - if(any(any(event_dependency^(size(event_dependency,1))))) - error('Found loop in trigger dependency. This can lead to the simulation getting stuck and is thus currently not supported. Please check your model definition!') - end - - P = 1:size(event_dependency,1); - - % make matrix upper triangular, this is to ensure that we dont end - % up with partially replaced trigger functions that we no longer recognise - while(~isempty(find(triu(event_dependency(P,P))-event_dependency(P,P)))) - for ip = 1:length(P) - jp = ip+find(event_dependency(P(ip+1:end),P(ip)),1,'first'); - while ~isempty(jp) - pp = P(ip); - P(ip) = P(jp); - P(jp) = pp; - jp = ip+find(event_dependency(P(ip+1:end),P(ip)),1,'first'); - end - end - end - trigger = trigger(P); - bolus = bolus(P); - z = z(P); - - - - for ix = 1:nx - symchar = char(this.sym.xdot(ix)); - symvariable = this.sym.xdot(ix); - if(strfind(symchar,'dirac')) - for ievent = 1:nevent - % remove the dirac function and replace by adding - % respective bolus - symvariable = subs(symvariable,dirac(trigger{ievent}),sym('polydirac')); - % extract coefficient - [cfp,t] = coeffs(symvariable,polydirac); - if(any(double(t==sym('polydirac')))) - tmp_bolus{ievent}(ix) = tmp_bolus{ievent}(ix) + cfp(logical(t==sym('polydirac'))); - idx_mirror = find(cellfun(@(x) double(x==-trigger{ievent}),trigger)); - if(~isempty(idx_mirror)) - tmp_bolus{idx_mirror}(ix) = tmp_bolus{idx_mirror}(ix) + cfp(logical(t==sym('polydirac'))); % for dirac we need both +to- and -to+ transitions - end - end - % remove dirac - symvariable = subs(symvariable,sym('polydirac'),sym('0')); - end - end - if(strfind(symchar,'heaviside')) - - for ievent = 1:nevent - % remove the heaviside function and replace by h - % variable which is updated upon event occurrence in the - % solver - - % h variables only change for one sign change but heaviside - % needs updating for both, thus we should - symvariable = subs(symvariable,heaviside( trigger{ievent}),betterSym(['h_' num2str(ievent-1)'])); - symvariable = subs(symvariable,heaviside(-trigger{ievent}),betterSym(['(1-h_' num2str(ievent-1) ')'])); - % set hflag - - % we can check whether dividing cfp(2) by - % trigger{ievent} reduced the length of the symbolic - % expression. If it does, this suggests that - % trigger{ievent} is a factor of cfp(2), which will be - if(or(... - ismember(sym(['h_' num2str(ievent-1)']),symvar(symvariable)),... - ismember(sym(['h_' num2str(find(-trigger{ievent}==trigger)-1)']),symvar(symvariable))... - )) - hflags(ix,ievent) = 1; - else - hflags(ix,ievent) = 0; - end - end - end - if(strfind(char(symvariable),'heaviside')) - warning(['Missed heaviside function in state ' num2str(ix) '. AMICI will continue and produce a running model, but this should be fixed!']) - end - % update xdot - this.sym.xdot(ix) = symvariable; - end - - % loop until we no longer found any dynamic heaviside functions in the triggers in the previous loop - nheavy = 1; - while nheavy>0 - nheavy = 0; - for ievent = 1:nevent - symchar = char(trigger{ievent}); - for jevent = 1:nevent - % remove the heaviside function and replace by h - % variable which is update on event occurrence in the - % solver - triggerchar = char(trigger{jevent}); - str_arg_h = ['heaviside(' triggerchar ')' ]; - nheavy = nheavy + length(strfind(symchar,str_arg_h)); - symchar = strrep(symchar,str_arg_h,['h_' num2str(jevent-1)]); - mtriggerchar = char(-trigger{jevent}); - str_arg_h = ['heaviside(' mtriggerchar ')' ]; - nheavy = nheavy + length(strfind(symchar,str_arg_h)); - symchar = strrep(symchar,str_arg_h,['(1-h_' num2str(jevent-1) ')']); - % set hflag - end - trigger{ievent} = betterSym(symchar); - end - end - - % compute dtriggerdt and constant trigger functions - for ievent = 1:nevent - dtriggerdt(ievent) = diff(trigger{ievent},sym('t')) + jacobian(trigger{ievent},this.sym.x)*this.sym.xdot(:); - end - triggeridx = logical(dtriggerdt~=0); - - % multiply by the dtriggerdt factor, this should stay here as we - % want the xdot to be cleaned of any dirac functions - ievent = 1; - for ievent = 1:nevent - if(not(triggeridx(ievent))) - if(any(bolus{ievent}~=0)) - error(['Event ' num2str(ievent) ' has a constant trigger function but non-zero bolus.' ... - ' Please check your model definition!']) - end - if(~isempty(z{ievent})) - error(['Event ' num2str(ievent) ' has a constant trigger function but non-empty output.' ... - ' Please check your model definition!']) - end - else - bolus{ievent} = bolus{ievent} + tmp_bolus{ievent}/abs(dtriggerdt(ievent)); - ievent = ievent+1; - end - end - - % update hflags according to bolus - for ievent = 1:nevent - if(any(double(bolus{ievent}~=0))) - for ix = 1:nx - if(~hflags(ix,ievent)) - for j = transpose(find(double(bolus{ievent}~=0))) - if(ismember(this.sym.x(j),symvar(this.sym.xdot(ix)))) - hflags(ix,ievent) = 1; - end - end - end - end - end - end - - this.event = amievent.empty(); - - % update events - for ievent = 1:nevent - this.event(ievent) = amievent(trigger{ievent},bolus{ievent}(:),z{ievent}); - % do not add a (:) after z{ievent} this will transform an - % [ empty sym ] into Empty sym: 0-by-1 which will lead to a - % zero entry if we apply [this.event.z] - this.event(ievent) = this.event(ievent).setHflag(hflags(:,ievent)); - end -end - -if(~isfield(this.sym,'sigma_z')) - this.sym.sigma_z = sym(ones(length([this.event.z]),1)); -end -if(numel(this.sym.sigma_z) == 1) - this.sym.sigma_z = this.sym.sigma_z*sym(ones(length([this.event.z]),1)); -end - -if(~isfield(this.sym,'Jz')) - this.sym.Jz = sym(zeros(length([this.event.z]),1)); - for iz = 1:length([this.event.z]) - this.sym.Jz(iz) = betterSym(['0.5*log(2*pi*sigma_z_' num2str(iz-1) '^2) + 0.5*((z_' num2str(iz-1) '-mz_' num2str(iz-1) ')/sigma_z_' num2str(iz-1) ')^2']); - end -end - -z = sym(arrayfun(@(iz) ['z_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); -var_z = sym(arrayfun(@(iz) ['var_z_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); -sigma_z = sym(arrayfun(@(iz) ['sigma_z_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); -var_sigma_z = sym(arrayfun(@(iz) ['sigma_z_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); - -this.sym.Jz = subs(this.sym.Jz,z,var_z); -this.sym.Jz = subs(this.sym.Jz,sigma_z,var_sigma_z); - -rz = sym(arrayfun(@(iz) ['rz_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); -mz = sym(arrayfun(@(iz) ['mz_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); -var_rz = sym(arrayfun(@(iz) ['var_rz_' num2str(iz-1)],1:length([this.event.z]),'UniformOutput',false)); - -if(~isfield(this.sym,'Jrz')) - this.sym.Jrz = sym(zeros(size(this.sym.Jz))); - for iz = 1:length([this.event.z]) - tmp = subs(this.sym.Jz(iz,:),var_z,var_rz); - this.sym.Jrz(iz,:) = subs(tmp,mz,sym(zeros(size(mz)))); - end -end - -this.sym.Jrz = subs(this.sym.Jrz,rz,var_rz); - -end diff --git a/matlab/@amimodel/makeSyms.m b/matlab/@amimodel/makeSyms.m deleted file mode 100644 index 4f6f382e40..0000000000 --- a/matlab/@amimodel/makeSyms.m +++ /dev/null @@ -1,143 +0,0 @@ -function makeSyms( this ) -% makeSyms extracts symbolic definition from the user provided model -% and checks them for consistency -% -% Parameters: -% -% Return values: -% void - - - -% check whether sym is properly defined -if(~isfield(this.sym,'xdot')) - if(isfield(this.sym,'f')) - try - this.sym.xdot = betterSym(this.sym.f(:)); - this.sym = rmfield(this.sym,'f'); - catch - error('Could not transform model.sym.f into a symbolic variable, please check the definition!') - end - else - error('field xdot/f is missing in model definition') - end -else - if(isfield(this.sym,'f')) - if(~isequaln(this.sym.f,this.sym.xdot)) - error('Model this contains conflicting definitions sym.f and sym.xdot of DE right hand side'); - end - else - try - if(~isa(this.sym.xdot(:),'sym')) - this.sym.xdot = betterSym(this.sym.xdot(:)); - else - this.sym.xdot = this.sym.xdot(:); - end - catch - error('Could not transform model.sym.xdot into a symbolic variable, please check the definition!') - end - end -end - -if(~isfield(this.sym,'x')) - error('Model this is missing the definition of the state vector x (.sym.x)!') -else - try - this.sym.x = sym(this.sym.x(:)); - catch - error('Could not transform model.sym.x into a symbolic variable, please check the definition!') - end -end -if(numel(this.sym.x)~=numel(this.sym.xdot)) - error('Size of model.sym.x and model.sym.xdot does not agree.') -end - -if(~isfield(this.sym,'p')) - error('Model this is missing the definition of the parameter vector p (.sym.p)!') -else - try - this.sym.p = sym(this.sym.p(:)); - catch - error('Could not transform model.sym.y into a symbolic variable, please check the definition!') - end -end -if(~isfield(this.sym,'x0')) - error('Model this is missing the definition of the vector of initial conditions x0 (.sym.x0)!') -else - try - if(~isa(this.sym.x0(:),'sym')) - this.sym.x0 = betterSym(this.sym.x0(:)); - else - this.sym.x0 = this.sym.x0(:); - end - catch - error('Could not transform model.sym.x0 into a symbolic variable, please check the definition!') - end -end -if(numel(this.sym.x)~=numel(this.sym.x0)) - error('Size of model.sym.x and model.sym.x0 does not agree.') -end -if(any(ismember(symvar(this.sym.x0),this.sym.x))) - error('initial states x0 must not contain state variables x'); -end - -if(~isfield(this.sym,'y')) - error('Model this is missing the definition of the vector of observables y (.sym.y)!') -else - try - this.sym.y = sym(this.sym.y(:)); - catch - error('Could not transform model.sym.y into a symbolic variable, please check the definition!') - end -end - -% complete optional fields -if(~isfield(this.sym,'u')) - this.sym.u = sym(zeros(0,0)); -end -if(~isfield(this.sym,'k')) - this.sym.k = sym(zeros(0,0)); -else - try - this.sym.k = sym(this.sym.k(:)); - catch - error('Could not transform model.sym.k into a symbolic variable, please check the definition!') - end - -end - -if(isfield(this.sym,'root')) - error('The definition of events via a root function is deprecated and no longer supported. Please update the model definition syntax!') -end -if(~isfield(this.sym,'sigma_y')) - this.sym.sigma_y = sym(ones(length(this.sym.y),1)); -end -if(numel(this.sym.sigma_y) == 1) - this.sym.sigma_y = this.sym.sigma_y*sym(ones(length(this.sym.y),1)); -end -if(numel(this.sym.sigma_y)~=length(this.sym.y)) - error('Size of model.sym.y and model.sym.sigma_y does not agree.') -end - -if(any(ismember(this.sym.k,this.sym.p))) - error(['Invalid Model: ' char(this.sym.k(find(ismember(this.sym.k,this.sym.p),1))) ' is contained in both p and k!']) -end - -if(~isfield(this.sym,'Jy')) - this.sym.Jy = sym(zeros(numel(this.sym.y),1)); - for iy = 1:length(this.sym.y) - this.sym.Jy(iy) = betterSym(['0.5*log(2*pi*sigma_y_' num2str(iy-1) '^2) + 0.5*((y_' num2str(iy-1) '-my_' num2str(iy-1) ')/sigma_y_' num2str(iy-1) ')^2']); - end -end - -symvars = symvar(this.sym.xdot); -for ivar = 1:length(symvars) - if(isequaln(symvars(ivar),sym('E'))) - error('The symbolic entities named ''E'' are currently not supported due to restrictions of the symbolic math toolbox!'); - end -end -% svaridx = not(ismember(symvars,[this.sym.p;this.sym.k;this.sym.x;sym('t')])); -% if(any(svaridx)) -% error(['The symbolic variable ' char(symvars(find(svaridx,1))) ' is used in the differential equation right hand side but was not specified as parameter/state/constant!']); -% end -end diff --git a/matlab/@amimodel/parseModel.m b/matlab/@amimodel/parseModel.m deleted file mode 100644 index 167819bb76..0000000000 --- a/matlab/@amimodel/parseModel.m +++ /dev/null @@ -1,264 +0,0 @@ -function parseModel(this) -% parseModel parses the model definition and computes all necessary symbolic expressions. -% -% Parameters: -% -% Return values: -% void - -% load old hashes -HTable = this.loadOldHashes(); - -nevent = length(this.event); -this.nevent = nevent; - -% fix z2event if z was computed previously -if(isfield(this.fun,'z')) - this.fun = rmfield(this.fun,'z'); - this.getFun([],'z'); -end - -nx = length(this.sym.x); -np = length(this.sym.p); -nk = length(this.sym.k); -ny = length(this.sym.y); -nz = length([this.event.z]); -if(this.nytrue == 0) % only set this if it still has the default value, if 0 is already the non default value it does not matter anyways - nytrue = length(this.sym.sigma_y); - this.nytrue = nytrue; -end -if(this.nztrue == 0) - nztrue = length(this.sym.sigma_z); - this.nztrue = nztrue; -end -if(this.nxtrue == 0) - nxtrue = length(this.sym.x); - this.nxtrue = nxtrue; -end - - -%check zero trigger events -for ievent = 1:nevent - if(isequaln(this.event(ievent).trigger,sym(0))) - error('Trigger functions cannot be equal to zero. Please check your event definition!') - end -end - -this.nx = nx; -this.ny = ny; -this.nz = nz; -this.np = np; -this.nk = nk; - -% initial hashes -this.HTable(1).y = CalcMD5(char(this.sym.y)); -this.HTable(1).x = CalcMD5(char(this.sym.x)); -this.HTable(1).p = CalcMD5(char(this.sym.p)); -this.HTable(1).k = CalcMD5(char(this.sym.k)); -this.HTable(1).x0 = CalcMD5(char(this.sym.x0)); -if(nevent>0) - this.HTable(1).root = CalcMD5(char([this.event.trigger])); - this.HTable(1).deltax = CalcMD5(char([this.event.bolus])); - this.HTable(1).z = CalcMD5(char([this.event.z])); -end -if(strcmp(this.wtype,'iw')) - this.HTable(1).xdot = CalcMD5(char(this.sym.xdot)); - this.HTable(1).dx0 = CalcMD5(char(this.sym.dx0)); - this.HTable(1).M = CalcMD5(char(this.sym.M)); -else - this.HTable(1).xdot = CalcMD5(char(this.sym.xdot)); -end -this.HTable(1).sigma_y = CalcMD5(char(this.sym.sigma_y)); -this.HTable(1).sigma_z = CalcMD5(char(this.sym.sigma_z)); -this.HTable(1).Jy = CalcMD5(char(this.sym.Jy)); -this.HTable(1).Jz = CalcMD5(char(this.sym.Jz)); -this.HTable(1).Jrz = CalcMD5(char(this.sym.Jrz)); - -% check if code generation changed -codegen_amifun = {'gccode','getArgs','getCVar',... - 'getSensiFlag','getSyms','writeCcode',... - 'writeCcode_sensi'}; -for ifile = 1:length(codegen_amifun) - this.HTable(1).(codegen_amifun{ifile}) = CalcMD5(fullfile(this.wrap_path,'matlab','@amifun',[codegen_amifun{ifile} '.m']),'File'); -end -codegen_amimodel = {'generateC','makeSyms','makeEvents'}; -for ifile = 1:length(codegen_amimodel) - this.HTable(1).(codegen_amimodel{ifile}) = CalcMD5(fullfile(this.wrap_path,'matlab','@amimodel',[codegen_amimodel{ifile} '.m']),'File'); -end -if(not(this.recompile)) - this.recompile = not(strcmp(this.HTable(1).x,HTable.x)); -end -if(not(this.recompile)) - this.recompile = not(strcmp(this.HTable(1).p,HTable.p)); -end -if(not(this.recompile)) - this.recompile = not(strcmp(this.HTable(1).k,HTable.k)); -end -if(nevent>0) - if(not(this.recompile)) - this.recompile = not(strcmp(this.HTable(1).root,HTable.root)); - end -end - - -ifile = 1; -while(not(this.recompile) & ifile<=length(codegen_amifun)) - this.recompile = not(strcmp(this.HTable(1).(codegen_amifun{ifile}),HTable.(codegen_amifun{ifile}))); - ifile = ifile+1; -end -ifile = 1; -while(not(this.recompile) & ifile<=length(codegen_amimodel)) - this.recompile = not(strcmp(this.HTable(1).(codegen_amimodel{ifile}),HTable.(codegen_amimodel{ifile}))); - ifile = ifile+1; -end -if(this.recompile) - if(~strcmp(HTable.generateC,'')) - disp('Code generation routines changed! Recompiling model!') - cleanUpModelFolder(this); - end -end -% compute functions - -funs = {'xdot','w','dwdx','x0','JSparse','y','z','rz','deltax','root','Jy','Jz','Jrz','sigma_y','sigma_z'}; - -if(this.forward) - funs = {funs{:},'sx0','sz','deltasx','stau','srz','dJydy','dJydsigma','dJzdz','dJzdsigma','dJrzdz','dJrzdsigma','dwdp','dxdotdp','dydp','dsigma_ydp','dsigma_zdp','dydx','dzdx','dzdp','drzdx','drzdp'}; -end -if(this.adjoint) - funs = {funs{:},'dydx','dzdx','dzdp','drzdx','drzdp','deltaxB','deltaqB','dsigma_ydp','dsigma_zdp','sx0','dJydy','dJydsigma','dJzdz','dJzdsigma','dJrzdz','dJrzdsigma','dwdp','dxdotdp','dydp'}; -end - -if(strcmp(this.wtype,'iw')) - funs = {funs{:},'M'}; -end - -funs = unique(funs); - -this.funs = funs; - -%this.mfuns = {'J','dxdotdp'}; -this.mfuns = {}; - -% compute symbolic expressions -for ifun = 1:length(funs) - this.getFun(HTable,funs{ifun}); -end - -if(isfield(this.fun,'J')) - fprintf('sparse | ') - M = double(logical(this.fun.J.sym~=sym(zeros(size(this.fun.J.sym))))); - this.sparseidx = find(M); - - [ubw,lbw] = ami_bandwidth(M); - - this.ubw = ubw; - this.lbw = lbw; - this.nnz = length(find(M(:))); - - I = arrayfun(@(x) find(M(:,x))-1,1:nx,'UniformOutput',false); - this.rowvals = []; - this.colptrs = []; - for ix = 1:nx - this.colptrs(ix) = length(this.rowvals); - this.rowvals = [this.rowvals; I{ix}]; - end - this.colptrs(ix+1) = length(this.rowvals); - - if(this.adjoint) - if(isfield(this.fun,'JB')) - fprintf('sparseB | ') - MB = double(logical(this.fun.JB.sym~=sym(zeros(size(this.fun.JB.sym))))); - this.sparseidxB = find(MB); - I = arrayfun(@(x) find(MB(:,x))-1,1:nx,'UniformOutput',false); - this.rowvalsB = []; - this.colptrsB = []; - for ix = 1:nx - this.colptrsB(ix) = length(this.rowvalsB); - this.rowvalsB = [this.rowvalsB; I{ix}]; - end - this.colptrsB(ix+1) = length(this.rowvalsB); - end - end -end - -if(strcmp(this.wtype, 'iw')) - if(isfield(this.fun, 'M')) - this.getFun([], 'M'); - this.id = double(any(this.fun.M.sym)); - else - - end -else - this.id = zeros(1, nx); -end - -switch(this.o2flag) - case 1 - this.ng = this.np + 1; - case 2 - this.ng = 2; - otherwise - this.ng = 1; -end - -% save hashtable -HTable = this.HTable; -nxtrue = this.nxtrue; -nytrue = this.nytrue; -nx = this.nx; -ny = this.ny; -np = this.np; -nk = this.nk; -nz = this.nz; -ng = this.ng; -nw = this.nw; -ndwdx = this.ndwdx; -ndwdp = this.ndwdp; -nevent = this.nevent; -z2event = this.z2event; -nnonzeros = this.nnz; -id = this.id; -ubw = this.ubw; -lbw = this.lbw; -colptrs = this.colptrs; -rowvals = this.rowvals; -sparseidx = this.sparseidx; -colptrsB = this.colptrsB; -rowvalsB = this.rowvalsB; -sparseidxB = this.sparseidxB; - -save(fullfile(this.wrap_path,'models',this.modelname,'hashes_new.mat'),'HTable','nxtrue','nytrue','nx','ny','np','nk','nevent','nz','z2event','nnonzeros','id','ubw','lbw','colptrs','rowvals','sparseidx','colptrsB','rowvalsB','sparseidxB','ndwdx','ndwdp','nw'); - -fprintf('\r') - -end - -function [ubw,lbw] = ami_bandwidth(M) -% ami_bandwidth implements a bandwidth function for older versionsn of MATLAB -% -% Parameters: -% M: matrix for which the bandwidth is to be computed -% -% Return values: -% ubw: upper matrix bandwidth -% lbw: lower matrix bandwidth -if(isempty(M) || isempty(find(M))) - ubw = 0; - lbw = 0; -else - [i,j] = find(M); - ubw = max(max(j-i),0); - lbw = max(max(i-j),0); -end -end - -function cleanUpModelFolder(this) - fileList = dir(fullfile(this.wrap_path,'models',this.modelname)); - for ifile = 1:length(fileList) - file = fileList(ifile); - if(any([regexp(file.name,'[\w]*\_[\w]*\.mat')==1, - regexp(file.name,['[' this.modelname '|main|wrapfunctions]+[\w_]*\.[h|cpp|md5|o|obj]'])==1])); - delete(fullfile(this.wrap_path,'models',this.modelname,file.name)); - end - end -end diff --git a/matlab/@amioption/amioption.m b/matlab/@amioption/amioption.m deleted file mode 100644 index 2a92dc32e1..0000000000 --- a/matlab/@amioption/amioption.m +++ /dev/null @@ -1,266 +0,0 @@ -% -% @file amioption -% @brief definition of amioption class -% -classdef amioption < matlab.mixin.CustomDisplay - % AMIOPTION provides an option container to pass simulation parameters to the - % simulation routine. - - properties - % absolute integration tolerace - atol = 1e-16; - % relative integration tolerace - rtol = 1e-8; - % maximum number of integration steps - maxsteps = 1e4; - % absolute quadrature tolerace - quad_atol = 1e-12; - % relative quadrature tolerace - quad_rtol = 1e-8; - % maximum number of integration steps - maxstepsB = 0; - % absolute steady state tolerace - ss_atol = 1e-16; - % relative steady state tolerace - ss_rtol = 1e-8; - % index of parameters for which the sensitivities are computed - sens_ind = double.empty(); - % starting time of the simulation - tstart = 0; - % linear multistep method. - lmm = 2; - % iteration method for linear multistep. - iter = 2; - % linear solver - linsol = 9; - % stability detection flag - stldet = true; - % interpolation type - interpType = 1; - % forward sensitivity mode - ism = 1; - % sensitivity method - sensi_meth = 1; - % sensitivity method for preequilibration - sensi_meth_preeq = 1; - % sensitivity order - sensi = 0; - % number of reported events - nmaxevent = 10; - % reordering of states - ordering = 0; - % steady state sensitivity flag - ss = 0; - % custom initial state - x0 = double.empty(); - % custom initial sensitivity - sx0 = double.empty(); - % newton solver: maximum newton steps - newton_maxsteps = 40; - % mapping of event ouputs to events - z2event = double.empty(); - % parameter scaling - % Single value or vector matching sens_ind. - % Valid options are "log","log10" and "lin" for log, log10 or - % unscaled parameters p. - % Use [] for default as specified in the model (fallback: 'lin'). - pscale = []; - % Mode for for computing sensitivities ({0: Newton}, 1: Simulation) - steadyStateSensitivityMode = 0; - end - - methods - function obj = amioption(varargin) - % amioptions Construct a new amioptions object - % OPTS = amioption() creates a set of options with each option set to its - % default value. - % - % OPTS = amioption(PARAM, VAL, ...) creates a set of options with the named - % parameters altered with the specified values. - % - % OPTS = amioption(OLDOPTS, PARAM, VAL, ...) creates a copy of OLDOPTS with - % the named parameters altered with the specified value - % - % Note: to see the parameters, check the - % documentation page for amioption - % - % Parameters: - % varargin: input to construct amioption object, see function - % function description - % - % Return values: - % obj: amioption object constructed from inputs - % - - % adapted from SolverOptions - - if nargin > 0 - - % Deal with the case where the first input to the - % constructor is a amioptions/struct object. - if isa(varargin{1},'amioption') - if strcmp(class(varargin{1}),class(obj)) - obj = varargin{1}; - else - % Get the properties from options object passed - % into the constructor. - thisProps = properties(obj); - % Set the common properties. Note that we - % investigated first finding the properties that - % are common to both objects and just looping over - % those. We found that in most cases this was no - % quicker than just looping over the properties of - % the object passed in. - for i = 1:length(thisProps) - try %#ok - % Try to set one of the properties of the - % old object in the new one. - obj.(thisProps{i}) = varargin{1}.(thisProps{i}); - end - end - end - firstInputObj = true; - elseif isstruct(varargin{1}) - fieldlist = fieldnames(varargin{1}); - for ifield = 1:length(fieldlist) - obj.(fieldlist{ifield}) = varargin{1}.(fieldlist{ifield}); - end - firstInputObj = true; - elseif isempty(varargin{1}) - firstInputObj = true; - else - firstInputObj = false; - end - - % Extract the options that the caller of the constructor - % wants to set. - if firstInputObj - pvPairs = varargin(2:end); - else - pvPairs = varargin; - end - - % Loop through each param-value pair and just try to set - % the option. When the option has been fully specified with - % the correct case, this is fast. The catch clause deals - % with partial matches or errors. - haveCreatedInputParser = false; - for i = 1:2:length(pvPairs) - try - obj.(pvPairs{i}) = pvPairs{i+1}; - catch ME %#ok - - % Create the input parser if we haven't already. We - % do it here to avoid creating it if possible, as - % it is slow to set up. - if ~haveCreatedInputParser - ip = inputParser; - % Structures are currently not supported as - % an input to optimoptions. Setting the - % StructExpand property of the input parser to - % false, forces the parser to treat the - % structure as a single input and not a set of - % param-value pairs. - ip.StructExpand = false; - % Get list of option names - allOptionNames = properties(obj); - for j = 1:length(allOptionNames) - % Just specify an empty default as we already have the - % defaults in the options object. - ip.addParameter(allOptionNames{j}, []); - end - haveCreatedInputParser = true; - end - - % Get the p-v pair to parse. - thisPair = pvPairs(i:min(i+1, length(pvPairs))); - ip.parse(thisPair{:}); - - % Determine the option that was specified in p-v pairs. - % These options will now be matched even if only partially - % specified (by 13a). Now set the specified value in the - % options object. - optionSet = setdiff(allOptionNames, ip.UsingDefaults); - obj.(optionSet{1}) = ip.Results.(optionSet{1}); - end - end - end - - - end - - function this = set.sensi_meth(this,value) - if(ischar(value)) - switch(value) - case 'forward' - this.sensi_meth = 1; - case 'adjoint' - this.sensi_meth = 2; - case 'ss' - this.sensi_meth = 3; - otherwise - error('Unknown sensitivity method. Must be either ''forward'' or ''adjoint''!'); - end - else - assert(isnumeric(value),'The option sensi_meth must have a numeric value!') - assert(floor(value)==value,'The option sensi_meth must be an integer!') - assert(value<=3,'Only 1 and 2 are valid options for sensi_meth!') - assert(value>=0,'Only 1 and 2 are valid options for sensi_meth!') - this.sensi_meth = value; - end - end - - function this = set.sensi(this,value) - assert(isnumeric(value),'The option sensi must have a numeric value!') - assert(floor(value)==value,'The option sensi must be an integer!') - assert(value<=4,'Only 0, 1, 2 are valid options for sensi!') - this.sensi = value; - end - - function this = set.pscale(this,value) - if(~isempty(value)) - if(isnumeric(value)) - arrayfun(@(x) assert(x == 0 || x == 1 || x == 2, ... - 'No valid parametrisation chosen! Valid integer options are 0 (lin), 1 (log), 2 (log10).'), value); - elseif(ischar(value)) - value = amioption.getIntegerPScale(value); - else - value = arrayfun(@(x) amioption.getIntegerPScale(x), value); - end - end - this.pscale = value; - end - - function this = set.newton_maxsteps(this,value) - assert(isnumeric(value),'The option newtons_maxsteps must have a numeric value!') - assert(floor(value)==value,'The option newton_maxsteps must be an integer!') - this.newton_maxsteps = value; - end - - end - - methods(Static) - function pscaleInt = getIntegerPScale(pscaleString) - % pscaleInt converts a parameter scaling string into the - % corresponding integer representation - % - % Parameters: - % pscaleString: parameter scaling string - % - % Return values: - % int - switch (pscaleString) - case 'lin' - pscaleInt = 0; - case 'log' - pscaleInt = 1; - case 'log10' - pscaleInt = 2; - otherwise - assert(0, ... - 'No valid parametrisation chosen! Valid string options are "log", "log10" and "lin".') - end - end - end - -end diff --git a/matlab/@amised/amised.m b/matlab/@amised/amised.m deleted file mode 100644 index a8f1be6400..0000000000 --- a/matlab/@amised/amised.m +++ /dev/null @@ -1,124 +0,0 @@ -% -% @file amised -% @brief definition of amised class -% -classdef amised < handle - % AMISED is a container for SED-ML objects - - properties ( GetAccess = 'public', SetAccess = 'private' ) - % amimodel from the specified model - model = struct('event',[],'sym',[]); - % cell array of model identifiers - modelname = {}; - % stores the struct tree from the xml definition - sedml = struct.empty(); - % count the number of outputs per model - outputcount = []; - % indexes for dataGenerators - varidx = []; - % symbolic expressions for variables - varsym = sym([]); - % symbolic expressions for data - datasym = sym([]); - - end - - properties ( GetAccess = 'public', SetAccess = 'public' ) - - end - - methods - function ASED = amised(sedname) - %amised reads in an SEDML document using the JAVA binding of - % of libSEDML - % - % Parameters: - % sedname: name/path of the SEDML document - % - % Return values: - % ASED: amised object which contains all the information from - % the SEDML document - - % get models - for imodel = 1:length(ASED.sedml.listOfModels.model) - % get the model sbml - % check if this is a biomodels model - if(length(ASED.sedml.listOfModels.model{imodel}.Attributes.source>=23)) - if(strcmp(ASED.sedml.listOfModels.model{imodel}.Attributes.source,ASED.modelname)) - ASED.model(imodel) = ASED.model(find(strcmp(ASED.sedml.listOfModels.model{imodel}.Attributes.source,ASED.modelname))); - ASED.modelname{imodel} = ASED.sedml.listOfModels.model{imodel}.Attributes.id; - ASED.model(imodel).sym.y = sym([]); - ASED.outputcount(imodel) = 0; - continue - end - if(strcmp(ASED.sedml.listOfModels.model{imodel}.Attributes.source(1:23),'urn:miriam:biomodels.db')) - modelxml = websave([ASED.sedml.listOfModels.model{imodel}.Attributes.source(25:end) '.xml'],['http://www.ebi.ac.uk/biomodels-main/download?mid=' ASED.sedml.listOfModels.model{imodel}.Attributes.source(25:end)]); - else - modelxml = ASED.sedml.listOfModels.model{imodel}.Attributes.source; - end - else - modelxml = ASED.sedml.listOfModels.model{imodel}.Attributes.source; - end - modelxml = strrep(modelxml,'.xml',''); - % convert model sbml to amici - SBML2AMICI(modelxml,ASED.sedml.listOfModels.model{imodel}.Attributes.id); - eval(['model = ' ASED.sedml.listOfModels.model{imodel}.Attributes.id '_syms();']) - if(~isfield(model,'event')) - model.event = []; - end - ASED.model(imodel) = model; - % clear output; - ASED.model(imodel).sym.y = sym([]); - ASED.outputcount(imodel) = 0; - ASED.modelname{imodel} = ASED.sedml.listOfModels.model{imodel}.Attributes.id; - end - % apply changes - if(isfield(ASED.sedml,'listOfChanges')) - %TBD apply changes - end - % construct outputs - for idata = 1:length(ASED.sedml.listOfDataGenerators.dataGenerator) - if(length(ASED.sedml.listOfDataGenerators.dataGenerator)>1) - dataGenerator = ASED.sedml.listOfDataGenerators.dataGenerator{idata}; - else - dataGenerator = ASED.sedml.listOfDataGenerators.dataGenerator; - end - tasks = [ASED.sedml.listOfTasks.task{:}]; - tasksatts = [tasks.Attributes]; - taskids = {tasksatts.id}; - for ivar = 1:length(dataGenerator.listOfVariables.variable) - if(length(dataGenerator.listOfVariables.variable)>1) - variable = dataGenerator.listOfVariables.variable{ivar}; - else - variable = dataGenerator.listOfVariables.variable; - end - task_id = find(strcmp(variable.Attributes.taskReference,taskids)); - if(isempty(task_id)) - error(['Invalid taskReference in dataGenerator ' num2str(idata) ': ' variable.Attributes.taskReference]); - end - model_idx = find(strcmp(ASED.sedml.listOfTasks.task{task_id}.Attributes.modelReference,ASED.modelname)); - if(isempty(model_idx)) - error(['Invalid modelReference in task ' num2str(task_id) ': ' ASED.sedml.listOfTasks.task{task_id}.Attributes.modelReference]); - end - if(isfield(variable.Attributes,'symbol')) - if(strcmp(variable.Attributes.symbol,'urn:sedml:symbol:time')) - ASED.model(model_idx).sym.y(ASED.outputcount(model_idx)+1) = sym('t'); - end - end - if(isfield(variable.Attributes,'target')) - if(strfind(variable.Attributes.target,'/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species')) - ASED.model(model_idx).sym.y(ASED.outputcount(model_idx)+1) = sym(variable.Attributes.target(60:end-2)); - end - end - ASED.outputcount(model_idx) = ASED.outputcount(model_idx)+1; - ASED.varidx(idata,ivar,:) = [model_idx,ASED.outputcount(model_idx)]; - ASED.varsym(idata,ivar) = sym(variable.Attributes.id); - end - ASED.datasym(idata) = sym(variable.Attributes.id); - - end - - - end - end -end diff --git a/matlab/@optsym/optsym.m b/matlab/@optsym/optsym.m deleted file mode 100644 index 6209712fe2..0000000000 --- a/matlab/@optsym/optsym.m +++ /dev/null @@ -1,35 +0,0 @@ -% -% @file optsym.m -% @brief wrapper class for sym to make symob::optimize accessible -% -classdef optsym0) - for ir = 1:nr - if(model.reaction(ir).isSetFast) - if(model.reaction(ir).fast) - error('Fast reactions are currently not supported!'); - end - end - end -end - -kLaw = [cellfun(@(x) x.math,{model.reaction.kineticLaw},'UniformOutput',false)]; - -checkIllegalFunctions(kLaw); - -this.flux = cleanedsym(kLaw); -this.flux = this.flux(:); -% add local parameters to global parameters, make them global by -% extending them by the reaction_id string -species_idx = transpose(sym(1:nx)); -if(length({model.reaction.id})>0) - try - tmp = cellfun(@(x,y) sym(cellfun(@(x) [x '_' y], ... - {x.parameter.id}, ... - 'UniformOutput',false)), ... - {model.reaction.kineticLaw}, ... - {model.reaction.id}, ... - 'UniformOutput',false); - plocal = transpose([tmp{:}]); - tmp = cellfun(@(x) cellfun(@double,{x.parameter.value}),{model.reaction.kineticLaw},'UniformOutput',false); - pvallocal = transpose([tmp{:}]); - % replace local parameters by globalized ones - tmp = cellfun(@(x,y,z) subs(x,sym({y.parameter.id}), ... - sym(cellfun(@(x) [x '_' z],{y.parameter.id},'UniformOutput',false))),... - transpose(num2cell(this.flux)),... - {model.reaction.kineticLaw},... - {model.reaction.id},... - 'UniformOutput',false); - this.flux = [tmp{:}]; - this.flux = this.flux(:); - - catch - tmp = cellfun(@(x,y) sym(cellfun(@(x) [x '_' y],{x.localParameter.id},'UniformOutput',false)),{model.reaction.kineticLaw},arrayfun(@(x) ['r' num2str(x)],1:length({model.reaction.id}),'UniformOutput',false),'UniformOutput',false); - plocal = transpose([tmp{:}]); - tmp = cellfun(@(x) cellfun(@double,{x.localParameter.value}),{model.reaction.kineticLaw},'UniformOutput',false); - pvallocal = transpose([tmp{:}]); - % replace local parameters by globalized ones - tmp = cellfun(@(x,y,z) subs(x,sym({y.localParameter.id}),sym(cellfun(@(x) [x '_' z],{y.localParameter.id},'UniformOutput',false))),transpose(num2cell(this.flux)),{model.reaction.kineticLaw},arrayfun(@(x) ['r' num2str(x)],1:length({model.reaction.id}),'UniformOutput',false),'UniformOutput',false); - this.flux = [tmp{:}]; - this.flux = this.flux(:); - - end - - this.param = [this.param;plocal]; - parameter_sym = [parameter_sym;plocal]; - parameter_val = [parameter_val;pvallocal]; - np = length(this.param); - - reactants = cellfun(@(x) {x.species},{model.reaction.reactant},'UniformOutput',false); - % species index of the reactant - reactant_sidx = double(subs(sym(cat(2,reactants{:})),species_sym,species_idx)); - % reaction index - tmp = cumsum(cell2mat(cellfun(@(x) [ones(1,1),zeros(1,max(length(x)-1,0))],reactants,'UniformOutput',false))); - wreact = cell2mat(cellfun(@(x) [ones(1,length(x)),zeros(1,isempty(x))],reactants,'UniformOutput',false)); - reactant_ridx = tmp(logical(wreact)); - products = cellfun(@(x) {x.species},{model.reaction.product},'UniformOutput',false); - % species index of the product - product_sidx = double(subs(sym(cat(2,products{:})),species_sym,species_idx)); - % reaction index - tmp = cumsum(cell2mat(cellfun(@(x) [ones(1,1),zeros(1,max(length(x)-1,0))],products,'UniformOutput',false))); - wprod = cell2mat(cellfun(@(x) [ones(1,length(x)),zeros(1,isempty(x))],products,'UniformOutput',false)); - product_ridx = tmp(logical(wprod)); - if(model.SBML_level>=3) - reactant_stochiometry = cellfun(@(x) stoich_initAssign_rule(x,initassignments_sym,initassignments_math,rulevars,rulemath),{model.reaction.reactant},'UniformOutput',false); -% reactant_math = cellfun(@(x) sym({x.stoichiometry}),{model.reaction.reactant},'UniformOutput',false); - reactant_id = cellfun(@getId,{model.reaction.reactant},'UniformOutput',false); - product_stochiometry = cellfun(@(x) stoich_initAssign_rule(x,initassignments_sym,initassignments_math,rulevars,rulemath),{model.reaction.product},'UniformOutput',false); -% product_math = cellfun(@(x) sym({x.stoichiometry}),{model.reaction.product},'UniformOutput',false); - product_id = cellfun(@getId,{model.reaction.product},'UniformOutput',false); - else - % addition is necessary due to 1x0 struct that is returned by libSBML which is not properly handled by MATLAB, - % the concatenation is necessary because MATLAB treats 1x0 structs as empty input - symbolic_expr = @(x) num2cell(cell2sym(cellfun(@(z) math_expr(z),arrayfun(@(y) y.stoichiometryMath,x,'UniformOutput',false),'UniformOutput',false)) + sym(arrayfun(@(y) y.stoichiometry,x)).*arrayfun(@(y) isempty(y.stoichiometryMath),x)); - reactant_stochiometry = cellfun(@(x) {symbolic_expr(x)},{model.reaction.reactant},'UniformOutput',false); - reactant_id = cellfun(@getId,{model.reaction.reactant},'UniformOutput',false); - product_stochiometry = cellfun(@(x) {symbolic_expr(x)},{model.reaction.product},'UniformOutput',false); - product_id = cellfun(@getId,{model.reaction.product},'UniformOutput',false); - end - eS = sym(zeros(nx,nr)); - pS = sym(zeros(nx,nr)); - tmp_rs = cellfun(@(x)[x{:}],reactant_stochiometry,'UniformOutput',false); - tmp_rs = [tmp_rs{:}]; - tmp_rid = cat(2,reactant_id{:}); - for iidx = 1:length(reactant_sidx) - if(strcmp(tmp_rid{iidx},'')) - tmp = tmp_rs(iidx); - else - tmp = sym(tmp_rid{iidx}); - end - eS(reactant_sidx(iidx),reactant_ridx(iidx)) = eS(reactant_sidx(iidx),reactant_ridx(iidx)) + tmp; - end - tmp_ps = cellfun(@(x)[x{:}],product_stochiometry,'UniformOutput',false); - tmp_ps = [tmp_ps{:}]; - tmp_pid = cat(2,product_id{:}); - for iidx = 1:length(product_sidx) - if(strcmp(tmp_pid{iidx},'')) - tmp = tmp_ps(iidx); - else - tmp = sym(tmp_pid{iidx}); - end - pS(product_sidx(iidx),product_ridx(iidx)) = pS(product_sidx(iidx),product_ridx(iidx)) + tmp; - end - - this.stochiometry = - eS + pS; - - this.xdot = this.stochiometry*this.flux; -else - this.xdot = sym(zeros(size(this.state))); -end - -reactionsymbols = sym({model.reaction.id}'); - -if(length({model.reaction.id})>0) - stoichsymbols = [reactant_id{:},product_id{:}]; - stoichmath = [tmp_rs,tmp_ps]; - - stoichidx = not(strcmp(stoichsymbols,'')); - stoichsymbols = stoichsymbols(stoichidx); - stoichmath = stoichmath(stoichidx); -else - stoichsymbols = sym([]); - stoichmath = sym([]); -end - -%% RATE RULES - -fprintf('converting to concentrations ...\n') - -%extract model conversion factor -if(isfield(model,'conversionFactor')) - if(strcmp(model.conversionFactor,'')) - conversionfactor = sym(ones(nx,1)); - else - conversionfactor = sym(model.conversionFactor)*ones(nx,1); - end -else - conversionfactor = ones(nx,1); -end - -if(isfield(model.species,'conversionFactor')) - if(any(not(strcmp({model.species.conversionFactor},'')))) - tmp = {model.species.conversionFactor}; - idx = ~strcmp({model.species.conversionFactor},''); - conversionfactor(idx) = sym(tmp(idx)); - end -end - -for irule = 1:length(model.rule) - if(strcmp(model.rule(irule).typecode,'SBML_RATE_RULE')) - state_rate_idx = find(this.state == sym(model.rule(irule).variable)); - param_rate_idx = find(parameter_sym == sym(model.rule(irule).variable)); - stoich_rate_idx = find(stoichsymbols == sym(model.rule(irule).variable)); - if(~isempty(state_rate_idx)) - this.xdot(state_rate_idx) = cleanedsym(model.rule(irule).formula); - if(~onlysubstance_idx(state_rate_idx)) - this.xdot(state_rate_idx) = this.xdot(state_rate_idx).*this.volume(state_rate_idx); - end - elseif(~isempty(param_rate_idx)) - this.state = [this.state; parameter_sym(param_rate_idx)]; - this.xdot = [this.xdot; cleanedsym(model.rule(irule).formula)]; - if(ismember(parameter_sym(param_rate_idx),initassignments_sym)) - this.initState = [this.initState; initassignments_math(find(initassignments_sym==parameter_sym(param_rate_idx)))]; - else - this.initState = [this.initState; parameter_val(param_rate_idx)]; - end - this.volume = [this.volume; 1]; - concentration_idx = [concentration_idx, false]; - onlysubstance_idx = [onlysubstance_idx, false]; - conversionfactor = [conversionfactor; 1]; - cond_idx = [cond_idx, false ]; - const_idx = [const_idx, false ]; - bound_idx = [bound_idx, false]; - nx = nx + 1; - parameter_val(param_rate_idx) = []; - parameter_sym(param_rate_idx) = []; - this.param(param_rate_idx) = []; - np = np - 1; - setInitialAssignment(this,model,'initState',initassignments_sym,initassignments_math); - elseif(~isempty(stoich_rate_idx)) - this.state = [this.state; stoichsymbols(stoich_rate_idx)]; - this.xdot = [this.xdot; cleanedsym(model.rule(irule).formula)]; - this.initState = [this.initState; stoichmath(stoich_rate_idx)]; - this.volume = [this.volume; 1]; - concentration_idx = [concentration_idx, false]; - onlysubstance_idx = [onlysubstance_idx, false]; - conversionfactor = [conversionfactor; 1]; - cond_idx = [cond_idx, false ]; - const_idx = [const_idx, false ]; - bound_idx = [bound_idx, false]; - nx = nx + 1; - stoichmath(stoich_rate_idx) = []; - stoichsymbols(stoich_rate_idx) = []; - end - end - if(strcmp(model.rule(irule).typecode,'SBML_ASSIGNMENT_RULE')) - state_rate_idx = find(this.state == sym(model.rule(irule).variable)); - param_rate_idx = find(parameter_sym == sym(model.rule(irule).variable)); - if(~isempty(state_rate_idx)) - this.state(state_rate_idx) = []; - this.xdot(state_rate_idx) = []; - this.initState(state_rate_idx) = []; - this.volume(state_rate_idx) = []; - concentration_idx(state_rate_idx) = []; - onlysubstance_idx(state_rate_idx) = []; - conversionfactor(state_rate_idx) = []; - cond_idx(state_rate_idx) = []; - const_idx(state_rate_idx) = []; - bound_idx(state_rate_idx) = []; - nx = nx-1; - this.observable = [this.observable;cleanedsym(model.rule(irule).formula)]; - this.observable_name = [this.observable_name;sym(model.rule(irule).variable)]; - end - end -end - -applyRule(this,model,'xdot',rulevars,rulemath) - - -%% CONVERSION FACTORS/VOLUMES - -% this.xdot = conversionfactor.*subs(this.xdot,this.state,this.state.*this.volume)./this.volume; -this.xdot = conversionfactor.*subs(this.xdot,this.state(onlysubstance_idx),this.state(onlysubstance_idx).*this.volume(onlysubstance_idx))./this.volume; - -% this.xdot = conversionfactor.*this.xdot; - -%% EVENTS - -fprintf('loading events ...\n') - -if(sum(cellfun(@(x)numel(x),{model.event.delay})>0)) - error('Events with delays are currently not supported!'); -end -if(~strcmp(model.delay_symbol,'')) - error('Delay symbols are currently not supported!'); -end -if(model.SBML_level>=3) - if(sum(cellfun(@(x)numel(x),{model.event.priority})>0)) - error('Event priorities are currently not supported!'); - end -end - -try - tmp = cellfun(@(x) sym(sanitizeString(x)),{model.event.trigger},'UniformOutput',false); - this.trigger = [tmp{:}]; -catch - tmp = cellfun(@(x) sym(sanitizeString(x.math)),{model.event.trigger},'UniformOutput',false); - this.trigger = [tmp{:}]; -end -this.trigger = this.trigger(:); -this.trigger = subs(this.trigger,sym('ge'),sym('am_ge')); -this.trigger = subs(this.trigger,sym('gt'),sym('am_gt')); -this.trigger = subs(this.trigger,sym('le'),sym('am_le')); -this.trigger = subs(this.trigger,sym('lt'),sym('am_lt')); - -this.bolus = sym(zeros([length(this.state),length(this.trigger)])); -if(length(this.trigger)>0) - for ievent = 1:length(this.trigger) - tmp = cellfun(@(x) {x.variable},{model.event(ievent).eventAssignment},'UniformOutput',false); - assignments = sym(cat(2,tmp{:})); - - tmp = cellfun(@(x) {x.math},{model.event(ievent).eventAssignment},'UniformOutput',false); - assignments_math = cleanedsym(cat(2,tmp{:})); - - for iassign = 1:length(assignments) - state_assign_idx = find(assignments(iassign)==this.state); - param_assign_idx = find(assignments(iassign)==this.param); - cond_assign_idx = find(assignments(iassign)==condition_sym); - bound_assign_idx = find(assignments(iassign)==boundary_sym); - stoich_assign_idx = find(assignments(iassign)==stoichsymbols); - vol_assign_idx = find(assignments(iassign)==compartments_sym); - - if(np>0 && ~isempty(param_assign_idx)) - error('Assignments of parameters via events are currently not supported') - this.param(param_assign_idx) = this.param(param_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); - end - - if(nk>0 && ~isempty(cond_assign_idx)) - error('Assignments of constants via events are currently not supported') - conditions(cond_assign_idx) = conditions(cond_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); - end - - if(length(boundaries)>0 && ~isempty(bound_assign_idx)) - error('Assignments of boundary conditions via events are currently not supported') - boundaries(bound_assign_idx) = conditions(bound_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); - end - - if(length(stoichsymbols)>0 && ~isempty(stoich_assign_idx)) - error('Assignments of stoichiometries via events are currently not supported') - stoichmath(stoich_assign_idx) = stoichmath(stoich_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); - end - - if(length(compartments_sym)>0 && ~isempty(vol_assign_idx)) - error('Assignments of compartment volumes via events are currently not supported') - end - - if(length(this.state)>0 && ~isempty(state_assign_idx)) - - this.bolus(state_assign_idx,ievent) = -this.state(state_assign_idx); - addToBolus = sym(zeros(size(this.bolus(:,ievent)))); - addToBolus(state_assign_idx) = assignments_math(iassign); - - this.bolus(:,ievent) = this.bolus(:,ievent) + addToBolus; - end - - end - end -else - addToBolus = sym([]); -end - - - - -%% FUNCTIONS - -fprintf('loading functions ...\n') - -tmp = cellfun(@(x) x(8:end-1),{model.functionDefinition.math},'UniformOutput',false); -lambdas = cellfun(@(x)argScan(x),tmp); - -if(~isempty(lambdas)) - this.funmath = cellfun(@(x) x{end},lambdas,'UniformOutput',false); - % temp replacement for any user defined function - tmpfun = cellfun(@(x) ['fun_' num2str(x)],num2cell(1:length(model.functionDefinition)),'UniformOutput',false); - this.funmath = strrep(this.funmath,{model.functionDefinition.id},tmpfun); - % replace helper functions - - checkIllegalFunctions(this.funmath); - this.funmath = replaceLogicalFunctions(this.funmath); - - this.funmath = strrep(this.funmath,tmpfun,{model.functionDefinition.id}); - this.funarg = cellfun(@(x,y) [y '(' strjoin(transpose(x(1:end-1)),',') ')'],lambdas,replaceReservedFunctionIDs({model.functionDefinition.id}),'UniformOutput',false); - - % make functions available in this file - - for ifun = 1:length(this.funmath) - token = regexp(this.funarg(ifun),'\(([0-9\w\,]*)\)','tokens'); - start = regexp(this.funarg(ifun),'\(([0-9\w\,]*)\)'); - eval([replaceReservedFunctions(this.funarg{ifun}(1:(start{1}-1))) ' = @(' token{1}{1}{1} ')' this.funmath{ifun} ';']); - end -end - - -%% CLEAN-UP - -fprintf('cleaning up ...\n') - -% remove constant/condition states -this.state(any([cond_idx;const_idx;bound_idx])) = []; -this.kvolume = this.volume(any([cond_idx;const_idx;bound_idx])); -this.volume(any([cond_idx;const_idx;bound_idx])) = []; -this.initState(any([cond_idx;const_idx;bound_idx])) = []; -this.xdot(any([cond_idx;const_idx;bound_idx])) = []; -this.bolus(any([cond_idx;const_idx;bound_idx]),:) = []; - -% substitute with actual expressions, do this twice to resolve co-dependencies, do we need a loop here? -makeSubs(this,boundary_sym,boundaries); -makeSubs(this,condition_sym,conditions); -makeSubs(this,compartments_sym,this.compartment); -%makeSubs(this,stoichsymbols,stoichmath); -makeSubs(this,reactionsymbols,this.flux); - -% set initial assignments -for iIA = 1:length(initassignments_sym) - if(ismember(initassignments_sym(iIA),this.param)) - if(ismember(sym(model.time_symbol),symvar(initassignments_math(iIA)))) - error('Time dependent initial assignments are currently not supported!') - end - param_idx = find(initassignments_sym(iIA)==this.param); - parameter_sym(param_idx) = []; - parameter_val(param_idx) = []; - this.param(param_idx) = []; - this.xdot = subs(this.xdot,initassignments_sym(iIA),initassignments_math(iIA)); - this.trigger = subs(this.trigger,initassignments_sym(iIA),initassignments_math(iIA)); - this.bolus = subs(this.bolus,initassignments_sym(iIA),initassignments_math(iIA)); - this.initState = subs(this.initState,initassignments_sym(iIA),initassignments_math(iIA)); - this.param = subs(this.param,initassignments_sym(iIA),initassignments_math(iIA)); - rulemath = subs(rulemath,initassignments_sym(iIA),initassignments_math(iIA)); - np = np-1; - end -end -applyRule(this,model,'param',rulevars,rulemath) - -makeSubs(this,parameter_sym(1:np),this.param); - -% apply rules to dynamics -for irule = 1:length(rulevars) - eval(['syms ' strjoin(arrayfun(@char,rulevars(irule),'UniformOutput',false),' ') ' ' strrep(strrep(strjoin(arrayfun(@char,symvar(sym(rulemath(irule))),'UniformOutput',false),' '),'true',''),'false','')]); - eval(['rule = ' char(rulemath(irule)) ';']); - rule_idx = find(rulevars(irule)==this.state); - if(nx>0) - this.xdot(rule_idx) = jacobian(rule,this.state)*this.xdot; - this.initState(rule_idx) = rulemath(irule); - if(~isempty(this.bolus)) - this.bolus(rule_idx,:) = jacobian(rule,this.state)*this.bolus; - end - end -end - -state_vars = [symvar(this.xdot),symvar(this.initState)]; -event_vars = [symvar(this.bolus),symvar(this.trigger)]; - -applyRule(this,model,'xdot',rulevars,rulemath) - -isUsedParam = or(ismember(parameter_sym,event_vars),ismember(parameter_sym,state_vars)); -isPartOfRule = and(ismember(parameter_sym,symvar(cleanedsym({model.rule.formula}))),ismember(parameter_sym,symvar(sym({model.rule.variable})))); -isRuleVar = ismember(parameter_sym,sym({model.rule.variable})); -hasAssignment = ismember(parameter_sym,initassignments_sym); -this.parameter = parameter_sym(and(not(isRuleVar),not(isPartOfRule))); -this.pnom = parameter_val(and(not(isRuleVar),not(isPartOfRule))); - -this.condition = [condition_sym;constant_sym]; -obs_idx = all([isRuleVar,not(isPartOfRule),not(isUsedParam),not(hasAssignment)],2); -this.observable = [this.observable;this.param(obs_idx(1:length(this.param)))]; -this.observable_name = [this.observable_name;parameter_sym(obs_idx(1:length(this.param)))]; - -equal_idx = logical(this.observable(:)==this.observable_name(:)); % this is the unused stuff -this.observable(equal_idx) = []; -this.observable_name(equal_idx) = []; - -this.observable = subs(this.observable,parameter_sym(1:np),this.param); -this.observable = subs(this.observable,condition_sym,conditions); -this.observable = subs(this.observable,boundary_sym,boundaries); -this.observable = subs(this.observable,compartments_sym,this.compartment); -this.observable = subs(this.observable,stoichsymbols,stoichmath); -this.observable = subs(this.observable,reactionsymbols,this.flux); - -applyRule(this,model,'observable',rulevars,rulemath); - -this.time_symbol = model.time_symbol; - -prohibited = sym({'null','beta'}); -alt_prohib = sym({'null_sym','beta_sym'}); -this.parameter = subs(this.parameter,prohibited,alt_prohib); -this.state = subs(this.state,prohibited,alt_prohib); -this.condition = subs(this.condition,prohibited,alt_prohib); -while(any(ismember(symvar(this.initState),this.state))) - this.initState = subs(this.initState,this.state,this.initState); -end -end - -function setInitialAssignment(this,~,field,initassignments_sym,initassignments_math) -this.(field) = subs(this.(field),initassignments_sym,initassignments_math); -end - - -function applyRule(this,~,field,rulevars,rulemath) -this.(field) = subs(this.(field),rulevars,rulemath); -end - -function args = argScan(str) -brl = computeBracketLevel(str); - -l1_idx = find(brl==0); % store indexes - -% find commas but only in lowest bracket level -c_idx = strfind(str(brl==0),','); -str(l1_idx(c_idx)) = '@'; % replace by something that should never occur in equations -args = textscan(str,'%s','Whitespace','@'); -end - -function makeSubs(this,old,new) -this.xdot = subs(this.xdot,old,new); -this.trigger = subs(this.trigger,old,new); -this.bolus = subs(this.bolus,old,new); -this.initState = subs(this.initState,old,new); -end - -function checkIllegalFunctions(str) - -if(any(cell2mat(strfind(str,'factorial')))) - error('Factorial functions are currently not supported!') -end -if(any(cell2mat(strfind(str,'ceil')))) - error('Ceil functions are currently not supported!') -end -if(any(cell2mat(strfind(str,'floor')))) - error('Floor functions are currently not supported!') -end -end - -function csym = cleanedsym(str) -if(nargin>0) - matVer = ver('MATLAB'); - if(str2double(matVer.Version)>=9.4) - csym = str2sym(sanitizeString(strrep(str,'time','__time_internal_amici__'))); - else - csym = sym(sanitizeString(strrep(str,'time','__time_internal_amici__'))); - end - csym = subs(csym,sym('__time_internal_amici__'),sym('time')); -else - csym = sym(0); -end -end - -function str = sanitizeString(str) -% wrapper for replaceDiscontinuousFunctions() and replaceReservedFunctions() -str = replaceLogicalFunctions(str); -str = replaceReservedFunctions(str); -end - -function str = replaceLogicalFunctions(str) -% replace imcompatible piecewise defintion -% execute twice for directly nested calls (overlapping regexp expressions) -for logicalf = {'piecewise','and','or','lt','gt','ge','le','ge','le','xor','eq'} - str = regexprep(str,['^' logicalf{1} '('],['am_' logicalf{1} '(']); - str = regexprep(str,['([\W]+)' logicalf{1} '('],['$1am_' logicalf{1} '(']); - str = regexprep(str,['([\W]+)' logicalf{1} '('],['$1am_' logicalf{1} '(']); -end -end - -function str = replaceReservedFunctions(str) -% replace reserved matlab functions - -if(strcmp(str,'this')) - error('SBML functions may not be called ''this'''); -end - -for logicalf = {'divide','minus','multiply','plus'} - str = regexprep(str,['^' logicalf{1} '('],['am_' logicalf{1} '(']); - % execute twice for directly nested calls (overlapping regexp expressions) - str = regexprep(str,['([\W]+)' logicalf{1} '('],['$1am_' logicalf{1} '(']); - str = regexprep(str,['([\W]+)' logicalf{1} '('],['$1am_' logicalf{1} '(']); -end -end - -function str = replaceReservedFunctionIDs(str) -% replace reserved matlab functions -for logicalf = {'divide','minus','multiply','plus'} - str = regexprep(str,['^' logicalf{1} '$'],['am_' logicalf{1} '']); -end -end - -function z = delay(x,y) -error('Events with delays are currently not supported!'); -end - -function x = stoich_initAssign_rule(y,initassignments_sym,initassignments_math,rulevars,rulemath) -x = {}; -for iy = 1:length(y) - x{iy} = sym(y(iy).stoichiometry); - if(~isempty(sym(y(iy).id))) - if(~isempty(initassignments_sym)) - if(ismember(sym(y(iy).id),initassignments_sym)) - x{iy} = subs(sym(y(iy).id),initassignments_sym,initassignments_math); - end - end - if(~isempty(rulevars)) - if(ismember(sym(y(iy).id),rulevars)) - x{iy} = subs(sym(y(iy).id),rulevars,rulemath); - end - end - end -end -end - -function expr = math_expr(y) - if(isfield(y,'math')) - expr = cleanedsym(y.math); - else - expr = cleanedsym(); - end -end - -function id = getId(x) - if(isfield(x,'id')) - id = {x.id}; - else - id = {x.species}; - end -end diff --git a/matlab/SBMLimporter/@SBMLode/writeAMICI.m b/matlab/SBMLimporter/@SBMLode/writeAMICI.m deleted file mode 100644 index 3550241cd7..0000000000 --- a/matlab/SBMLimporter/@SBMLode/writeAMICI.m +++ /dev/null @@ -1,102 +0,0 @@ -function writeAMICI(this,modelname) - % writeAMICI writes the symbolic information from an SBMLode object - % into an AMICI model definition file - % - % Parameters: - % modelname: target name of the model (_syms.m will be appended to the name ) - % - % Return values: - % void - - fprintf('writing file ...\n') - fid = fopen([modelname '_syms.m'],'w'); - - fprintf(fid,['function model = ' modelname '_syms()\n']); - fprintf(fid,'\n'); - if(strcmp(this.time_symbol,'')) - fprintf(fid,'t = sym(''t'');\n'); - else - fprintf(fid,[this.time_symbol ' = sym(''t'');\n']); - end - fprintf(fid,'\n'); - fprintf(fid,'avogadro = 6.02214179e23;'); - -% fprintf(fid,'model.debug = true;\n'); - writeDefinition('STATES','x','state',this,fid) - writeDefinition('PARAMETERS','p','parameter',this,fid) - writeDefinition('CONDITIONS','k','condition',this,fid) - writeDerived('DYNAMICS','xdot','xdot',this,fid) - writeDerived('INITIALIZATION','x0','initState',this,fid) - writeDerived('OBSERVABLES','y','observable',this,fid) - fprintf(fid,'\n'); - fprintf(fid,'\n'); - fprintf(fid,['% EVENTS\n']); - for ievent = 1:length(this.trigger) - str_trigger = char(this.trigger(ievent)); - str_bolus = strjoin(arrayfun(@char,this.bolus(:,ievent),'UniformOutput',false),','); - fprintf(fid,['model.event(' num2str(ievent) ') = amievent(' ... - str_trigger ', ...\n' ... - '[' str_bolus '], ...\n' ... - '[]);\n']); - end - fprintf(fid,'\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - fprintf(fid,'function r = pow(x,y)\n'); - fprintf(fid,'\n'); - fprintf(fid,' r = x^y;\n'); - fprintf(fid,'\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - fprintf(fid,'function r = power(x,y)\n'); - fprintf(fid,'\n'); - fprintf(fid,' r = x^y;\n'); - fprintf(fid,'\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - - for ifun = 1:length(this.funmath) - fprintf(fid,['function r = ' this.funarg{ifun} '\n']); - fprintf(fid,'\n'); - fprintf(fid,[' r = ' this.funmath{ifun} ';\n']); - fprintf(fid,'\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); - end - - for fun = {'factorial','cei','psi'} - fprintUnsupportedFunctionError(fun{1},fid) - end - - fclose(fid); -end - -function writeDefinition(header,identifier,field,this,fid) - fprintf(fid,'\n'); - fprintf(fid,'\n'); - fprintf(fid,['%%%%\n%% ' header '\n']); - if(length(this.(field))>0) - fprintf(fid,['syms ' strjoin(cellfun(@char,num2cell(this.(field)),'UniformOutput',false)) '\n']); - end - fprintf(fid,['model.sym.' identifier ' = [' strjoin(cellfun(@char,num2cell(this.(field)),'UniformOutput',false),',') '];\n']); -end - -function writeDerived(header,identifier,field,this,fid) - fprintf(fid,'\n'); - fprintf(fid,'\n'); - fprintf(fid,['%%%%\n%% ' header '\n']); - fprintf(fid,'\n'); - if(strcmp(header,'OBSERVABLES')) - fprintf(fid,['%% ' strjoin(cellfun(@char,num2cell(this.observable_name),'UniformOutput',false),'\n%% ') '\n']); - end - fprintf(fid,['model.sym.' identifier ' = [' strjoin(cellfun(@char,num2cell(this.(field)),'UniformOutput',false),', ...\n') '];']); -end - -function fprintUnsupportedFunctionError(functionName,fid) - fprintf(fid,['function r = ' functionName '(x)\n']); - fprintf(fid,'\n'); - fprintf(fid,[' error(''The ' functionName ' function is currently not supported!'');\n']); - fprintf(fid,'\n'); - fprintf(fid,'end\n'); - fprintf(fid,'\n'); -end diff --git a/matlab/SBMLimporter/LICENSE b/matlab/SBMLimporter/LICENSE deleted file mode 100644 index 38d252073e..0000000000 --- a/matlab/SBMLimporter/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2017, Data-driven Computational Modelling -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/matlab/SBMLimporter/README.md b/matlab/SBMLimporter/README.md deleted file mode 100644 index 3336013a86..0000000000 --- a/matlab/SBMLimporter/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# SBMLimporter -MATLAB toolbox to generate ODE models from SBML files diff --git a/matlab/SBMLimporter/computeBracketLevel.m b/matlab/SBMLimporter/computeBracketLevel.m deleted file mode 100644 index 5bbffb961b..0000000000 --- a/matlab/SBMLimporter/computeBracketLevel.m +++ /dev/null @@ -1,28 +0,0 @@ -function [ brl ] = computeBracketLevel( cstr ) - % Compute the bracket level for the input string cstr. The bracket - % level is computed for every char in cstr and indicates how many - % brackets have been opened up to this point. The bracket level is - % useful to parse the arguments of functions in cstr. For this purpose - % functions will have the same bracket level as the opening bracket of - % the corresponding function call. - % - % Parameters: - % cstr: input string @type *char - % - % Return values: - % brl: bracket levels @type *int - - % compute bracket levels add one for each (, (before) remove 1 for each - % ) (after) - open = (cstr == '('); - close = (cstr == ')'); - close = [0,close(1:end-1)]; - brl = cumsum(open) - cumsum(close); - % take care of functions - fun_startidx = regexp(cstr,'([\w_]+\()','start'); - fun_endidx = regexp(cstr,'([\w_]+\()','end'); - for ifun = 1:length(fun_startidx) - brl(fun_startidx(ifun):(fun_endidx(ifun)-1)) = brl(fun_endidx(ifun)); - end - -end diff --git a/matlab/amiwrap.m b/matlab/amiwrap.m deleted file mode 100644 index 6fbf8353ae..0000000000 --- a/matlab/amiwrap.m +++ /dev/null @@ -1,201 +0,0 @@ -function amiwrap( varargin ) - % AMIWRAP generates c++ mex files for the simulation of systems of differential equations via CVODES and IDAS. - % - % Parameters: - % varargin: - % modelname: specifies the name of the model which will be later used for the naming of the simulation file @type string - % symfun: specifies a function which executes model definition @type string. - % tdir: target directory where the simulation file should be placed @type string @default $AMICIDIR/models/modelname - % o2flag: boolean whether second order sensitivities should be enabled @type boolean @default false - % - % Return values: - % void - - matVer = ver('MATLAB'); - if(str2double(matVer.Version) >= 9.4) - error('MATLAB R2018a or higher is currently not supported (see https://github.com/AMICI-dev/AMICI/issues/307)') - end - - %% - % check for MSVS - if(~isempty(strfind(mex.getCompilerConfigurations('c++').Name,'Microsoft Windows')) || ~isempty(strfind(mex.getCompilerConfigurations('c++').Name,'Microsoft Visual'))) - warning('AMICI does not officially support Microsoft Visual Studio Compilers. If the compilation fails, we recommend using MinGW.') - end - - %% - % check inputs - if(nargin<2) - error('Must provide modelname and symfun.') - end - modelname = varargin{1}; % this is the target modelname - if(~ischar(modelname)) - error('modelname must be a string.') - end - symfun = varargin{2}; % this is the function which generates the symbolic struct - if nargin > 2 - tdir = varargin{3}; - else - tdir = pwd; - end - - if nargin > 3 - o2flag = varargin{4}; - if(~ismember(o2flag,[0,1,2])) - error('Parameter o2flag must have value 0, 1 or 2.'); - end - else - o2flag = false; - end - - - if(isempty(mex.getCompilerConfigurations('C'))) - error('No C compiler setup. Please install and configure with MATLAB') - end - if(~isempty(tdir)) - if(exist(tdir,'file') ~= 7) - error('provided tdir is not a valid path') - end - end - - warningreset = warning; - warning('off','symbolic:mupadmex:MuPADTextWarning') - warning('off','MATLAB:dispatcher:nameConflict') - warning('off','symbolic:sym:sym:DeprecateExpressions') - warning('off','symbolic:generate:FunctionNotVerifiedToBeValid') - - %% - % Display AMICI version - disp(['amiwrap version ' getCommitHash(fileparts(mfilename('fullpath')))]) - - %% - % computations - matlabRootPath=fileparts(mfilename('fullpath')); - amiciRootPath=fileparts(matlabRootPath); - - addpath(genpath(fullfile(matlabRootPath,'auxiliary'))); - addpath(fullfile(matlabRootPath,'symbolic')); - - - % try to load - if(~isstruct(symfun)) - if(exist(symfun,'file')==2) - model_hash = CalcMD5(which(symfun),'File'); - else - model_hash = []; - end - else - model_hash = []; - end - - commit_hash = getCommitHash(amiciRootPath); - - if(~exist(fullfile(amiciRootPath,'models',modelname),'dir')) - mkdir(fullfile(amiciRootPath,'models',modelname)); - end - addpath(fullfile(amiciRootPath,'models',modelname)); - if(exist([commit_hash '_' model_hash '.mat'],'file')==2); - load([commit_hash '_' model_hash '.mat']); - % update modelname according to this function call - model.updateModelName(modelname); - % update wrap_path to this function call - model.updateWrapPath(amiciRootPath); - end - - if(~exist('model','var')) - disp('Generating model struct ...') - model = amimodel(symfun,modelname); - - - if(~isempty(model_hash) && ~isempty(commit_hash)) - save(fullfile(amiciRootPath,'models',modelname,[commit_hash '_' model_hash]),'model') - end - end - - switch(o2flag) - case 0 - o2string = []; - case 1 - o2string = 'o2'; - case 2 - o2string = 'o2vec'; - end - - if(~isempty(o2string)) - o2_hash = CalcMD5(fullfile(matlabRootPath,'@amimodel',['augment' o2string '.m']),'File'); - try - if(~exist(fullfile(amiciRootPath,'models',[modelname '_' o2string]),'dir')) - mkdir(fullfile(amiciRootPath,'models',[modelname '_' o2string])); - end - addpath(fullfile(amiciRootPath,'models',[modelname '_' o2string])); - end - if(exist([commit_hash '_' model_hash '_' o2_hash '.mat'],'file')==2); - load([commit_hash '_' model_hash '_' o2_hash '.mat']); - % update modelname according to this function call - modelo2.updateModelName([modelname '_' o2string]); - % update wrap_path to this function call - modelo2.updateWrapPath(amiciRootPath); - end - if(~exist('modelo2','var')) - disp('Augmenting to second order ...') - modelo2 = feval(['augment' o2string],model); - - - if(~isempty(model_hash) && ~isempty(commit_hash)) - save(fullfile(amiciRootPath,'models',[modelname '_' o2string],[commit_hash '_' model_hash '_' o2_hash]),'modelo2') - end - end - end - - disp('Parsing model struct ...') - model.parseModel(); - if(o2flag) - modelo2.parseModel(); - end - - % generate C code out of symbolic computations - disp('Generating C code ...') - model.generateC(); - if(o2flag) - modelo2.generateC(); - end - - % compile the previously generated C code - disp('Compiling mex file ...') - model.compileC(); - if(o2flag) - modelo2.compileC(); - end - - % generate the matlab wrapper - disp('Generating M code ...') - if(o2flag) - model.generateRebuildM() - model.generateM(modelo2); - else - model.generateRebuildM() - model.generateM([]); - end - - if(~isempty(tdir)) - clear(['simulate_' modelname ]); - clear(['ami_' modelname ]); - clear(['ami_' modelname o2string]); - movefile(fullfile(amiciRootPath,'models',modelname,['simulate_' modelname '.m']),fullfile(tdir,['simulate_' modelname '.m'])); - movefile(fullfile(amiciRootPath,'models',modelname,['ami_' modelname '.' mexext]),fullfile(tdir,['ami_' modelname '.' mexext])); - % make files available in the path - tmp = which(fullfile(tdir,['simulate_' modelname '.m'])); - tmp = which(fullfile(tdir,['ami_' modelname '.' mexext])); - for fun = model.mfuns - copyfile(fullfile(amiciRootPath,'models',modelname,[fun{1} '_' modelname '.m']),fullfile(tdir,[fun{1} '_' modelname '.m'])); - tmp = which(fullfile(tdir,[fun{1} '_' modelname '.m'])); - end - % clear .m and .mex files from memory - if(~isempty(o2string)) - movefile(fullfile(amiciRootPath,'models',[modelname '_' o2string],[ 'ami_' modelname '_' o2string '.' mexext]),fullfile(tdir,['ami_' modelname '_' o2string '.' mexext])); - tmp = which(fullfile(tdir,['ami_' modelname '_' o2string '.' mexext])); - end - else - addpath(fullfile(amiciRootPath,'models',modelname)); - end - warning(warningreset); -end diff --git a/matlab/auxiliary/CalcMD5/CalcMD5.c b/matlab/auxiliary/CalcMD5/CalcMD5.c deleted file mode 100644 index a078a23496..0000000000 --- a/matlab/auxiliary/CalcMD5/CalcMD5.c +++ /dev/null @@ -1,650 +0,0 @@ -/* CalcMD5.c */ -/* 128 bit MD5 checksum: file, string, byte stream */ -/* This function calculates a 128 bit checksum for arrays or files. */ -/* Digest = CalcMD5(Data, [InClass], [OutClass]) */ -/* INPUT: */ -/* Data: Data array or file name. Either numerical or CHAR array. */ -/* Currently only files and arrays with up to 2^32 bytes (2.1GB) are */ -/* accepted. */ -/* InClass: String to declare the type of the 1st input. */ -/* Optional. Default: 'Char'. */ -/* 'File': [Data] is a file name as string. The digest is calculated */ -/* for this file. */ -/* 'Char': [Data] is a char array to calculate the digest for. Only the */ -/* ASCII part of the Matlab CHARs is used, such that the digest */ -/* is the same as if the Matlab string is written to a file as */ -/* UCHAR, e.g. with FWRITE. */ -/* 'Unicode': All bytes of the input [Data] are used to calculate the */ -/* digest. If [Data] has a numerical type, this method is */ -/* applied ever. */ -/* OutClass: String, format of the output. Just the first character matters. */ -/* Optional, default: 'hex'. */ -/* 'hex': [1 x 32] string as lowercase hexadecimal number. */ -/* 'HEX': [1 x 32] string as lowercase hexadecimal number. */ -/* 'Dec': [1 x 16] double vector with UINT8 values. */ -/* 'Base64': [1 x 22] string, encoded to base 64 (A:Z,a:z,0:9,+,/). */ -/* */ -/* OUTPUT: */ -/* Digest: A 128 bit number is replied in a format depending on [OutClass]. */ -/* */ -/* EXAMPLES: */ -/* Three methods to get the MD5 of a file: */ -/* 1. Direct file access (recommended): */ -/* MD5 = CalcMD5(which('CalcMD5.m'), 'File') */ -/* 2. Import the file to a CHAR array (binary mode for exact line breaks!): */ -/* FID = fopen(which('CalcMD5.m'), 'rb'); */ -/* S = fread(FID, inf, 'uchar=>char'); */ -/* fclose(FID); */ -/* MD5 = CalcMD5(S, 'char') */ -/* 3. Import file as a byte stream: */ -/* FID = fopen(which('CalcMD5.m'), 'rb'); */ -/* S = fread(FID, inf, 'uint8=>uint8'); */ -/* fclose(FID); */ -/* MD5 = CalcMD5(S, 'unicode'); // 'unicode' can be omitted here */ -/* */ -/* Test data: */ -/* CalcMD5(char(0:511), 'char', 'HEX') */ -/* => F5C8E3C31C044BAE0E65569560B54332 */ -/* CalcMD5(char(0:511), 'unicode') */ -/* => 3484769D4F7EBB88BBE942BB924834CD */ -/* */ -/* Compile with: */ -/* mex -O CalcMD5.c */ -/* On Linux the C99 comments must be considered (thanks Sebastiaan Breedveld): */ -/* mex -O CFLAGS="\$CFLAGS -std=C99" CalcMD5.c */ -/* */ -/* Tested: Matlab 6.5, 7.7, 7.8, WinXP, [UnitTest] */ -/* Compiler: BCC5.5, LCC2.4/3.8, OpenWatcom 1.8 */ -/* Author: Jan Simon, Heidelberg, (C) 2006-2010 J@n-Simon.De */ -/* License: BSD. This program is based on: */ -/* RFC 1321, MD5 Message-Digest Algorithm, April 1992 */ -/* RSA Data Security, Inc. MD5 Message Digest Algorithm */ -/* Modifications: */ -/* - Acceleration: Unrolled loops. Compacted macros FF, GG, HH, II. */ -/* - Mex-interface: Input and output from and to Matlab. */ -/* */ -/* See also: CalcCRC32. */ -/* */ -/* Michael Kleder has published a Java call to compute the MD5 and SHA sums: */ -/* http://www.mathworks.com/matlabcentral/fileexchange/8944 */ - -/********************************************************************** - ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** - ** ** - ** License to copy and use this software is granted provided that ** - ** it is identified as the "RSA Data Security, Inc. MD5 Message ** - ** Digest Algorithm" in all material mentioning or referencing this ** - ** software or this function. ** - ** ** - ** License is also granted to make and use derivative works ** - ** provided that such works are identified as "derived from the RSA ** - ** Data Security, Inc. MD5 Message Digest Algorithm" in all ** - ** material mentioning or referencing the derived work. ** - ** ** - ** RSA Data Security, Inc. makes no representations concerning ** - ** either the merchantability of this software or the suitability ** - ** of this software for any particular purpose. It is provided "as ** - ** is" without express or implied warranty of any kind. ** - ** ** - ** These notices must be retained in any copies of any part of this ** - ** documentation and/or software. ** - ********************************************************************** - */ - -/* -% $JRev: R5.00z V:025 Sum:/kHGslMmCpAS Date:17-Dec-2009 12:46:26 $ -% $File: CalcMD5\CalcMD5.c $ -% History: -% 011: 20-Oct-2006 20:50, [16 x 1] -> [1 x 16] replied as double. -% 012: 01-Nov-2006 23:10, BUGFIX: hex output for 'Hex' input now. -% 015: 02-Oct-2008 14:47, Base64 output. -% 017: 19-Oct-2008 22:33, Accept numerical arrays as byte stream. -% 023: 15-Dec-2009 16:53, BUGFIX: UINT32 has 32 bits on 64 bit systems now. -% Thanks to Sebastiaan Breedveld! -*/ - -/* Headers: */ -#include -#include -#include -#include -#include "mex.h" - -/* Assume 32 bit array dimensions for Matlab 6.5: */ -/* See option "compatibleArrayDims" for MEX in Matlab >= 7.7. */ -#ifndef mwSize -#define mwSize int -#define mwIndex int -#endif - -/* Types: */ -typedef unsigned char UCHAR; -typedef unsigned int UINT; -typedef unsigned char * POINTER; /* generic pointer */ -typedef UINT32_T UINT32; /* four byte word (defined in tmwtypes.h) */ - -typedef struct { - UINT32 state[4]; /* state (ABCD) */ - UINT32 count[2]; /* number of bits, modulo 64 (lsb first) */ - UCHAR buffer[64]; /* input buffer */ -} MD5_CTX; - -/* Prototypes: */ -void MD5Init (MD5_CTX *); -void MD5Update (MD5_CTX *, UCHAR *, UINT); -void MD5Final (UCHAR[16], MD5_CTX *); -void MD5Transform(UINT32[4], UCHAR[64]); -void MD5Encode (UCHAR *, UINT32 *, UINT); -void MD5Array (UCHAR *data, mwSize N, UCHAR digest[16]); -void MD5File (char *FileName, UCHAR digest[16]); -void MD5Char (mxChar *data, mwSize N, UCHAR digest[16]); -void ToHex (const UCHAR In[16], char *Out, int LowerCase); -void ToBase64 (const UCHAR In[16], char *Out); - -/* Constants for MD5Transform routine: */ -#define S11 7 -#define S12 12 -#define S13 17 -#define S14 22 -#define S21 5 -#define S22 9 -#define S23 14 -#define S24 20 -#define S31 4 -#define S32 11 -#define S33 16 -#define S34 23 -#define S41 6 -#define S42 10 -#define S43 15 -#define S44 21 - -static UCHAR PADDING[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - -/* F, G, H and I are basic MD5 functions: */ -#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) -#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) -#define I(x, y, z) ((y) ^ ((x) | (~z))) - -/* ROTATE_LEFT rotates x left n bits: */ -/* Rotation is separate from addition to prevent recomputation. */ -#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) - -/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4: */ -#define FF(a, b, c, d, x, s, ac) { \ - (a) = ROTATE_LEFT((a) + F((b), (c), (d)) + (x) + (UINT32)(ac), (s)) + (b); } -#define GG(a, b, c, d, x, s, ac) { \ - (a) = ROTATE_LEFT((a) + G((b), (c), (d)) + (x) + (UINT32)(ac), (s)) + (b); } -#define HH(a, b, c, d, x, s, ac) { \ - (a) = ROTATE_LEFT((a) + H((b), (c), (d)) + (x) + (UINT32)(ac), (s)) + (b); } -#define II(a, b, c, d, x, s, ac) { \ - (a) = ROTATE_LEFT((a) + I((b), (c), (d)) + (x) + (UINT32)(ac), (s)) + (b); } - -/* Length of the file buffer (must be < 2^31 for INT conversion): */ -#define BUFFER_LEN 1024 -static UCHAR buffer[BUFFER_LEN]; - -/* MD5 initialization. Begins an MD5 operation, writing a new context. ========= */ -void MD5Init(MD5_CTX *context) -{ - /* Load magic initialization constants: */ - context->count[0] = 0; - context->count[1] = 0; - context->state[0] = 0x67452301; - context->state[1] = 0xefcdab89; - context->state[2] = 0x98badcfe; - context->state[3] = 0x10325476; -} - -/* MD5 block update operation. Continues an MD5 message-digest operation, */ -/* processing another message block, and updating the context. */ -void MD5Update(MD5_CTX *context, UCHAR *input, UINT inputLen) -{ - UINT index, partLen; - int inputLenM63; - - /* Compute number of bytes mod 64: */ - index = (UINT)((context->count[0] >> 3) & 0x3F); - - /* Update number of bits: */ - if ((context->count[0] += ((UINT32)inputLen << 3)) < ((UINT32)inputLen << 3)) { - context->count[1]++; - } - context->count[1] += ((UINT32)inputLen >> 29); - - partLen = 64 - index; - - /* Transform as many times as possible: */ - if (inputLen >= partLen) { - int i; - memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); - MD5Transform(context->state, context->buffer); - - inputLenM63 = inputLen - 63; - for (i = partLen; i < inputLenM63; i += 64) { - MD5Transform(context->state, &input[i]); - } - - /* Buffer remaining input: index = 0 */ - memcpy((POINTER)&context->buffer[0], (POINTER)&input[i], inputLen - i); - } else { - /* Buffer remaining input: i = 0 */ - memcpy((POINTER)&context->buffer[index], (POINTER)input, inputLen); - } - - return; -} - -/* Finalize MD5: =============================================================== */ -/* Ends an MD5 message-digest operation, writing the message digest and zeroing */ -/* the context. */ -void MD5Final(UCHAR digest[16], MD5_CTX *context) -{ - UCHAR bits[8]; - UINT index, padLen; - - /* Save number of bits: */ - MD5Encode(bits, context->count, 2); - - /* Pad out to 56 mod 64: */ - index = (UINT)((context->count[0] >> 3) & 0x3f); - padLen = (index < 56) ? (56 - index) : (120 - index); - MD5Update(context, PADDING, padLen); - - /* Append length before padding: */ - MD5Update(context, bits, 8); - - /* Store state in digest: */ - MD5Encode(digest, context->state, 4); - - /* Zero sensitive information: */ - memset((POINTER)context, 0, sizeof(MD5_CTX)); -} - -/* MD5 basic transformation. Transforms state based on block: ================== */ -void MD5Transform(UINT32 state[4], UCHAR block[64]) -{ - UINT32 a = state[0], - b = state[1], - c = state[2], - d = state[3], - x[16]; - - /* Unroll the loop for speed: */ - /* UINT i, j; */ - /* for (i = 0, j = 0; j < 64; i++, j += 4) { */ - /* x[i] = ((UINT32)block[j]) | (((UINT32)block[j + 1]) << 8) | */ - /* (((UINT32)block[j + 2]) << 16) | (((UINT32)block[j + 3]) << 24); */ - /* } */ - x[0] = ( (UINT32)block[0]) | (((UINT32)block[1]) << 8) | - (((UINT32)block[2]) << 16) | (((UINT32)block[3]) << 24); - x[1] = ( (UINT32)block[4]) | (((UINT32)block[5]) << 8) | - (((UINT32)block[6]) << 16) | (((UINT32)block[7]) << 24); - x[2] = ( (UINT32)block[8]) | (((UINT32)block[9]) << 8) | - (((UINT32)block[10]) << 16) | (((UINT32)block[11]) << 24); - x[3] = ( (UINT32)block[12]) | (((UINT32)block[13]) << 8) | - (((UINT32)block[14]) << 16) | (((UINT32)block[15]) << 24); - x[4] = ( (UINT32)block[16]) | (((UINT32)block[17]) << 8) | - (((UINT32)block[18]) << 16) | (((UINT32)block[19]) << 24); - x[5] = ( (UINT32)block[20]) | (((UINT32)block[21]) << 8) | - (((UINT32)block[22]) << 16) | (((UINT32)block[23]) << 24); - x[6] = ( (UINT32)block[24]) | (((UINT32)block[25]) << 8) | - (((UINT32)block[26]) << 16) | (((UINT32)block[27]) << 24); - x[7] = ( (UINT32)block[28]) | (((UINT32)block[29]) << 8) | - (((UINT32)block[30]) << 16) | (((UINT32)block[31]) << 24); - x[8] = ( (UINT32)block[32]) | (((UINT32)block[33]) << 8) | - (((UINT32)block[34]) << 16) | (((UINT32)block[35]) << 24); - x[9] = ( (UINT32)block[36]) | (((UINT32)block[37]) << 8) | - (((UINT32)block[38]) << 16) | (((UINT32)block[39]) << 24); - x[10] = ( (UINT32)block[40]) | (((UINT32)block[41]) << 8) | - (((UINT32)block[42]) << 16) | (((UINT32)block[43]) << 24); - x[11] = ( (UINT32)block[44]) | (((UINT32)block[45]) << 8) | - (((UINT32)block[46]) << 16) | (((UINT32)block[47]) << 24); - x[12] = ( (UINT32)block[48]) | (((UINT32)block[49]) << 8) | - (((UINT32)block[50]) << 16) | (((UINT32)block[51]) << 24); - x[13] = ( (UINT32)block[52]) | (((UINT32)block[53]) << 8) | - (((UINT32)block[54]) << 16) | (((UINT32)block[55]) << 24); - x[14] = ( (UINT32)block[56]) | (((UINT32)block[57]) << 8) | - (((UINT32)block[58]) << 16) | (((UINT32)block[59]) << 24); - x[15] = ( (UINT32)block[60]) | (((UINT32)block[61]) << 8) | - (((UINT32)block[62]) << 16) | (((UINT32)block[63]) << 24); - - /* Round 1 */ - FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ - FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ - FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ - FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ - FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ - FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ - FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ - FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ - FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ - FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ - FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ - FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ - FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ - FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ - FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ - FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ - - /* Round 2 */ - GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ - GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ - GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ - GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ - GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ - GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ - GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ - GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ - GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ - GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ - GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ - - GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ - GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ - GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ - GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ - GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ - - /* Round 3 */ - HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ - HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ - HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ - HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ - HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ - HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ - HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ - HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ - HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ - HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ - HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ - HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ - HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ - HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ - HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ - HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ - - /* Round 4 */ - II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ - II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ - II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ - II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ - II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ - II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ - II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ - II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ - II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ - II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ - II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ - II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ - II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ - II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ - II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ - II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - memset((POINTER)x, 0, sizeof(x)); -} - -/* Encodes input (UINT32) into output (UCHAR) (length is divided by 4) ========= */ -void MD5Encode(UCHAR *output, UINT32 *input, UINT len) -{ - UINT j; - - for (j = 0; j < len; j++) { - *output++ = (UCHAR)( *input & 0xff); - *output++ = (UCHAR)((*input >> 8) & 0xff); - *output++ = (UCHAR)((*input >> 16) & 0xff); - *output++ = (UCHAR)((*input++ >> 24) & 0xff); - } -} - -/* Calcualte digest: =========================================================== */ -void MD5Char(mxChar *array, mwSize inputLen, UCHAR digest[16]) -{ - /* Process string: Matlab stores strings as mxChar, which are 2 bytes per */ - /* character. This function considers the first byte of each CHAR only, which */ - /* is equivalent to calculate the sum after a conversion to a ASCII UCHAR */ - /* string. */ - MD5_CTX context; - UINT Chunk; - UCHAR *bufferP, *bufferEnd = buffer + BUFFER_LEN, *arrayP; - - /* Limit length to 32 bit address, because I cannot test this function */ - /* with 64 bit arrays currently (under construction): */ - if (inputLen >> 31 != 0) { /* Detect sign-bit if mwSize is int */ - mexErrMsgTxt("*** CalcMD5[mex]: Input > 2^31 byte not handled yet."); - } - - arrayP = (UCHAR *) array; /* UCHAR *, not mxChar *!*/ - - MD5Init(&context); - - /* Copy chunks of input data - only the first byte of each mxChar: */ - Chunk = inputLen / BUFFER_LEN; - while (Chunk--) { - bufferP = buffer; - while (bufferP < bufferEnd) { - *bufferP++ = *arrayP; - arrayP += 2; - } - - MD5Update(&context, buffer, BUFFER_LEN); - } - - /* Last chunk: */ - Chunk = inputLen % BUFFER_LEN; - if (Chunk != 0) { - bufferEnd = buffer + Chunk; - bufferP = buffer; - while (bufferP < bufferEnd) { - *bufferP++ = *arrayP; - arrayP += 2; - } - - MD5Update(&context, buffer, Chunk); - } - - MD5Final(digest, &context); - - return; -} - -/* Array of any type as byte stream: =========================================== */ -void MD5Array(UCHAR *array, mwSize inputLen, UCHAR digest[16]) -{ - MD5_CTX context; - - /* Limit length to 32 bit address, because I cannot test this function */ - /* with 64 bit arrays currently (under construction): */ - if (inputLen >> 31 != 0) { /* Detect sign-bit if mwSize is signed int */ - mexErrMsgTxt("*** CalcMD5[mex]: Input > 2^31 byte not handled yet."); - } - - MD5Init(&context); - MD5Update(&context, array, (UINT) inputLen); - MD5Final(digest, &context); -} - -/* File as byte stream: ======================================================== */ -void MD5File(char *filename, UCHAR digest[16]) -{ - FILE *FID; - MD5_CTX context; - int len; - UINT32 allLen = 0; - - /* Open the file in binary mode: */ - if ((FID = fopen(filename, "rb")) == NULL) { - mexPrintf("*** Error for file: [%s]\n", filename); - mexErrMsgTxt("*** CalcMD5[mex]: Cannot open file."); - } - - MD5Init(&context); - while ((len = fread(buffer, 1, BUFFER_LEN, FID)) != 0) { - /* Limit length to 32 bit address, because I cannot test this function */ - /* with 64 bit arrays currently (under construction): */ - allLen += len; - if (allLen > 2147483647) { /* 2^31 */ - fclose(FID); - mexErrMsgTxt("*** CalcMD5[mex]: Cannot handle files > 2.1GB yet."); - } - - MD5Update(&context, buffer, (UINT) len); - } - MD5Final(digest, &context); - - fclose(FID); -} - -/* Output of 16 UCHARs as 32 character hexadecimals: =========================== */ -void ToHex(const UCHAR digest[16], char *output, int LowerCase) -{ - char *outputEnd; - - if (LowerCase) { - for (outputEnd = output + 32; output < outputEnd; output += 2) { - sprintf(output, "%02x", *(digest++)); - } - } else { /* Upper case: */ - for (outputEnd = output + 32; output < outputEnd; output += 2) { - sprintf(output, "%02X", *(digest++)); - } - } - - return; -} - -/* BASE64 encoded output: ====================================================== */ -void ToBase64(const UCHAR In[16], char *Out) -{ - /* The base64 encoded string is shorter than the hex string. */ - /* Needed length: ((len + 2) / 3 * 4) + 1, here fixed to 22+1 here (trailing */ - /* 0 included). */ - static const UCHAR B64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - int i; - char *p; - const UCHAR *s; - - p = Out; - s = In; - for (i = 0; i < 5; i++) { - *p++ = B64[(*s >> 2) & 0x3F]; - *p++ = B64[((*s & 0x3) << 4) | ((s[1] & 0xF0) >> 4)]; - *p++ = B64[((s[1] & 0xF) << 2) | ((s[2] & 0xC0) >> 6)]; - *p++ = B64[s[2] & 0x3F]; - s += 3; - } - - *p++ = B64[(*s >> 2) & 0x3F]; - *p++ = B64[((*s & 0x3) << 4)]; - *p = '\0'; - - return; -} - -/* Main function: ============================================================== */ -void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) -{ - /* Mex interface: */ - /* - Define default values of optional arguments. */ - /* - Forward input data to different calculators according to the input type. */ - /* - Convert digest to output format. */ - - char *FileName, InType, hexOut[33], b64Out[23]; - UCHAR digest[16], *digestP, OutType = 'h'; - int isFile = false, isUnicode = false; - double *outP, *outEnd; - - /* Check number of inputs and outputs: */ - if (nrhs == 0 || nrhs > 3) { - mexErrMsgTxt("*** CalcMD5[mex]: 1 to 3 inputs required."); - } - if (nlhs > 1) { - mexErrMsgTxt("*** CalcMD5[mex]: Too many output arguments."); - } - - /* If 2nd input starts with 'f', treat string in 1st argument as file name: */ - if (nrhs >= 2 && mxGetNumberOfElements(prhs[1]) > 0) { - if (mxIsChar(prhs[1]) == 0) { - mexErrMsgTxt("*** CalcMD5[mex]: 2nd input must be a string."); - } - - InType = (char) tolower(*(POINTER) mxGetData(prhs[1])); - isFile = (InType == 'f'); - isUnicode = (InType == 'u'); - } /* Default otherwise! */ - - /* Output type - default: hex: */ - if (nrhs == 3 && !mxIsEmpty(prhs[2])) { - if (mxIsChar(prhs[2]) == 0) { - mexErrMsgTxt("*** CalcMD5[mex]: 3rd input must be a string."); - } - - OutType = *(POINTER) mxGetData(prhs[2]); /* Just 1st character */ - } - - /* Calculate check sum: */ - if (isFile) { - if ((FileName = mxArrayToString(prhs[0])) == NULL) { - mexErrMsgTxt("*** CalcMD5[mex]: Cannot get file name."); - } - MD5File(FileName, digest); - mxFree(FileName); - - } else if (mxIsNumeric(prhs[0]) || isUnicode) { - MD5Array((POINTER) mxGetData(prhs[0]), - mxGetNumberOfElements(prhs[0]) * mxGetElementSize(prhs[0]), - digest); - - } else if (mxIsChar(prhs[0])) { - MD5Char((mxChar *) mxGetData(prhs[0]), - mxGetNumberOfElements(prhs[0]), - digest); - - } else { - mexErrMsgTxt("*** CalcMD5[mex]: Input type not accepted."); - } - - /* Create output: */ - switch (OutType) { - case 'H': - case 'h': /* Hexadecimal upper/lower case: */ - ToHex(digest, hexOut, OutType == 'h'); - plhs[0] = mxCreateString(hexOut); - break; - - case 'D': - case 'd': /* DOUBLE with integer values: */ - plhs[0] = mxCreateDoubleMatrix(1, 16, mxREAL); - outP = mxGetPr(plhs[0]); - digestP = digest; - for (outEnd = outP + 16; outP < outEnd; outP++) { - *outP = (double) *digestP++; - } - break; - - case 'B': - case 'b': /* Base64: */ - /* strtobase64(b64Out, 26, digest, 16); // included in LCC3.8 */ - /* b64Out[24] = '\0'; */ - ToBase64(digest, b64Out); /* Locally implemented */ - plhs[0] = mxCreateString(b64Out); - break; - - default: - mexErrMsgTxt("*** CalcMD5[mex]: Unknown output type."); - } - - return; -} diff --git a/matlab/auxiliary/CalcMD5/CalcMD5.m b/matlab/auxiliary/CalcMD5/CalcMD5.m deleted file mode 100644 index 80d4d0c416..0000000000 --- a/matlab/auxiliary/CalcMD5/CalcMD5.m +++ /dev/null @@ -1,85 +0,0 @@ -function MD5 = CalcMD5(Data, varargin) %#ok -% 128 bit MD5 checksum: file, string, byte stream [MEX] -% This function calculates a 128 bit checksum for arrays and files. -% Digest = CalcMD5(Data, [InClass], [OutClass]) -% INPUT: -% Data: Data array or file name. Either numerical or CHAR array. -% Currently only files and arrays with up to 2^32 bytes (2.1GB) are -% accepted. -% InClass: String to declare the type of the 1st input. -% Optional. Default: 'Char'. -% 'File': [Data] is a file name as string. The digest is calculated -% for this file. -% 'Char': [Data] is a char array to calculate the digest for. Only the -% ASCII part of the Matlab CHARs is used, such that the digest -% is the same as if the array is written to a file as UCHAR, -% e.g. with FWRITE. -% 'Unicode': All bytes of the input [Data] are used to calculate the -% digest. This is the standard for numerical input. -% OutClass: String, format of the output. Just the first character matters. -% Optional, default: 'hex'. -% 'hex': [1 x 32] string as lowercase hexadecimal number. -% 'HEX': [1 x 32] string as uppercase hexadecimal number. -% 'Dec': [1 x 16] double vector with UINT8 values. -% 'Base64': [1 x 22] string, encoded to base 64 (A:Z,a:z,0:9,+,/). -% -% OUTPUT: -% Digest: A 128 bit number is replied in a format depending on [OutClass]. -% The chance, that different data sets have the same MD5 sum is about -% 2^128 (> 3.4 * 10^38). Therefore MD5 can be used as "finger-print" -% of a file rather than e.g. CRC32. -% -% EXAMPLES: -% Three methods to get the MD5 of a file: -% 1. Direct file access (recommended): -% MD5 = CalcMD5(which('CalcMD5.m'), 'File') -% 2. Import the file to a CHAR array (binary mode for exact line breaks!): -% FID = fopen(which('CalcMD5.m'), 'rb'); -% S = fread(FID, inf, 'uchar=>char'); -% fclose(FID); -% MD5 = CalcMD5(S, 'char') -% 3. Import file as a byte stream: -% FID = fopen(which('CalcMD5.m'), 'rb'); -% S = fread(FID, inf, 'uint8=>uint8'); -% fclose(FID); -% MD5 = CalcMD5(S, 'unicode'); // 'unicode' can be omitted here -% -% Test string: -% CalcMD5(char(0:511), 'char', 'HEX') -% => F5C8E3C31C044BAE0E65569560B54332 -% CalcMD5(char(0:511), 'unicode', 'HEX') -% => 3484769D4F7EBB88BBE942BB924834CD -% -% Tested: Matlab 6.5, 7.7, 7.8, WinXP, [UnitTest] -% Author: Jan Simon, Heidelberg, (C) 2009-2010 J@n-Simon.De -% License: This program is derived from the RSA Data Security, Inc. -% MD5 Message Digest Algorithm, RFC 1321, R. Rivest, April 1992 -% -% See also CalcCRC32. -% Michael Kleder has published a Java call to compute the MD5 (and further -% check sums): http://www.mathworks.com/matlabcentral/fileexchange/8944 - -% $JRev: R5.00j V:015 Sum:zh2gTrvHwbd7 Date:17-Dec-2009 02:46:53 $ -% $File: CalcMD5\CalcMD5.m $ -% History: -% 015: 15-Dec-2009 16:53, BUGFIX: UINT32 has 32 bits on 64 bit systems now. -% Thanks to Sebastiaan Breedveld! - -% If the current Matlab path is the parent folder of this script, the -% MEX function is not found - change the current directory! - -% If a CalcMD5 call ends up here, this means CalcMD5 is not compiled or -% not found in the matlab path. -% Therefore, compile CalcMD5 -disp('CalcMD5 has not been compiled yet. Compiling now!') -md5_path=fileparts(mfilename('fullpath')); -addpath(fullfile(md5_path)) -tmpdir = pwd; -cd(fullfile(md5_path)) -mex(fullfile(md5_path,'CalcMD5.c')) -addpath(md5_path) -cd(tmpdir); - -% Make actual call to mex file -MD5 = CalcMD5(Data, varargin{:}); -end diff --git a/matlab/auxiliary/CalcMD5/TestCalcMD5.m b/matlab/auxiliary/CalcMD5/TestCalcMD5.m deleted file mode 100644 index ec09ecfc6b..0000000000 --- a/matlab/auxiliary/CalcMD5/TestCalcMD5.m +++ /dev/null @@ -1,191 +0,0 @@ -function TestCalcMD5(doSpeed) -% Automatic test: CalcMD5 (Mex) -% This is a routine for automatic testing. It is not needed for processing and -% can be deleted or moved to a folder, where it does not bother. -% -% TestCalcMD5(doSpeed) -% INPUT: -% doSpeed: Optional logical flag to trigger time consuming speed tests. -% Default: TRUE. If no speed test is defined, this is ignored. -% OUTPUT: -% On failure the test stops with an error. -% The speed is compared to a Java method. -% -% Tested: Matlab 6.5, 7.7, 7.8, WinXP -% Author: Jan Simon, Heidelberg, (C) 2009-2010 J@n-Simon.De - -% $JRev: R5.00g V:013 Sum:uNknB6D/Ksze Date:12-Dec-2009 00:14:15 $ -% $File: CalcMD5\TestCalcMD5.m $ - -% Initialize: ================================================================== -% Global Interface: ------------------------------------------------------------ -FuncName = 'TestCalcMD5'; % $Managed by AutoFuncPath$ - -% Initial values: -------------------------------------------------------------- -if nargin == 0 - doSpeed = true; -end - -% Program Interface: ----------------------------------------------------------- -% User Interface: -------------------------------------------------------------- -% Do the work: ================================================================= -disp(['==== Test CalcMD5, ', datestr(now, 0)]); - -TestData = {'', 'd41d8cd98f00b204e9800998ecf8427e'; ... - 'a', '0cc175b9c0f1b6a831c399e269772661'; ... - 'abc', '900150983cd24fb0d6963f7d28e17f72'; ... - 'message digest', 'f96b697d7cb7938d525a2f31aaf161d0'; ... - 'abcdefghijklmnopqrstuvwxyz', 'c3fcd3d76192e4007dfb496cca67e13b'; ... - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', ... - 'd174ab98d277d9f5a5611c2c9f419d9f'; ... - ['123456789012345678901234567890123456789012345678901234567890123456', ... - '78901234567890'], '57edf4a22be3c955ac49da2e2107b67a'; ... - char(0:255), 'e2c865db4162bed963bfaa9ef6ac18f0'}; % Not in RFC1321 - -fprintf(' Known answer test from RFC 1321 for strings and files:'); -TestFile = tempname; - -% Loop over test data: -for iTest = 1:size(TestData, 1) - % Check string input: - Str = CalcMD5(TestData{iTest, 1}, 'char'); - if strcmpi(Str, TestData{iTest, 2}) == 0 - fprintf('\n'); - error(['*** ', FuncName, ': Failed for string:', ... - char(10), '[', TestData{iTest, 1}, ']']); - end - - % Check file input: - FID = fopen(TestFile, 'wb+'); - if FID < 0 - fprintf('\n'); - error(['*** ', FuncName, ': Cannot open test file [', TestFile, ']']); - end - fwrite(FID, TestData{iTest, 1}, 'uchar'); - fclose(FID); - - Str2 = CalcMD5(TestFile, 'file'); - if strcmpi(Str2, TestData{iTest, 2}) == 0 - fprintf('\n'); - error(['*** ', FuncName, ': Failed for file:', ... - char(10), '[', TestData{iTest, 1}, ']']); - end -end -fprintf(' ok\n'); -delete(TestFile); - -% Check different output types: -N = 1000; -fprintf(' %d random tests with hex, HEX, dec and base64 output: ', N); -for i = 1:N - data = uint8(fix(rand(1, 1 + fix(rand * 100)) * 256)); - lowHexOut = CalcMD5(data, 'char', 'hex'); - upHexOut = CalcMD5(data, 'char', 'HEX'); - decOut = CalcMD5(data, 'char', 'Dec'); - b64Out = CalcMD5(data, 'char', 'Base64'); - - if not(strcmpi(lowHexOut, upHexOut) && ... - isequal(sscanf(lowHexOut, '%2x'), decOut(:)) && ... - isequal(Base64decode(b64Out), decOut)) - fprintf('\n'); - error(['*** ', FuncName, ': Different results for output types.']); - end - - % Check unicode, if the data length is a multiple of 2: - if rem(length(data), 2) == 0 - doubleData = double(data); - uniData = char(doubleData(1:2:end) + 256 * doubleData(2:2:end)); - uniOut = CalcMD5(uniData, 'unicode', 'dec'); - if not(isequal(uniOut, decOut)) - fprintf('\n'); - error(['*** ', FuncName, ': Different results for unicode input.']); - end - end -end -fprintf('ok\n'); -fprintf(' Unicode input: ok\n\n'); - -% Speed test: ------------------------------------------------------------------ -if doSpeed - disp('== Test speed:'); - disp('(Short data: mainly the overhead of calling the function)'); - Delay = 2; - - for Len = [10, 100, 1000, 10000, 1e5, 1e6, 1e7] - [Number, Unit] = UnitPrint(Len); - fprintf(' Data length: %s %s:\n', Number, Unit); - data = uint8(fix(rand(1, Len) * 256)); - - % Measure java time: - iniTime = cputime; - finTime = iniTime + Delay; - javaLoop = 0; - while cputime < finTime - x = java.security.MessageDigest.getInstance('MD5'); - x.update(data); - javaHash = double(typecast(x.digest, 'uint8')); - javaLoop = javaLoop + 1; - end - javaLoopPerSec = javaLoop / (cputime - iniTime); - [Number, Unit] = UnitPrint(javaLoopPerSec * Len); - fprintf(' java: %6s %s/sec\n', Number, Unit); - - % Measure Mex time: - iniTime = cputime; - finTime = iniTime + Delay; - mexLoop = 0; - while cputime < finTime - mexHash = CalcMD5(data, 'char', 'dec'); - mexLoop = mexLoop + 1; - end - mexLoopPerSec = mexLoop / (cputime - iniTime); - [Number, Unit] = UnitPrint(mexLoopPerSec * Len); - fprintf(' mex: %6s %s/sec: %.1f times faster\n', ... - Number, Unit, mexLoopPerSec / javaLoopPerSec); - - % Compare the results: - if ~isequal(javaHash(:), mexHash(:)) - error(['*** ', FuncName, ': Different results from java and Mex.']); - end - end -end - -fprintf('\nCalcMD5 seems to work well.\n'); - -return; - -% ****************************************************************************** -function Out = Base64decode(In) -% Decode from base 64 - -% Initialize: ================================================================== -Pool = [65:90, 97:122, 48:57, 43, 47]; % [0:9, a:z, A:Z, +, /] -v8 = [128, 64, 32, 16, 8, 4, 2, 1]; -v6 = [32; 16; 8; 4; 2; 1]; - -% Do the work: ================================================================= -In = reshape(In, 1, []); -Table = zeros(1, 256); -Table(Pool) = 1:64; -Value = Table(In) - 1; - -X = rem(floor(Value(ones(6, 1), :) ./ v6(:, ones(length(In), 1))), 2); -Out = v8 * reshape(X(1:fix(numel(X) / 8) * 8), 8, []); - -return; - -% ****************************************************************************** -function [Number, Unit] = UnitPrint(N) - -if N < 1000 - Number = sprintf('%d', round(N)); - Unit = 'Byte'; -elseif N < 1e6 - Number = sprintf('%.1f', N / 1000); - Unit = 'kB'; -else - Number = sprintf('%.1f', N / 1e6); - Unit = 'MB'; -end - -return; diff --git a/matlab/auxiliary/CalcMD5/license.txt b/matlab/auxiliary/CalcMD5/license.txt deleted file mode 100644 index 02a8987481..0000000000 --- a/matlab/auxiliary/CalcMD5/license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2009, Jan Simon -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/matlab/auxiliary/am_setdefault.m b/matlab/auxiliary/am_setdefault.m deleted file mode 100755 index f96ac7461d..0000000000 --- a/matlab/auxiliary/am_setdefault.m +++ /dev/null @@ -1,15 +0,0 @@ -function robj = am_setdefault(obj,dobj) - % sets the undefined fields of the struct obj to the default values specified in the default struct dobj. - % - % Parameters: - % obj: struct which is supposed to be updated based on default struct @type struct - % dobj: obj which carries d fields @type struct - % - % Return values: - % robj: updated obj @type struct - - fieldlist = fieldnames(obj); - for i = 1:length(fieldlist) - dobj.(fieldlist{i}) = obj.(fieldlist{i}); - end - robj = dobj; diff --git a/matlab/auxiliary/ami_mfun.m b/matlab/auxiliary/ami_mfun.m deleted file mode 100644 index 2873445e2f..0000000000 --- a/matlab/auxiliary/ami_mfun.m +++ /dev/null @@ -1,225 +0,0 @@ -function g = mfun(f,varargin) -% adapted from matlabFunction, follows the same syntax, but supports -% sparsity -%matlabFunction Generate a MATLAB file or anonymous function from a sym -% G = matlabFunction(F) generates a MATLAB anonymous function from sym object -% F. The free variables of F become the inputs for the resulting function -% handle G. For example, if x is the free variable of F then G(z) computes in -% MATLAB what subs(F,x,z) computes in the symbolic engine. The function handle -% G can be used by functions in MATLAB like FZERO or by functions in other -% toolboxes. The order of the inputs of G matches the order returned by -% symvar(F). -% -% G = matlabFunction(...,PARAM1,VALUE1,...) uses the specified parameter/value -% pairs to customize the generated function. The parameters modify the -% declaration that appears in the generated code. The template for the -% generated code is -% function [OUT] = NAME(IN1,IN2) -% The parameter names can be any of the following -% -% 'file': The value, FILE, must be a string with a valid MATLAB function -% name. If FILE does not end in '.m' it will be appended. If the -% file already exists it will be overwritten. A function handle to -% the function will be returned in G. If the file name parameter is -% empty an anonymous function is generated. The NAME in the function -% template is the file name in FILE. -% -% 'vars': The value, IN, must be either -% 1) a cell array of M strings or sym arrays, or -% 2) a vector of M symbolic variables. -% IN specifies the input variable names and their order IN1, -% IN2,... INM in the function template. If INj is a sym array then -% the name used in the function template is 'inj'. The variables -% listed in IN must be a superset of the free variables in all the -% Fk. The default value for IN is the union of the free variables in -% all the Fk. -% -% Note: not all MuPAD expressions can be converted to a MATLAB function. -% For example piecewise expressions and sets will not be converted. - - -narginchk(1,inf); - -% process inputs -funs = sym(f); -args = varargin(1:end); -opts = getOptions(args); - -% compute non-trivial defaults and verify inputs -funvars = getFunVars(funs); -vars = checkVars(funvars,opts); -varnames = format(opts.varnames); - -% generate anonymous function or file -if ~isempty(opts.file) - file = normalize(opts.file,'.m'); -% body = renameFileInputs(vars,inputs,funvars); - clear(char(file)); % necessary to avoid problems in for-loops - g = writeMATLAB(funs,file,varnames); - tmp = exist(char(file),'file'); %#ok - % necessary to make the function available - % on the PATH right away -end - -% compute the default value for 'vars'. -% returns the sorted union of the symvars of the funs -function funvars = getFunVars(f) -vars = symvar(f); -vars = unique(vars); -funparams = argnames(f); -funvars = unique([funparams, vars], 'stable'); - -% get the 'vars' value and check it for errors -function v = checkVars(funvars,opts) -if isempty(opts.vars) - v = funvars; -else - v = opts.vars; -end -[v,vexpanded] = var2cell(v); -if ~isempty(vexpanded) - if ~iscellstr(vexpanded) - error(message('symbolic:sym:matlabFunction:InvalidVars')); - elseif ~all(cellfun(@(x)isvarname(x),vexpanded)) - error(message('symbolic:sym:matlabFunction:InvalidVarName')); - end -end -checkVarsSubset(vexpanded,funvars); - -% check that the funvars are a subset of the expanded vars. -function checkVarsSubset(vexpanded,funvars) -vars = var2cell(funvars); -missing = cellfun(@(x)~any(strcmp(char(x),vexpanded)),vars); -if any(missing) - misvars = vars(missing); - varnames = format(misvars); - if length(misvars) > 1 - error(message('symbolic:sym:matlabFunction:FreeVariables', varnames)); - end - error(message('symbolic:sym:matlabFunction:FreeVariable', varnames)); -end - -% convert a string or sym array into a 1-by-N cell array of strings -% also optionally return the cellstr of expanded sym array vars joined together -function [v,vexpand] = var2cell(v) -if isa(v,'sym') - v = privsubsref(v,':'); - v = num2cell(v.'); - v = cellfun(@(x)char(x),v,'UniformOutput',false); -elseif ischar(v) - v = {v}; -end -if nargout > 1 - vexpand = cellfun(@var2cell,v,'UniformOutput',false); - vexpand = [vexpand{:}]; -end - -function inputs = getInputs(v) -inputs = cell(size(v)); -for k = 1:length(v) - inputs{k} = sprintf('in%d',k); -end - -% file = normalizeFile(file,ext) append extension ext if file doesn't -% have one already. -function file = normalize(file,ext) -[~,~,x] = fileparts(file); -if isempty(x) - file = [file ext]; -end - -% Horzcat sym array or cell array v with commas -function varnames = format(v) -if isempty(v) - varnames = ''; -else - if ~iscell(v) - v = num2cell(v); - end - varnames = cellfun(@(x)[char(x) ','],v,'UniformOutput',false); - varnames = [varnames{:}]; - varnames(end) = []; -end - -% Generate MATLAB. f is the expr to generate. file is file name. -% varnames is the formatted input variables -% outputs is the cell array of output names -% mapping is string with input to variable mapping -function g = writeMATLAB(f,file,varnames) -[fid,msg] = fopen(file,'wt'); -if fid == -1 - error(message('symbolic:sym:matlabFunction:FileError', file, msg)); -end -tmp = onCleanup(@()fclose(fid)); -% [f,tvalues,tnames] = optimize(f); -[~,fname] = fileparts(file); -writeHeader(fid,fname,varnames); -writeOutput(fid,f); -g = str2func(fname); - -% write out the function declaration and help -function writeHeader(fid,fname,varnames) -symver = ver('symbolic'); -if ~isempty(varnames) - varnames = ['(' varnames ')']; -end -symver = symver(1); -fprintf(fid,'function out = %s%s\n',fname,varnames); -fprintf(fid,'%%%s\n',upper(fname)); -fprintf(fid,'%% out = %s%s\n\n',upper(fname),upper(varnames)); -fprintf(fid,'%% This function was generated by the Symbolic Math Toolbox version %s.\n',symver.Version); -fprintf(fid,'%% %s\n\n',datestr(now)); - - -% write the assignments to the output variables -function writeOutput(fid,expr) -idx = find(expr); % find nonzero entries -fprintf(fid,['out = sparse([],[],[],' num2str(size(expr,1)) ',' num2str(size(expr,2)) ',' num2str(length(idx)) ');\n']); % initialise with zero values - -for k = 1:length(idx) - str = char(expr(idx(k))); - for var = {'p','x','k'} - t = regexp(str,[var{1} '_([0-9]*)'],'tokens'); - ts = cellfun(@(x) [var{1} '_' x{1} ],t,'UniformOutput',false); - td = cellfun(@(x) [var{1} '(' num2str(str2double(x)+1) ')'],t,'UniformOutput',false); - ts = unique(ts); - td = unique(td); - [~,idx_ts] = sort(cellfun(@(x) length(x),ts),'descend'); - ts = ts(idx_ts); - td = ts(idx_ts); - for it = 1:length(ts) - str = strrep(str,ts{it},td{it}); - end - end - fprintf(fid,['out(' num2str(idx(k)) ') = ' str ';\n']); -end - -% validator for variable parameter -function t = isVars(x) -t = iscell(x) || (ischar(x)&&size(x,1)==1) || isa(x,'sym'); - -% validator for file parameter -function t = isFunc(x) -[~,file] = fileparts(x); -if length(file)>namelengthmax - error('filename is longer than what matlab can handle.') -end -t = isempty(file) || isvarname(file); - -% parse inputs and return option structure output -function opts = getOptions(args) -ip = inputParser; -verMAT = ver('MATLAB'); -if(str2double(verMAT.Version)>=8.2) - ip.addParameter('vars',{},@isVars); - ip.addParameter('file','',@isFunc); - ip.addParameter('outputs',{},@iscellstr); - ip.addParameter('varnames',{},@iscellstr); -else - ip.addParamValue('vars',{},@isVars); - ip.addParamValue('file','',@isFunc); - ip.addParamValue('outputs',{},@iscellstr); - ip.addParamValue('varnames',{},@iscellstr); -end -ip.parse(args{:}); -opts = ip.Results; diff --git a/matlab/auxiliary/betterSym.m b/matlab/auxiliary/betterSym.m deleted file mode 100644 index cf93b74098..0000000000 --- a/matlab/auxiliary/betterSym.m +++ /dev/null @@ -1,8 +0,0 @@ -function csym = betterSym(str) - matVer = ver('MATLAB'); - if(str2double(matVer.Version)>=9.4) - csym = str2sym(str); - else - csym = sym(str); - end -end diff --git a/matlab/auxiliary/compileAMICIDependencies.m b/matlab/auxiliary/compileAMICIDependencies.m deleted file mode 100644 index 5b4d526836..0000000000 --- a/matlab/auxiliary/compileAMICIDependencies.m +++ /dev/null @@ -1,362 +0,0 @@ -function [objectsstr, includesstr] = compileAMICIDependencies(dependencyPath, objectFolder, o_suffix, COPT, DEBUG) - %COMPILEAMICIDEPENDENCIES Compiles Sundials and SuiteSparse libraries required by AMICI - COPT = ['CFLAGS=''$CFLAGS -std=c99'' ' COPT]; - sundials_path = fullfile(dependencyPath,'sundials'); - sundials_ver = '5.2.0'; - - ssparse_path = fullfile(dependencyPath,'SuiteSparse'); - ssparse_ver = '5.4.0'; - - lapack_path = fullfile(dependencyPath,'lapack-3.5.0'); % currently not used, lapack implementation still needs to be done - lapack_ver = '3.5.0'; - - - version_file = fullfile(objectFolder, 'versions.txt'); - [del_sundials, del_ssparse, del_lapack] = checkVersions(version_file, sundials_ver, ssparse_ver, lapack_ver); - - % assemble objectsstr - objectsstr = ''; - objects_sundials = getObjectsSundials(o_suffix); - for j=1:length(objects_sundials) - objectsstr = strcat(objectsstr,' "',fullfile(objectFolder,objects_sundials{j}),'"'); - end - - objects_ssparse = getObjectsSSparse(o_suffix); - for j=1:length(objects_ssparse) - objectsstr = strcat(objectsstr,' "',fullfile(objectFolder,objects_ssparse{j}),'"'); - end - - includesstr = getIncludeString(fullfile(fileparts(dependencyPath)), sundials_path, ssparse_path); - - % collect files that need to be recompiled - sources_sundials = getSourcesSundials(); - sourcesToCompile = ''; - for j=1:length(sources_sundials) - if(del_sundials || ~exist(fullfile(objectFolder,objects_sundials{j}), 'file')) - sourcesToCompile = [sourcesToCompile, ' "', fullfile(sundials_path,sources_sundials{j}), '"']; - end - end - sources_ssparse = getSourcesSSparse(); - for j=1:length(sources_ssparse) - if(del_ssparse || ~exist(fullfile(objectFolder,objects_ssparse{j}), 'file')) - sourcesToCompile = [sourcesToCompile, ' "', fullfile(ssparse_path,sources_ssparse{j}), '"']; - end - end - - % compile - if(~strcmp(sourcesToCompile, '')) - cmd = ['mex ' DEBUG ' ' COPT ' -c -outdir "' ... - objectFolder '" ' ... - includesstr ' ' sourcesToCompile]; - try - eval(cmd); - catch ME - disp(cmd); - rethrow(ME); - end - end - - % only write versions.txt if we are done compiling - fid = fopen(version_file,'w'); - fprintf(fid,[sundials_ver '\r']); - fprintf(fid,[ssparse_ver '\r']); - fprintf(fid,[lapack_ver '\r']); - fclose(fid); -end - -function includesstr = getIncludeString(amici_root_path, sundials_path, ssparse_path) - includesstr = ''; - includesstr = strcat(includesstr,' -I"', fullfile(sundials_path, 'include'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(sundials_path, 'include', 'sundials', 'amici_matlab'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(sundials_path, 'src'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(sundials_path, 'src', 'sundials'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(amici_root_path), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(amici_root_path, 'src'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(amici_root_path, 'include'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(ssparse_path, 'KLU','Include'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(ssparse_path, 'AMD','Include'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(ssparse_path, 'COLAMD','Include'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(ssparse_path, 'BTF','Include'), '"'); - includesstr = strcat(includesstr,' -I"', fullfile(ssparse_path, 'SuiteSparse_config'), '"'); -end - - -function [del_sundials, del_ssparse, del_lapack] = checkVersions(version_file, sundials_ver, ssparse_ver, lapack_ver) - % read version number from versions.txt and decide whether object have to - % be regenerated - if(~exist(version_file,'file')) - del_sundials = true; - del_ssparse = true; - del_lapack = true; - fid = fopen(version_file,'w'); - fprintf(fid,[sundials_ver '\r']); - fprintf(fid,[ssparse_ver '\r']); - fprintf(fid,[lapack_ver '\r']); - fclose(fid); - else - fid = fopen(version_file,'r'); - sundials_objver = fgetl(fid); - ssparse_objver = fgetl(fid); - lapack_objver = fgetl(fid); - fclose(fid); - del_sundials = isnewer(sundials_ver,sundials_objver); - if(del_sundials) - display('Newer version of Sundials! Recompiling ...') - end - del_ssparse = isnewer(ssparse_ver,ssparse_objver); - if(del_ssparse) - display('Newer version of SuiteSparse! Recompiling ...') - end - del_lapack = isnewer(lapack_ver,lapack_objver); - if(del_lapack) - display('Newer version of Lapack! Recompiling ...') - end - end -end - -function sources_sundials = getSourcesSundials() - sources_sundials = { - fullfile('src', 'sunmatrix', 'dense', 'sunmatrix_dense.c'); - fullfile('src', 'sunmatrix', 'sparse', 'sunmatrix_sparse.c'); - fullfile('src', 'sunmatrix', 'band', 'sunmatrix_band.c'); - fullfile('src', 'sunlinsol', 'spgmr', 'sunlinsol_spgmr.c'); - fullfile('src', 'sunlinsol', 'sptfqmr', 'sunlinsol_sptfqmr.c'); - fullfile('src', 'sunlinsol', 'klu', 'sunlinsol_klu.c'); - fullfile('src', 'sunlinsol', 'dense', 'sunlinsol_dense.c'); - fullfile('src', 'sunlinsol', 'spfgmr', 'sunlinsol_spfgmr.c'); - fullfile('src', 'sunlinsol', 'pcg', 'sunlinsol_pcg.c'); - fullfile('src', 'sunlinsol', 'spbcgs', 'sunlinsol_spbcgs.c'); - fullfile('src', 'sunlinsol', 'band', 'sunlinsol_band.c'); - fullfile('src', 'idas', 'idaa.c'); - fullfile('src', 'idas', 'idas_ic.c'); - fullfile('src', 'idas', 'idas_nls_stg.c'); - fullfile('src', 'idas', 'idas.c'); - fullfile('src', 'idas', 'idas_bbdpre.c'); - fullfile('src', 'idas', 'idas_nls.c'); - fullfile('src', 'idas', 'idas_ls.c'); - fullfile('src', 'idas', 'idas_io.c'); - fullfile('src', 'idas', 'idas_nls_sim.c'); - fullfile('src', 'idas', 'idaa_io.c'); - fullfile('src', 'sundials', 'sundials_math.c'); - fullfile('src', 'sundials', 'sundials_matrix.c'); - fullfile('src', 'sundials', 'sundials_direct.c'); - fullfile('src', 'sundials', 'sundials_nvector_senswrapper.c'); - fullfile('src', 'sundials', 'sundials_dense.c'); - fullfile('src', 'sundials', 'sundials_nvector.c'); - fullfile('src', 'sundials', 'sundials_version.c'); - fullfile('src', 'sundials', 'sundials_iterative.c'); - fullfile('src', 'sundials', 'sundials_nonlinearsolver.c'); - fullfile('src', 'sundials', 'sundials_linearsolver.c'); - fullfile('src', 'sundials', 'sundials_band.c'); - fullfile('src', 'sundials', 'sundials_context.c'); - fullfile('src', 'sundials', 'sundials_logger.c'); - fullfile('src', 'sundials', 'sundials_errors.c'); - fullfile('src', 'sundials', 'sundials_hashmap.c'); - fullfile('src', 'sunnonlinsol', 'newton', 'sunnonlinsol_newton.c'); - fullfile('src', 'sunnonlinsol', 'fixedpoint', ... - 'sunnonlinsol_fixedpoint.c'); - fullfile('src', 'nvector', 'serial', 'nvector_serial.c'); - fullfile('src', 'cvodes', 'cvodes_nls_stg.c'); - fullfile('src', 'cvodes', 'cvodes_ls.c'); - fullfile('src', 'cvodes', 'cvodes_nls_stg1.c'); - fullfile('src', 'cvodes', 'cvodes_bbdpre.c'); - fullfile('src', 'cvodes', 'cvodes.c'); - fullfile('src', 'cvodes', 'cvodes_bandpre.c'); - fullfile('src', 'cvodes', 'cvodea.c'); - fullfile('src', 'cvodes', 'cvodes_nls_sim.c'); - fullfile('src', 'cvodes', 'cvodea_io.c'); - fullfile('src', 'cvodes', 'cvodes_nls.c'); - fullfile('src', 'cvodes', 'cvodes_diag.c'); - fullfile('src', 'cvodes', 'cvodes_io.c'); - fullfile('src', 'cvodes', 'cvodes_proj.c'); - }; -end - - - -function sources_ssparse = getSourcesSSparse() - sources_ssparse = { - fullfile('KLU','Source','klu_l_analyze_given.c'); - fullfile('KLU','Source','klu_l_analyze.c'); - fullfile('KLU','Source','klu_l_defaults.c'); - fullfile('KLU','Source','klu_l_diagnostics.c'); - fullfile('KLU','Source','klu_l_dump.c'); - fullfile('KLU','Source','klu_l_extract.c'); - fullfile('KLU','Source','klu_l_factor.c'); - fullfile('KLU','Source','klu_l_free_numeric.c'); - fullfile('KLU','Source','klu_l_free_symbolic.c'); - fullfile('KLU','Source','klu_l_kernel.c'); - fullfile('KLU','Source','klu_l_memory.c'); - fullfile('KLU','Source','klu_l_refactor.c'); - fullfile('KLU','Source','klu_l_scale.c'); - fullfile('KLU','Source','klu_l_sort.c'); - fullfile('KLU','Source','klu_l_solve.c'); - fullfile('KLU','Source','klu_l_tsolve.c'); - fullfile('KLU','Source','klu_l.c'); - fullfile('AMD','Source','amd_l1.c'); - fullfile('AMD','Source','amd_l2.c'); - fullfile('AMD','Source','amd_l_aat.c'); - fullfile('AMD','Source','amd_l_control.c'); - fullfile('AMD','Source','amd_l_defaults.c'); - fullfile('AMD','Source','amd_l_dump.c'); - fullfile('AMD','Source','amd_l_info.c'); - fullfile('AMD','Source','amd_l_order.c'); - fullfile('AMD','Source','amd_l_postorder.c'); - fullfile('AMD','Source','amd_l_post_tree.c'); - fullfile('AMD','Source','amd_l_preprocess.c'); - fullfile('AMD','Source','amd_l_valid.c'); - fullfile('COLAMD','Source','colamd_l.c'); - fullfile('BTF','Source','btf_l_maxtrans.c'); - fullfile('BTF','Source','btf_l_order.c'); - fullfile('BTF','Source','btf_l_strongcomp.c'); - fullfile('SuiteSparse_config','SuiteSparse_config.c'); - }; -end - -function objects_ssparse = getObjectsSSparse(o_suffix) - - objects_ssparse = { - 'klu_l_analyze_given.o'; - 'klu_l_analyze.o'; - 'klu_l_defaults.o'; - 'klu_l_diagnostics.o'; - 'klu_l_dump.o'; - 'klu_l_extract.o'; - 'klu_l_factor.o'; - 'klu_l_free_numeric.o'; - 'klu_l_free_symbolic.o'; - 'klu_l_kernel.o'; - 'klu_l_memory.o'; - 'klu_l_refactor.o'; - 'klu_l_scale.o'; - 'klu_l_sort.o'; - 'klu_l_solve.o'; - 'klu_l_tsolve.o'; - 'klu_l.o'; - 'amd_l1.o'; - 'amd_l2.o'; - 'amd_l_aat.o'; - 'amd_l_control.o'; - 'amd_l_defaults.o'; - 'amd_l_dump.o'; - 'amd_l_info.o'; - 'amd_l_order.o'; - 'amd_l_post_tree.o'; - 'amd_l_postorder.o'; - 'amd_l_preprocess.o'; - 'amd_l_valid.o'; - 'colamd_l.o'; - 'btf_l_maxtrans.o'; - 'btf_l_order.o'; - 'btf_l_strongcomp.o'; - 'SuiteSparse_config.o'; - }; - - if(~strcmp(o_suffix, '.o')) - objects_ssparse = strrep(objects_ssparse, '.o', o_suffix); - end -end - -function objects_sundials = getObjectsSundials(o_suffix) - objects_sundials = { - 'sunmatrix_dense.o'; - 'sunlinsol_spgmr.o'; - 'sunlinsol_sptfqmr.o'; - 'sunlinsol_klu.o'; - 'idaa.o'; - 'idas_ic.o'; - 'idas_nls_stg.o'; - 'idas.o'; - 'idas_bbdpre.o'; - 'idas_nls.o'; - 'idas_ls.o'; - 'idas_io.o'; - 'idas_nls_sim.o'; - 'idaa_io.o'; - 'sundials_context.o'; - 'sundials_logger.o'; - 'sundials_errors.o'; - 'sundials_hashmap.o'; - 'sundials_math.o'; - 'sundials_matrix.o'; - 'sundials_direct.o'; - 'sundials_nvector_senswrapper.o'; - 'sundials_dense.o'; - 'sundials_nvector.o'; - 'sundials_version.o'; - 'sundials_iterative.o'; - 'sundials_nonlinearsolver.o'; - 'sundials_linearsolver.o'; - 'sundials_band.o'; - 'sunmatrix_band.o'; - 'sunmatrix_sparse.o'; - 'sunnonlinsol_newton.o'; - 'sunnonlinsol_fixedpoint.o'; - 'nvector_serial.o'; - 'sunlinsol_pcg.o'; - 'sunlinsol_dense.o'; - 'sunlinsol_spbcgs.o'; - 'sunlinsol_band.o'; - 'sunlinsol_spfgmr.o'; - 'cvodes_nls_stg.o'; - 'cvodes_ls.o'; - 'cvodes_nls_stg1.o'; - 'cvodes_bbdpre.o'; - 'cvodes.o'; - 'cvodes_bandpre.o'; - 'cvodea.o'; - 'cvodes_nls_sim.o'; - 'cvodea_io.o'; - 'cvodes_nls.o'; - 'cvodes_diag.o'; - 'cvodes_io.o'; - 'cvodes_proj.o'; - }; - - if(~strcmp(o_suffix, '.o')) - objects_sundials = strrep(objects_sundials, '.o', o_suffix); - end -end - -function result = isnewer(ver1str,ver2str) - % isnewer checks whether the version indicated in ver1str is newer than - % the on in ver2str - % - % Parameters: - % ver1str: version string 1, this should be a string in the format %d.%d.%d @type string - % ver2str: version string 1, this should be a string in the format %d.%d.%d @type string - % - % Return values: - % result: flag indicating whether ver1 ist newer than ver2 @type boolean - - ver1Parts = getParts(ver1str); - ver2Parts = getParts(ver2str); - if ver2Parts(1) ~= ver1Parts(1) % major version - result = ver2Parts(1) < ver1Parts(1); - elseif ver2Parts(2) ~= ver1Parts(2) % minor version - result = ver2Parts(2) < ver1Parts(2); - elseif ver2Parts(3) ~= ver1Parts(3) % revision version - result = ver2Parts(3) < ver1Parts(3); - else - result = ver2Parts(4) < ver1Parts(4); - end -end - -function parts = getParts(V) - % getParts takes an input version string and returns an array - % containing the major minor and subminor version number - % - % Parameters: - % V: version string, this should be a string in the format %d.%d.%d @type string - % - % Return values: - % parts: array containing the version numbers @type double - - parts = sscanf(V, '%d.%d.%d.%d')'; - if length(parts) < 3 - parts(3) = 0; % zero-fills to 3 elements - end - if length(parts) < 4 - parts(4) = 0; % zero-fills to 3 elements - end -end diff --git a/matlab/auxiliary/getCommitHash.m b/matlab/auxiliary/getCommitHash.m deleted file mode 100644 index c39f33f7a3..0000000000 --- a/matlab/auxiliary/getCommitHash.m +++ /dev/null @@ -1,40 +0,0 @@ -function [ commit_hash,branch,url ] = getCommitHash( wrap_path ) - % extracts the commit hash from the git HEAD, when available. returns - % empty array when no git directory is available. - % - % Parameters: - % wrap_path: path to AMICI folder @type char - % - % Return values: - % commit_hash: extracted hash value @type char - % branch: branch of the repository @type char - % url: employed remote origin @type char - - try - fid = fopen(fullfile(wrap_path,'..','.git','FETCH_HEAD')); - str = fgetl(fid); - fclose(fid); - if(str~=-1) - t_hash = regexp(str,'^([\w]*)','tokens'); - commit_hash = t_hash{1}{1}; - t_branch = regexp(str,'branch ''([\w]*)''','tokens'); - branch = t_branch{1}{1}; - idx_url = strfind(str,'https://github.com'); - url = str(idx_url:end); - else - fid = fopen(fullfile(wrap_path,'.git','ORIG_HEAD')); - commit_hash = ['dev_' fgetl(fid)]; - fclose(fid); - - fid = fopen(fullfile(wrap_path,'.git','HEAD')); - branch = strrep(fgetl(fid),'ref: refs/heads/',''); - fclose(fid); - - url = 'local'; - end - catch - commit_hash = []; - branch = 'unknown branch'; - url = 'unknown repository'; - end -end diff --git a/matlab/auxiliary/struct2xml/license.txt b/matlab/auxiliary/struct2xml/license.txt deleted file mode 100644 index 21b570d1cf..0000000000 --- a/matlab/auxiliary/struct2xml/license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2010, Wouter Falkena -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/matlab/auxiliary/struct2xml/struct2xml.m b/matlab/auxiliary/struct2xml/struct2xml.m deleted file mode 100644 index 7fa900ff0e..0000000000 --- a/matlab/auxiliary/struct2xml/struct2xml.m +++ /dev/null @@ -1,198 +0,0 @@ -function varargout = struct2xml( s, varargin ) -%Convert a MATLAB structure into a xml file -% [ ] = struct2xml( s, file ) -% xml = struct2xml( s ) -% -% A structure containing: -% s.XMLname.Attributes.attrib1 = "Some value"; -% s.XMLname.Element.Text = "Some text"; -% s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2"; -% s.XMLname.DifferentElement{1}.Text = "Some more text"; -% s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2"; -% s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1"; -% s.XMLname.DifferentElement{2}.Text = "Even more text"; -% -% Will produce: -% -% Some text -% Some more text -% Even more text -% -% -% Please note that the following strings are substituted -% '_dash_' by '-', '_colon_' by ':' and '_dot_' by '.' -% -% Written by W. Falkena, ASTI, TUDelft, 27-08-2010 -% On-screen output functionality added by P. Orth, 01-12-2010 -% Multiple space to single space conversion adapted for speed by T. Lohuis, 11-04-2011 -% Val2str subfunction bugfix by H. Gsenger, 19-9-2011 - - if (nargin ~= 2) - if(nargout ~= 1 || nargin ~= 1) - error(['Supported function calls:' sprintf('\n')... - '[ ] = struct2xml( s, file )' sprintf('\n')... - 'xml = struct2xml( s )']); - end - end - - if(nargin == 2) - file = varargin{1}; - - if (isempty(file)) - error('Filename can not be empty'); - end - - if (isempty(strfind(file,'.xml'))) - file = [file '.xml']; - end - end - - if (~isstruct(s)) - error([inputname(1) ' is not a structure']); - end - - if (length(fieldnames(s)) > 1) - error(['Error processing the structure:' sprintf('\n') 'There should be a single field in the main structure.']); - end - xmlname = fieldnames(s); - xmlname = xmlname{1}; - - %substitute special characters - xmlname_sc = xmlname; - xmlname_sc = strrep(xmlname_sc,'_dash_','-'); - xmlname_sc = strrep(xmlname_sc,'_colon_',':'); - xmlname_sc = strrep(xmlname_sc,'_dot_','.'); - - %create xml structure - docNode = com.mathworks.xml.XMLUtils.createDocument(xmlname_sc); - - %process the rootnode - docRootNode = docNode.getDocumentElement; - - %append childs - parseStruct(s.(xmlname),docNode,docRootNode,[inputname(1) '.' xmlname '.']); - - if(nargout == 0) - %save xml file - xmlwrite(file,docNode); - else - varargout{1} = xmlwrite(docNode); - end -end - -% ----- Subfunction parseStruct ----- -function [] = parseStruct(s,docNode,curNode,pName) - - fnames = fieldnames(s); - for i = 1:length(fnames) - curfield = fnames{i}; - - %substitute special characters - curfield_sc = curfield; - curfield_sc = strrep(curfield_sc,'_dash_','-'); - curfield_sc = strrep(curfield_sc,'_colon_',':'); - curfield_sc = strrep(curfield_sc,'_dot_','.'); - - if (strcmp(curfield,'Attributes')) - %Attribute data - if (isstruct(s.(curfield))) - attr_names = fieldnames(s.Attributes); - for a = 1:length(attr_names) - cur_attr = attr_names{a}; - - %substitute special characters - cur_attr_sc = cur_attr; - cur_attr_sc = strrep(cur_attr_sc,'_dash_','-'); - cur_attr_sc = strrep(cur_attr_sc,'_colon_',':'); - cur_attr_sc = strrep(cur_attr_sc,'_dot_','.'); - - [cur_str,succes] = val2str(s.Attributes.(cur_attr)); - if (succes) - curNode.setAttribute(cur_attr_sc,cur_str); - else - disp(['Warning. The text in ' pName curfield '.' cur_attr ' could not be processed.']); - end - end - else - disp(['Warning. The attributes in ' pName curfield ' could not be processed.']); - disp(['The correct syntax is: ' pName curfield '.attribute_name = ''Some text''.']); - end - elseif (strcmp(curfield,'Text')) - %Text data - [txt,succes] = val2str(s.Text); - if (succes) - curNode.appendChild(docNode.createTextNode(txt)); - else - disp(['Warning. The text in ' pName curfield ' could not be processed.']); - end - else - %Sub-element - if (isstruct(s.(curfield))) - %single element - curElement = docNode.createElement(curfield_sc); - curNode.appendChild(curElement); - parseStruct(s.(curfield),docNode,curElement,[pName curfield '.']) - elseif (iscell(s.(curfield))) - %multiple elements - for c = 1:length(s.(curfield)) - curElement = docNode.createElement(curfield_sc); - curNode.appendChild(curElement); - if (isstruct(s.(curfield){c})) - parseStruct(s.(curfield){c},docNode,curElement,[pName curfield '{' num2str(c) '}.']) - else - disp(['Warning. The cell ' pName curfield '{' num2str(c) '} could not be processed, since it contains no structure.']); - end - end - else - %eventhough the fieldname is not text, the field could - %contain text. Create a new element and use this text - curElement = docNode.createElement(curfield_sc); - curNode.appendChild(curElement); - [txt,succes] = val2str(s.(curfield)); - if (succes) - curElement.appendChild(docNode.createTextNode(txt)); - else - disp(['Warning. The text in ' pName curfield ' could not be processed.']); - end - end - end - end -end - -%----- Subfunction val2str ----- -function [str,succes] = val2str(val) - - succes = true; - str = []; - - if (isempty(val)) - return; %bugfix from H. Gsenger - elseif (ischar(val)) - %do nothing - elseif (isnumeric(val)) - val = num2str(val); - else - succes = false; - end - - if (ischar(val)) - %add line breaks to all lines except the last (for multiline strings) - lines = size(val,1); - val = [val char(sprintf('\n')*[ones(lines-1,1);0])]; - - %transpose is required since indexing (i.e., val(nonspace) or val(:)) produces a 1-D vector. - %This should be row based (line based) and not column based. - valt = val'; - - remove_multiple_white_spaces = true; - if (remove_multiple_white_spaces) - %remove multiple white spaces using isspace, suggestion of T. Lohuis - whitespace = isspace(val); - nonspace = (whitespace + [zeros(lines,1) whitespace(:,1:end-1)])~=2; - nonspace(:,end) = [ones(lines-1,1);0]; %make sure line breaks stay intact - str = valt(nonspace'); - else - str = valt(:); - end - end -end diff --git a/matlab/auxiliary/structToHDF5Attribute.m b/matlab/auxiliary/structToHDF5Attribute.m deleted file mode 100644 index 78b2f4096c..0000000000 --- a/matlab/auxiliary/structToHDF5Attribute.m +++ /dev/null @@ -1,42 +0,0 @@ -function [ ] = structToHDF5Attribute( hdffile, path, mystruct ) -%STRUCTTOHDF5ATTRIBUTE Write structs to HDF5 file -% ... - -try - try - fid = H5F.open(hdffile, 'H5F_ACC_RDWR', 'H5P_DEFAULT'); - catch - fid = H5F.create(hdffile, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); - end - lcpl = H5P.create('H5P_LINK_CREATE'); - H5P.set_create_intermediate_group(lcpl,1); - gid = H5G.create(fid, path, lcpl, 'H5P_DEFAULT', 'H5P_DEFAULT'); - H5G.close(gid); - H5F.close(fid); -catch me - if ~strcmp(me.identifier,'MATLAB:imagesci:h5create:datasetAlreadyExists') - rethrow(me) - end -end - -fields = fieldnames(mystruct); -for i = 1:numel(fields) - f = fields{i}; - d = mystruct.(f); - if(isstruct(d)) - structToHDF5Attribute( hdffile, [path '/' f], d ) - else - if isa(d, 'logical') - d = int32(d); - end - if(numel(d)>0) - if(numel(d)>1 && sum(size(d)>1)>1) - h5create(hdffile, [path '/' f], size(d)); - h5write(hdffile, [path '/' f], d); - else - h5writeatt(hdffile, path, f, d); - end - end - end -end -end diff --git a/matlab/auxiliary/template.m b/matlab/auxiliary/template.m deleted file mode 100644 index 24fd06ff86..0000000000 --- a/matlab/auxiliary/template.m +++ /dev/null @@ -1,44 +0,0 @@ -classdef template < handle - %TEMPLATE A class to replace strings in template files - - properties - % strings in the template to be replaced - templateStrings = {}; - % strings for replacement - templateReplacements = {}; - end - - methods - function replace(this, infile, outfile) - % apply all provided template substitutions to infile and write - % results to outfile - fin = fopen(infile, 'r'); - fout = fopen(outfile, 'w'); - while(~feof(fin)) - s = fgetl(fin); - s = this.replaceStr(s); - fprintf(fout, '%s\n', s); - end - fclose(fin); - fclose(fout); - end - - function add(this, templateStr, replacementStr) - % add a new template string and replacement - nextIdx = numel(this.templateStrings); - this.templateStrings{nextIdx + 1} = templateStr; - this.templateReplacements{nextIdx + 1} = replacementStr; - end - - function s = replaceStr(this, s) - % apply all provided template substitutions to s - - % do not use cellfun to guarantee order of replacements - for n = 1:numel(this.templateStrings) - s = strrep(s, this.templateStrings(n), this.templateReplacements(n)); - s = s{1}; - end - end - end - -end diff --git a/matlab/auxiliary/xml2struct/license.txt b/matlab/auxiliary/xml2struct/license.txt deleted file mode 100644 index 21b570d1cf..0000000000 --- a/matlab/auxiliary/xml2struct/license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2010, Wouter Falkena -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/matlab/auxiliary/xml2struct/xml2struct.m b/matlab/auxiliary/xml2struct/xml2struct.m deleted file mode 100644 index a7b93058f1..0000000000 --- a/matlab/auxiliary/xml2struct/xml2struct.m +++ /dev/null @@ -1,183 +0,0 @@ -function [ s ] = xml2struct( file ) -%Convert xml file into a MATLAB structure -% [ s ] = xml2struct( file ) -% -% A file containing: -% -% Some text -% Some more text -% Even more text -% -% -% Will produce: -% s.XMLname.Attributes.attrib1 = "Some value"; -% s.XMLname.Element.Text = "Some text"; -% s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2"; -% s.XMLname.DifferentElement{1}.Text = "Some more text"; -% s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2"; -% s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1"; -% s.XMLname.DifferentElement{2}.Text = "Even more text"; -% -% Please note that the following characters are substituted -% '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_' -% -% Written by W. Falkena, ASTI, TUDelft, 21-08-2010 -% Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011 -% Added CDATA support by I. Smirnov, 20-3-2012 -% -% Modified by X. Mo, University of Wisconsin, 12-5-2012 - - if (nargin < 1) - clc; - help xml2struct - return - end - - if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl') - % input is a java xml object - xDoc = file; - else - %check for existance - if (exist(file,'file') == 0) - %Perhaps the xml extension was omitted from the file name. Add the - %extension and try again. - if (isempty(strfind(file,'.xml'))) - file = [file '.xml']; - end - - if (exist(file,'file') == 0) - error(['The file ' file ' could not be found']); - end - end - %read the xml file - xDoc = xmlread(file); - end - - %parse xDoc into a MATLAB structure - s = parseChildNodes(xDoc); - -end - -% ----- Subfunction parseChildNodes ----- -function [children,ptext,textflag] = parseChildNodes(theNode) - % Recurse over node children. - children = struct; - ptext = struct; textflag = 'Text'; - if hasChildNodes(theNode) - childNodes = getChildNodes(theNode); - numChildNodes = getLength(childNodes); - - for count = 1:numChildNodes - theChild = item(childNodes,count-1); - [text,name,attr,childs,textflag] = getNodeData(theChild); - - if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section')) - %XML allows the same elements to be defined multiple times, - %put each in a different cell - if (isfield(children,name)) - if (~iscell(children.(name))) - %put existsing element into cell format - children.(name) = {children.(name)}; - end - index = length(children.(name))+1; - %add new element - children.(name){index} = childs; - if(~isempty(fieldnames(text))) - children.(name){index} = text; - end - if(~isempty(attr)) - children.(name){index}.('Attributes') = attr; - end - else - %add previously unknown (new) element to the structure - children.(name) = childs; - if(~isempty(text) && ~isempty(fieldnames(text))) - children.(name) = text; - end - if(~isempty(attr)) - children.(name).('Attributes') = attr; - end - end - else - ptextflag = 'Text'; - if (strcmp(name, '#cdata_dash_section')) - ptextflag = 'CDATA'; - elseif (strcmp(name, '#comment')) - ptextflag = 'Comment'; - end - - %this is the text in an element (i.e., the parentNode) - if (~isempty(regexprep(text.(textflag),'[\s]*',''))) - if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag))) - ptext.(ptextflag) = text.(textflag); - else - %what to do when element data is as follows: - %Text More text - - %put the text in different cells: - % if (~iscell(ptext)) ptext = {ptext}; end - % ptext{length(ptext)+1} = text; - - %just append the text - ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)]; - end - end - end - - end - end -end - -% ----- Subfunction getNodeData ----- -function [text,name,attr,childs,textflag] = getNodeData(theNode) - % Create structure of node info. - - %make sure name is allowed as structure name - name = toCharArray(getNodeName(theNode))'; - name = strrep(name, '-', '_dash_'); - name = strrep(name, ':', '_colon_'); - name = strrep(name, '.', '_dot_'); - - attr = parseAttributes(theNode); - if (isempty(fieldnames(attr))) - attr = []; - end - - %parse child nodes - [childs,text,textflag] = parseChildNodes(theNode); - - if (isempty(fieldnames(childs)) && isempty(fieldnames(text))) - %get the data of any childless nodes - % faster than if any(strcmp(methods(theNode), 'getData')) - % no need to try-catch (?) - % faster than text = char(getData(theNode)); - text.(textflag) = toCharArray(getTextContent(theNode))'; - end - -end - -% ----- Subfunction parseAttributes ----- -function attributes = parseAttributes(theNode) - % Create attributes structure. - - attributes = struct; - if hasAttributes(theNode) - theAttributes = getAttributes(theNode); - numAttributes = getLength(theAttributes); - - for count = 1:numAttributes - %attrib = item(theAttributes,count-1); - %attr_name = regexprep(char(getName(attrib)),'[-:.]','_'); - %attributes.(attr_name) = char(getValue(attrib)); - - %Suggestion of Adrian Wanner - str = toCharArray(toString(item(theAttributes,count-1)))'; - k = strfind(str,'='); - attr_name = str(1:(k(1)-1)); - attr_name = strrep(attr_name, '-', '_dash_'); - attr_name = strrep(attr_name, ':', '_colon_'); - attr_name = strrep(attr_name, '.', '_dot_'); - attributes.(attr_name) = str((k(1)+2):(end-1)); - end - end -end diff --git a/matlab/examples/amiExamples.m b/matlab/examples/amiExamples.m deleted file mode 100644 index 82ee79c76c..0000000000 --- a/matlab/examples/amiExamples.m +++ /dev/null @@ -1,17 +0,0 @@ -oldpath = addpath(genpath('.')); -example_events -example_dirac -example_dirac_adjoint -example_dirac_secondorder -example_dirac_secondorder_vectmult -example_adjoint -example_jakstat_adjoint -example_steadystate -example_nested_events -example_adjoint_hessian -example_dirac_adjoint_hessVecProd -example_jakstat_adjoint_hvp -example_robertson -example_neuron -example_calvetti -path(oldpath); diff --git a/matlab/examples/example_adjoint/example_adjoint.m b/matlab/examples/example_adjoint/example_adjoint.m deleted file mode 100644 index 3e787c2110..0000000000 --- a/matlab/examples/example_adjoint/example_adjoint.m +++ /dev/null @@ -1,130 +0,0 @@ -function example_adjoint() - -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_adjoint.m')); -% compile the model -amiwrap('model_adjoint','model_adjoint_syms',exdir) - -%% -% SIMULATION - -% time vector -t = linspace(0,4,5); -p = [1.1,0.3,1]; -k = []; - -D.Y = [ 1.0171 - 1.3423 - 1.6585 - 0.9814 - 0.3288]; - -D.Sigma_Y = 0.1*ones(size(D.Y)); - - -options.sensi = 1; -options.sensi_meth = 'adjoint'; -options.maxsteps = 1e4; -options.rtol = 1e-12; -options.atol = 1e-12; - -% load mex into memory -[~] = which('simulate_model_adjoint'); % fix for inaccessability problems -sol = simulate_model_adjoint(t,log10(p),k,D,options); - -%% -% Plot -if(usejava('jvm')) - figure - subplot(3,1,1) - errorbar(t,D.Y,D.Sigma_Y) - hold on - % plot(t,sol.y) - - xlabel('time t') - ylabel('observable') - title(['log-likelihood: ' num2str(sol.llh) ]) - - y = (p(2)*t + p(3)).*(t<2) + ( (2*p(2)+p(3)-p(2)/p(1))*exp(-p(1)*(t-2))+p(2)/p(1) ).*(t>=2); - - - tfine = linspace(0,4,100001); - xfine = (p(2)*tfine + 1).*(tfine<2) + ( (2*p(2)+p(3)-p(2)/p(1))*exp(-p(1)*(tfine-2))+p(2)/p(1) ).*(tfine>=2); - - mu = zeros(1,length(tfine)); - for it = 1:length(t) - if(t(it)<=2) - mu = mu + ((y(it)-D.Y(it))/(D.Sigma_Y(it)^2))*(tfine<=t(it)); - else - mu = mu + ((y(it)-D.Y(it))/(D.Sigma_Y(it)^2))*exp(p(1)*(tfine-t(it))).*(tfine<=t(it)).*(tfine>2) + ((y(it)-D.Y(it))/(D.Sigma_Y(it)^2))*exp(p(1)*(2-t(it))).*(tfine2)))*p(1)*log(10)*(t(end)/numel(tfine))) - hold on - plot(fliplr(tfine),-cumsum(fliplr(mu))*p(2)*log(10)*(t(end)/numel(tfine))) - plot(tfine,-mu(1)*p(3)*log(10)*(tfine<2)) - xlim([min(t)-0.5,max(t)+0.5]) - ylabel('integral') - xlabel('time t') - - legend('p1','p2','p3') - - grad(1,1) = -trapz(tfine,-mu.*xfine.*(tfine>2))*p(1)*log(10); - grad(2,1) = -trapz(tfine,mu)*p(2)*log(10); - grad(3,1) = -mu(1)*p(3)*log(10); - - plot(zeros(3,1),grad,'ko') -end - -%% -% FD - -eps = 1e-5; -xi = log10(p); -grad_fd_f = NaN(3,1); -grad_fd_b = NaN(3,1); -for ip = 1:3; - options.sensi = 0; - xip = xi; - xip(ip) = xip(ip) + eps; - solp = simulate_model_adjoint(t,xip,k,D,options); - grad_fd_f(ip,1) = (solp.llh-sol.llh)/eps; - xip = xi; - xip(ip) = xip(ip) - eps; - solp = simulate_model_adjoint(t,xip,k,D,options); - grad_fd_b(ip,1) = -(solp.llh-sol.llh)/eps; -end - -if(usejava('jvm')) - figure - plot(abs(grad),abs(grad_fd_f),'o') - hold on - plot(abs(grad),abs(grad_fd_b),'o') - plot(abs(grad),mean([abs(grad_fd_b),abs(grad_fd_f)],2),'o') - plot(abs(grad),abs(sol.sllh),'o') - plot([1e1,1e2],[1e1,1e2],'k:') - set(gca,'XScale','log') - set(gca,'YScale','log') - axis square - legend('forward FD','backward FD','central FD','adjoint sensintivity analysis','Location','SouthEast') - xlabel('analytic absolute value of gradient element') - ylabel('computed absolute value of gradient element') - set(gcf,'Position',[100 300 1200 500]) - - drawnow -end - -end diff --git a/matlab/examples/example_adjoint/model_adjoint_syms.m b/matlab/examples/example_adjoint/model_adjoint_syms.m deleted file mode 100644 index 0eddd4d122..0000000000 --- a/matlab/examples/example_adjoint/model_adjoint_syms.m +++ /dev/null @@ -1,51 +0,0 @@ -function [model] = model_adjoint_syms() - -%% -% STATES - -% create state syms -syms x1 - -% create state vector -model.sym.x = [x1]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 - -% create parameter vector -model.sym.p = [p1 p2 p3]; - - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1*heaviside(t-2) + p2; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = p3; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x1; - -end diff --git a/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m b/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m deleted file mode 100644 index 91aa105bd5..0000000000 --- a/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m +++ /dev/null @@ -1,94 +0,0 @@ -function success = example_adjoint_hessian() - -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_adjoint_hessian.m')); -% compile the model -amiwrap('model_adjoint_hessian','model_adjoint_hessian_syms',exdir,1) - -%% -% SIMULATION - -% time vector -t = [linspace(0,4,5)]; -p = [1.1,0.3,1]; -k = []; - -D.Y = [ 1.0171 - 1.3423 - 1.6585 - 0.9814 - 0.3288]; - -D.Sigma_Y = 0.1*ones(size(D.Y)); - - -options.sensi = 1; -options.sensi_meth = 'adjoint'; -options.maxsteps = 1e4; -options.rtol = 1e-12; -options.atol = 1e-12; -% load mex into memory -[~] = which('simulate_model_adjoint'); % fix for inaccessability problems -sol = simulate_model_adjoint_hessian(t,log10(p),k,D,options); -g = sol.sllh; - -% fprintf('First calculated gradient: \n') -% disp(g); - -eps1 = [1e-7, 0, 0]; -eps2 = [0, 1e-7, 0]; -eps3 = [0, 0, 1e-7]; - -sol = simulate_model_adjoint_hessian(t,log10(p)+eps1,k,D,options); -g_p1 = sol.sllh; -sol = simulate_model_adjoint_hessian(t,log10(p)-eps1,k,D,options); -g_m1 = sol.sllh; -h1 = (g_p1-g_m1) / (2e-7); - -sol = simulate_model_adjoint_hessian(t,log10(p)+eps2,k,D,options); -g_p2 = sol.sllh; -sol = simulate_model_adjoint_hessian(t,log10(p)-eps2,k,D,options); -g_m2 = sol.sllh; -h2 = (g_p2-g_m2) / (2e-7); - -sol = simulate_model_adjoint_hessian(t,log10(p)+eps3,k,D,options); -g_p3 = sol.sllh; -sol = simulate_model_adjoint_hessian(t,log10(p)-eps3,k,D,options); -g_m3 = sol.sllh; -h3 = (g_p3-g_m3) / (2e-7); -H = [h1, h2, h3]; - -% fprintf(' Finite difference computations: \n\n'); -% fprintf(' Gradient: \n'); -% disp(g); -% fprintf(' Hessian: \n'); -% disp(H); -% fprintf(' Hessian vector product: \n'); -% disp(H * g); - -fprintf('\n\n Second order adjoint computations: \n\n') -options.sensi = 2; -options.sensi_meth = 'adjoint'; -options.maxsteps = 1e4; -options.rtol = 1e-12; -options.atol = 1e-12; -% load mex into memory -[~] = which('simulate_model_adjoint'); % fix for inaccessability problems -sol1 = simulate_model_adjoint_hessian(t,log10(p),k,D,options); -% fprintf(' Likelihood: \n'); -% disp(sol1.llh); -% fprintf(' Gradient: \n'); -% disp(sol1.sllh); -% fprintf(' Hessian: \n'); -% disp(sol1.s2llh); - -if (sum(sum(abs(H - sol1.s2llh))) < 0.01) - disp(sum(sum(abs(H - sol1.s2llh)))); - success=1; -else - success=0; -end - -end diff --git a/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m b/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m deleted file mode 100644 index 3648341dbb..0000000000 --- a/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m +++ /dev/null @@ -1,51 +0,0 @@ -function [model] = model_adjoint_hessian_syms() - -%% -% STATES - -% create state syms -syms x1 - -% create state vector -model.sym.x = [x1]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 - -% create parameter vector -model.sym.p = [p1 p2 p3]; - - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1*heaviside(t-2) + p2; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = p3; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x1; - -end diff --git a/matlab/examples/example_calvetti/example_calvetti.m b/matlab/examples/example_calvetti/example_calvetti.m deleted file mode 100755 index e786677161..0000000000 --- a/matlab/examples/example_calvetti/example_calvetti.m +++ /dev/null @@ -1,101 +0,0 @@ -function example_calvetti() -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_calvetti.m')); -% compile the model -amiwrap('model_calvetti','model_calvetti_syms',exdir) - -% time vector -t = linspace(0,20,201); -p = []; -k = [0.29 0.74 0.44 0.08 0.27 0.18]; - -options = amioption('sensi',0,... - 'maxsteps',1e4); -D = amidata(length(t),6,0,2,4); -% load mex into memory -[~] = which('simulate_model_calvetti'); % fix for inaccessability problems -sol = simulate_model_calvetti(t,p,k,D,options); - -tic -sol = simulate_model_calvetti(t,p,k,D,options); -disp(['Time elapsed with cvodes: ' num2str(toc) ]) - -%% -% ODE15S - -y0 = [k(1); k(3); k(5); 1; 1; 1;]; -M = [1 0 0 0 0 0 - 0 1 0 0 0 0 - 0 0 1 0 0 0 - 0 0 0 0 0 0 - 0 0 0 0 0 0 - 0 0 0 0 0 0]; - -function [xdot] = dae_system(t,x,p,k,it) - if it<3 - h0 = 0; - else - h0 = 1; - end - if it<4 - h1 = 0; - else - h1 = 1; - end - xdot = [ - h0/31 - h1/31 - (100*x(1))/(899*k(1)) + (2*x(1)*(k(2)/2 - 1))/(31*k(1)*(x(4)*((k(2)*k(1)^2)/x(1)^2 + (k(4)*k(3)^2)/x(2)^2) + x(5)*((k(4)*k(3)^2)/x(2)^2 + (k(6)*k(5)^2)/x(3)^2) + (k(6)*k(5)^2*x(6))/x(3)^2)) + 129/899; - (2*x(2)*(k(2) + k(4)/2 - 1))/(163*k(3)*(x(5)*((k(4)*k(3)^2)/x(2)^2 + (k(6)*k(5)^2)/x(3)^2) + (k(6)*k(5)^2*x(6))/x(3)^2)) - (100*x(2))/(8313*k(3)) + 151/8313; - (x(3)^3*(k(2) + k(4) +k(6)/2 - 1))/(61*k(6)*k(5)^3*x(6)) - x(3)/(121999878*k(5)) + 500000/60999939; - h1/31 - h0/31 - x(4) + (100*x(1))/(899*k(1)) + (2*x(1)^2)/(k(2)*k(1)^2) - (x(1)^2*(x(4)*((k(2)*k(1)^2)/x(1)^2 + (k(4)*k(3)^2)/x(2)^2) + x(5)*((k(4)*k(3)^2)/x(2)^2 + (k(6)*k(5)^2)/x(3)^2) + (k(6)*k(5)^2*x(6))/x(3)^2))/(k(2)*k(1)^2) - (2*x(1)*(k(2)/2 - 1))/(31*k(1)*(x(4)*((k(2)*k(1)^2)/x(1)^2 + (k(4)*k(3)^2)/x(2)^2) + x(5)*((k(4)*k(3)^2)/x(2)^2 + (k(6)*k(5)^2)/x(3)^2) + (k(6)*k(5)^2*x(6))/x(3)^2)) - 129/899; - x(4) - x(5) + (100*x(2))/(8313*k(3)) - (2*x(2)*(k(2) + k(4)/2 - 1))/(163*k(3)*(x(5)*((k(4)*k(3)^2)/x(2)^2 + (k(6)*k(5)^2)/x(3)^2) + (k(6)*k(5)^2*x(6))/x(3)^2)) - 151/8313; - x(5) - x(6) + x(3)/(121999878*k(5)) - (x(3)^3*(k(2) + k(4) +k(6)/2 - 1))/(61*k(6)*k(5)^3*x(6)) - 500000/60999939; -]; -end - -options_ode15s = odeset('Mass',M, ... - 'RelTol',options.rtol, ... - 'AbsTol',options.atol); - -tic -X_ode15s = []; -tt = t; -tstop = [0,10,12,20]; -for it = 2:4 - [~, y] = ode15s(@(t,x) dae_system(t,x,p,k,it),t(t>=tstop(it-1) & t<=tstop(it)),y0,options_ode15s); - X_ode15s = [X_ode15s;y(1:end-1,:)]; - y0 = y(end,:); -end -X_ode15s = [X_ode15s;y(end,:)]; -disp(['Time elapsed with ode15s: ' num2str(toc) ]) - -%% -% PLOTTING -if(usejava('jvm')) - figure - c_x = get(gca,'ColorOrder'); - subplot(2,1,1) - for ix = 1:size(sol.x,2) - plot(t,sol.x(:,ix),'.-','Color',c_x(ix,:)) - hold on - plot(t,X_ode15s(:,ix),'d','Color',c_x(ix,:)) - end - legend('x1','x1_{ode15s}','x2','x2_{ode15s}', ... - 'x3','x3_{ode15s}','x4','x4_{ode15s}', ... - 'x5','x5_{ode15s}','x6','x6_{ode15s}', ... - 'Location','NorthEastOutside') - legend boxoff - xlabel('time t') - ylabel('x') - box on - subplot(2,1,2) - plot(t,abs(sol.x-X_ode15s),'--') - set(gca,'YScale','log') - legend('error x1','error x2','error x3','error x4','error x5','error x6','Location','NorthEastOutside') - legend boxoff - ylabel('x') - - set(gcf,'Position',[100 300 1200 500]) -end -end diff --git a/matlab/examples/example_calvetti/model_calvetti_syms.m b/matlab/examples/example_calvetti/model_calvetti_syms.m deleted file mode 100755 index 697e4c1340..0000000000 --- a/matlab/examples/example_calvetti/model_calvetti_syms.m +++ /dev/null @@ -1,74 +0,0 @@ -function [model] = model_calvetti_syms() - -% set the parametrisation of the problem options are 'log', 'log10' and - -model.param = 'lin'; - -%% -% STATES -% create state syms -syms V1 V2 V3 f1 f2 f3 -% create state vector -model.sym.x = [V1, V2, V3, f1, f2, f3]; -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -% create parameter vector -model.sym.p = [ ]; -%% -% CONSTANTS ( for these no sensitivities will be computed ) -% this part is optional and can be ommited - -% create parameter syms -syms V1ss R1ss V2ss R2ss V3ss R3ss -% create parameter vector -model.sym.k = [V1ss, R1ss, V2ss, R2ss, V3ss, R3ss]; -%% -% SYSTEM EQUATIONS -% create symbolic variable for time -syms t f0 -model.sym.xdot = sym(zeros(size(model.sym.x))); -p1=1; -p2=1-R1ss; -p3=1-(R1ss+R2ss); -L1=(R1ss*(V1ss^2))^(1/3); -C1ss=V1ss/(p1-0.5*R1ss); - -C2ss=V2ss/(p2-0.5*R2ss); -L2=(R2ss*(V2ss^2))^(1/3); -C3ss=V3ss/(p3-0.5*R3ss); -L3=(R3ss*(V3ss^2))^(1/3); -R2=(L2^3)/(V2^2); -R1=(L1^3)/(V1^2); -R3=(L3^3)/(V3^2); -s=am_stepfun(t,10,1,12,0); -model.sym.xdot(1)=(1/31)*(((1.29 -(V1/V1ss))/(1.29-1))+s-2*(V1/(C1ss*((R1+R2)*f1+(R2+R3)*f2+R3*f3)))); -model.sym.xdot(2)=(1/163)*(((1.51 -(V2/V2ss))/(1.51-1))- 2*(V2/(C2ss*((R2+R3)*f2+R3*f3)))); -model.sym.xdot(3)=(1/122)*(((1000000 -(V3/V3ss))/(1000000-1))- 2*(V3/(C3ss*(R3*f3)))); -f0=(2/R1)-((R1+R2)*f1 + (R2+R3)*f2 +R3*f3)/R1; -model.sym.xdot(4)=f0-model.sym.xdot(1)-f1; -model.sym.xdot(5)=f1-model.sym.xdot(2)-f2; -model.sym.xdot(6)=f2-model.sym.xdot(3)-f3; - - -model.sym.M=[1 0 0 0 0 0; 0 1 0 0 0 0; 0 0 1 0 0 0; 0 0 0 0 0 0;0 0 0 0 0 0;0 0 0 0 0 0;]; -%% -% INITIAL CONDITIONS -model.sym.x0(1) = V1ss; -model.sym.x0(2)=V2ss; -model.sym.x0(3)=V3ss; -model.sym.x0(4)=1; -model.sym.x0(5)=1; -model.sym.x0(6)=1; -model.sym.dx0 = sym(zeros(size(model.sym.x))); - -% OBSERVALES -model.sym.y = sym(zeros(1,6)); -model.sym.y(1) =V1; -model.sym.y(2)=V2; -model.sym.y(3)=V3; -model.sym.y(4)=f0; -model.sym.y(5)=f1; -model.sym.y(6)=f2; -end diff --git a/matlab/examples/example_dirac/example_dirac.m b/matlab/examples/example_dirac/example_dirac.m deleted file mode 100644 index ec1dc38326..0000000000 --- a/matlab/examples/example_dirac/example_dirac.m +++ /dev/null @@ -1,179 +0,0 @@ -function example_dirac() -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_dirac.m')); -% compile the model -amiwrap('model_dirac','model_dirac_syms',exdir) - -%% -% SIMULATION - -% time vector -t = linspace(0,3,1001); -p = [1;0.5;2;3]; -k = []; - -options = amioption('sensi',0,... - 'maxsteps',1e4); - -options.sens_ind = 1:4; - -options.sens_ind = [3,4,1,2]; - -% load mex into memory -[msg] = which('simulate_model_dirac'); % fix for inaccessability problems -sol = simulate_model_dirac(t,log10(p),k,[],options); - -tic -sol = simulate_model_dirac(t,log10(p),k,[],options); -disp(['Time elapsed with amiwrap: ' num2str(toc) ]) - -%% -% ODE15S - -sig = 1e-2; -delta_num = @(tau) exp(-1/2*(tau/sig).^2)/(sqrt(2*pi)*sig); - -ode_system = @(t,x,p,k) [-p(1)*x(1)+delta_num(t-p(2)); - +p(3)*x(1) - p(4)*x(2)]; - -options_ode45 = odeset('RelTol',options.rtol,'AbsTol',options.atol,'MaxStep',options.maxsteps); - -tic -[~, X_ode45] = ode45(@(t,x) ode_system(t,x,p,k),t,[0;0],options_ode45); -disp(['Time elapsed with ode45: ' num2str(toc) ]) - -%% -% PLOTTING - -if(usejava('jvm')) - figure - c_x = get(gca,'ColorOrder'); - subplot(2,2,1) - for ix = 1:size(sol.x,2) - plot(t,sol.x(:,ix),'.-','Color',c_x(ix,:)) - hold on - plot(t,X_ode45(:,ix),'--','Color',c_x(ix,:)) - end - - legend('x1','x1_{ode45}','x2','x2_{ode15s}','Location','NorthEastOutside') - legend boxoff - xlabel('time t') - ylabel('x') - box on - subplot(2,2,2) - plot(t,abs(sol.x-X_ode45),'--') - set(gca,'YScale','log') - ylim([1e-10,1e0]) - legend('error x1','error x2','Location','NorthEastOutside') - legend boxoff - - subplot(2,2,3) - plot(t,sol.y,'.-','Color',c_x(1,:)) - hold on - plot(t,X_ode45(:,2),'--','Color',c_x(1,:)) - legend('y1','y1_{ode45}','Location','NorthEastOutside') - legend boxoff - xlabel('time t') - ylabel('y') - box on - - subplot(2,2,4) - plot(t,abs(sol.y-X_ode45(:,2)),'--') - set(gca,'YScale','log') - ylim([1e-10,1e0]) - legend('error y1','Location','NorthEastOutside') - legend boxoff - xlabel('time t') - ylabel('y') - box on - set(gcf,'Position',[100 300 1200 500]) -end - - -%% -% FORWARD SENSITIVITY ANALYSIS - -options.sensi = 1; - -sol = simulate_model_dirac(t,log10(p),k,[],options); - -%% -% FINITE DIFFERENCES - -eps = 1e-4; -xi = log10(p); -for ip = 1:4; - xip = xi; - xip(options.sens_ind(ip)) = xip(options.sens_ind(ip)) + eps; - solp = simulate_model_dirac(t,xip,k,[],options); - sx_fd(:,:,ip) = (solp.x - sol.x)/eps; - sy_fd(:,:,ip) = (solp.y - sol.y)/eps; -end - -%% -% PLOTTING -if(usejava('jvm')) - figure - for ip = 1:length(options.sens_ind) - subplot(length(options.sens_ind),2,ip*2-1) - hold on - for ix = 1:size(sol.x,2) - plot(t,sol.sx(:,ix,ip),'.-','Color',c_x(ix,:)) - plot(t,sx_fd(:,ix,ip),'--','Color',c_x(ix,:)) - end - ylim([-2,2]) - legend('x1','x1_{fd}','x2','x2_{fd}','Location','NorthEastOutside') - legend boxoff - title(['state sensitivity for p' num2str(options.sens_ind(ip))]) - xlabel('time t') - ylabel('x') - box on - - subplot(length(options.sens_ind),2,ip*2) - plot(t,abs(sol.sx(:,:,ip)-sx_fd(:,:,ip)),'r--') - legend('error x1','error x2','Location','NorthEastOutside') - legend boxoff - title(['state sensitivity for p' num2str(options.sens_ind(ip))]) - xlabel('time t') - ylabel('error') - ylim([1e-12,1e0]) - set(gca,'YScale','log') - box on - end - set(gcf,'Position',[100 300 1200 500]) - - figure - for ip = 1:length(options.sens_ind) - subplot(length(options.sens_ind),2,ip*2-1) - hold on - for iy = 1:size(sol.y,2) - plot(t,sol.sy(:,iy,ip),'.-','Color',c_x(iy,:)) - plot(t,sy_fd(:,iy,ip),'--','Color',c_x(iy,:)) - end - ylim([-2,2]) - legend('y1','y1_{fd}','Location','NorthEastOutside') - legend boxoff - title(['observable sensitivity for p' num2str(options.sens_ind(ip))]) - xlabel('time t') - ylabel('y') - box on - - subplot(length(options.sens_ind),2,ip*2) - plot(t,abs(sol.sy(:,:,ip)-sy_fd(:,:,ip)),'r--') - legend('error y1','Location','NorthEastOutside') - legend boxoff - title(['observable sensitivity for p' num2str(options.sens_ind(ip))]) - xlabel('time t') - ylabel('error') - ylim([1e-12,1e0]) - set(gca,'YScale','log') - box on - end - set(gcf,'Position',[100 300 1200 500]) - - drawnow -end - -end diff --git a/matlab/examples/example_dirac/model_dirac_syms.m b/matlab/examples/example_dirac/model_dirac_syms.m deleted file mode 100644 index 3c0c9dd80a..0000000000 --- a/matlab/examples/example_dirac/model_dirac_syms.m +++ /dev/null @@ -1,52 +0,0 @@ -function [model] = model_dirac_syms() - -%% -% STATES - -% create state syms -syms x1 x2 - -% create state vector -model.sym.x = [ x1 x2 ]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 p4 - -% create parameter vector -model.sym.p = [p1,p2,p3,p4]; - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1 + dirac(p2-t); -% inhomogeneous -model.sym.xdot(2) = p3*x1 - p4*x2 ; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = 0; -model.sym.x0(2) = 0; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x2; -end diff --git a/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m b/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m deleted file mode 100644 index 60c1d327f9..0000000000 --- a/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m +++ /dev/null @@ -1,85 +0,0 @@ -function example_dirac_adjoint() -%% -% COMPILATION -[exdir,~,~]=fileparts(which('example_dirac_adjoint.m')); -% compile the model -amiwrap('model_dirac_adjoint','model_dirac_adjoint_syms',exdir) - -%% -% SIMULATION - -% time vector -tout = linspace(0,4,9); -tfine = linspace(0,4,10001); -p = [1;0.4;2;3]; -k = []; - -D.Y = [ 0.00714742903826096 - -0.00204966058299775 - 0.382159034587845 - 0.33298932672138 - 0.226111476113441 - 0.147028440865854 - 0.0882468698791813 - 0.0375887796628869 - 0.0373422340295005]; - -D.Sigma_Y = 0.01*ones(size(D.Y)); - - -options.sensi = 1; -options.sensi_meth = 'adjoint'; -options.maxsteps = 1e5; -sol = simulate_model_dirac_adjoint(tout,log10(p),k,D,options); -options.sensi = 0; -solfine = simulate_model_dirac_adjoint(tfine,log10(p),k,[],options); -if(usejava('jvm')) - figure - errorbar(tout,D.Y,D.Sigma_Y) - hold on - plot(tfine,solfine.y) - legend('data','simulation') - xlabel('time t') - ylabel('observable') - title(['log-likelihood: ' num2str(sol.llh) ]) -end - -%% -% FD - -eps = 1e-4; -xi = log10(p); -grad_fd_f = NaN(4,1); -grad_fd_b = NaN(4,1); -for ip = 1:4; - options.sensi = 0; - xip = xi; - xip(ip) = xip(ip) + eps; - solpf = simulate_model_dirac_adjoint(tout,xip,k,D,options); - grad_fd_f(ip,1) = (solpf.llh-sol.llh)/eps; - xip = xi; - xip(ip) = xip(ip) - eps; - solpb = simulate_model_dirac_adjoint(tout,xip,k,D,options); - grad_fd_b(ip,1) = -(solpb.llh-sol.llh)/eps; -end - -if(usejava('jvm')) - figure - plot(abs(grad_fd_f),abs(sol.sllh),'o') - hold on - plot(abs(grad_fd_b),abs(sol.sllh),'o') - set(gca,'XScale','log') - set(gca,'YScale','log') - hold on - axis square - plot([1e2,1e4],[1e2,1e4],'k:') - xlim([1e2,1e4]) - ylim([1e2,1e4]) - legend('forward FD','backward FD','Location','SouthEast') - xlabel('adjoint sensitivity absolute value of gradient element') - ylabel('computed absolute value of gradient element') - set(gcf,'Position',[100 300 1200 500]) - - drawnow -end -end diff --git a/matlab/examples/example_dirac_adjoint/example_model_5_paper.m b/matlab/examples/example_dirac_adjoint/example_model_5_paper.m deleted file mode 100644 index 93aab4b315..0000000000 --- a/matlab/examples/example_dirac_adjoint/example_model_5_paper.m +++ /dev/null @@ -1,243 +0,0 @@ -clear -%% COMPILATION - -[exdir,~,~]=fileparts(which('example_model_5.m')); -cd(exdir) -% compile the model -amiwrap('model_example_5','example_model_5_syms',exdir) - -%% SIMULATION - -% time vector -tout = linspace(0,4,9); -tfine = linspace(0,4,10001); -p = [1;0.5;2;3]; -k = []; - -D.Y = [ 0.00714742903826096 - -0.00204966058299775 - 0.382159034587845 - 0.33298932672138 - 0.226111476113441 - 0.147028440865854 - 0.0882468698791813 - 0.0375887796628869 - 0.0373422340295005]; - -D.Sigma_Y = 0.01*ones(size(D.Y)); - - -options.sensi = 1; -options.sensi_meth = 'adjoint'; -options.maxsteps = 2e4; -sol = simulate_model_example_5(tout,log10(p),k,D,options); -options.sensi = 0; -solfine = simulate_model_example_5(tfine,log10(p),k,[],options); - -figure -errorbar(tout,D.Y,D.Sigma_Y) -hold on -plot(tfine,solfine.y) -legend('data','simulation') -xlabel('time t') -ylabel('observable') -title(['log-likelihood: ' num2str(sol.llh) ]) - -%% SYMBOLIC SOLUTION - -syms x1(t) x2(t) -eqn1 = diff(x1) == -p(1)*x1; -eqn2 = x1(p(2)) == 1; -eqn3 = diff(x2) == p(3)*x1 - p(4)*x2; -eqn4 = x2(p(2)) == 0; - -S = dsolve(eqn1,eqn2,eqn3,eqn4); -xx1 = matlabFunction(S.x1); -xx2 = matlabFunction(S.x2); - -tfine = linspace(0,4,10001); -x1fine = xx1(tfine).*(tfine>=p(2)); -x2fine = xx2(tfine).*(tfine>=p(2)); - -syms xB1(t) xB2(t) -eqn1B = diff(xB1) == p(1)*xB1 - p(3)*xB2; -eqn3B = diff(xB2) == p(4)*xB2; - -syms sigma my -x = sym('x',[2,1]); -J = -0.5*((x(2) - my)/sigma)^2; -dJdx = jacobian(J,x); -m0 = matlabFunction(dJdx); - -mu1fine = zeros(size(x1fine)); -mu2fine = zeros(size(x2fine)); -for it = 1:length(tout); - mm0 = m0(D.Y(it),D.Sigma_Y(it),x2fine(tfine == tout(it))); - eqn2B = xB1(tout(it)) == mm0(1); - eqn4B = xB2(tout(it)) == mm0(2); - S = dsolve(eqn1B,eqn2B,eqn3B,eqn4B); - xxB1 = matlabFunction(S.xB1); - xxB2 = matlabFunction(S.xB2); - mu1fine = mu1fine + xxB1(tfine).*(tfine<=tout(it)); - mu2fine = mu2fine + xxB2(tfine).*(tfine<=tout(it)); -end - -grad(1,1) = trapz(tfine,-x1fine.*mu1fine)*p(1)*log(10); -t_idx = find(tfine == p(2)); -grad(2,1) = -(p(3)*mu2fine(t_idx)-p(1)*mu1fine(t_idx))*p(2)*log(10); -grad(3,1) = trapz(tfine,x1fine.*mu2fine)*p(3)*log(10); -grad(4,1) = trapz(tfine,-x2fine.*mu2fine)*p(4)*log(10); - -%% FD - -eps = 1e-7; -xi = log10(p); -grad_fd_f = NaN(4,1); -grad_fd_b = NaN(4,1); -for ip = 1:4; - options.sensi = 0; - xip = xi; - xip(ip) = xip(ip) + eps; - solpf = simulate_model_example_5(tout,xip,k,D,options); - grad_fd_f(ip,1) = (solpf.llh-sol.llh)/eps; - xip = xi; - xip(ip) = xip(ip) - eps; - solpb = simulate_model_example_5(tout,xip,k,D,options); - grad_fd_b(ip,1) = -(solpb.llh-sol.llh)/eps; -end - -figure -scatter(abs(grad_fd_f),abs(sol.sllh)) -hold on -scatter(abs(grad_fd_b),abs(sol.sllh)) -set(gca,'XScale','log') -set(gca,'YScale','log') -hold on -plot([1e1,1e4],[1e1,1e4],'k:') -xlim([1e1,1e4]) -ylim([1e1,1e4]) -legend('forward FD','backward FD') - -%% figures paper - -fs = 20; -lw = 2; -lwf = 1.5; -cm = lines; - -% system - -figure -plot(tout,D.Y,'x','LineWidth',2*lw,'Color',cm(5,:),'MarkerSize',15) -hold on -plot(tfine,solfine.y,'k','LineWidth',lw) -xlabel('time t') -ylabel('observable') -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -xlim([-0.1,4.1]) -ylim([-0.05,0.4]) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['y']) - -% finite differences -figure -plot(tfine,solfine.y,'k:','LineWidth',lw) -hold on -xip = xi; -xip(1) = xip(1) + 1e-1; -solp = simulate_model_example_5(tfine,xip,k,[],options); -plot(tfine,solp.y,'LineWidth',lw,'Color',cm(1,:)) -xlabel('time') -ylabel('observable') -xlim([-0.1,4.1]) -ylim([-0.05,0.4]) -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['sy_fd']) - - -figure -bar(1:4,grad_fd_b,'LineWidth',lw,'FaceColor',[0.5,0.5,0.5]) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -ylabel('sensitivity J') -xlabel('parameters') -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -ylim([-300,300]) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['sJ_fd']) - -% forward sensitivities -options.sensi = 1; -options.sensi_meth = 'forward'; -options.maxsteps = 1e6; -% load mex into memory -sol = simulate_model_example_5(tfine,log10(p),k,[],options); - -figure -hold on -plot(tfine,sol.sy(:,:,1),'LineWidth',lw,'Color',cm(2,:)) -box on -xlabel('time') -ylabel('obs. sensitivity') -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -ylim([-0.6,0.4]) -xlim([-0.1,4.1]) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['sy_fsa']) - -sol = simulate_model_example_5(tout,log10(p),k,[],options); -sJ = -(squeeze(sol.sy)')*((sol.y-D.Y)./(D.Sigma_Y.^2)); -figure -bar(1:4,sJ,'LineWidth',lw,'FaceColor',[0.5,0.5,0.5]) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -xlabel('parameters') -ylabel('sensitivity J') -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -ylim([-300,300]) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['sJ_fsa']) - -% adjoint sensitivities - -figure -plot(tfine,mu1fine,'LineWidth',lw,'Color',cm(3,:)) -hold on -plot(tfine,mu2fine,'LineWidth',lw,'Color',cm(3,:)) -xlabel('time') -ylabel('adjoint state') -xlim([-0.1,4.1]) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['sy_asa']) - -figure -bar(0,sum(-x1fine.*mu1fine*(4/(length(tfine)-1))*p(1)*log(10)),'LineWidth',lw,'FaceColor',[0.5,0.5,0.5]) -hold on -plot(tfine,fliplr(cumsum(fliplr(-x1fine.*mu1fine*(4/(length(tfine)-1))*p(1)*log(10)))),'LineWidth',lw,'Color',cm(3,:)) -scatter(0,sum(-x1fine.*mu1fine*(4/(length(tfine)-1))*p(1)*log(10)),40,cm(3,:),'o','LineWidth',lw) -xlabel('time') -ylabel('sensitivity J') -set(gca,'FontSize',fs) -set(gca,'LineWidth',lwf) -set(gca,'XTick',[]) -set(gca,'YTick',[]) -ylim([-300,300]) -xlim([-0.1,4.1]) -set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) -print('-depsc','-r300',['sJ_asa']) diff --git a/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m b/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m deleted file mode 100644 index ce2d21f207..0000000000 --- a/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m +++ /dev/null @@ -1,54 +0,0 @@ -function [model] = model_dirac_adjoint_syms() - - -%% -% STATES - -% create state syms -syms x1 x2 - -% create state vector -model.sym.x = [ x1 x2 ]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 p4 - -% create parameter vector -model.sym.p = [p1,p2,p3,p4]; - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1 + dirac(t-p2); -% inhomogeneous -model.sym.xdot(2) = p3*x1 - p4*x2 ; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = 0; -model.sym.x0(2) = 0; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x2; - -end diff --git a/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m b/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m deleted file mode 100644 index 4c4a76c9b1..0000000000 --- a/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m +++ /dev/null @@ -1,58 +0,0 @@ - -% COMPILATION -[exdir,~,~]=fileparts(which('example_dirac_adjoint_hessVecProd.m')); -% compile the model -amiwrap('model_dirac_adjoint_hessVecProd','model_dirac_adjoint_hessVecProd_syms',exdir,2) - -%% -% SIMULATION - -% time vector -tout = linspace(0,4,9); -tfine = linspace(0,4,10001); -p = [1;0.4;2;3]; -k = []; -options.rtol = 1e-13; -options.atol = 1e-13; -options.maxsteps = 1e5; -options.sensi_meth = 'adjoint'; - -D.Y = [ 0.00714742903826096 - -0.00204966058299775 - 0.382159034587845 - 0.33298932672138 - 0.226111476113441 - 0.147028440865854 - 0.0882468698791813 - 0.0375887796628869 - 0.0373422340295005]; -D.Sigma_Y = 0.01*ones(size(D.Y)); - - -% Calculation of gradient for direction -% options.sensi = 1; -% preSol = simulate_model_dirac_adjoint(tout,log10(p),k,D,options); -% v = preSol.sllh; -% vNorm = v / sqrt(sum(v.^2)); -v = [-489.2922358301243; 0; -145.7917085979483; -499.2157643879079]; -vNorm = v / sqrt(sum(v.^2)); - -% Calculation of Hessian vector product -options.sensi = 2; -sol = simulate_model_dirac_adjoint_hessVecProd(tout,log10(p),k,D,options,v); -ASA_HVP = sol.s2llh; -format long; -fprintf('2nd order Adjoints, HVP: \n'); -disp(ASA_HVP); - -% Verification using Finite Differences -delta = 1e-7; -options.sensi = 1; -sol_p = simulate_model_dirac_adjoint_hessVecProd(tout, log10(p) + delta * v, k, D, options); -sol_m = simulate_model_dirac_adjoint_hessVecProd(tout, log10(p) - delta * v, k, D, options); -FD_HVP = (sol_p.sllh - sol_m.sllh)/(2*delta); - -% Output of results - -fprintf('Finite differences, HVP: \n'); -disp(FD_HVP); diff --git a/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m b/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m deleted file mode 100644 index 8f144cb4c9..0000000000 --- a/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m +++ /dev/null @@ -1,54 +0,0 @@ -function [model] = model_dirac_adjoint_hessVecProd_syms() - - -%% -% STATES - -% create state syms -syms x1 x2 - -% create state vector -model.sym.x = [ x1 x2 ]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 p4 - -% create parameter vector -model.sym.p = [p1,p2,p3,p4]; - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1 + dirac(t-p2); -% inhomogeneous -model.sym.xdot(2) = p3*x1 - p4*x2 ; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = 0; -model.sym.x0(2) = 0; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x2; - -end diff --git a/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m b/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m deleted file mode 100644 index fe9392ead6..0000000000 --- a/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m +++ /dev/null @@ -1,94 +0,0 @@ -function example_dirac_secondorder() -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_dirac_secondorder.m')); -% compile the model -amiwrap('model_dirac_secondorder','model_dirac_secondorder_syms',exdir,1) - -%% -% SIMULATION - -% time vector -t = linspace(0,3,1001); -p = [1;0.5;2;3]; -k = []; - -options = amioption('sensi',0,... - 'maxsteps',1e4); -options.sens_ind = [3,1,2]; - -% load mex into memory -[msg] = which('simulate_model_secondorder_dirac'); % fix for inaccessability problems -options.sensi = 2; -sol = simulate_model_dirac_secondorder(t,log10(p),k,[],options); - -%% -% FORWARD SENSITIVITY ANALYSIS - -options.sensi = 2; - -sol = simulate_model_dirac_secondorder(t,log10(p),k,[],options); - -%% -% FINITE DIFFERENCES - -optionsfd = options; -optionsfd.sensi = 1; -optionsfd.sens_ind = 1:4; - -eps = 1e-4; -xi = log10(p); -solfd = simulate_model_dirac_secondorder(t,xi,k,[],optionsfd); -for ip = 1:length(options.sens_ind); - xip = xi; - xip(options.sens_ind(ip)) = xip(options.sens_ind(ip)) + eps; - solp = simulate_model_dirac_secondorder(t,xip,k,[],optionsfd); - s2x_fd(:,:,:,ip) = (solp.sx - solfd.sx)/eps; - s2y_fd(:,:,:,ip) = (solp.sy - solfd.sy)/eps; -end - -%% -% PLOTTING -if(usejava('jvm')) - figure - c_x = get(gca,'ColorOrder'); - for ip = 1:4 - for jp = 1:length(options.sens_ind) - subplot(length(options.sens_ind),4,(jp-1)*4+ip) - hold on - for ix = 1:size(sol.x,2) - plot(t,sol.s2x(:,ix,ip,jp),'.-','Color',c_x(ix,:)) - plot(t,s2x_fd(:,ix,ip,jp),'--','Color',c_x(ix,:)) - end - ylim([-10,10]) - legend('x1','x1_{fd}','x2','x2_{fd}','Location','NorthEastOutside') - legend boxoff - title(['state sensitivity for p' num2str(ip) '-p' num2str(options.sens_ind(jp))]) - xlabel('time t') - ylabel('x') - box on - end - end - set(gcf,'Position',[100 300 1200 500]) - figure - for ip = 1:4 - for jp = 1:length(options.sens_ind) - subplot(length(options.sens_ind),4,(jp-1)*4+ip) - plot(t,abs(sol.s2x(:,:,ip,jp)-s2x_fd(:,:,ip,jp)),'r--') - legend('error x1','error x2','Location','NorthEastOutside') - legend boxoff - title(['state sensitivity for p' num2str(ip) '-p' num2str(options.sens_ind(jp))]) - xlabel('time t') - ylabel('error') - ylim([1e-12,1e0]) - set(gca,'YScale','log') - box on - end - end - set(gcf,'Position',[100 300 1200 500]) - - drawnow -end - -end diff --git a/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m b/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m deleted file mode 100644 index c2db18d77d..0000000000 --- a/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m +++ /dev/null @@ -1,52 +0,0 @@ -function [model] = model_dirac_secondorder_syms() - -%% -% STATES - -% create state syms -syms x1 x2 - -% create state vector -model.sym.x = [ x1 x2 ]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 p4 - -% create parameter vector -model.sym.p = [p1,p2,p3,p4]; - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1 + dirac(t-p2); -% inhomogeneous -model.sym.xdot(2) = p3*x1 - p4*x2; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = 0; -model.sym.x0(2) = 0; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x2; -end diff --git a/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m b/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m deleted file mode 100644 index e7f4c9e3b3..0000000000 --- a/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m +++ /dev/null @@ -1,113 +0,0 @@ -function example_dirac_secondorder_vectmult() -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_dirac_secondorder_vectmult.m')); -% compile the model -amiwrap('model_dirac_secondorder_vectmult','model_dirac_secondorder_vectmult_syms',exdir,2) - -%% -% SIMULATION - -% time vector -t = linspace(0,3,1001); -p = [1;0.5;2;3]; -k = []; -v = [0.7;0.3;1.4;0.1]; - -options = amioption('sensi',0,... - 'maxsteps',1e4); - -% load mex into memory -[msg] = which('model_dirac_secondorder_vectmult'); % fix for inaccessability problems -options.sensi = 2; -sol = simulate_model_dirac_secondorder_vectmult(t,log10(p),k,[],options,v); - -%% -% FORWARD SENSITIVITY ANALYSIS - -options.sensi = 2; - -sol = simulate_model_dirac_secondorder_vectmult(t,log10(p),k,[],options,v); - -%% -% FINITE DIFFERENCES - -options.sensi = 1; - -eps = 1e-4; -xi = log10(p); -for ip = 1:4; - xip = xi; - xip(ip) = xip(ip) + eps; - solp = simulate_model_dirac_secondorder_vectmult(t,xip,k,[],options); - s2x_fd(:,:,ip) = sum(bsxfun(@times,(solp.sx - sol.sx)/eps,permute(v,[3,2,1])),3); - s2y_fd(:,:,ip) = sum(bsxfun(@times,(solp.sy - sol.sy)/eps,permute(v,[3,2,1])),3); -end - -%% -% PLOTTING -if(usejava('jvm')) - figure - c_x = get(gca,'ColorOrder'); - for ip = 1:4 - subplot(4,2,ip*2-1) - hold on - for ix = 1:size(sol.x,2) - plot(t,sol.s2x(:,ix,ip),'.-','Color',c_x(ix,:)) - plot(t,s2x_fd(:,ix,ip),'--','Color',c_x(ix,:)) - end - ylim([-10,10]) - legend('x1','x1_{fd}','x2','x2_{fd}','Location','NorthEastOutside') - legend boxoff - title(['state sensitivity for p' num2str(ip)]) - xlabel('time t') - ylabel('x') - box on - - subplot(4,2,ip*2) - plot(t,abs(sol.s2x(:,:,ip)-s2x_fd(:,:,ip)),'r--') - legend('error x1','error x2','Location','NorthEastOutside') - legend boxoff - title(['state sensitivity for p' num2str(ip)]) - xlabel('time t') - ylabel('error') - ylim([1e-12,1e0]) - set(gca,'YScale','log') - box on - end - set(gcf,'Position',[100 300 1200 500]) - - figure - for ip = 1:4 - subplot(4,2,ip*2-1) - hold on - for iy = 1:size(sol.y,2) - plot(t,sol.s2y(:,iy,ip),'.-','Color',c_x(iy,:)) - plot(t,s2y_fd(:,iy,ip),'--','Color',c_x(iy,:)) - end - ylim([-10,10]) - legend('y1','y1_{fd}','Location','NorthEastOutside') - legend boxoff - title(['observable sensitivity for p' num2str(ip)]) - xlabel('time t') - ylabel('y') - box on - - subplot(4,2,ip*2) - plot(t,abs(sol.s2y(:,:,ip)-s2y_fd(:,:,ip)),'r--') - legend('error y1','Location','NorthEastOutside') - legend boxoff - title(['observable sensitivity for p' num2str(ip)]) - xlabel('time t') - ylabel('error') - ylim([1e-12,1e0]) - set(gca,'YScale','log') - box on - end - set(gcf,'Position',[100 300 1200 500]) - - drawnow -end - -end diff --git a/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m b/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m deleted file mode 100644 index 5f217f4dad..0000000000 --- a/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m +++ /dev/null @@ -1,52 +0,0 @@ -function [model] = model_dirac_secondorder_vectmult_syms() - -%% -% STATES - -% create state syms -syms x1 x2 - -% create state vector -model.sym.x = [ x1 x2 ]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 p4 - -% create parameter vector -model.sym.p = [p1,p2,p3,p4]; - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*x1 + dirac(t-p2); -% inhomogeneous -model.sym.xdot(2) = p3*x1 - p4*x2 ; - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = 0; -model.sym.x0(2) = 0; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = x2; -end diff --git a/matlab/examples/example_events/example_events.m b/matlab/examples/example_events/example_events.m deleted file mode 100644 index 8dfcdbb21e..0000000000 --- a/matlab/examples/example_events/example_events.m +++ /dev/null @@ -1,195 +0,0 @@ -function example_events() -%% -% COMPILATION - -[exdir,~,~]=fileparts(which('example_events.m')); -% compile the model -amiwrap('model_events','model_events_syms',exdir) - -%% -% SIMULATION - -% time vector -t = linspace(0,10,20); -p = [0.5;2;0.5;0.5]; -k = [4,8,10,4]; - -options = amioption('sensi',0,... - 'maxsteps',1e4); -D = amidata(length(t),1,2,2,4); -% load mex into memory -[~] = which('simulate_model_events'); % fix for inaccessability problems -sol = simulate_model_events(t,log10(p),k,D,options); - -tic -sol = simulate_model_events(t,log10(p),k,D,options); -disp(['Time elapsed with cvodes: ' num2str(toc) ]) - -%% -% ODE15S - -ode_system = @(t,x,p,k) [-p(1)*heaviside(t-p(4))*x(1); - +p(2)*x(1)*exp(-0.1*t)-p(3)*x(2); - -1*x(3)+heaviside(t-4)]; -% event_fn = @(t,x) [x(3) - x(2); -% x(3) - x(1)]; -% 'Events',event_fn -options_ode15s = odeset('RelTol',options.rtol,'AbsTol',options.atol,'MaxStep',options.maxsteps); - -tic -[~, X_ode15s] = ode15s(@(t,x) ode_system(t,x,p,k),t,k(1:3),options_ode15s); -disp(['Time elapsed with ode15s: ' num2str(toc) ]) - -%% -% PLOTTING -if(usejava('jvm')) - figure - c_x = get(gca,'ColorOrder'); - subplot(2,2,1) - for ix = 1:size(sol.x,2) - plot(t,sol.x(:,ix),'.-','Color',c_x(ix,:)) - hold on - plot(t,X_ode15s(:,ix),'d','Color',c_x(ix,:)) - end - stem(sol.z(:,1),sol.z(:,1)*0+10,'r') - stem(sol.z(:,2),sol.z(:,2)*0+10,'k') - legend('x1','x1_{ode15s}','x2','x2_{ode15s}','x3','x3_{ode15s}','x3x2','x3>x1','x3>x2 fd','x3>x1 fd','Location','NorthEastOutside') - legend boxoff - title(['event sensitivity for p' num2str(ip)]) - xlabel('event #') - ylabel('sz') - box on - - subplot(4,2,2*ip) - bar(1:D.ne,sol.sz(1:D.ne,:,ip)-sz_fd(1:D.ne,:,ip),0.8) - hold on - legend('error x3>x2','error x3>x1','Location','NorthEastOutside') - legend boxoff - title(['error event sensitivity for p' num2str(ip)]) - xlabel('event #') - ylabel('sz') - box on - end - set(gcf,'Position',[100 300 1200 500]) - - drawnow -end -end diff --git a/matlab/examples/example_events/model_events_syms.m b/matlab/examples/example_events/model_events_syms.m deleted file mode 100644 index 7bf9ac6d32..0000000000 --- a/matlab/examples/example_events/model_events_syms.m +++ /dev/null @@ -1,82 +0,0 @@ -function [model] = model_events_syms() - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% STATES - - -% create state syms -syms x1 x2 x3 - -% create state vector -model.sym.x = [ -x1 x2 x3 -]; - -%% -% PARAMETERS ( for these sensitivities will be computed ) - -% create parameter syms -syms p1 p2 p3 p4 - -% create parameter vector -model.sym.p = [p1,p2,p3,p4]; - -% set the parametrisation of the problem options are 'log', 'log10' and -% 'lin' (default). -model.param = 'log10'; - -%% -% CONSTANTS ( for these no sensitivities will be computed ) -% this part is optional and can be ommited - -% create parameter syms -syms k1 k2 k3 k4 - -% create parameter vector -model.sym.k = [k1 k2 k3 k4]; - -%% -% SYSTEM EQUATIONS - -% create symbolic variable for time -syms t - -model.sym.xdot = sym(zeros(size(model.sym.x))); - -% piecewise defined function -model.sym.xdot(1) = -p1*heaviside(t-p4)*x1; -% inhomogeneous -model.sym.xdot(2) = +p2*x1*exp(-0.1*t)-p3*x2 ; -model.sym.xdot(3) = -1*x3+heaviside(t-4); - -%% -% INITIAL CONDITIONS - -model.sym.x0 = sym(zeros(size(model.sym.x))); - -model.sym.x0(1) = k1; -model.sym.x0(2) = k2; -model.sym.x0(3) = k3; - -%% -% OBSERVALES - -model.sym.y = sym(zeros(1,1)); - -model.sym.y(1) = p4 * (x1+x2+x3); - - -%% -% EVENTS -% this part is optional and can be ommited -syms t - -% events fire when there is a -zero crossing of the root function -model.event(1) = amievent(am_ge(x2,x3),0,t); -model.event(2) = amievent(am_ge(x1,x3),0,t); - -end diff --git a/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m b/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m deleted file mode 100644 index 45a9002629..0000000000 --- a/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m +++ /dev/null @@ -1,117 +0,0 @@ -function example_jakstat_adjoint() - - % compile the model - [exdir,~,~]=fileparts(which('example_jakstat_adjoint.m')); - amiwrap('model_jakstat_adjoint','model_jakstat_adjoint_syms',exdir,1) - - num = xlsread(fullfile(exdir,'pnas_data_original.xls')); - - D.t = num(:,1); - D.condition= [1.4,0.45]; - D.Y = num(:,[2,4,6]); - D.Sigma_Y = NaN(size(D.Y)); - D = amidata(D); - - xi = [0.60 - 3 - -0.95 - -0.0075 - 0 - -2.8 - -0.26 - -0.075 - -0.41 - -5 - -0.74 - -0.64 - -0.11 - 0.027 - -0.5 - 0 - -0.5]; - - options.sensi = 0; - sol = simulate_model_jakstat_adjoint([],xi,[],D,options); - - if(usejava('jvm')) - figure - for iy = 1:3 - subplot(2,2,iy) - plot(D.t,D.Y(:,iy),'rx') - hold on - plot(sol.t,sol.y(:,iy),'.-') - xlim([0,60]) - xlabel('t') - switch(iy) - case 1 - ylabel('pStat') - case 2 - ylabel('tStat') - case 3 - ylabel('pEpoR') - end - ylim([0,1.2]) - end - set(gcf,'Position',[100 300 1200 500]) - end - - % generate new - xi_rand = xi + 0.1; - options.sensi = 2; - options.sensi_meth = 'adjoint'; - sol = simulate_model_jakstat_adjoint([],xi_rand,[],D,options); - options.sensi_meth = 'forward'; - solf = simulate_model_jakstat_adjoint([],xi_rand,[],D,options); - - options.sensi = 1; - eps = 1e-4; - fd_grad = NaN(length(xi),1); - for ip = 1:length(xi) - xip = xi_rand; - xip(ip) = xip(ip) + eps; - psol = simulate_model_jakstat_adjoint([],xip,[],D,options); - fd_grad(ip) = (psol.llh-sol.llh)/eps; - fd_hess(:,ip) = (psol.sllh-sol.sllh)/eps; - end - - if(usejava('jvm')) - figure - subplot(1,2,1) - plot(abs(solf.sllh),abs(fd_grad),'rx') - hold on - plot(abs(solf.sllh),abs(sol.sllh),'bo') - hold on - set(gca,'YScale','log') - set(gca,'XScale','log') - ylim([1e-2,1e2]) - xlim([1e-2,1e2]) - plot([1e-2,1e2],[1e-2,1e2],'k:') - legend('finite differences','adjoint sensitivities','Location','best') - box on - axis square - xlabel('absolute value forward sensitivity gradient entries') - ylabel('absolute value gradient entries') - - subplot(1,2,2) - plot(abs(solf.s2llh(:)),abs(fd_hess(:)),'rx') - hold on - plot(abs(solf.s2llh(:)),abs(sol.s2llh(:)),'bo') - hold on - set(gca,'YScale','log') - set(gca,'XScale','log') - ylim([1e-5,1e3]) - xlim([1e-5,1e3]) - plot([1e-5,1e3],[1e-5,1e3],'k:') - legend('finite differences','adjoint sensitivities','Location','best') - box on - axis square - xlabel('absolute value forward sensitivity hessian entries') - ylabel('absolute value hessian entries') - - set(gcf,'Position',[100 300 1200 500]) - end - - - drawnow - -end diff --git a/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m b/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m deleted file mode 100644 index a1e937e162..0000000000 --- a/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m +++ /dev/null @@ -1,69 +0,0 @@ -function [model] = model_jakstat_syms() - - %% - % STATES - - syms STAT pSTAT pSTAT_pSTAT npSTAT_npSTAT nSTAT1 nSTAT2 nSTAT3 nSTAT4 nSTAT5 - - model.sym.x = [ - STAT, pSTAT, pSTAT_pSTAT, npSTAT_npSTAT, nSTAT1, nSTAT2, nSTAT3, nSTAT4, nSTAT5 ... - ]; - - %% - % PARAMETERS - - syms p1 p2 p3 p4 init_STAT Omega_cyt Omega_nuc sp1 sp2 sp3 sp4 sp5 offset_tSTAT offset_pSTAT scale_tSTAT scale_pSTAT sigma_pSTAT sigma_tSTAT sigma_pEpoR - - model.sym.p = [p1,p2,p3,p4,init_STAT,sp1,sp2,sp3,sp4,sp5,offset_tSTAT,offset_pSTAT,scale_tSTAT,scale_pSTAT,sigma_pSTAT,sigma_tSTAT,sigma_pEpoR]; - - model.param = 'log10'; - - model.sym.k = [Omega_cyt,Omega_nuc]; - - %% - % INPUT - syms t - % u(1) = spline_pos5(t, 0.0, sp1, 5.0, sp2, 10.0, sp3, 20.0, sp4, 60.0, sp5, 0, 0.0); - u(1) = am_spline_pos(t, 5, 0.0, sp1, 5.0, sp2, 10.0, sp3, 20.0, sp4, 60.0, sp5, 0, 0.0); - - %% - % SYSTEM EQUATIONS - - model.sym.xdot = sym(zeros(size(model.sym.x))); - - model.sym.xdot(1) = (Omega_nuc*p4*nSTAT5 - Omega_cyt*STAT*p1*u(1))/Omega_cyt; - model.sym.xdot(2) = STAT*p1*u(1) - 2*p2*pSTAT^2; - model.sym.xdot(3) = p2*pSTAT^2 - p3*pSTAT_pSTAT; - model.sym.xdot(4) = -(Omega_nuc*p4*npSTAT_npSTAT - Omega_cyt*p3*pSTAT_pSTAT)/Omega_nuc; - model.sym.xdot(5) = -p4*(nSTAT1 - 2*npSTAT_npSTAT); - model.sym.xdot(6) = p4*(nSTAT1 - nSTAT2); - model.sym.xdot(7) = p4*(nSTAT2 - nSTAT3); - model.sym.xdot(8) = p4*(nSTAT3 - nSTAT4); - model.sym.xdot(9) = p4*(nSTAT4 - nSTAT5); - - %% - % INITIAL CONDITIONS - - model.sym.x0 = sym(zeros(size(model.sym.x))); - - model.sym.x0(1) = init_STAT; - - %% - % OBSERVABLES - - model.sym.y = sym(zeros(3,1)); - - model.sym.y(1) = offset_pSTAT + scale_pSTAT/init_STAT*(pSTAT + 2*pSTAT_pSTAT); - model.sym.y(2) = offset_tSTAT + scale_tSTAT/init_STAT*(STAT + pSTAT + 2*(pSTAT_pSTAT)); - model.sym.y(3) = u(1); - - %% - % SIGMA - - model.sym.sigma_y = sym(size(model.sym.y)); - - model.sym.sigma_y(1) = sigma_pSTAT; - model.sym.sigma_y(2) = sigma_tSTAT; - model.sym.sigma_y(3) = sigma_pEpoR; - -end diff --git a/matlab/examples/example_jakstat_adjoint/pnas_data_original.xls b/matlab/examples/example_jakstat_adjoint/pnas_data_original.xls deleted file mode 100644 index 012e624cd199c9900402711d95c474551d780da9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21504 zcmeHv2UHZ<^7ow~4aq^03W@_F269k9MMOk#MMcDjN*sbB-~fhQ5zKJ~!33g$Syaqf zS23-w=pu@!h+_G>L_vjZ)DDYJ5__g&i|W(4G|-*G#do*ICv)HG(D5P%nx%u zGDrAz<>fM#4czu(*vJTyNCad8iGbfk`2AN8L&9cZ+=_K*N_m8K^kXCcV%XO5(n1_Dui|feCz|U{h$siSf#R#i0(84D{{Q7#khh=?hlsV( zV);^y;-JN#cFxe7B8T=5I9SA+oIs;NQY?c-+Kj2`2 z_m%n9LfsX5=oW<4j@ZE<+m1LvbLJ5%vXf^;y2G_S(ByM0)V*LD&{|os5E4ZAB#Mkx zA;25v5N#v{H~_XPblH3h_5Kj}R;ooP(?nj_B+{95C9F`Wac8iAqFQ4fV-wXJVi{Jz zZzW+*raYc|5jF@-VV5M+|bVeU}I-`#~U77rURi7Llwj+7@%IJrb z@GH~jAmzt(Cv8W|n>Rnz_EcLUiXU8yis`F#SLurBEa-439S2K#h}(N7g>+ocO3PvF zfNNYzzX4M$vKN-Gj2v9|Qo2mPfoow(Z;10Esz0uwDLub=b5mVY(36R0TsKSeGk(PJ zo6=?U$2GPz|1K)uKunj>2iM}1{{{&pB%?upT%R}6aVgK#3)k!-I!}_nfi5H8xRE~$ zE@4?OwzC~0M@D}Zj9FB^3?26fC|yQA?i*10^5|&B?}vHQImTo<21LWrlYH^~bTEzM zL(-(^XcaZspD?CtG3@JM45kBYGnfIf)oBJl8W)Q=3&LSWUI?172}01r%7vi4n;-woc_j(6>JnQW~JzJGiOT1R<)u1I?l-LR5PP zx3!v}iE8iQzE~54sP+zStTjQ1YVY9AT%!=UQPg%^TvdAqxAvMKM74KtZ?FkMRC@kNwRd<;6QbHXmQ53)+B;TF5yEWWDsO^^-dQFy?sNnpT6EmOb%D3Hw@_}}!HoCs-wVZt)q=4|%#15eT)uE5Xw3R5 z6s84@e9Y>q7iI57bpe@5I6qYs!5eMDAefxxB zA>yE;G^`?2M;vsNhH8W3#X;9V1INo(z!?NIRv@3Rqyoa1`B(vpEic4XS_si>s6fLs z4y_>komxR8MAr6hz`@ua{e(%<_AGhSE|g6?d6X@{mh`xC)w?!Dh|pUUN=oYQ-V`AX zL$Q+jGguO?leKz}22|=HV3)Kypvv}AUQmGPI;q9s zPO5-7iZ^fG+<+>MV(e$d!7A1cY2siNM={bMSn2kZIEs-b?xk|rM(`XHR%R|vy!JeM zp3j7z`3Wh`*_^>78wU^3!BV#nHX|;uX5hhNZyt!_5g&M5^)qaP;zJuI^jYwTR7;&7 zefP;bp$1;FXmMeCT!0`_b2%gzc_b^9NG>2!4YsmCx&}Y`{oN|XNQQDquJTAWDv_9$ zNRc%8(J$ZKQ;cLJhvX)Y)LA7GHdj%dwD{4F@4r%vWGsi&MIOmPB@)xlX`MLy=sSPc zDn>GqL+UDzFGO)k<8?f z6vUpRN+ikHD-Ae&@0nsGb2%ghvFEB1Niz0I1HM)qP>j?Xk-GjM_IQBQwMnt(;<{pg zC4&U*86QSB^m}dOkQBt8wMrz(*mH5cvvRp&Bnvqt1+mvrC6Z+9xwxLIT%#DNtsIhq z*t1iKBpG`yu6ql9SB%7yLsAfXPAZWkW6#Ak`^sI#NS1O)3S!SqC6Z+9xww8lm8l3x z51wSf`mSNH^^qBD+X%Ik4zGj^o_SKS&~}YNA--gVTFMBOj40U33>>IfsCA=I2qjse z?PY{Y1`i=?j*n6-w0)ych#Fa;on(Yc#tCfHMr}|m)TU7=1c;9PMN@A#lY67BEZRais3FrS5XrTzEnY4+MJi1^md^gw%wXY)-n2 zKBht)(i^l8K#C->FyTvu6r)rtoIXowg{!DWt(ZHpR?MAPE9Nc-cB?yQs0p(<*3znI zsDWwvfEqu;s|fglQamU@2=d!zXac{T48NvOSB9TQLZCMvD=Mc44~(H9`o$$Ec7-wE z`2iMISKbx4Qg3v{{@27jp3jL>n!Xk7{iP2G{IOVu=x*93;YBV;vWn62~m6jFPJ|yC7aVv8jsyYXh88M zfSn&x_b0$h4;TnM5)8jnVdjquEwK&Id!jLqLmYA4*)J8udP<9BnF-ZMKZqeL%uIVR zba7oT7KDp`ksv+r7I?@%E@28EZ4xUjjI9G6@~0k}0va(E(P(HaxB(z;8@PCHj6&v4 z9EHrCI0~7&9JU^;YXEx?KQ%=V9xvDWS*-zk5TIgJ1fpnctPrmA#TCkj%>Yq_)QL-m zhJw)60AYb3Cb3~rg)y4L)`u4;U?ny*GBzGsD45e%+5#+dh>D@KVa9_l*g&NXi(3T^ zdcb}{gB~PnU6hB9OZ*ko=5G%}@mp zGh`|OVkVVr3gjURBtLYj8L0rmVN~8LurN0vp4cG_=L;qzd`*f@;qylGwT?5RXW>fr4F-I_Z|7uAmX9Ln9ag zeGPhCJjgKpd-n`21^v4g^zYLA(*3&;=>dU3MuZCzBs)5pCdAf-DcuMf>j_W;)twx4 zRpKUOLcGv!)L6W)5YtJ5uhIO3Y|dgCyR`tjHCl)Vmcvn90QM8Wbu`749OH4vhXw># zvtc12?#Jl%jno*g2^~b^h_4Cz=8|_yIU~tZiM^aG5ZWC6f}z3@&{X-*d!)dtzj!qe zQWUf`d?nZux}!KU89F3O-j(I7rLm#kd5 zk^lp7%>@E9MOR3gm`eldZpl(>m};7A%8 z>=PUso~qY)8Je6D1vx2=IpRxEPLf|zVi1%O!mN)&_S-R^HLT>AHCrix2{0sZfSS57 zAR^h4ta+9+_$yqs26c?!H^etFAqBPyLxZO!@sr0oOr03NbJ^wbXPpfE>gIl*U3Tb} z;8@4ax2|r;9NTvB2Q|%U@r!S@vmDs_eO;UG4$BSMa}(6dOHbC7ZX46~g3oJbo2T2$ zcbq8Gx%BzMkP*-SyfR?&?qLxHnwo=0K5u7pvUvCIKdv~31#Q?8{Cu5fkCr*{ne8^+ z)w14xpy$%lf?wBahaD~}JZitFd!(1MVN8!{&yGz$V>(%F$J5z8*8BG^W9$kiv?r&w0oX}{T*_slG~twFth-v>^1z7PMb zHSzDexSs#S_1bR}*Pg#nRo(IB#h6FuUkxTZgbTl4b*sI9GZaiAg!zoLu{Ph_`t&s9 zbOo2wjcMdT$Mje$^D6U~ zRfB)m)H%+Xv*}IUcCYWni|ZdOf66n>E7Cms(AWEN?1RZ(r_yI#n!9OhdrrvIqXBJkSA9#@>rH~SxQT4BgtaC`8HpyJ+9*De{9|KwNY7Ga&fb=joQ%C@W5wW{eF zaQ&lcP^EiVapty5!u@BRO4ki8N#AYvm!Eoq`|^Oe#}S3w93KrmnYE2ybi<9 z-=He;6`e3`rRi*_;37!)5IrT9KauY!aUK|+5FW!9V0h@24%9izGwAgot#C@#ZTHCI z9sMHqT`oTUiTLK5`TSl`zP_sRuSc`puiiJVSTjI>lwH1-{v!SrhXse+^Q;4}Ld!@_cH`O9y^6y5Ij|@{qK46wa|CZc)1w^-}4fD;8gxX9MoKfvpqX#EeT{Jn>;@OmzZ~q>7;QL6! z4R&^;ekwm_ezttetw#%5FEiqLzAC+VD9~=tMGIZyj>qhO?o<}S^{XPsdpSPe+W)xU zagFyTpQGn(@-Q7{yZ!L%L!U~l<|p10)~#{(y(g^c9R6$;r~kBWZ@>Pc&t2wLV>xi+ z%(`pw`zk+X1$*c_KkC$Ks;$*YuEq4a?sbzw&s$q^o^0KhsWonA@Xh=8SF!XWr)9=i z&yCS_Fx*))yw8`y)2mLqhkw!fwQr1fiQC7(MMhy?;`bJ?Zg;oI?z_FNpr&Uu&uUl@ zS3Mx0OX9--=K~Lp?(X#Xr=_cVwOFm+!@&6c1=}GBr>uUQ4 zOw0|*b$3p$KQX=c)EQw>i(<#=eC)96-h)Y(?i)BA?C)-o*)Q_c;}}koLB{6ct2+OA zbJI(%?m82zUyfe-B+uN?ud^0w8^rdWXEJcvXnxd8HUCG=2R)e;VNdF`i+k+#oq4xq`MxYY z^(!|{e;GHg(E8@rd%t~uT~!%caCmp<$!Ya}tyz^mwf1&Q;<)ek-V1Dco{0-y`_ISS z>F?`8XDs^u>HO~BzJHDR_+j5q!l(DYJsX_P?ICWh6_1EV3f3Q5-C#DChYDwgnE0@Ncs9G{=$#z$@-%lT(_i^O;W)J4umcDp4 z?_BD=WLC|Pe*YxZpShN5ws`Y~F4^nPE%cpw#?xFd7s?gWv}XHubUmwq(6gP-OM*%e&3+Dx6u9?P&aY z^#;o=2OV;+9nJB7**`OuJ!w?MZN0+D)05w~*7w}8|MTiGN0tW~tgh(j-L7=fBi|Fj z9@`^5&uZ`J_O;XSNo%vOuizA=L~iGmHxF<9tFINmIzNW}YM1&qZOs!!`d_p|+B<9x z{BUCF)1jr)R=i)mJ~JXeD)qq7ybHtb{QkamknzT$#V;-nJ`!U2+2Ucw^<58U3|eUP z_nMhC?aVwo?=tG+J!($ll4hqgvMAywu*;(>1G>uoFVGr|j{1yELr% z4%>D8Do_7xUVYeLL*_ddul9i^YX;nC)BC>VfjaNZva0d*pC(PyKc7*4%yRF^L(fm- z?E1BCmP^-Rwtd(Abp4kBBQwru>%Ev3UD^MSUq_mr-CP`HP*di|UgPPX^f9gIVcZhe zXH{u~T^B}O?Uvo~k>RJs(MMJu-ZKAQ=xli1TB}xVDG7T%@L9aU2j7~% zn!WmRF!t7)u)e)tPch$UWoM&vKVX4i$1Y3fW()O~xsG|aB{5>erm}fycY3U8_vXo! z@(DEu)~s-f+v}A+IYm$s_Bv|y)RTE5NAI#=7vt?2hX4Uyz~8ow-EzMJox$A+n4TLGEwi!#vZ|T71=w#YrS4M z=unk&fo8zu>l<#*HLlN$V--&J9bl?!xNZC%+je)ReCpb-_iw>{0v^3JymC7zKb+(J zPjHn{khYfTmV%G^`NeUGYx+4Hdg4FS>$*$ImKJm&Zi!D3D+iklVJ4b!)UwzKc${L!{J}1rHskV0W@B5-W z+vN-~N##dZtjp0o6K$r}-E6j(v2T&N+okZ+Wn=&JuIstGVA`f%mXAN_=|9|I?IF$Y z)xR#bk9K-7+Oe?q@Z0q55l4^h9^u{net_f6i$#{7OO6c~Z!+wG-Hemt9A0=0yTxY} z?>uO_x2s*x*cN~I7`xlgE7`aHQcY{l^YlI6hctJJC?Dy#&2@(6twl8-XMb!vYv;Y| z+caBkVwI1(Qut=??5??PwlD9TsJ%AM*7RjaLhyj2mHE56RJFhN=Gm)#3kByr4^CZk z^{Usaf9@=unA)Y!r=J&|Dcf*s{_4}K-N#l;88ZDlo1Lb&_4|P6`-3j&{Tp+5(eVg*Y}38i)au&c`iPmudgpctGG=#q^;03yOp7%PJMe7b;!Y(&ojsnpTz{ug zu;@~CN4w&rF=I4F#7*k-zQ>$%`!DJ^%$(rwWyHT***|7s$(ORx3%W=Ree@h#aN#y8K0*;%eR%$hQ9fe-g)S2dmJ6%*0N&h$IuB=cJ%B# zx*+jjX1874I!t^%c~aPqvklb#f5d_e2omxln)Avs~j;eDEyDf zg>xPsXp?sPQ||LXm#h=V3R2$LX|$Q@-6QXS`TSkcQ{KGYQ+;S+Jyv`(q-a9HsbsCRTQ5Ey^LsUULigR>&(^f@IQUN&O&??H(|KK!ubwp_t*1K| ztSdRC@yMVin8{@OOLnx*eK$9{O#Vr$Z@hf}!6-fGNraSv;^ywG!mPwm5qTp!mK zYW}V6&Q2O|+;gUf`CiYR`#v-aozQdpk+YR+yt~)$uD#agx4d=So`!cOjXHIiD>D6N8q`QnyCOXz9+J8+BdJ`ybeiQZNhCzm~QB5agJ{9Ib=QER30Y(y5Ka zTud|-9jJ50$#_oPtSOwt#Ouclmf2JIZO-E^`dz)PhrD{NUu?&5m>>RIZrt%z zYcCl^y4-V)Ei&VDd%j{)UdhBGua@=-G99$=cK4_~VY8Mdy1zDwSvH{KKpX3zjtyr@%zf_P`D+BX{`@eaZ zEsSw&YdZG(5TE4Rb2U0{xKLWtZcR~u`X3?3j-9*TP<0mD9^UU!wlD!Ui9^9MC2)YW z0NKfUV`+P^r^M7~*UjOKAIx*e*X{4h2Sx;Xhf;cHO1GuwS(K7N*PD#Y_OMSx;PcLzkW&I{QEOO- z5*uF|t6*6FCi4dIr|^OjCx$231@U82B%5Lp_!N0tUV#Bwx@DOo=3%4BJ{ z)gVj5w@+nh_yvnB4cFPSG_<)a4Zm8ErJ=24X=q1T8d^|8!dBn#(HR`bO9*qvf;+S+ zbH}C)KzyeLcW6f@PaW>ig3O%;+@bAez&<;cM?Z`2upHV@4ZO;NGF)QrSZ%F2^Po3F z!Ll8?wym7EXqh*b$C6yC8|GsJ1Cxuc(pD2P*4KV$rfl#L<^;I7S42Ua1#?GVKk?|9h=Mpwhf^S;AWni?#^GMGo>3gMI2YP6Qy+YA z#3k-KdX5O$`&yLGSjwyy>Y)MN0sFB}o5K$20Ejo1JONxrE zi3>02F*x|Nm`lo{E1r(GWo)5EanLJeRy(K+En1fq`~{I??>-b!5XW}D>Fuj^j9;{< z|8u&r2F}rcPI0h>a!EH44u|4=Mn~o_I2^h9aHMe1CLC%r7BQeUYz(85)Egc{-cm-1G*H0LET1W=u#Y;RCXT6{E0}0WIc2#MpnT;&QFRN4D8*w zuvN}r=ur$$zkY&xE`xysF!<`gMbL|hEj=j=jA%Vtmyw>7iRuAw7CzFU%zyL^#;fLCfiueL3C#z9^sZiz)RhZpm}W_Ab~Mnb^dBqS0^ITDfGf?zj&o zn{sJ+?4`Nn50QN2DYqaLr<8sNd{(5ZN`CCLK5QFxRU@PpcrTa(hvAHCv zpNIX_Yx8Ld93i;my$AzGS1wt9rRdL!;bj!VfMP6fsXn}!{XP)|F`&y`m0k_nHLnE%7#xP+9%_G>L_vjZ)DDYJ5__g&i|W(4G|-*G#do*ICv)HG(D5P%nx%u zGDrAz<>fM#4czu(*vJTyNCad8iGbfk`2AN8L&9cZ+=_K*N_m8K^kXCcV%XO5(n1_Dui|feCz|U{h$siSf#R#i0(84D{{Q7#khh=?hlsV( zV);^y;-JN#cFxe7B8T=5I9SA+oIs;NQY?c-+Kj2`2 z_m%n9LfsX5=oW<4j@ZE<+m1LvbLJ5%vXf^;y2G_S(ByM0)V*LD&{|os5E4ZAB#Mkx zA;25v5N#v{H~_XPblH3h_5Kj}R;ooP(?nj_B+{95C9F`Wac8iAqFQ4fV-wXJVi{Jz zZzW+*raYc|5jF@-VV5M+|bVeU}I-`#~U77rURi7Llwj+7@%IJrb z@GH~jAmzt(Cv8W|n>Rnz_EcLUiXU8yis`F#SLurBEa-439S2K#h}(N7g>+ocO3PvF zfNNYzzX4M$vKN-Gj2v9|Qo2mPfoow(Z;10Esz0uwDLub=b5mVY(36R0TsKSeGk(PJ zo6=?U$2GPz|1K)uKunj>2iM}1{{{&pB%?upT%R}6aVgK#3)k!-I!}_nfi5H8xRE~$ zE@4?OwzC~0M@D}Zj9FB^3?26fC|yQA?i*10^5|&B?}vHQImTo<21LWrlYH^~bTEzM zL(-(^XcaZspD?CtG3@JM45kBYGnfIf)oBJl8W)Q=3&LSWUI?172}01r%7vi4n;-woc_j(6>JnQW~JzJGiOT1R<)u1I?l-LR5PP zx3!v}iE8iQzE~54sP+zStTjQ1YVY9AT%!=UQPg%^TvdAqxAvMKM74KtZ?FkMRC@kNwRd<;6QbHXmQ53)+B;TF5yEWWDsO^^-dQFy?sNnpT6EmOb%D3Hw@_}}!HoCs-wVZt)q=4|%#15eT)uE5Xw3R5 z6s84@e9Y>q7iI57bpe@5I6qYs!5eMDAefxxB zA>yE;G^`?2M;vsNhH8W3#X;9V1INo(z!?NIRv@3Rqyoa1`B(vpEic4XS_si>s6fLs z4y_>komxR8MAr6hz`@ua{e(%<_AGhSE|g6?d6X@{mh`xC)w?!Dh|pUUN=oYQ-V`AX zL$Q+jGguO?leKz}22|=HV3)Kypvv}AUQmGPI;q9s zPO5-7iZ^fG+<+>MV(e$d!7A1cY2siNM={bMSn2kZIEs-b?xk|rM(`XHR%R|vy!JeM zp3j7z`3Wh`*_^>78wU^3!BV#nHX|;uX5hhNZyt!_5g&M5^)qaP;zJuI^jYwTR7;&7 zefP;bp$1;FXmMeCT!0`_b2%gzc_b^9NG>2!4YsmCx&}Y`{oN|XNQQDquJTAWDv_9$ zNRc%8(J$ZKQ;cLJhvX)Y)LA7GHdj%dwD{4F@4r%vWGsi&MIOmPB@)xlX`MLy=sSPc zDn>GqL+UDzFGO)k<8?f z6vUpRN+ikHD-Ae&@0nsGb2%ghvFEB1Niz0I1HM)qP>j?Xk-GjM_IQBQwMnt(;<{pg zC4&U*86QSB^m}dOkQBt8wMrz(*mH5cvvRp&Bnvqt1+mvrC6Z+9xwxLIT%#DNtsIhq z*t1iKBpG`yu6ql9SB%7yLsAfXPAZWkW6#Ak`^sI#NS1O)3S!SqC6Z+9xww8lm8l3x z51wSf`mSNH^^qBD+X%Ik4zGj^o_SKS&~}YNA--gVTFMBOj40U33>>IfsCA=I2qjse z?PY{Y1`i=?j*n6-w0)ych#Fa;on(Yc#tCfHMr}|m)TU7=1c;9PMN@A#lY67BEZRais3FrS5XrTzEnY4+MJi1^md^gw%wXY)-n2 zKBht)(i^l8K#C->FyTvu6r)rtoIXowg{!DWt(ZHpR?MAPE9Nc-cB?yQs0p(<*3znI zsDWwvfEqu;s|fglQamU@2=d!zXac{T48NvOSB9TQLZCMvD=Mc44~(H9`o$$Ec7-wE z`2iMISKbx4Qg3v{{@27jp3jL>n!Xk7{iP2G{IOVu=x*93;YBV;vWn62~m6jFPJ|yC7aVv8jsyYXh88M zfSn&x_b0$h4;TnM5)8jnVdjquEwK&Id!jLqLmYA4*)J8udP<9BnF-ZMKZqeL%uIVR zba7oT7KDp`ksv+r7I?@%E@28EZ4xUjjI9G6@~0k}0va(E(P(HaxB(z;8@PCHj6&v4 z9EHrCI0~7&9JU^;YXEx?KQ%=V9xvDWS*-zk5TIgJ1fpnctPrmA#TCkj%>Yq_)QL-m zhJw)60AYb3Cb3~rg)y4L)`u4;U?ny*GBzGsD45e%+5#+dh>D@KVa9_l*g&NXi(3T^ zdcb}{gB~PnU6hB9OZ*ko=5G%}@mp zGh`|OVkVVr3gjURBtLYj8L0rmVN~8LurN0vp4cG_=L;qzd`*f@;qylGwT?5RXW>fr4F-I_Z|7uAmX9Ln9ag zeGPhCJjgKpd-n`21^v4g^zYLA(*3&;=>dU3MuZCzBs)5pCdAf-DcuMf>j_W;)twx4 zRpKUOLcGv!)L6W)5YtJ5uhIO3Y|dgCyR`tjHCl)Vmcvn90QM8Wbu`749OH4vhXw># zvtc12?#Jl%jno*g2^~b^h_4Cz=8|_yIU~tZiM^aG5ZWC6f}z3@&{X-*d!)dtzj!qe zQWUf`d?nZux}!KU89F3O-j(I7rLm#kd5 zk^lp7%>@E9MOR3gm`eldZpl(>m};7A%8 z>=PUso~qY)8Je6D1vx2=IpRxEPLf|zVi1%O!mN)&_S-R^HLT>AHCrix2{0sZfSS57 zAR^h4ta+9+_$yqs26c?!H^etFAqBPyLxZO!@sr0oOr03NbJ^wbXPpfE>gIl*U3Tb} z;8@4ax2|r;9NTvB2Q|%U@r!S@vmDs_eO;UG4$BSMa}(6dOHbC7ZX46~g3oJbo2T2$ zcbq8Gx%BzMkP*-SyfR?&?qLxHnwo=0K5u7pvUvCIKdv~31#Q?8{Cu5fkCr*{ne8^+ z)w14xpy$%lf?wBahaD~}JZitFd!(1MVN8!{&yGz$V>(%F$J5z8*8BG^W9$kiv?r&w0oX}{T*_slG~twFth-v>^1z7PMb zHSzDexSs#S_1bR}*Pg#nRo(IB#h6FuUkxTZgbTl4b*sI9GZaiAg!zoLu{Ph_`t&s9 zbOo2wjcMdT$Mje$^D6U~ zRfB)m)H%+Xv*}IUcCYWni|ZdOf66n>E7Cms(AWEN?1RZ(r_yI#n!9OhdrrvIqXBJkSA9#@>rH~SxQT4BgtaC`8HpyJ+9*De{9|KwNY7Ga&fb=joQ%C@W5wW{eF zaQ&lcP^EiVapty5!u@BRO4ki8N#AYvm!Eoq`|^Oe#}S3w93KrmnYE2ybi<9 z-=He;6`e3`rRi*_;37!)5IrT9KauY!aUK|+5FW!9V0h@24%9izGwAgot#C@#ZTHCI z9sMHqT`oTUiTLK5`TSl`zP_sRuSc`puiiJVSTjI>lwH1-{v!SrhXse+^Q;4}Ld!@_cH`O9y^6y5Ij|@{qK46wa|CZc)1w^-}4fD;8gxX9MoKfvpqX#EeT{Jn>;@OmzZ~q>7;QL6! z4R&^;ekwm_ezttetw#%5FEiqLzAC+VD9~=tMGIZyj>qhO?o<}S^{XPsdpSPe+W)xU zagFyTpQGn(@-Q7{yZ!L%L!U~l<|p10)~#{(y(g^c9R6$;r~kBWZ@>Pc&t2wLV>xi+ z%(`pw`zk+X1$*c_KkC$Ks;$*YuEq4a?sbzw&s$q^o^0KhsWonA@Xh=8SF!XWr)9=i z&yCS_Fx*))yw8`y)2mLqhkw!fwQr1fiQC7(MMhy?;`bJ?Zg;oI?z_FNpr&Uu&uUl@ zS3Mx0OX9--=K~Lp?(X#Xr=_cVwOFm+!@&6c1=}GBr>uUQ4 zOw0|*b$3p$KQX=c)EQw>i(<#=eC)96-h)Y(?i)BA?C)-o*)Q_c;}}koLB{6ct2+OA zbJI(%?m82zUyfe-B+uN?ud^0w8^rdWXEJcvXnxd8HUCG=2R)e;VNdF`i+k+#oq4xq`MxYY z^(!|{e;GHg(E8@rd%t~uT~!%caCmp<$!Ya}tyz^mwf1&Q;<)ek-V1Dco{0-y`_ISS z>F?`8XDs^u>HO~BzJHDR_+j5q!l(DYJsX_P?ICWh6_1EV3f3Q5-C#DChYDwgnE0@Ncs9G{=$#z$@-%lT(_i^O;W)J4umcDp4 z?_BD=WLC|Pe*YxZpShN5ws`Y~F4^nPE%cpw#?xFd7s?gWv}XHubUmwq(6gP-OM*%e&3+Dx6u9?P&aY z^#;o=2OV;+9nJB7**`OuJ!w?MZN0+D)05w~*7w}8|MTiGN0tW~tgh(j-L7=fBi|Fj z9@`^5&uZ`J_O;XSNo%vOuizA=L~iGmHxF<9tFINmIzNW}YM1&qZOs!!`d_p|+B<9x z{BUCF)1jr)R=i)mJ~JXeD)qq7ybHtb{QkamknzT$#V;-nJ`!U2+2Ucw^<58U3|eUP z_nMhC?aVwo?=tG+J!($ll4hqgvMAywu*;(>1G>uoFVGr|j{1yELr% z4%>D8Do_7xUVYeLL*_ddul9i^YX;nC)BC>VfjaNZva0d*pC(PyKc7*4%yRF^L(fm- z?E1BCmP^-Rwtd(Abp4kBBQwru>%Ev3UD^MSUq_mr-CP`HP*di|UgPPX^f9gIVcZhe zXH{u~T^B}O?Uvo~k>RJs(MMJu-ZKAQ=xli1TB}xVDG7T%@L9aU2j7~% zn!WmRF!t7)u)e)tPch$UWoM&vKVX4i$1Y3fW()O~xsG|aB{5>erm}fycY3U8_vXo! z@(DEu)~s-f+v}A+IYm$s_Bv|y)RTE5NAI#=7vt?2hX4Uyz~8ow-EzMJox$A+n4TLGEwi!#vZ|T71=w#YrS4M z=unk&fo8zu>l<#*HLlN$V--&J9bl?!xNZC%+je)ReCpb-_iw>{0v^3JymC7zKb+(J zPjHn{khYfTmV%G^`NeUGYx+4Hdg4FS>$*$ImKJm&Zi!D3D+iklVJ4b!)UwzKc${L!{J}1rHskV0W@B5-W z+vN-~N##dZtjp0o6K$r}-E6j(v2T&N+okZ+Wn=&JuIstGVA`f%mXAN_=|9|I?IF$Y z)xR#bk9K-7+Oe?q@Z0q55l4^h9^u{net_f6i$#{7OO6c~Z!+wG-Hemt9A0=0yTxY} z?>uO_x2s*x*cN~I7`xlgE7`aHQcY{l^YlI6hctJJC?Dy#&2@(6twl8-XMb!vYv;Y| z+caBkVwI1(Qut=??5??PwlD9TsJ%AM*7RjaLhyj2mHE56RJFhN=Gm)#3kByr4^CZk z^{Usaf9@=unA)Y!r=J&|Dcf*s{_4}K-N#l;88ZDlo1Lb&_4|P6`-3j&{Tp+5(eVg*Y}38i)au&c`iPmudgpctGG=#q^;03yOp7%PJMe7b;!Y(&ojsnpTz{ug zu;@~CN4w&rF=I4F#7*k-zQ>$%`!DJ^%$(rwWyHT***|7s$(ORx3%W=Ree@h#aN#y8K0*;%eR%$hQ9fe-g)S2dmJ6%*0N&h$IuB=cJ%B# zx*+jjX1874I!t^%c~aPqvklb#f5d_e2omxln)Avs~j;eDEyDf zg>xPsXp?sPQ||LXm#h=V3R2$LX|$Q@-6QXS`TSkcQ{KGYQ+;S+Jyv`(q-a9HsbsCRTQ5Ey^LsUULigR>&(^f@IQUN&O&??H(|KK!ubwp_t*1K| ztSdRC@yMVin8{@OOLnx*eK$9{O#Vr$Z@hf}!6-fGNraSv;^ywG!mPwm5qTp!mK zYW}V6&Q2O|+;gUf`CiYR`#v-aozQdpk+YR+yt~)$uD#agx4d=So`!cOjXHIiD>D6N8q`QnyCOXz9+J8+BdJ`ybeiQZNhCzm~QB5agJ{9Ib=QER30Y(y5Ka zTud|-9jJ50$#_oPtSOwt#Ouclmf2JIZO-E^`dz)PhrD{NUu?&5m>>RIZrt%z zYcCl^y4-V)Ei&VDd%j{)UdhBGua@=-G99$=cK4_~VY8Mdy1zDwSvH{KKpX3zjtyr@%zf_P`D+BX{`@eaZ zEsSw&YdZG(5TE4Rb2U0{xKLWtZcR~u`X3?3j-9*TP<0mD9^UU!wlD!Ui9^9MC2)YW z0NKfUV`+P^r^M7~*UjOKAIx*e*X{4h2Sx;Xhf;cHO1GuwS(K7N*PD#Y_OMSx;PcLzkW&I{QEOO- z5*uF|t6*6FCi4dIr|^OjCx$231@U82B%5Lp_!N0tUV#Bwx@DOo=3%4BJ{ z)gVj5w@+nh_yvnB4cFPSG_<)a4Zm8ErJ=24X=q1T8d^|8!dBn#(HR`bO9*qvf;+S+ zbH}C)KzyeLcW6f@PaW>ig3O%;+@bAez&<;cM?Z`2upHV@4ZO;NGF)QrSZ%F2^Po3F z!Ll8?wym7EXqh*b$C6yC8|GsJ1Cxuc(pD2P*4KV$rfl#L<^;I7S42Ua1#?GVKk?|9h=Mpwhf^S;AWni?#^GMGo>3gMI2YP6Qy+YA z#3k-KdX5O$`&yLGSjwyy>Y)MN0sFB}o5K$20Ejo1JONxrE zi3>02F*x|Nm`lo{E1r(GWo)5EanLJeRy(K+En1fq`~{I??>-b!5XW}D>Fuj^j9;{< z|8u&r2F}rcPI0h>a!EH44u|4=Mn~o_I2^h9aHMe1CLC%r7BQeUYz(85)Egc{-cm-1G*H0LET1W=u#Y;RCXT6{E0}0WIc2#MpnT;&QFRN4D8*w zuvN}r=ur$$zkY&xE`xysF!<`gMbL|hEj=j=jA%Vtmyw>7iRuAw7CzFU%zyL^#;fLCfiueL3C#z9^sZiz)RhZpm}W_Ab~Mnb^dBqS0^ITDfGf?zj&o zn{sJ+?4`Nn50QN2DYqaLr<8sNd{(5ZN`CCLK5QFxRU@PpcrTa(hvAHCv zpNIX_Yx8Ld93i;my$AzGS1wt9rRdL!;bj!VfMP6fsXn}!{XP)|F`&y`m0k_nHLnE%7#xP+9% for the suggestion. -% - Restructured the configuration, now only the project name function has to be implemented -% (the preferences tag depends on it, there might be more than one project within the same -% Matlab installation whos documentation is created using this tool). The rest can be provided -% either at setup time or later via suitable setters for the version, description and logo. -% - Automatically setting HaveDot in the doxygen config whenever its found on the environment -% path. -% - Added basic support for LaTeX documentation creation. Using the parameter 'latex'=true for -% the create method creates the LaTeX version of the documentation in a folder "latex" in the -% OutputDirectory (default behaviour) -% -% @change{1,4,dw,2012-09-27} Added automatic dot Graphviz tool detection on -% call to create. -% -% @change{1,3,dw,2012-02-16} -% - Now also collecting error messages from mtocpp_post and adding them to -% the warnings.log file. -% - Added the directive "LD_LIBRARY_PATH= " for unix systems, as MatLab -% sets it inside its executing environment. This can lead to errors if -% doxygen and/or mtoc++ have been built using never GLIBC (libstd) versions -% than the one shipped with MatLab. -% -% @change{1,3,dw,2012-01-16} -% - Properly using the correct file separators everywhere now -% - Hyperlinked the log file so it can be opened directly -% -% @change{1,3,dw,2012-01-14} Not displaying the "generated warnings"-text if there have not -% been any during documentation creation. -% -% @change{1,2,dw,2011-11-27} -% - Included documentation creation for the Windows platform and -% combined the old methods into one (small effective differences) -% - No longer storing the doxygen binary file in the prefs as a lot of -% tools must be present on the path anyways. The new paradigm is to -% expect all required 3rd-party programmes to be available on PATH. As -% backup the configuration files directory is -% added to the Matlab PATH environment \b nonpermanently and any -% executables found there will thus also be usable. -% - Included checks for \c dot and \c latex at the setup stage to -% recommend installation of those tools if not present (the default -% doxygen settings in Doxyfile.m4 are to use both) -% -% @change{1,2,dw,2011-11-08} Improved the createUnix method by displaying the warnings and writing the output to -% a log file afterwards. Not using cprintf anymore as this is 3rd party software. -% -% @change{1,2,dw,2011-11-07} Fixed a recursion bug caused by copy and paste. Now the preferences -% are stored on an per-application basis. -% -% @change{1,2,dw,2011-11-04} Changed the name to MatlabDocMaker in -% order to export it into the mtoc++ distribution later. -% -% @new{1,2,dw,2011-10-13} Added this class and moved documentation related -% stuff here from the KerMor class. -% -% This class is part of the mtoc++ tool -% - \c Homepage http://www.morepas.org/software/mtocpp/ -% - \c License http://www.morepas.org/software/mtocpp/docs/licensing.html -% -% Copyright (c) 2012, Daniel Wirtz -% All rights reserved. -% -% Redistribution and use in source and binary forms, with or without -% modification, are permitted only in compliance with the BSD license, see -% http://www.opensource.org/licenses/bsd-license.php - - properties(Constant) - % File name for the doxygen configuration file processed by the MatlabDocMaker. - % - % Assumed to reside in the MatlabDocMaker.getConfigDirectory - % - % @type char @default 'Doxyfile.template' - DOXYFILE_TEMPLATE = 'Doxyfile.template'; - - % File name for the latex extras style file processed by the MatlabDocMaker. - % - % Assumed to reside in the MatlabDocMaker.getConfigDirectory. - % If not found, no latex extras are used. - % - % @type char @default 'latexextras.template' - LATEXEXTRAS_TEMPLATE = 'latexextras.template'; - - % File name the mtoc++ configuration file. - % - % Assumed to reside in the MatlabDocMaker.getConfigDirectory. - % If not found, no special configuration is used. - % - % @type char @default 'mtocpp.conf' - MTOCPP_CONFIGFILE = 'mtocpp.conf'; - end - - methods(Static) - function name = getProjectName - % Returns the project name. - % - % @note Changing the return value of this method will require - % another execution of MatlabDocMaker.setup as the preferences - % storage key also depends on it. - % - % Return values: - % name: The project name @type char - - %error('Please replace this by returning your project name as string.'); - % Example: - name = 'AMICI'; - end - end - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %% End of user defined part. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - methods(Static, Sealed) - function dir = getOutputDirectory - % Returns the directory where the applications source files - % reside - % - % Return values: - % dir: The output directory @type char - dir = MatlabDocMaker.getPref('outdir'); - end - - function dir = getSourceDirectory - % Returns the directory where the applications source files - % reside - % - % Return values: - % dir: The project source directory @type char - dir = MatlabDocMaker.getPref('srcdir'); - end - - function dir = getConfigDirectory - % Returns the directory where the applications documentation - % configuration files reside - % - % This folder must contain at least the files "mtoc.conf" and - % "Doxyfile.template" - % - % Return values: - % dir: The documentation configuration directory @type char - dir = MatlabDocMaker.getPref('confdir'); - end - - function desc = getProjectDescription - % Returns the short project description. - % - % Return values: - % desc: The short project description @type char @default [] - % - % See also: setProjectDescription - desc = MatlabDocMaker.getPref('proj_desc', ''); - end - - function setProjectDescription(value) - % Sets the project description. - % - % Parameters: - % value: The description @type char - % - % See also: getProjectDescription - if ~ischar(value) - error('The description must be a char array.'); - end - MatlabDocMaker.setPref('proj_desc', value); - end - - function version = getProjectVersion - % Returns the current version of the project. - % - % @note The built-in @@new and @@change tags from the - % Doxyfile.template support two-level versioning a la X.X. - % - % Return values: - % version: The project version @type char @default [] - % - % See also: setProjectVersion - version = MatlabDocMaker.getPref('proj_ver', '0'); - end - - function setProjectVersion(value) - % Sets the project version. - % - % Parameters: - % value: The version string @type char - % - % See also: getProjectVersion - if ~ischar(value) - error('The project version must be a char array.'); - end - MatlabDocMaker.setPref('proj_ver', value); - end - - function fullPath = getProjectLogo - % Returns the logo image file for the project. Either an absolute path or a plain - % filename. For the latter case the image file is assumed to reside inside the - % directory returned by MatlabDocMaker.getConfigDirectory. - % - % Return values: - % logoFile: The projects logo image file. @type char @default [] - % - % See also: setProjectLogo - logoFile = MatlabDocMaker.getPref('proj_logo',''); - fullPath = ''; - if ~isempty(logoFile) - if isempty(fileparts(logoFile)) - fullPath = fullfile(MatlabDocMaker.getConfigDirectory,logoFile); - else - fullPath = logoFile; - end - if exist(fullPath,'file') ~= 2 - warning('MatlabDocMaker:getLogo',['Could not find logo file "%s".\n'... - 'No logo will be shown in documentation output.'],logoFile); - end - end - end - - function setProjectLogo(value) - % Sets the project logo. Set to '' to unset. - % - % See the doxygen documentation for valid logo file types (wont be checked here). - % - % Parameters: - % value: The logo file to use. Must be either an absolute path or a plain filename, - % in which case the image is assumed to reside inside the - % MatlabDocMaker.getConfigDirectory directory. @type char - % - % See also: getProjectLogo - if nargin < 1 - [f, p] = uigetfile('*.*', 'Select project logo', pwd); - if f ~= 0 - value = fullfile(p,f); - else - fprintf(2,'No file selected. Aborting.\n'); - return; - end - end - if ~ischar(value) - error('The project logo file must be a char array.'); - end - if ~isempty(value) - fullPath = value; - if isempty(fileparts(value)) - fullPath = fullfile(MatlabDocMaker.getConfigDirectory,value); - end - if ~exist(fullPath,'file') - error('Invalid logo file: Could not find "%s"',fullPath); - end - end - MatlabDocMaker.setPref('proj_logo', value); - end - end - - methods(Static) - - function open - % Opens the generated documentation. - % - % Depending on the system's type the generated index.html is opened in the system's - % default browser. - index = fullfile(MatlabDocMaker.getOutputDirectory, 'index.html'); - if ispc - winopen(index); - else - [s, m] = system(sprintf('xdg-open "%s"',index)); - if s ~= 0 - fprintf(2,'Could not find/execute xdg-open: %s',m); - web(index); - end - end - end - - function create(varargin) - % Creates the Doxygen documentation - % - % Parameters: - % varargin: Optional parameters for creation. - % open: Set to true if the documentation should be opened after - % successful compilation @type logical @default false - % latex: Set to true if `\text{\LaTeX}` output should be generated, too. @type logical - % @default false - - %% Preparations - ip = inputParser; - ip.addParameter('open',false,@islogical); - ip.addParameter('latex',false,@islogical); - ip.parse(varargin{:}); - genlatex = ip.Results.latex; - - % Check for correct setup - cdir = MatlabDocMaker.getConfigDirectory; - srcdir = MatlabDocMaker.getSourceDirectory; - outdir = MatlabDocMaker.getOutputDirectory; - % Check if doxygen config file template exists - doxyfile_in = fullfile(cdir,MatlabDocMaker.DOXYFILE_TEMPLATE); - if exist(doxyfile_in,'file') ~= 2 - error('No doxygen configuration file template found at "%s"',doxyfile_in); - end - - lstr = ''; - if genlatex - lstr = '(+Latex)'; - end - fprintf(['Starting creation of doxygen/mtoc++ powered HTML%s documentation for "%s" (%s)\n'... - 'Sources: %s\nOutput to: %s\nCreating config files...'],lstr,... - MatlabDocMaker.getProjectName,MatlabDocMaker.getProjectVersion,... - srcdir,outdir); - - % Operation-system dependent strings - strs = struct; - if isunix - strs.null = '/dev/null'; - strs.silencer = ''; - elseif ispc - strs.null = 'NUL'; - strs.silencer = '@'; % argh that took a while to remember.. - else - error('Current platform not supported.'); - end - - % Save current working dir and change into the KerMor home - % directory; only from there all classes and packages are - % detected properly. - curdir = pwd; - cd(srcdir); - - % Append the configuration file directory to the current PATH - pathadd = [pathsep cdir]; - setenv('PATH',[getenv('PATH') pathadd]); - - mtoc_conf = fullfile(cdir,MatlabDocMaker.MTOCPP_CONFIGFILE); - filter = sprintf('%smtocpp',strs.silencer); - if exist(mtoc_conf,'file') - if isunix - strs.filter = 'mtocpp_filter.sh'; - strs.farg = '$1'; - elseif ispc - strs.filter = 'mtocpp_filter.bat'; - strs.farg = '%1'; - else - error('Current platform not supported.'); - end - %% Creation part - cdir = MatlabDocMaker.getConfigDirectory; - % Create "configured" filter script for inclusion in doxygen - filter = fullfile(cdir,strs.filter); - f = fopen(filter,'w'); - fprintf(f,'%smtocpp %s %s',strs.silencer,strs.farg,mtoc_conf); - fclose(f); - if isunix - unix(['chmod +x ' filter]); - end - end - - %% Prepare placeholders in the Doxyfile template - m = {'_OutputDir_' strrep(outdir,'\','\\'); ... - '_SourceDir_' strrep(MatlabDocMaker.getSourceDirectory,'\','\\');... - '_ConfDir_' strrep(cdir,'\','\\');... - '_ProjectName_' MatlabDocMaker.getProjectName; ... - '_ProjectDescription_' MatlabDocMaker.getProjectDescription; ... - '_ProjectLogo_' strrep(MatlabDocMaker.getProjectLogo,'\','\\'); ... - '_ProjectVersion_' MatlabDocMaker.getProjectVersion; ... - '_MTOCFILTER_' strrep(filter,'\','\\'); ... - }; - - % Check for latex extra stuff - texin = fullfile(cdir,MatlabDocMaker.LATEXEXTRAS_TEMPLATE); - latexextras = ''; - if exist(texin,'file') == 2 - latexstr = strrep(fileread(texin),'_ConfDir_',strrep(cdir,'\','/')); - latexextras = fullfile(cdir,'latexextras.sty'); - fid = fopen(latexextras,'w+'); fprintf(fid,'%s',latexstr); fclose(fid); - end - % Always use "/" for latex usepackage commands, so replace "\" (effectively windows - % only) by "/", and dont pass the extension ".sty" as latex automatically adds it. - m(end+1,:) = {'_LatexExtras_' strrep(latexextras(1:end-4),'\','/')}; - L = 'NO'; - if genlatex - L = 'YES'; - end - m(end+1,:) = {'_GenLatex_',L}; - - % Check how to set the HAVE_DOT flag - [s, ~] = system('dot -V'); - if s == 0 - HD = 'YES'; - else - HD = 'NO'; - fprintf('no "dot" found...'); - end - m(end+1,:) = {'_HaveDot_',HD}; - - % Read, replace & write doxygen config file - doxyfile = fullfile(cdir,'Doxyfile'); - doxyconfstr = regexprep(fileread(doxyfile_in),m(:,1),m(:,2)); - fid = fopen(doxyfile,'w'); fprintf(fid,'%s',doxyconfstr); fclose(fid); - - % Fix for unix systems where the MatLab installation uses older - % GLIBSTD libraries than doxygen/mtoc++ - ldpath = ''; - if isunix - ldpath = 'LD_LIBRARY_PATH= '; - end - % Call doxygen - fprintf('running doxygen with mtoc++ filter...'); - [~,warn] = system(sprintf('%sdoxygen "%s" 1>%s',ldpath, doxyfile, strs.null)); - - % Postprocess - fprintf('running mtoc++ postprocessor...'); - [~,postwarn] = system(sprintf('%smtocpp_post "%s" 1>%s',ldpath,... - outdir, strs.null)); - if ~isempty(postwarn) - warn = [warn sprintf('mtoc++ postprocessor messages:\n') postwarn]; - end - - % Create latex document if desired - if genlatex - oldd = pwd; - latexerr = false; - latexdir = fullfile(outdir,'latex'); - if exist(latexdir,'dir') == 7 - if exist(fullfile(latexdir,'refman.tex'),'file') == 2 - fprintf('compiling LaTeX output...'); - cd(latexdir); - [s, latexmsg] = system('make'); - if s ~= 0 - warn = [warn sprintf('LaTeX compiler output:\n') latexmsg]; - latexerr = true; - end - else - fprintf('cannot compile LaTeX output: no refman.tex found...'); - end - end - cd(oldd); - end - - % Tidy up - fprintf('cleaning up...'); - if isfield(strs,'filter') - delete(filter); - end - if ~isempty(latexextras) - delete(latexextras); - end - delete(doxyfile); - - %% Post generation phase - cd(curdir); - % Restore PATH to previous value - curpath = getenv('PATH'); - setenv('PATH',curpath(1:end-length(pathadd))); - fprintf('done!\n'); - - % Process warnings - showchars = 800; - warn = strtrim(warn); - if ~isempty(warn) - dispwarn = [warn(1:min(showchars,length(warn))) ' [...]']; - fprintf('First %d characters of warnings generated during documentation creation:\n%s\n',showchars,dispwarn); - % Write to log file - log = fullfile(outdir,'warnings.log'); - f = fopen(log,'w'); fprintf(f,'%s',warn); fclose(f); - fprintf(2,'MatlabDocMaker finished with warnings!\n'); - fprintf('Complete log file at %s.\n',log,log); - % Check for latex log file - log = fullfile(outdir,'_formulas.log'); - if exist(log,'file') - fprintf('Found LaTeX formula log file. Check %s for any errors.\n',log,log); - end - % Check for errors on latex generation - if genlatex && latexerr - log = fullfile(latexdir,'refman.log'); - fprintf('There have been errors with LaTeX compilation. See log file at %s.\n',log,log); - end - else - fprintf('MatlabDocMaker finished with no warnings!\n'); - end - - % Open index.html if wanted - if ip.Results.open - MatlabDocMaker.open; - end - end - - function setup - % Runs the setup script for MatlabDocMaker and collects all - % necessary paths in order for the documentation creation to - % work properly. - - %% Validity checks - fprintf('<<<< Welcome to the MatlabDocMaker setup for your project "%s"! >>>>\n',MatlabDocMaker.getProjectName); - - %% Setup directories - % Source directory - srcdir = MatlabDocMaker.getPref('srcdir',''); - word = 'keep'; - if isempty(srcdir) || exist(srcdir,'dir') ~= 7 - srcdir = pwd; - word = 'set'; - end - str = sprintf('Do you want to %s %s as your projects source directory?\n(Y)es/(N)o?: ',word,strrep(srcdir,'\','\\')); - ds = lower(input(str,'s')); - if isequal(ds,'n') - d = uigetdir(srcdir,'Please select the projects source directory'); - if d == 0 - error('No source directory specified. Aborting setup.'); - end - srcdir = d; - end - MatlabDocMaker.setPref('srcdir',srcdir); - - % Config directory - confdir = MatlabDocMaker.getPref('confdir',''); - word = 'keep'; - if isempty(confdir) || exist(confdir,'dir') ~= 7 - confdir = fullfile(srcdir,'documentation'); - word = 'set'; - end - str = sprintf('Do you want to %s %s as your documentation configuration files directory?\n(Y)es/(N)o?: ',word,strrep(confdir,'\','\\')); - ds = lower(input(str,'s')); - if isequal(ds,'n') - d = uigetdir(confdir,'Please select the documentation configuration directory'); - if d == 0 - error('No documentation configuration directory specified. Aborting setup.'); - end - confdir = d; - end - MatlabDocMaker.setPref('confdir',confdir); - - % Output directory - outdir = MatlabDocMaker.getPref('outdir',''); - word = 'keep'; - if isempty(outdir) || exist(outdir,'dir') ~= 7 - outdir = confdir; - word = 'set'; - end - str = sprintf('Do you want to %s %s as your documentation output directory?\n(Y)es/(N)o?: ',word,strrep(outdir,'\','\\')); - ds = lower(input(str,'s')); - if isequal(ds,'n') - d = uigetdir(outdir,'Please select the documentation output directory'); - if d == 0 - error('No documentation output directory specified. Aborting setup.'); - end - outdir = d; - end - MatlabDocMaker.setPref('outdir',outdir); - - %% Additional Project properties - if isequal(lower(input(['Do you want to specify further project details?\n'... - 'You can set them later using provided set methods. (Y)es/(N)o?: '],'s')),'y') - MatlabDocMaker.setPref('proj_ver',input('Please specify the project version, e.g. "0.1": ','s')); - MatlabDocMaker.setPref('proj_desc',input('Please specify a short project description: ','s')); - MatlabDocMaker.setProjectLogo; - end - - %% Check for necessary and recommended tools - hasall = true; - setenv('PATH',[getenv('PATH') pathsep confdir]); - fprintf('[Required] Checking for doxygen... '); - [st, vers] = system('doxygen --version'); - if st == 0 - fprintf(' found %s\n',vers(1:end-1)); - else - fprintf(2,' not found!\n'); - hasall = false; - end - fprintf('[Required] Checking for mtoc++... '); - ldpath = ''; - if isunix - ldpath = 'LD_LIBRARY_PATH= '; - end - [st, vers] = system(sprintf('%smtocpp --version',ldpath)); - if st == 0 - fprintf(' found %s\n',vers(1:end-1)); - else - fprintf(2,' not found!\n'); - hasall = false; - end - fprintf('[Recommended] Checking for dot... '); - [st, vers] = system('dot -V'); - if st == 0 - fprintf(' found %s\n',vers(1:end-1)); - else - fprintf(2,'dot Graphviz tool not found!\nInstall dot for nicer class diagrams.\n'); - end - fprintf('[Recommended] Checking for latex... '); - [st, vers] = system('latex --version'); - if st == 0 - fprintf(' found %s\n',vers(1:strfind(vers,sprintf('\n'))-1)); - else - fprintf(2,'latex not found!\nA LaTeX installation available on the system path is strongly recommended with mtoc++.\n'); - end - % Ghostscript - fprintf('[Recommended] Checking for ghostscript... '); - if ispc - [st, vers] = system('gswin32c --version'); - if st ~= 0 - [st, vers] = system('gswin64c --version'); - end - else - [st, vers] = system('gs --version'); - end - if st == 0 - fprintf(' found %s\n',vers(1:strfind(vers,sprintf('\n'))-1)); - else - fprintf(2,'ghostscript not found!\nCreating LaTeX formulas might not work properly.\n'); - end - - if ~hasall - fprintf(2,'Please make sure all prerequisites can be found on PATH or copy the executables into %s.\n',confdir); - fprintf('<<<< MatlabDocMaker setup finished with warnings. >>>>\n'); - else - fprintf('<<<< MatlabDocMaker setup successful. >>>>\nYou can now create your projects documentation by running MatlabDocMaker.create!\n'); - end - end - end - - methods(Static, Access=private) - function value = getProjPrefTag - % Gets the tag for the MatLab preferences struct. - % - % @change{0,7,dw,2013-04-02} Now also removing "~" and "-" characters from ProjectName tags for preferences. - str = regexprep(strrep(strtrim(MatlabDocMaker.getProjectName),' ','_'),'[^\d\w]',''); - value = sprintf('MatlabDocMaker_on_%s',str); - end - - function value = getPref(name, default) - if nargin < 2 - def = []; - else - def = default; - end - value = getpref(MatlabDocMaker.getProjPrefTag,name,def); - if nargin < 2 && isempty(value) - error('MatlabDocMaker preferences not found/set correctly. (Re-)Run the MatlabDocMaker.setup method.'); - end - end - - function value = setPref(name, value) - setpref(MatlabDocMaker.getProjPrefTag,name,value); - end - end -end diff --git a/matlab/mtoc/config/customdoxygen.css b/matlab/mtoc/config/customdoxygen.css deleted file mode 100644 index 8a2de47fbf..0000000000 --- a/matlab/mtoc/config/customdoxygen.css +++ /dev/null @@ -1,1524 +0,0 @@ -body, table, div, p, dl { - font: 400 14px/22px "Lato", "proxima-nova", "Helvetica Neue", Arial,sans-serif; -} - -/* @group Heading Levels */ - -h1 { - font-size: 150%; - font-weight: bold; -} - -.title { - font-size: 175%; - font-weight: bold; -} - -h2 { - color: #354C7B; - font-size: 150%; - font-weight: bold; - margin-top: 1.75em; - padding-top: 8px; - padding-bottom: 4px; - width: 100%; -} - -h3 { - font-size: 125%; - font-weight: bold; -} - -h1, h2, h3, h4, h5, h6 { - margin-right: 15px; -} - -h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { - text-shadow: 0 0 15px cyan; -} - -dt { - font-weight: bold; -} - -div.multicol { - -moz-column-gap: 1em; - -webkit-column-gap: 1em; - -moz-column-count: 3; - -webkit-column-count: 3; -} - -p.startli, p.startdd { - margin-top: 2px; -} - -p.starttd { - margin-top: 0px; -} - -p.endli { - margin-bottom: 0px; -} - -p.enddd { - margin-bottom: 4px; -} - -p.endtd { - margin-bottom: 2px; -} - -/* @end */ - -caption { - font-weight: bold; -} - -span.legend { - font-size: 70%; - text-align: center; -} - -h3.version { - font-size: 90%; - text-align: center; -} - -div.qindex, div.navtab{ - background-color: #EBEFF6; - border: 1px solid #A3B4D7; - text-align: center; -} - -div.qindex, div.navpath { - width: 100%; - line-height: 140%; -} - -div.navtab { - margin-right: 15px; -} - -/* @group Link Styling */ - -a { - color: #3D578C; - font-weight: normal; - text-decoration: none; -} - -.contents a:visited { - color: #4665A2; -} - -a:hover { - text-decoration: underline; -} - -a.qindex { - font-weight: bold; -} - -a.qindexHL { - font-weight: bold; - background-color: #9CAFD4; - color: #ffffff; - border: 1px double #869DCA; -} - -.contents a.qindexHL:visited { - color: #ffffff; -} - -a.el { - font-weight: bold; -} - -a.elRef { -} - -a.code, a.code:visited, a.line, a.line:visited { - color: #4665A2; -} - -a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { - color: #4665A2; -} - -/* @end */ - -dl.el { - margin-left: -1cm; -} - -pre.fragment { - border: 1px solid #C4CFE5; - background-color: #FBFCFD; - padding: 4px 6px; - margin: 4px 8px 4px 2px; - overflow: auto; - word-wrap: break-word; - line-height: 125%; - font-family: monospace, fixed; - font-size: 105%; -} - -div.fragment { - padding: 4px 6px; - margin: 4px 8px 4px 2px; - background-color: #FBFCFD; - border: 1px solid #C4CFE5; -} - -div.line { - font-family: monospace, fixed; - font-size: 13px; - min-height: 13px; - line-height: 1.0; - text-wrap: unrestricted; - white-space: -moz-pre-wrap; /* Moz */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - white-space: pre-wrap; /* CSS3 */ - word-wrap: break-word; /* IE 5.5+ */ - text-indent: -53px; - padding-left: 53px; - padding-bottom: 0px; - margin: 0px; - -webkit-transition-property: background-color, box-shadow; - -webkit-transition-duration: 0.5s; - -moz-transition-property: background-color, box-shadow; - -moz-transition-duration: 0.5s; - -ms-transition-property: background-color, box-shadow; - -ms-transition-duration: 0.5s; - -o-transition-property: background-color, box-shadow; - -o-transition-duration: 0.5s; - transition-property: background-color, box-shadow; - transition-duration: 0.5s; -} - -div.line.glow { - background-color: cyan; - box-shadow: 0 0 10px cyan; -} - - -span.lineno { - padding-right: 4px; - text-align: right; - border-right: 2px solid #0F0; - background-color: #E8E8E8; - white-space: pre; -} -span.lineno a { - background-color: #D8D8D8; -} - -span.lineno a:hover { - background-color: #C8C8C8; -} - -div.ah, span.ah { - background-color: black; - font-weight: bold; - color: #ffffff; - margin-bottom: 3px; - margin-top: 3px; - padding: 0.2em; - border: solid thin #333; - border-radius: 0.5em; - -webkit-border-radius: .5em; - -moz-border-radius: .5em; - box-shadow: 2px 2px 3px #999; - -webkit-box-shadow: 2px 2px 3px #999; - -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; - background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); - background-image: -moz-linear-gradient(top, #eee 0%, #444 40%, #000); -} - -div.classindex ul { - list-style: none; - padding-left: 0; -} - -div.classindex span.ai { - display: inline-block; -} - -div.groupHeader { - margin-left: 16px; - margin-top: 12px; - font-weight: bold; -} - -div.groupText { - margin-left: 16px; - font-style: italic; -} - -body { - background-color: white; - color: black; - margin: 0; -} - -div.contents { - margin-top: 10px; - margin-left: 12px; - margin-right: 8px; -} - -td.indexkey { - background-color: #EBEFF6; - font-weight: bold; - border: 1px solid #C4CFE5; - margin: 2px 0px 2px 0; - padding: 2px 10px; - white-space: nowrap; - vertical-align: top; -} - -td.indexvalue { - background-color: #EBEFF6; - border: 1px solid #C4CFE5; - padding: 2px 10px; - margin: 2px 0px; -} - -tr.memlist { - background-color: #EEF1F7; -} - -p.formulaDsp { - text-align: center; -} - -img.formulaDsp { - -} - -img.formulaInl { - vertical-align: middle; -} - -div.center { - text-align: center; - margin-top: 0px; - margin-bottom: 0px; - padding: 0px; -} - -div.center img { - border: 0px; -} - -address.footer { - text-align: right; - padding-right: 12px; -} - -img.footer { - border: 0px; - vertical-align: middle; -} - -/* @group Code Colorization */ - -span.keyword { - color: #008000 -} - -span.keywordtype { - color: #604020 -} - -span.keywordflow { - color: #e08000 -} - -span.comment { - color: #800000 -} - -span.preprocessor { - color: #806020 -} - -span.stringliteral { - color: #002080 -} - -span.charliteral { - color: #008080 -} - -span.vhdldigit { - color: #ff00ff -} - -span.vhdlchar { - color: #000000 -} - -span.vhdlkeyword { - color: #700070 -} - -span.vhdllogic { - color: #ff0000 -} - -blockquote { - background-color: #F7F8FB; - border-left: 2px solid #9CAFD4; - margin: 0 24px 0 4px; - padding: 0 12px 0 16px; -} - -/* @end */ - -/* -.search { - color: #003399; - font-weight: bold; -} - -form.search { - margin-bottom: 0px; - margin-top: 0px; -} - -input.search { - font-size: 75%; - color: #000080; - font-weight: normal; - background-color: #e8eef2; -} -*/ - -td.tiny { - font-size: 75%; -} - -.dirtab { - padding: 4px; - border-collapse: collapse; - border: 1px solid #A3B4D7; -} - -th.dirtab { - background: #EBEFF6; - font-weight: bold; -} - -hr { - height: 0px; - border: none; - border-top: 1px solid #4A6AAA; -} - -hr.footer { - height: 1px; -} - -/* @group Member Descriptions */ - -table.memberdecls { - border-spacing: 0px; - padding: 0px; -} - -.memberdecls td, .fieldtable tr { - -webkit-transition-property: background-color, box-shadow; - -webkit-transition-duration: 0.5s; - -moz-transition-property: background-color, box-shadow; - -moz-transition-duration: 0.5s; - -ms-transition-property: background-color, box-shadow; - -ms-transition-duration: 0.5s; - -o-transition-property: background-color, box-shadow; - -o-transition-duration: 0.5s; - transition-property: background-color, box-shadow; - transition-duration: 0.5s; -} - -.memberdecls td.glow, .fieldtable tr.glow { - background-color: cyan; - box-shadow: 0 0 15px cyan; -} - -.mdescLeft, .mdescRight, -.memItemLeft, .memItemRight, -.memTemplItemLeft, .memTemplItemRight, .memTemplParams { - background-color: #F9FAFC; - border: none; - margin: 4px; - padding: 1px 0 0 8px; -} - -.mdescLeft, .mdescRight { - padding: 0px 8px 4px 8px; - color: #555; -} - -.memSeparator { - border-bottom: 1px solid #DEE4F0; - line-height: 1px; - margin: 0px; - padding: 0px; -} - -.memItemLeft, .memTemplItemLeft { - white-space: nowrap; -} - -.memItemRight { - width: 100%; -} - -.memTemplParams { - color: #4665A2; - white-space: nowrap; - font-size: 80%; -} - -/* @end */ - -/* @group Member Details */ - -/* Styles for detailed member documentation */ - -.memtemplate { - font-size: 80%; - color: #4665A2; - font-weight: normal; - margin-left: 9px; -} - -.memnav { - background-color: #EBEFF6; - border: 1px solid #A3B4D7; - text-align: center; - margin: 2px; - margin-right: 15px; - padding: 2px; -} - -.mempage { - width: 100%; -} - -.memitem { - padding: 0; - margin-bottom: 10px; - margin-right: 5px; - -webkit-transition: box-shadow 0.5s linear; - -moz-transition: box-shadow 0.5s linear; - -ms-transition: box-shadow 0.5s linear; - -o-transition: box-shadow 0.5s linear; - transition: box-shadow 0.5s linear; - display: table !important; - width: 100%; -} - -.memitem.glow { - box-shadow: 0 0 15px cyan; -} - -.memname { - font-weight: bold; - margin-left: 6px; -} - -.memname td { - vertical-align: bottom; -} - -.memproto, dl.reflist dt { - border-top: 1px solid #A8B8D9; - border-left: 1px solid #A8B8D9; - border-right: 1px solid #A8B8D9; - padding: 6px 0px 6px 0px; - color: #253555; - font-weight: bold; - text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); - background-image:url('nav_f.png'); - background-repeat:repeat-x; - background-color: #E2E8F2; - /* opera specific markup */ - box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); - border-top-right-radius: 4px; - border-top-left-radius: 4px; - /* firefox specific markup */ - -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; - -moz-border-radius-topright: 4px; - -moz-border-radius-topleft: 4px; - /* webkit specific markup */ - -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); - -webkit-border-top-right-radius: 4px; - -webkit-border-top-left-radius: 4px; - -} - -.memdoc, dl.reflist dd { - border-bottom: 1px solid #A8B8D9; - border-left: 1px solid #A8B8D9; - border-right: 1px solid #A8B8D9; - padding: 6px 10px 2px 10px; - border-top-width: 0; - background-image:url('nav_g.png'); - background-repeat:repeat-x; - background-color: #FFFFFF; - /* opera specific markup */ - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); - /* firefox specific markup */ - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-bottomright: 4px; - -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; - /* webkit specific markup */ - -webkit-border-bottom-left-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); -} - -dl.reflist dt { - padding: 5px; -} - -dl.reflist dd { - margin: 0px 0px 10px 0px; - padding: 5px; -} - -.paramkey { - text-align: right; -} - -.paramtype { - white-space: nowrap; -} - -.paramname { - color: #602020; - white-space: nowrap; -} -.paramname em { - font-style: normal; -} -.paramname code { - line-height: 14px; -} - -.params, .retval, .exception, .tparams { - margin-left: 0px; - padding-left: 0px; -} - -.params .paramname, .retval .paramname { - font-weight: bold; - vertical-align: top; -} - -.params .paramtype { - font-style: italic; - vertical-align: top; -} - -.params .paramdir { - font-family: "courier new",courier,monospace; - vertical-align: top; -} - -table.mlabels { - border-spacing: 0px; -} - -td.mlabels-left { - width: 100%; - padding: 0px; -} - -td.mlabels-right { - vertical-align: bottom; - padding: 0px; - white-space: nowrap; -} - -span.mlabels { - margin-left: 8px; -} - -span.mlabel { - background-color: #728DC1; - border-top:1px solid #5373B4; - border-left:1px solid #5373B4; - border-right:1px solid #C4CFE5; - border-bottom:1px solid #C4CFE5; - text-shadow: none; - color: white; - margin-right: 4px; - padding: 2px 3px; - border-radius: 3px; - font-size: 7pt; - white-space: nowrap; - vertical-align: middle; -} - - - -/* @end */ - -/* these are for tree view inside a (index) page */ - -div.directory { - margin: 10px 0px; - border-top: 1px solid #9CAFD4; - border-bottom: 1px solid #9CAFD4; - width: 100%; -} - -.directory table { - border-collapse:collapse; -} - -.directory td { - margin: 0px; - padding: 0px; - vertical-align: top; -} - -.directory td.entry { - white-space: nowrap; - padding-right: 6px; - padding-top: 3px; -} - -.directory td.entry a { - outline:none; -} - -.directory td.entry a img { - border: none; -} - -.directory td.desc { - width: 100%; - padding-left: 6px; - padding-right: 6px; - padding-top: 3px; - border-left: 1px solid rgba(0,0,0,0.05); -} - -.directory tr.even { - padding-left: 6px; - background-color: #F7F8FB; -} - -.directory img { - vertical-align: -30%; -} - -.directory .levels { - white-space: nowrap; - width: 100%; - text-align: right; - font-size: 9pt; -} - -.directory .levels span { - cursor: pointer; - padding-left: 2px; - padding-right: 2px; - color: #3D578C; -} - -.arrow { - color: #9CAFD4; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - font-size: 80%; - display: inline-block; - width: 16px; - height: 22px; -} - -.icon { - font-family: Arial, Helvetica, sans-serif; - font-weight: bold; - font-size: 12px; - height: 14px; - width: 16px; - display: inline-block; - background-color: #728DC1; - color: white; - text-align: center; - border-radius: 4px; - margin-left: 2px; - margin-right: 2px; -} - -.icona { - width: 24px; - height: 22px; - display: inline-block; -} - -.iconfopen { - width: 24px; - height: 18px; - margin-bottom: 4px; - background-image:url('folderopen.png'); - background-position: 0px -4px; - background-repeat: repeat-y; - vertical-align:top; - display: inline-block; -} - -.iconfclosed { - width: 24px; - height: 18px; - margin-bottom: 4px; - background-image:url('folderclosed.png'); - background-position: 0px -4px; - background-repeat: repeat-y; - vertical-align:top; - display: inline-block; -} - -.icondoc { - width: 24px; - height: 18px; - margin-bottom: 4px; - background-image:url('doc.png'); - background-position: 0px -4px; - background-repeat: repeat-y; - vertical-align:top; - display: inline-block; -} - -table.directory { - font: 400 14px Roboto,sans-serif; -} - -/* @end */ - -div.dynheader { - margin-top: 8px; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -address { - font-style: normal; - color: #2A3D61; -} - -table.doxtable { - border-collapse:collapse; - margin-top: 4px; - margin-bottom: 4px; -} - -table.doxtable td, table.doxtable th { - border: 1px solid #2D4068; - padding: 3px 7px 2px; -} - -table.doxtable th { - background-color: #374F7F; - color: #FFFFFF; - font-size: 110%; - padding-bottom: 4px; - padding-top: 5px; -} - -table.fieldtable { - /*width: 100%;*/ - margin-bottom: 10px; - border: 1px solid #A8B8D9; - border-spacing: 0px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; - -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); - box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); -} - -.fieldtable td, .fieldtable th { - padding: 3px 7px 2px; -} - -.fieldtable td.fieldtype, .fieldtable td.fieldname { - white-space: nowrap; - border-right: 1px solid #A8B8D9; - border-bottom: 1px solid #A8B8D9; - vertical-align: top; -} - -.fieldtable td.fieldname { - padding-top: 3px; -} - -.fieldtable td.fielddoc { - border-bottom: 1px solid #A8B8D9; - /*width: 100%;*/ -} - -.fieldtable td.fielddoc p:first-child { - margin-top: 0px; -} - -.fieldtable td.fielddoc p:last-child { - margin-bottom: 2px; -} - -.fieldtable tr:last-child td { - border-bottom: none; -} - -.fieldtable th { - background-image:url('nav_f.png'); - background-repeat:repeat-x; - background-color: #E2E8F2; - font-size: 90%; - color: #253555; - padding-bottom: 4px; - padding-top: 5px; - text-align:left; - -moz-border-radius-topleft: 4px; - -moz-border-radius-topright: 4px; - -webkit-border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom: 1px solid #A8B8D9; -} - - -.tabsearch { - top: 0px; - left: 10px; - height: 36px; - background-image: url('tab_b.png'); - z-index: 101; - overflow: hidden; - font-size: 13px; -} - -.navpath ul -{ - font-size: 11px; - background-image:url('tab_b.png'); - background-repeat:repeat-x; - background-position: 0 -5px; - height:30px; - line-height:30px; - color:#8AA0CC; - border:solid 1px #C2CDE4; - overflow:hidden; - margin:0px; - padding:0px; -} - -.navpath li -{ - list-style-type:none; - float:left; - padding-left:10px; - padding-right:15px; - background-image:url('bc_s.png'); - background-repeat:no-repeat; - background-position:right; - color:#364D7C; -} - -.navpath li.navelem a -{ - height:32px; - display:block; - outline: none; - color: #283A5D; - font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; - text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); - text-decoration: none; -} - -.navpath li.navelem a:hover -{ - color:#6884BD; -} - -.navpath li.footer -{ - list-style-type:none; - float:right; - padding-left:10px; - padding-right:15px; - background-image:none; - background-repeat:no-repeat; - background-position:right; - color:#364D7C; - font-size: 8pt; -} - - -div.summary -{ - float: right; - font-size: 8pt; - padding-right: 5px; - width: 50%; - text-align: right; -} - -div.summary a -{ - white-space: nowrap; -} - -div.ingroups -{ - font-size: 8pt; - width: 50%; - text-align: left; -} - -div.ingroups a -{ - white-space: nowrap; -} - -div.header -{ - background-image:url('nav_h.png'); - background-repeat:repeat-x; - background-color: #F9FAFC; - margin: 0px; - border-bottom: 1px solid #C4CFE5; -} - -div.headertitle -{ - padding: 5px 5px 5px 10px; -} - -dl -{ - padding: 0 0 0 10px; -} - -/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */ -dl.section -{ - margin-left: 0px; - padding-left: 0px; -} - -dl.note -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #D0C000; -} - -dl.warning, dl.attention -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #FF0000; -} - -dl.pre, dl.post, dl.invariant -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #00D000; -} - -dl.deprecated -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #505050; -} - -dl.todo -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #00C0E0; -} - -dl.test -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #3030E0; -} - -dl.bug -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #C08050; -} - -dl.section dd { - margin-bottom: 6px; -} - - -#projectlogo -{ - text-align: center; - vertical-align: bottom; - border-collapse: separate; -} - -#projectlogo img -{ - border: 0px none; -} - -#projectalign -{ - vertical-align: middle; -} - -#projectname -{ - font-size: 200%; - font-weight: bold; -} - -#projectbrief -{ - font-size: 100%; - font-weight: bold; -} - -#projectnumber -{ - font: 50% Tahoma, Arial,sans-serif; - margin: 0px; - padding: 0px; -} - -#titlearea -{ - padding: 0px; - margin: 0px; - width: 100%; - margin-bottom: 0em; -} - -.image -{ - text-align: center; -} - -.dotgraph -{ - text-align: center; -} - -.mscgraph -{ - text-align: center; -} - -.diagraph -{ - text-align: center; -} - -.caption -{ - font-weight: bold; -} - -div.zoom -{ - border: 1px solid #90A5CE; -} - -dl.citelist { - margin-bottom:50px; -} - -dl.citelist dt { - color:#334975; - float:left; - font-weight:bold; - margin-right:10px; - padding:5px; -} - -dl.citelist dd { - margin:2px 0; - padding:5px 0; -} - -div.toc { - padding: 14px 25px; - background-color: #F4F6FA; - border: 1px solid #D8DFEE; - border-radius: 7px 7px 7px 7px; - float: right; - height: auto; - margin: 0 20px 10px 10px; - width: 200px; -} - -div.toc li { - background: url("bdwn.png") no-repeat scroll 0 5px transparent; - font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif; - margin-top: 5px; - padding-left: 10px; - padding-top: 2px; -} - -div.toc h3 { - font: bold 12px/1.2 Arial,FreeSans,sans-serif; - color: #4665A2; - border-bottom: 0 none; - margin: 0; -} - -div.toc ul { - list-style: none outside none; - border: medium none; - padding: 0px; -} - -div.toc li.level1 { - margin-left: 0px; -} - -div.toc li.level2 { - margin-left: 15px; -} - -div.toc li.level3 { - margin-left: 30px; -} - -div.toc li.level4 { - margin-left: 45px; -} - -.inherit_header { - font-weight: bold; - color: gray; - cursor: pointer; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.inherit_header td { - padding: 6px 0px 2px 5px; -} - -.inherit { - display: none; -} - -tr.heading h2 { - margin-top: 12px; - margin-bottom: 4px; -} - -/* tooltip related style info */ - -.ttc { - position: absolute; - display: none; -} - -#powerTip { - cursor: default; - white-space: nowrap; - background-color: white; - border: 1px solid gray; - border-radius: 4px 4px 4px 4px; - box-shadow: 1px 1px 7px gray; - display: none; - font-size: smaller; - max-width: 80%; - opacity: 0.9; - padding: 1ex 1em 1em; - position: absolute; - z-index: 2147483647; -} - -#powerTip div.ttdoc { - color: grey; - font-style: italic; -} - -#powerTip div.ttname a { - font-weight: bold; -} - -#powerTip div.ttname { - font-weight: bold; -} - -#powerTip div.ttdeci { - color: #006318; -} - -#powerTip div { - margin: 0px; - padding: 0px; - font: 12px/16px Roboto,sans-serif; -} - -#powerTip:before, #powerTip:after { - content: ""; - position: absolute; - margin: 0px; -} - -#powerTip.n:after, #powerTip.n:before, -#powerTip.s:after, #powerTip.s:before, -#powerTip.w:after, #powerTip.w:before, -#powerTip.e:after, #powerTip.e:before, -#powerTip.ne:after, #powerTip.ne:before, -#powerTip.se:after, #powerTip.se:before, -#powerTip.nw:after, #powerTip.nw:before, -#powerTip.sw:after, #powerTip.sw:before { - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; -} - -#powerTip.n:after, #powerTip.s:after, -#powerTip.w:after, #powerTip.e:after, -#powerTip.nw:after, #powerTip.ne:after, -#powerTip.sw:after, #powerTip.se:after { - border-color: rgba(255, 255, 255, 0); -} - -#powerTip.n:before, #powerTip.s:before, -#powerTip.w:before, #powerTip.e:before, -#powerTip.nw:before, #powerTip.ne:before, -#powerTip.sw:before, #powerTip.se:before { - border-color: rgba(128, 128, 128, 0); -} - -#powerTip.n:after, #powerTip.n:before, -#powerTip.ne:after, #powerTip.ne:before, -#powerTip.nw:after, #powerTip.nw:before { - top: 100%; -} - -#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { - border-top-color: #ffffff; - border-width: 10px; - margin: 0px -10px; -} -#powerTip.n:before { - border-top-color: #808080; - border-width: 11px; - margin: 0px -11px; -} -#powerTip.n:after, #powerTip.n:before { - left: 50%; -} - -#powerTip.nw:after, #powerTip.nw:before { - right: 14px; -} - -#powerTip.ne:after, #powerTip.ne:before { - left: 14px; -} - -#powerTip.s:after, #powerTip.s:before, -#powerTip.se:after, #powerTip.se:before, -#powerTip.sw:after, #powerTip.sw:before { - bottom: 100%; -} - -#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { - border-bottom-color: #ffffff; - border-width: 10px; - margin: 0px -10px; -} - -#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { - border-bottom-color: #808080; - border-width: 11px; - margin: 0px -11px; -} - -#powerTip.s:after, #powerTip.s:before { - left: 50%; -} - -#powerTip.sw:after, #powerTip.sw:before { - right: 14px; -} - -#powerTip.se:after, #powerTip.se:before { - left: 14px; -} - -#powerTip.e:after, #powerTip.e:before { - left: 100%; -} -#powerTip.e:after { - border-left-color: #ffffff; - border-width: 10px; - top: 50%; - margin-top: -10px; -} -#powerTip.e:before { - border-left-color: #808080; - border-width: 11px; - top: 50%; - margin-top: -11px; -} - -#powerTip.w:after, #powerTip.w:before { - right: 100%; -} -#powerTip.w:after { - border-right-color: #ffffff; - border-width: 10px; - top: 50%; - margin-top: -10px; -} -#powerTip.w:before { - border-right-color: #808080; - border-width: 11px; - top: 50%; - margin-top: -11px; -} - -@media print -{ - #top { display: none; } - #side-nav { display: none; } - #nav-path { display: none; } - body { overflow:visible; } - h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } - .summary { display: none; } - .memitem { page-break-inside: avoid; } - #doc-content - { - margin-left:0 !important; - height:auto !important; - width:auto !important; - overflow:inherit; - display:inline; - } -} - -/* @group custom modifications */ - -#top { - background-color: #2980B9; - color: #fcfcfc; - font-weight: normal; -} - -div.header { - background-image: none; - border-bottom: none; - background-color: #FFFFFF; -} - -#nav-tree { - background-color: #343131; - background-image: none; -} - -#nav-tree a { - color: #b3b3b3; - line-height: 18px; - font-size: 110%; - font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; -} - -#nav-tree .selected { - background-image: none; -} - -div .ui-resizable-handle.ui-resizable-e { - width: 0px; -} - -div#main-nav ul.sm.sm-dox { - background-image: none; - border-top: none; -} - -.sm-dox a { - background-image: none; - color: #fcfcfc; - font-size: 110%; - font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; - text-shadow: none; -} - -.sm-dox a:hover { - background-image: none; - color: #fcfcfc; - font-size: 110%; - font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; -} - -li.footer { - display: none; -} - -div#nav-path ul { - background-image: none; - border: none; -} - -.memproto { - background-image: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - border-bottom: 1px solid #d1d5da; - border-left: 1px solid #d1d5da; - border-right: 1px solid #d1d5da; - background-color: #f6f8fa; -} - -.memdoc { - background-image: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - border-bottom: 1px solid #d1d5da; - border-left: 1px solid #d1d5da; - border-right: 1px solid #d1d5da; -} diff --git a/matlab/mtoc/config/latexextras.template b/matlab/mtoc/config/latexextras.template deleted file mode 100644 index 5abd2c045c..0000000000 --- a/matlab/mtoc/config/latexextras.template +++ /dev/null @@ -1,15 +0,0 @@ -% Additional LaTeX inclusions for mtoc++/doxygen tools -% -% Use the _ConfDir_ tag to insert the folder where this file resides. -% Thus you can include more custom latex files/styles/packages which reside in this folder - -% Default packages -\usepackage{amsmath} -\usepackage{amssymb} - -% Inclusion example: -% Please note that, independent of windows or unix, latex uses forward-slash-type paths in bibliography. -%\input{_ConfDirFwdSlash_/myexternalstyle.sty} -\setcounter{tocdepth}{2} - -%\uchyph=0 diff --git a/matlab/mtoc/config/mtocpp b/matlab/mtoc/config/mtocpp deleted file mode 100755 index bf9aabd13855997acf1e9b5df6ae230a613876c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1446116 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
AYP4z7_F78I1bZ2&C! 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

;3qP4rdL>))y71 zcX7)1?$rsA_))M+K0rxzeDb`P6fVJ?i|DYUiAgpy2ApfiwG_zj`0)0)x(`2a9WVaI zS9^yOclLskqF%@AUyCF)xyMZK6z8&H5Oxi^n-*oV9c@i-cSN&{ z;Xdsxy#HyL$in)~j%b>u(Y06>DI{CJ_ZrI2?eHbKy{35HB*X4_O=+ zi$6wDY>O*bz^~eU{v6kFC0oD#c;(0mkNv%y%aL)mV5LaVoFil?M_!E|rHFqUIYKJr z$Z3ps!@Y9EZj~ck+sA!y%8|!W>TmiGI3Y*K-kZJ@3&1iNUvF}59Gxcrq7KcEJ@8{R0Y zu%fcE6$Ok#0TJfmwWH#%x)EweL{0I{Y}UTmD3{ldZ$UxBt_sfiZEAU4Tp6I_>MNPE z*p?=At&)@sV*M^$^2O3$Px82FJi)SGyMs10yb1nCFV6PflW6%zEK=*n67yyXaHF9q zgT@lLw1{Jg6&XaLaDl6s(VERxZ6*ruxQNNIgu=sNI~xnIzJ(%+am|PzF;$+Bm33o@ zQ8NWlYBD8=E0d-K7+IJSKqHJ41{h+R!V5TJmgj6XYco-$iYSdG6b~#4*jRubh*8Bp zf}BQ!GK)mvp(Qg%1j(??L}@G`M$1^@G?c8%BL2OZvBXJdqbR=ReJYBbSy6O(#Jv)V zPSoeH46S2{)3^$QD}uQ!wG~tCrh@CmL6#Do+ql+S7#oWCLi8Mjf`W=;581ETEoWWB zi+H_Cb4iu_!kIUATy^r@T@NOr4#c%%-mgOg^ zw0@Z!<+7kckrG-`xN5Is{j9y33AseSu^>`#rYv2j)GY#V?ht>wk#I3f37M#}sUTUA zm2U+rW=5;FvSw&=X0}VR6*}m+s!SIJtUcf7gV_QiqL@|2Tp9Ll?K#hyq-^#(qMfqp zn)t4--Ofo172QkF)I{;olDoie?OX6?-Px?#-668z<``6ESF{8l^}3XdF!rTLfVyW& zfQ1NAi2&)4e~-UN6pl~w7uxImZVJ*W zAhg&xK1p`Sa{g>SKB-bS_Z;{U^UiiAFa5{&tA?~)nl;wR*6j~w-tAJdkk^~|$CwGYe z3XP@&P!cs|kd|o*N12vs@L6oCBOotDM3Ddn=w-WB~DWUOb;0;tl2wmOEQZL zNk~NDhqO`_K+Q)~MS%NaO$l&8jVZ!LEz^}3GWq3YKrRvz5Xm?kAPWR2cBTXKw5aQc^fC!Rfzb=Y#v4; zObf6QCy%2msM`S2z4nM!A*3EQdY%my=ctS5R+zeon;ztQ8_d_WwprWqSMNb{|0bBj zKY5!sDqRd7+8l(1s2#wT4c24;jb58~8+r`AfU9UP{wYG}RO5W;f_Oe{XE@-8t^p_H z-Ec4Q6~W|`^YGXByH$b|HX@4iDDhTK;ifHcUMD8Ga3kAp2QFN;X$ zed|SzriJA5^~vMfPk?V8)w-M7Q^=16m~riMvJqtSJewy6oBFK_<+mN|X!y{i98qD9 zoj&WIRf8rGudD_&h9o>S+3+*ViR=DnCZPk!j#_(Tp~Sr*r*B6*DB0~aH^SgS$wnBp z7dwYNain4<|83$M22AMAalVonZQp3Sv6Z||9`}&Hk1X`w1>MoMEAcpadgw|eePUhG zOB3`61FK})>-u23boN(}owSu+VHe}Mi)7p7@Cn9brMqoBmKisSQPZz|jVL!)3C>tH ztrE|aeP`rLADb7I-cejxAd=vUZXJbR5n{{!=l&159?D%O+a3&Gk(*?t|MX5759Y=$XQf$AU15m^>AhWj3c00Z+ZOvl zR(gs3=*x|9Rsp|J!x!JI=45>KCQ!*dDcq$7sDF|5KXYJV;4%QDC4JdUkSdp5L8ZAlzHI5Aj{&hr;#$Ob(8Poy{6u9zKICBwWS!Z)R`8j3t z+>D?i^Gn1`@5KnJxaZE7mA>rwReUIZzny>7&fl+dS(jW+X8KJ;Erik>p?$b0GMlE^ zJKw%m`k4GU^KhZ`F)4|k`P0G{ zDg7fb8WkSTcb$rS#N{pc>$jxkyd*X&{OjPmbNGZua`nvNETidb%J-zIzz(eED#Ul^ zT&ReIMvrmE!Xsy=#e?Y;ZEerE_){n^DWm)t#E`kXfY+~C#x8pAP4(wK;c$H)zPIAL zPvLNV2;bQk9In3?-TLMIhwJ}5aJc?ueBZ}+Bfi%@@o@d)_+EG6;rgjhKU{wjzE|M; z$k5^XTd)fFK74=jvcvT$e24Je?d6B-kHI&I@2RgkTQalFT%z5 z*ZAh~t>Js%zQgrld{^LG#P@4_XGe$YBl!Lv-;3rt65l?2@51+GeAnYU@d}vZdkDVA;M;|71mCOhy%FC(;QJ811$_U8???FB z%7^QB#rFz)&wd;1;yZxvf8qNvzT01QxPBhK=i>V)zMtVc^^=F|FUI%EPa%HzK8x>{ z_+EJMaJ`K0xnF=8zQ4wI$yb0-O!y!8?tks!`s45|e(P}kl-l9?Hhk~I_gQ>D$M=lu z4%a{R6HukX8ez5dku*XuW)Td$wBqh24u_bz;E`1;SoTJIz4^%w1| z*DoA^`L25Xt{2zqC%&v+|J5t&^@0ChuU~=h>-Zk_s(SsE_}+~LleeI4y%yi^|4qIA zM10RK*Xv{W-iYtL_{RUXUZ2MIV|=In9m4!w!o>W_ zdi@#r=4UXb{GW&yzKh;nuixoC_4?`f&cQc;?}qo)>+kzuz5av`)$8BF_lgSK<9ptR zvH3e!uRs2y_4>)xdi`8{BlzBe?;yUP;oEjqz5X3QMOyJK zeWPCg$TyKrd=LBeAuj}laSS=*GZ}=$Ma}LkqJ!=y?Zzz=Z ztflY%TePTq4|ZPssgBe7+GM|Y9p@UD&}~PTf>~ZK0dp&Xsn~iec$n2z3N{m9S431K zz>0+_0aTYw5jsBHUi>y=RQwg&Fa+2`L!9iO;XVwY+18VQ_5@T&2B1PR0JRT%@xU|? zpB5LoV5R0!d$1mj&=pog{fL7%+ZdlREp=^@}*UQst7clyK#&;iDfjFLg|4Oo{mb3{y2$Okc_ahr|VOcXRp z#cW5chi$81V*$oP+oSUyzH6_mV!BTWe~zcWQ>PI${9okWp)#MK@9pvj6`1;2o};<9m1 z@a7fqNVOEFl~q~Y@MP_eTQ%H5C$pqSmE3?8FjIt$#egS*ro()+C`=L7^}8MW zUhB$YY5V96E~vK9d+83BP72E1@}(*!X0M>qM`HjHN1vk**xi-%)BE;UTs{8nEfWa~ zd+i@!rupU+s5rSo5wDU+OK;02TE!|ur8k~Gp_EaRbhGSKFaFeX`R~CUr|qL3>TjP?C|)oym`M$MqHkw?+a>iSls$?oS2-_{#kk*2R-inY;It- z2)JN@YpZRb6gn)pQX!bE#k} zk}n2=pdh?9G8dvXB!(funmDC0Iq_!>JYXTJ(j}^F;Ms*9+v>J0Q<$Ons02#1 z_yLfNEy(mX`5&#&D2YO+v}`IglS}qaR-h?H3QuV;*?59k5Ut6I8&vM!R?J^ODVC+*aWy-VKz`Rr-?N_%{vbG%eZGhqL44 z)v!~$8pR{5(u~o~hyVVju%i&tifY1;!>k1RHOjv*MqtI}vsB$;8uAPCddw7LGdWM; zbYb2oyj_3)EP3fUOqO%iXU&ll4pfj`nAc~X0GAM(G9>h;;0yCcWKcF_)1hH9Bmud5C{Y!K*(BroPj3uyZHcg->v7XuXs_K%B{VzXp)~&4o0{E) zM!w3zm2Us*Ecs|u7{--NjFgOfs}W6yC+c>+4!Qmjv{%W2m^>qf4p;| zKOPwG=Mjl`_cZ7!OhKkWW4f|?7W540S$%H7h(>xXBEFOSK;(>7x1jY^S>mCY{AV|IH8F7W0bzF)3eOZ~T^}s{ae8v&QWR8G4k_6D<@1kk(Ap2M^g)-RmoXhj2(2Gbv z>1{XFC&PM2H}^;4HFOs4}T$fAHvGcT!q6{K3A0; zuPRT{UCDqG!1AR!13)uAZ8K6R)uQ6^ysU&HMS*gEf;%v5wqgea6f(9bh@cyztbOZN zp*iYgAv7lo(cNGlB7+2?Uinw;0Tip`vvbNGVIeI=mo5gdaKdNks3hk>(ScG@nxf}oXm`{m zkgf@&YZ8dIA*_UX1+0xx-G!(kUA7(;+7h?2H#5u2=OANFhyV>0LK#Pb=rcSO#5<>v zQVYy8bL`)vDZz;@MX|D`3Yooe4E$GyhA||{k(Gq(>a}G-{XLtw z>s8+{AjDl#ZMAOvQG3(}uj1r9d1#;B%z$uF6eM6qkzQlPj&_O%S;H^>RLDpTVAA^~ zd4q5rquN)#$?K)X>uyPVpw#_WYY&{S)ouv!H`E@iN&nE7)mk8AZYwTO7$wI+##3_Kjsj5@@_Bb0|K$}rUErTnls;ep*vSC#?{CVWH zl#Oc}td6-TTsL_n^N6j8s$pnFZ2dRYiqK~WOrTaIoP7=gI+VV3LJa{YTdfVj*^tl8 zGz0}0LyL++w6xP0K4lF7#h;GRYbZX^&iaOc&eMA#JHqh$yKD%~x%{Sj$7$m%m|=XN zi^8xz&iQ?;D*@5zj%c=D;RS){_h3l?M1jrxj^%z;)@@BOEhN6a>S-@ zP`?_C$^;ph1_JmGg9CA(Cqo11hYPBc1U$z;Sdgv{CJe2I;6+4Q!4en&{hS~$b)_53 zdTd5OPHndp!a(URn47m@QL&+cmnzwQ_bNzy^USLStG_ zn+Q%9os|MS8jNM~p}Jw?BVUzP%#N`y*#V)ybQvD&9XN z(2a12ai47#WHTa#G|Ofw*p%$yZh|&(g(R(D%0dbkMv7UP6cym%XvG%A5cHRB2m8Zf zJQy?d??QVU~N`Zby`rFB9zLek~vWUFmpKxRbpPe0Py@z+6$#V;A3#n zqA!U@K12Sj)C3G$O$pFml3LJ3Zmh$wCJySFtR(`hhnO-TXkYnO%CCb{lU3eMfJjXV z5XoY7J6y|mXs)8HcI^8QS}6oY0x>Gcej#WIpRdIbwUkM!&!Ju){EzE#%xOv)>TX8ad}jpkXZmM ze3eJV4|igP%dos08dgQ~)wf`Ckb&oG{?l^avY?8pL*{~}!W1gRDGqez6nY}SmVzk* zi~g(`bA)U&r98_XGpu)pMNSocT9)rvaBMF6B;b0U&Pv{O!7_j;L0p78Z$lGH!sSdB*O#+4M^Avoa#C0bR!B^9XJ?qLD zlhabp)`fs&W&6-cz%c+oC}%PV7kif~G(aZy+4goprIηSQ4RS9XiB_d%=?g8)C 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 z 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�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�Db(n$f zV1~t)9cdqq?ZYE_psORk=CH3LkBkvyR7KiGMzM{GVjCUBHip>78sm)d##m#5f$l^m z8k3C4#+djtP@Q5-HKrL;jOo}q!}!^lX$(|n8MBQ!#w=qlX6G3w4TSG4XB+gJ&VlND zV}Y^Im=9j)47e$za0L5+O*xq9%HYu51*&^8wZSokaih|jU#}g_>6MQIBuMP zbjZP;bY)Kw_Ox-vIBT4aFXLl8E&H5t-nd|#GcMZdyacsgf`n>5?-I{tM?za}yOd~e_XE-vJNuBZkBrC0-!3h5 z^0b9djHkvk?w^$Shg*`_EXmE!%oJvF)2id+7JKcVnUSL| za>ilIkDsSwK5Ao4F5c#TrshAoWpTz6#U@0tiBW74v3X5&ruLde8J}5#k>hz3rDCEp zwPKcK{APJ3V8RnYfDndiYVfI>=uEAfRT;yq&X~Z&nzfjeW*sJpFO^x3No^+eCG*v1 zkWB8Y$)qtG02(qWd}+O=>o~b`4S_U(tnaOmXsVPU#aG6bX zrp|1(CUv;|1|$o_k;N?GE9ooc%ZlsBW;S86nb}Nu+a%bsn^}BWec60pkowV?I)_8c zNwhD`TxM?5d8VcujV%w3EsvSkq-E1HbuQ=^>iZQ$@s(NCSIt-5mk-yJA0o_eq7(-f zYAq1eS}?*|$V6xALh)rs+6!ZQ;fNl{=!j2+eI0ph6d|K3(pEHztymOW@hG+uQEVln z*h)pQm5yR7Lu_Tua%Oq6togNxt_;30E12J!W#ZEgwW3+ctZY^^t6*zYvzl4m+@aPm zYnrvp8fIf(8 zvDwu8&TJB&_Q6v>&CKTJ_hvJQ0NGoZEzMTAl;)07oX-Ze29NOPh1y`Qt=Z0OZ_;Oo zI+z{JPG$$Qvx)BDyO>?gZssnCv68KH`%_8XaXaZ#N(jA=8tAS z6Wzi0HwTzMLF!`;G6w^OnCK3Es5#6W4r!o+9pTE3BVSFBJY?>UPbbyG<`MI#dDuLLt;fw1=1KFUddfU)o-t3EXEA%u zLKJgJ^HFPIn2^WcT%rb{MD=-I&w4zJ7R74xcj8R|z~*UTH{P4jwux(Lr6 z-ZF2Scg$N50kYpU@0s^;DYqS^IG<2_03P8_Ek4BDBlEHOw@II5d}2N|pP5h0=Xi#D zVZJn9nP(x!Bev3^UBFn+M*d^9ljyHO|JQtDzIFJ&v1#wj_vQyPNi3S-K8gJ_HYue4 z%;d43#U_tU5sPLxCN?IPg_H~!%5ttOPgqZ^5G%%d;>-9LPs^5Ky|KPnDOR@CsX(pp zuh4PLUYB_Mv4PlNtUoq{w^3vD*h3}@k0~Oclq}YWHDhBzrov9oCjn=Uqe?B^~mbn>)?8DcZWW{S-ao7oYIy^R@NBTDbe3NGv^^|5Ww{%2;5 z%@&(I_KVmgv1o?N5t}piOGsIuUvkIhiOmZ#MJ$@(zKYEkn;%jxC^L^sx#9i=h<~`H zpv_V!ws35b*h2AjeB5HMy|7E|x`uweQ8cz#Z1LElu_a zD#0?f%2cZyUnRaOv}*C>o}pTN_4pd`GTwfj9rm|l{xH+t#&n497~d(r1ImfcTBUc6C-)zn zJd>_PVy}Q}stb2s7 z9W9CWe{%Z8_eZU<{u|VQ_-~TQF)$hbpcwwa)E7H~Au+ZmTMovG=vXZnn#}gYlJO5u z#y=t%e@d(Yi51_-WVRobjDK`8{xLTH*m&~)YRATRjvE)>73+pbWL9+J(a8z0{@ zZbE!-tR1rMvnR5#N5?ozmm?tm6(C6 zFaw*z8-82#YBuM{3|zykwejoX*GJ4ii?e3n208;bUa$!xzhhJRc9cIvhELbMkC9HRuXKgH5va-n|~J_L%T4B zc4G`>!E-_LwhiWe#02b4sUo2?Pa5ejG=wZ+8=)){$RuyvN&rD9in6C5XMlr zWNQo^NOlYzwzVKy455Yv1I(mlkuNO#(y#y|EXmB)9HR9$H9hi(b|7HneERc z<3F2>|6DTu-;?p5PsV>C8Gmk@zoTQckkM$4vWxMTkgFm6Z{a3$IsT6b=1PQp)nWe$ z%UQx-4nO|uG_DuzHC&7EUyrbFMA&~v*ta6eyBXpCC&IoJVc(|qo%lv^chD<$F)It% zuDtHCSxS28KC>Rg{~P}>V$NEeHD@2u`;ABO?O4gyoc;GLd*N|Jgij*urxEtE2>W@2 z{UXAif%e(=z<&|`ml5`>2>W$}{U*Xr!8yxM8DW2jvx&X_cOv}nI_&pgIrIO|;cpiA zeuSMW!j6luQ&T$)*C;Lx*ElXM*A%N$tUA)cwygBb%D`phKH!|!+7@T+@k|`K!_36B zVobnDsa5{Q}JT zg!_~$$bEwTvz}vEoYniEI(j(H@jK~4Tw$&VSBNXhea3yx72`Z9igP8nl3a1F6jz!i zPl^&?%W!46FSs&XIj%fck*ma2NKBp-mANWhRjx8ujjPVp;A$do6`&SSo1zX^7oYXG z`dkC99@mg-#5LxcfN91x=UQ+rxmH|jq^4XOt}WM&YtMB+N}yIpht-K$ow+XDmt1G$ zkH%SLbmh8n-MOw@kHm6&qU4@P3E=H@_2PPSUva&-KHS$_KeVj_*O#I%J`=e9+yL$y z$bsA-ZZO(s#y7xG0*QSHi;q}CnKg_X&W(tO*5a&aM{=XM(cDOG46DbAca)>H;SQ^d z!>WUxEev>4jN`_0W4Q_3MDAP2?-KWKc%M$Bc_wj_xhdQvZYnp8rm?Wew$r&8+)Qpd zH;bFi&EbCFzE4bJ;hoFPu|2%MtAka1Xc-JOKU$9s-ZJ$J`_C361e7_mF!A`x*C~+5d4bx&OFVH0syf3*<<_ zr{v$^Q}9n{+;70VOPP21=ZVaF{7ddV{(n4aDcQze$NT(4hn)&`9G{v`!^fdS(o=*< zOPRDNkuW5#M_g*&9_J%2rNhSifw=U12L2qE5lF}5tv7roJ{_MKDGQAxE4XZYHaPCy9FjK|GROPGj)%mI@Cpv4DUV|scz#4o_z7}7bufvn$ zt94wd#nzVn^Z5Dv zti<$?Tfi^m7x4@D#niimU&=4T@nSi@g8z|U&ab5GQ*ISc5;ZEI?B@6IMB5Gg2J8j)0sDaiz(L>;a2PlO90iU6$NA&@3H~IsQ^0A8 zGyGY6p5xE)zw_szorM1af04h$U*`XyxB~Vn|0hp?H|+3#L0;ppL0;#tL*C$TK>p4D z4SAEl3HcBI59BTW7UXT<4u6Ng%irVg^ABMC%Rl4^aJJwd^8|RO8UF}GHe~>TvmyoacSCFrPH~bqug^*HsM|fAjyUm36Apa-)5AuECeaKWo zD#$n>wUAm!Bcv753F%>F05Vd1AY{Vld9*&WkVVKUWD~MesQssdHORTHWUSNIwLDZM(iYQicd5W5y~)TXE-)Wv5#p&m-B50n=g zz^(%{1R7B^7MkF*Db3Ric}R}NKy#sm&{Akc(Hfs5;k|0W2f!KBlYq}QKwF?4&>rXj zbObs9oq;aEmq1sctI$p84y^~!lcJZ<8=qeZUkQDLuc38^zpv0w=r0TqzM&Whc91Yw zAi%rGgdvbag`tqcgkg}wh2fARgb|Pgi(;Ah0%~>fU&|@VVp2tm>^7q^{w!o zK!7)q36li^yo*ej0y$Nf3TbXVO_&CKx-cDbhA;zirZ5w7mM{x)wlEv=dti<*NBBXQ zE6fw-!&)FLfLtgngj^&nf?O;thFk(H6_yIigyq5t;YV02fmIZ%g*Ev68m(U|tP|D? z8-$-IHiF$GY!WsLTZFB`HeowWc&C`~v+xV8aR+kz3hboVCG4g%VUMs!_)XX=>=X8b z-vt~H4hRQ@L&9O<2unCV2}j{OCLDu2E*ytEA)J6bDV&5nC7gmhEu4lNBamJ^Bb*fo zaNH7p7YHzah4YXXgbR>n-iyLT=$C{`ke7wakbek&Kwc59Kz=WfKD#Phh5S?a6Y?+N zFUTbV>7jwbK;fEj9oh}xZ;G43Klt1zY!q$@x1rq;?h5yW`@jR?U*G|_hr%Ol`4}Lj zKcdzXSl6lj6x=h4=fVqo{wMs0(q00$g;%hj1FwNM6e(OOU3hDwi{yFNwUOpX0p>l| z|6K38Qc=X=lO!B@vCV3r7qAtx!MqAg?FwHHrlDFI7dhvcIyobyW&U(bOV6|nOnbww zfip7h1EytST4ttYVOmzEWn)@)rsZH-PNwCeT5cDKFt_VNS03j1h-vXui+6Fja8Aby zF2QA9m%Ci#EGD`{mw9FGc9HX)2R-Da@VO)xIZFFraR9E)X+3#depkRHyA+p75o9%K zF5N{C?n%RCFg@f7F+Hy_Axvw|yyP|;P%RhfCKxT-L{s;er~tGTK%y}GM9(`!(z zCM~U|D~(XgRohjEdFwK*9@Xl(>bn|1Yv^i-v5;9H_lu2O&hvR=S7Y?v1$t%G#O1uk zX^PR%jH0=#1sx;JVb!N&q$Q1^r7M@v%GKJH;A%q?>EX82)7Hh&wzgw2wP#ufs&#O6 zbajH(k(Sq)Zq?Z(3te1ay1KFy-e=uh-PrcsUEP`9!_|Z7JzYJS-pkdC>AhXOnf{gQ zE2e+yGDjAfRefAvy9mPd^>y`g5rpmjuKr9P;2OYmdwJiuzG3`8*FdHZat&hoVAo)# z4{;4)dU=;QQb;UAT|*f^%r%VZ!(GFfUc+UMg&^&>plgI{B=e18+Gwhcc8zh3g*MJL zF7cQ!-bJolCb%Xf9up?I$TiNl*c0DTOma=8`+E|sF?9b-q4i94O><2LX1HboGid3v zsAsk-==vToTQQq)bHI(Xwe1HM+gz&6bHp)SVytmwSl&7J*;_v zd1m;DMYfS@8(o`Ro1txCk!?j}pH~^R@2|En=XQI5B%f$hv~Omx0rt0b(`sTTz8m$*L9ca_gwdwe&2PU=?`2F znEo%-9@3~Dy2c5QT#sE(nD?pcnTsGC&2!fa7eUzm&-EYEU%Fm0-Co-(*DJ=qcD-i$ z8`m4Arw~(!;WAT-DVhF`_zu(GrP_NUISRZd{viBMd|yn(ym4Y2(^HG7nVv>W!}PRb zTBfI?T6$VqdU2VMLCh$Az`U85mYHgq#Vlf0XxYSUVitZaJzHcKo!7iM#2jLFzOO*; z!*hzx`+!^`p6^k7DCQB#6~%|JvH+eGAJG^-5;qI+A}8{qKojW=7xlQrUuauJ7L%K4 z9;$gnujqs3rR7ODzL+NX`~*#5Uq}t}WUaK3lYgza7w?qJ!9xwyp!L`ha=1=)@xH zOtsEp7x7DIU0Gz^5E;3L>CWQnfw;(BP*0T7i=wyq6)n3rtS+?dKC~~s7W<0*DEi}* zq;z=NHbDGF94HPDf1p>pgTx`?P;qc#BHt4X6Nif<#9`uM>K`eN5=V>V+1qf@{$xM$ z8-_8+6UmLG+&FQ(I6<_36ETr8);AH~irkNb{{6)FlyQDPG0V}4AH=!hJaN9bKwKy;5*Le0kmiWX#O1&Wkvs|i zQCummLRu=W2D@5Z6T#Z^u4Q@c>^jD-7dMDMiR%+fw)l;d-fVIWW9%Sqx@vwL#N(+xtK4}Y&iO0ng;xX~0BNlrbk4KH@a8!0f6!sMM z@M-Z(#2$XcjdzTLIpR6-ck#S<0b}8!cuBl0{vlpLI*VTUQ~XQ32Dw7KF5VFT7H^9G zAYFy$7WLf1esg-3flu0x3EXw@ws;45?}`=pd!qTR&TY~Dt&S%J`F1QLpC0Elds;wv$QJEi*__q*O!g#94jYrxoy(mY|A6nV7@f7s%jI_V%!h8$8y~tK zis#6~38ZxRCMS=Zd{36g{gFG~U6JS9yjyUaJ&UUXx9E1eU2c!tOVbwK=O*76`rHz2 z>92fp->>-HZ~uNJ5W^?O@F_8T>RWxmL{9~rYm$Bsq`h^!3Y;FpXTpQF^&oXg(wQUi{P`Un|ym()ZLK#%-xv# z9858HGp@M11y{md(%p(Hvzmv^`4D!41UE4e#xL{`S= z3$F{Q;;stTloneJ9D&7Dhecp9HDD1~Oifq>7E=orfyLB@MPM;?U=dhMU04JrQ_pR! z=BST0)ps{=H*`00H%6<>?^c_DZQ^d~w!VuEZ`+LJA?tQPK;?pE#==pAdVi^W;> zwRFs?aG$kyCm=dgT2Z%gw*_lTi){yvz+&3NBCwdGJ zF3AA~2c0 zw%YokwtnvZ?g8#^+yfo84RTn6nKi^c)IH4YT%Tld);=5VCg05uclTf;e}sD^V(7_{ z&&qCdt<0*3AY<6#5FJlMIVan(^ex*4~89m2Nn!}XQbL^ry zOc_1LZkofCW{y2{tn6|BMrZ!sC}SmZ|Lmh>m@;}T`)LkSM$d78<}hXS90zF*Q<^yr z(MS)u57T4wktmTK#j)oo#?~>6*yGqfe0)E_Mlc!ACz*B1ecF8{;>c}r)_6WkkB(`x7?*TvXUWucks7kK9ZVm zvzm!@hgo;s_uTg*;;=X?jt90l%(mO3xaX+vU-ZXAFpu1i-A~a=Pk?9cCp0B+miKu~ z?~5qj|LFPRB`rIe{}uJWhTpk|UfcL^FOoh?;7&UtAL_73PmvyYgZK%otBn+%4jh5S zq=ZFaG4H@4u$XsY5m?N7um~*Xf3OHFrXFf0u$cEfsgT2z78~bD4c3$vn+6<##iWHr zU@=MSOb0)Kl_xzc0*lE2i@;(s!XmJk4`2~kOjopoz+y7NPhc^bVG&qN7FYxp(-&nB zByfdsf0FdhE34=2cZk_y__D|F<%r?S`Bq=9L{IcP#N2P)t^)UA3}2oYzK>$~;+c>0 zkUK=qQw~dTD{?|2Poi{r$Q`1~Q;icnH8{5i&pZGxh0i15)9)d7h<;B)F5qd*$zT*u zGfwri;DR2_(~8qQh9~4{%@LW`)0E5SX#=$7@_Rn^wC4(VKJk2ty-8$2kF`$=c?yFy zrNtHjM_@5UVG&r&XRrt?=5tsC7E=rsfyESuMPM-{U=dhMNmv9XQ_5o<5lf>@r9EXl zWj$Yb%Ar-(*@@&S@2P;TOlg%?(NhVmDJ`}#I0B2Q0*k<6l15e)egZ2`HCO}|Qymt8 z#ngaBU@Fnv^`O?#s*3k`lx_P=`%yx&byQc^3iJn&dIAip3SiPC`m8XyA zYmf6ufW=v{^`)`(_4KoqZC#!8!7Q@!_D2-`Jp(-7cm{d~(V7M$!oePr@XHsQcSua{ z&?Mer9&+b4EV1mQ{^8U=JV_5kUR#bpJbgJ+M!zl`NpqModX7;vhbg1y7)^7SGJ1|N zG>0jp=NLdp3A}LY*6dO`eTN3EXD*w!pX4v(>YW##0;Y7RGJ|YsN`c>(@ZHj{j$XT@>A7Khn(dldT|^&Lci90GILbHsBDy>t{f?m0?R0%v(o z#Pptw;yp!=(WhzI(fntq|1A8@J#^N_hkKFqVFI_x5qVpOMS6<#z&XTEV4dNAhecp9 z=V1|8%mr8k7IP66fyG>cMPM2+<--3F@M7%u$Y^$2rT9wSOgYR8y11Z+=4}5F}Gn6Sj-(*1Qyd27C{2% z!?n7IJ0|>JXwN;*ea~Ib1JA#nN1n%?hlyzmzKeX~dFpxQdE$BQdExob^U|}0e~RtM zO6zU>D_ExXnrg2-Z*X(4g-_`v_XH`u?|8{-fp@*+H+SJR7w>t=9mRXz|9QzbneTha zFYm%@Hd1-j^Em(H8so57pW`++x;H#1UNZvvOqo6Vcuo5P#a zn+qw6H>=lvMHKeuX8thqp^eGo{m2{d&4Y5HvsUSxms}}wUfxT-^AWu5IhR+&*OvBQ z;gON=fyX-HdYR?(N?yO$hZwEze=N>AVn~iwL|zk+Yp4L~BxeHho91YKIT^o_j9*R0 zzf+7|e=wQtwPgHyGJYc&e<+4OFZIQaKVOXP$(H$(@qe6*zd$nnPi+2Az2vI#Q*R%x zptm2sy(RTq(HHWPXZ?k|1G&Q9!T1K(8h1t5xFch-D6>BEe(o*ib>0_QoHYuId&zVD z;@&Z=Wa~`-d9ow4gslb9Mrg@o{H2odmrllCCWgN(^;vxwExs>eY)`feD;5nyrF z2&hd*Ky8eGaLLvPsOcC1A33b>ubk_6!@r9TGj(lDJ$l@&=XL(>*m@62+M=)9VGpziYp=V5ZLgTWXzyraI(a*LyLcmipKh_%e)^K0AHVe0sIhQP^A14H0sr-LD|*cpx< zoJsp>rnkF$mUp)Ids`1${WJ&L6If-CKKcO+fs>sJhQP_r14Cf3^BqwvpiwOF_IEGz zF7hr;62+1v%u*Y(%)8vXBFT)kSZnY9NN4Pin6c&PxA`kEA1k<{tz03qbrqYf#9Ga) zHQu$}brEyd;;gy5)-hX`JFEn5J#F)P?`Zc1?@!*1v^|^9N1MEx=*-7LnQ zYp=OywkBb=*_iF#pS`~%=@W~!`sC+`xOUKbcX(&IfA#M4?xJPxMqInSB#}Mn%&{lg z9KS`$vDZu907I z7z}}vJpzWn$sPqm;ABUDA#k$CyvMhGfs;KChQP_r07KwpFMuI%vKPS+IN3{J2%PMEFa(6XOh@cxoCW{D zNV@{op8F~teRk$g8}paPX*=(so%g*Dy#Jz|4}nMChe)I!9#j7l_%C~(dY{pL zm_a>HY@X-vn0;jW%%jN*Tc4QBe>Uc&_m%f`l3ub{tC!x;UV1}&DTObk&s-BidMUhW z;~ifc?j7Gq{#_sG!O{E}KK$kx(r531wXW#@$E^2#seEz1)IR&O1dFqJFpZBqvqodzw`otm?%CPdM_NDWsr{!8)23kf2lrakL#z|TNi8CXMj94Eq%PP-$=8?`(Bgt>p zmB|;rb207A5q6dcJ8Oi!6i+3r=Ug4QY!UwK5q6FUJ73Pc@ zkRl@VN7#V~TaK_5hpoa2x5kVs=|u#E^iMD4ujvAk$OKJ-aG+x>2S)<>kr zK4w+{-zUCLBYMo@tR5>!d#oUOEL^hHW1l4Hu|lkTQd?nW74a4IedcrC$yl6KTTw@e zuN=oVytBqf-j4RUPvFV#y{+F;n8%`GzQ{Z4;x?v)ucWV(FY=DfVy&Z7Y1-oO(WwlM zPUaCjd~6~uE6Z9&tS^{V&R5=7A)=iYXSK7uqn#xkRsvViM_N?LSDvrztKzHXtHf6Y zs{5+aM2_P295s^7Q8Q|eT0U}hRh!llJx3jyqb_npo-69wJkie;W`EX0+|F}yCBA+X zu0a&8VHB=W60R|rq;)h&lCKiqGz!-&3fDXe*CLT?N%u@k^hhg=t=3@ebtl-yn>i-i z*qFAycE0vWMxe!7Bd`MMY!%E;<({{E-JG=S1qYXWLJ$=2uUmti(>GXUJp1{fW1w-Iu`+*^F zvi-pjIN1SU2%PLUUiyiE<`agjiLfb#YSH?5cH_SKOwinI)O7f2I zjYJ8iw8|l#QNGden9}JP1D?Rijs-*DWTQtu4*3Y2dB=kxaIzD?5IEV1U~~-YEOwHkpC;3On(V9Vnc|!3n-;a7rek{os|?adGr$ly*_mJnoa`(x1Qt8n5ykg3 zitl~RJ#&0N_~s^wVqOwvzKvPnTj*PqWL{dVH7*v@dAS(#GQWpB&0B(5SHKf(WfYl{ zOWB+x)-q--_pR{#7%@XF&YGbs9CLD^!%E;*(l)R3b@r_Ct@f>hQP`G28O`N z?gc~OWcPs~aI*Ws5IEVvU}fCrPWB8K0w;SG41trK4TgZQ=ja$chqK%7 z7+dGT+H+r^_={QB zeAj(9B90LjXB{K1J7(Wyhn2wnP1|`B?fl1g%Xb^?yaU|z-9aM#aF6=$!+*~A!1pih zhe_0P-{yG;kJ(42&pduSvh|6{Jhm}Ud{2GPlJt_rTD|m~_R@3OOD}x?`OFnhq?f{L zl3wD@<|Up5yrMlA`7Gcy>od}WZfi z=5fw~$hRwqlJ#~4=X)32HjhX0N)ywPKh=$FV_9|F>u zh_^nB$Lb)w5nNIvRg$Hkq)CPplJvxM6~A4`E9H~&OL?V_r2^6?(x=i@E}ulUA#Zvw z2l->YQH3S*~je#aWQ=l2p9B2Ww1X=;DrPfk{)CO8xpdCefsRKSc zN*$$6QfFvw;J?6kk-n7d?@aCrPdAG0QV)D~M&8UqPpOw=f5UKZskdalVfZVl4^4Oj ztMs*GzSFobw(AGET|Dr#2ZjN| zff2w+U=%PK7z2z2#sTAj3BW{YqV%ow9kfZnWQr-$RD4d8rb*MK8PL9izppS;nkCua zj5`~i?3d{XOX1{6F$bP0z-ElTHTYahu})eqZGiTZv{Bk5 z{UmLcwn$s0Z4&vP9owZgc(?m@>1QeYKOFGZJmlRV{UYt4Tm35Sly*tKO1rVu9_a+O z`HlMV20VN&gWZ(dC+(LGNC%}u(qZWc(q4@3W72Wygmh9mC7nh(Djjo-z_9-e^M{$U zHs+l4yL4VUhjOB`R_Pa{i_#_Of^=E>L%Jf7H{V;=9Ty!nUzOT$SEWCtzi@QFCS8|q zNapiNyyaH9Dg7h;E!~oC)3k}ZBazj?cci=0dhVWdUwR;gR|qHlec8X#+kap7Foy3@ z4Bz7zz9-E0RC*>om!3*5B(j$BKk23PN_vu*_H(bLH&P1!Ybm9lc;E5A>wnL`pZlNx zeSa$d|NL>3P3S_X~cPpZAM?vdYx$_xQd3BZ%>hWVOWppF@1Koz{O6kzo7%0l#dE<^UJ4 zS*kzi*ZjJF0p2}j_(T4@NQytd|6`zlpWGLJ;{ViN5GkL7Efke4%-ACSqW;hPMH0(+ z8*i2Uxxbjdxc_s130s{dQEN#gQgg8=@s#qH_LuRO@|UICeBm$ee-uZ^>p5bv zx3Nyth-yY1k*4wD))Lcl5VUtmEw-d+i;f)SmQc)XD$$qfzG=zAiC* zU&ioteXFlqq9^*VUAw<^y9!*77`~n{e7$1$dNbcw{<8SB>sS7ATpxc$?(0OJMA_F* zuJHQ$t8xAOHMstMTwws;Pz>}B!slQ=xi%c^Z^#YtH|B(1j_Ke?;>)=%C% zW7ZLE{myrY<2%3k=9x*fj>-NG++_b`e*!lJzA1k59XwN6{p3Dqn!}pTtQr29{#kzK z-Jiu-vCXEj&GvuqC-3($%eKzWGtnGs{R@-yzywEp1F^4@&bCEtR3-H-j^SGp!?!esZ`oUY%M(4(XWNRm zZdZZ(F@|qt4Bx64zSYdPhR(J%m~Cq@+twxWB+B)4wyno(+kn~j6Z&@}u!&-`e+xdh z(%H5Zvuzt@+jcNNW48T**%tKdz-;@~@BD4~PK=VB*lYPcyD$oN`;-2*d=DKR;intF zF>9}XpMQVEY_m9PlpdfX`T%BIxQ=Mg#1A5lgMRbZ!iQ)bhcT89`wwFzAA#?P-~8S5 zQC2^hZO0teab}(HpY)%Km~9qk#dey;cG`c&PkxhamTk?plaA4R7Ezq_o4*!5M{D{W z5&rHcDFGhKdp@T3LK5#qI-4#gmYvjpiTW=k>4ANY_%>r--^5)8-aYOA!+*tp*?-ml zr~jJ&y8o}lvP5%x5KmJ?(+x|QLP2Atuj@$)p;qJmRwR=>%=fCfN0PSBo zrvCLm^gqIwYQp{--D5f$AN!y9pJFuDWdFVH8A^Ok@xuR~pZu4)7qIRFO}SV8*Zw#D z6oHh1cLMJsy+q&rFYtaKRUj^qI* zx`IGT~oQ%If4F4z8XZ2yU_&$xX zJ=wBgGX6rz_zNfFFJki-r6ZsyM!;tn0qY`0z~^)Xe2x)N3?twt+XyJm_Bj~=C74w* zP%2P5Vgy*6H3G`e5l{vrAY8IF0!leXKn90JYBNhIi&ofI2!0V^my58=N7(t$8vFJY zBK#F2>`D=K$U;S$;K#AMbC8uxmuvH6!d=)UF+9!_`KQ)IpDrvh9z$Y!8qg zs>iJQfd+ww5&Og9to_l5o_iVvda#nM{n6kpd!TVdgiRvsrV)0t2)nt%ZUM{LV=W#2 z4qU4UyLE(}5Mj5Wc3bpVThapb$xd63wP$@qdaMJpItDrgI!E-F#aTVph4xq%^jNrL ztH(Me>9H?a`J}e4%<2~C9_SJ1hWyrf+v2R+x;skz-SI56XP{T0cc5qBt3aQ?*MYtP zJZlg14-5$O3w#q8NE4pCgB=tY92gQ96c`#978nkUNKAN|9vBrE9T*uH6Brv92jJN{ zFdmpdF){EhKEDf03QP`s7nl;58kh#)DLc>$z*F+T%)s=(tibHR_mFcEBMH|&o96i; zFgGwS@IzpJU;$0jVUuka1{MVt2NnjF1eOMt0m~E9ba+<;NXaV#KL%C?RsqvthwEDn z%dB|~)z$>o2G&7aPor2L*nlX;27U@`WWBH{uqm)PuqCiHur07X@N?joz>dJLw7gw` z-GQBfJxIR=_6GI^_6H6G4h9Yd4hN3Fe-t=IaXfG$K(yn)$-pVd(}6RPXMuCT@4$KB z0&p>KDR41x8GGc9z=gooz@NZhfop;5fh(~84%`g<19>@cD{wn-137Qna`&bCv~S=J z-JhhFdqcmAbT4o}@F4JS;9=lV;Bnwd;3;atRc+u!;91~5q?dtLf!BdIffRB|`5pOP z`90Z_;(x&V6shDmnP{ni)N&fgv~oJg^gsq6Bk%!`3CJvGku%F#WfFfjIg^|nc6K=j zvvbM06_)IZXlBWZx8%i_0bCVsc5j zlw2AplbA>(W#upAa&lR@yj(%92n1l0ZAgTbV3}HFs#TV&$W@_LlgT`+CRa!7HRPHy znTfSzGJ|W$wGmYvxvos+a6KAnJ-NPoKhQvKC^wQD%T45_ax>YAxw+gzZYejHTgk0y zdWPdewsN}|-u5wk9p38enCKaSRwRw5(_6P25$Mc(UF0w2u5uT- z8+xR>+(YgucTP;1Cd9n(W)l0HJ7!H#@*nkbXKI7}WckB~>oqvX-@7~YRVopyUG+OiGU4*U%K0_*^O1$F|v`L=uq+I9Hv%J<~^@&ox_iicny$&Y0Mybnfx0{K*a z3i(Wa2Kiim4*5cU0r{W&ALL8MVp`=vaQQlSXUp$rnDFisbD5(?z zy!AzigG{ZYhBUWMqojeJR!Iw)PDuxuUP%v`LCFA_QOOAT0gy?_q-0jIC|Q+ku(B)J zA#*4>Aag1?A#*9YAaesBDjzC&l#i5ng@eTd0)-3zL<;}Y6s>nF9>uHp6p6wQHlPF) zSy2>K2`U;*_`k)9t{Ak&5OU-N@=@ehK33@QUn!t`qI{|pR0@I54-{4kD@Bx|%4f>w zEa4ch6oaq0QXH~`QUbE1QWCP1QVO!PQW~<1QU>z1OnR}b@`Xa+Nl{KIuMpt>GAR`x zD=HNs&AgSAO3*7Sl_9GrRUoS>RUxY>)gV7mNS{?#szcULYCzUhYC`5#NDn=fAIi0q z+R*9%bt&p8_3??XJ(UJZLuieZ#!3^VDbP%54m1PTLTRZylv@F$^p@0W4XZY-Edg8` zindBSe70BGqqGh{L!~3^wm>JKGesBWOMG^vdAcDF$_N(4W)D?{DMOXv3c0ozp$tKeQOam# z4CFTona3l+jHS$2Y-KXzu+2DSJln=z#{}fJ+Y@1bt9++Sf;6Kb%w)<;w#CsOj6Kf& zsMBswL0hH*)0F9uGk}@EEMPY9JupX^qx_)Eg*FeEPq9E*h|fjJB4x3%1lnBqmnzGY z<;n`>M~anTS1GF%0<0WS)*dSMa@7UPHc7-aw{MQ>dO4Dbo;?HI+($W1<=dnOaQ^Y35C%rh%STO$(V$O$V7?O%Itt%>a2& zA$^un%?SMi^#jOEY9`3j3hAMh%1R}(ngw=NAR9$?H3vSgDc6*oYA$HG)eqG?>PJAl z$^r4CH0r$9lV5KtH>0u%*4Q$JTfQ;Vr& zt`}E}swH5TP)jnqv|2_jt(H~ET>nBXg&gJ83Tj2jVk(*0<-k;;OeJh(GL^ASWwi?1 z#$HENHu|tdO&@k0niX=1T9^43s%TooOp`sw0M>>MR=NENnyeC5dvlBZt|?vsv4|XWAU5{Xn(3D(R!S z>O6Hm^DJQ6LaHrP7paS(Em4=K=AH0Tbs0wGa&@_C-V3i#e}ugfSVgf~U89ogqSdf| zgm107PF=5VP=BJ>2zHaYStY>PMBM_pRox1?P2C2$UEL1(v-&gSFX}IlJJcPJzpB4N z?gVzJyVTw49`!eMFRXp)ew6^{5%r)-fb)oY2=cIc7}DJOhQLn0hs(-<{ z23)7Oq5h4}W$2@u>Obl&^|pG4;x5>G>OJ+o`au0xeW*U7$&=!-`dEEJYkZ0v&w%F? zFVz2NFTPY?s;|`7>Kiph5Leysr3|JFz7u>m_+IdTEa5y7d>{T)!BmiO!8pj&!PJmx zf@vVr2Gc^O3#NnIsghnyAIuOWz!@a?L689Fk6^ZO8P8kFemg}!Ca8JgSjCutE7iEsvFf0gLzAV0;& z!2E(jum|i|up6L~W6_{QzSdr3Vi z1~P3B)dmFz2Zul#Ov@Wew;GE1JuEmpID)0{J{uVv$+jOA9L4m}!O=_~6CA_zvB9xS z9~T_Q^zp&*Os^a?M-~}Z6M_?i1mXI=4Sp9S2-}l_lbAj^IGO48@}>l*Fn(%qD$}P0 zr!jqca5~dx1ZOb4Y0w-gB$k=MnT($moW=Cn!P!i28#Kp4N!o8EgWm_|Fy9YMn@hF1 z!Fj>?&=v$2U>uOE!iDr0v?#a;`<`4yE~ZDTCD;>7DV7D7)BU{+);zj@R?vEW46Y2W z0#*mt0IO-~YpG{luw-yOV76i%<2HbsV{6+_EVhkQ+Zfyw+zf3?a0`uXYj7JoqHPau zr}6z9{DmC_cLa9??*)EEypd1+b|TJQ6uX0ag5){=Z^7S!F9Ung*2t%ayOD1nu%F^U z@E~pN0a%*=^E%=Xi}Wzn4hN3}k3u`fB0ZjXTs^_!JehdxK7~?FQ=AE&rDdOib%d6E zj`q#(!Slfj6c_PHQh20YV%lY<{lT;=OuNdoKbf|YUH|>XxNA(i&a@j$`G43|g?lA2x)9x|tKGPmB?O&=rq&@I3_$c_8d7d!sDb=0^p9P;odl7t*IA;F~ zlBaergD(@u?5iMozV{k^`i3HfmQo|fs1zEmCTLGTru94yzN5XXy{G+8d!Hhe7Vhad zEwx4vw$o^7n4VTk%k*?wI;N-B(lb4SmVxORwTw*vK>L8{nY2ty&rG!}8kyr+v=mBK zEt{5|d2?tvHG*(7xwPCGLD>FK`;h5*v^-3=*Y=V25#!^vc&2k2$8=ufnJ#Do(_Na2 z=_1wKG*Y*gS@CFI&Bwg>2^!P!vjL_DG$iZ+(mwn#U7?yvOH;KxN>I}@op}wWg{T(N z@@n~@<=66S2A))syfPjAVw>l4lS1out7=u5UQMgU^vs$$jz}+8*J@}4;reQ7wKRgT zU0bWo^g3D{rrXP_tJP(EJ*^(o>udFy-au=>^oCkPri+?6Qb;U~v__0?tTkqO6Rio; z$%ADQTPoUbskEkAGv;f~v=&rrp|#XnL2IqG#yB9OAVF)0CnRmOHrV%M&$rdc6Wn&# z6YVKFXdUVP?f|PL-9MdZJ)N~K+Lu6AtsBslmfoFudT6P%o`Bhk9*pY+u9>ZEy;*Eu zQSB?OkM=dRzFJ=zTR*M87JkY*KpQ~g`$ik6g`W%$(gtbw0)r86-`MznYn#b#{_?T5{<*3f=fN$XjuZPm7E+qIvyUnq94nts)GY6Rg{ z?$UNKeYduo>3g(2O#e;$jp=)}y-eSy?PL0WZ9mfwXa|^nkZOl$REM;!%3F2fcOuwLAVERR> zU80d*(he(^wLi2g%zIV4%Je_AKbiiQ_7~HyY1f#3ooY8|X*aa9%HP^e?H}g7#kAX0 zyRF^P?n1k#-NT;$gPv3FYtE2XX?t*2&s8a)lu)9PuNo=#84^n;o?j!4bv^$a>e zxQ>ka2RcF6&ZK8zdS*Q{)9vMD(X%iN>p`946@Mcq87xOMW) zfd_lSOX0&0?a5W84;KE#ANG%*?wpjBj%RzjrY zJC4dM&MJtLd~Z?}rBtJ+uGgStSBF)emR*zfO)b5)UWcMCK1m9Xw0cad&$I?iYsj=l zOl!=vCQNI}v~uhTZ!^XiVy$`%y^=^81oveoGqC3}M_0Y-cs2+MxeT3Xg@2!8O_tC%B`|ADl z@MbR_ztQ{a1NA{PO^^rc{ju6=Ft#FBLsRAG>zIMQkCx_xcYYRXt^ zi`M9C^>z9heZ5XrxoyyY(w8FUZyoj7*B5Q1ZMW7qZGyd7-=c5T?dy%UQO3F6XiG$y zD;;*yv9+D;v!uSCWB7iF;oA|z_iGH_&KSO3F?_q3Z;$?)zE|I)@1x^xzkWd9otSpY z2lYexVf~xX$*L8z(f9p5(e{}m=uUnL{)_UF6@91~++xk5^?(XXk^!DYS4_E}Qj^SAn5BzmH+rTp)$+l>gkjNy9~ z!}mId?+x>%Fa~2adJ2QAMo(!B3tYFY`FzJ1h1IO@7-Irs1MeDR;lrv51M8S6-ZxU= zGtMBZVB-w38a=f!H9(%6y$?QV-=s0#elAEG!>@@{<1@p)0_$_iI9Fg5jVLqZu#=9FVr-u!^%Z}suY^%D(HDJom3r%zBLbyk z_{zlam5t&1g89nP*;S6tuJV{&a}{$AkT@&Q*;Rqgu8NpllG)tFAgLVkb&AOMrP0;sW_*bV$ZYOz^fY=TCbHhCm(kn!%IJj{$ZYOo zd`-*f9Z`mJom5}siCjC?k8=Hu0me6meLdAc%2?~E1{s5mA;utMD4os2jNwKp#N5JB zpM6c$2-u!q<8Gy~ z+E|sC$ojK2##&>Yu?8`aakt*sK+9MgQHFEf+E2(6xt47s}%Y1(zaXc>~_K4ZR|0AGwf^L_EN^V=50?znVTK< z0D6bN&)9DqF!mV-jYGy^;7DScfa{T?#xdi#anv|roHR}W6Xat!hT?Z~SP^#`mZ_bg z+8N`laSqz=^vL_Wah|sB0*<|r&oeI4rD%Gj9lM=P2@rJ`R!Rz4>9OK(ziSV2e8Gy!<4l_tTi(f-${?VtNb5^e%{bJSY-# z+oCbOpT+ck9@ASarnh)ZZ;6=R%Q4$qGUm3WVt7j@`rdX_E)!$hGIH6N-Y;T$%f7kHkAwD^REf@{N^~Yw#!Rx-;8bBVsVdc~hN^|CL#sh&QjNr!R1-6a zJWr@aXHu=inN%Ayi9Cs@gE>=|qF$&zok{gzReQ@!YG8{x=}c-E)7vPfw{c9b#Lh#} z=692r+cu5iZ5C>t=zZJxXc1%UGIGn9-c~Watz&ue6*qCqby=_q^9UouC^!ADA{W_+%Z%l8$nBM*|y#r!;d&C@<-^AQ@U`+3z znBKuLy+dMphsN{{i|Jj(+8gJIh~a)W3vwAmU$v$otBjsAU($?$DkP`PO|mtS!KAz+Q@dq5brj zw+~honT|O$JkZCSygoL zr36tFR6;^fSQ>$SbGr+WkPhjP5CrK)LTTxg?(PmrX%Im_a+eh86xgNt&dlBS-rTqE z?e1@W=e?Q#oO@>G%$dCR&OJ@&Ow3vMKNoW@=6npE;hup|EKnTK1)z&Smw+zET#30H zbCs-}F2!7n`4{Lqw2)rafb$l`eFv_;3v>_YKG1`h2Qd$0 z9)a&apvQ!s#5{%n_?Y;ZgqUaGdj#?8OFfTy5ktQL^%7!U5qcf-2L7J`@2ygAW1@WY z`$B1aX?*VcLTP>Jh=Y3^zVtr#4W$e|xSIi#kx(YzyFNT)&+NiTA53ers0cU=S`*XPd3!s8Pg@B@g3Ii1ZDhgE0R~(MlEtf03-rQK(bHvDLxf^8jw!N@R{&$ z`7EF9bHJxU{QVN;eC2(QsB;f0K+IQ!D*7tHzXQC5N>=t&@zJLQeC_+%SAv`pP}NtB zIC#RtSKa5HOHc!@s|i$#P;FlwQZIFVb$#`G_>_Qe0Iv;H-&fz~o)XZ&=Xda~Kg1Y7 z4S^Z~H3n(|)D))Cs6FP#2)CK;3-ZeBFILz}FL~7opz1KJeey*Vot2_apdvK>V_j{e1&` z^c&>^A!ZPvpL{>V|Bt|Xx#VEq5Fh>i^-$kXpZosxFyCJ$8aNRFJ zqX>=mjUiea>l^DE=fm$`j|Y4-&;;KEpZosxM4#Wm9X*KY4KxX8GSC#DsX)I1O#_+^ zGy`ZR&@A6<-z?u8qQ#lMdA|8T3y2oy`eynT`4;<@!0#NQ#f8ARnBrav*DnKF4zvPj zCD1CM)j(^2*80}@*80{H*=u|oeVc$b6WJSlYkXUM+kD&McRi841vs}+-20#x{taa8 zAhgrB%eNbRdwhF+zx($1_WAbvrWQZoI|$ezA3p2pkngb1e|i&{GJ-10?`G19}eh z0_Y{sE1=gvZ-CwcMWLI%G(c&A(gCFh$^i5ZP)49kK<@%&26_+ZeV{Br9{_y_lojF8 z@+0&y`UGVIe-4xrWk)#(ngikd!W)n%H_C-R1uPFxUPAfMX9)Z90p&-JfvW&|0>59N zFHk}BIVyyrQDIaB6-C8>qc{nbKqX126#5dCMrBZ06a#V5KqUh4I5!CS0Amq{7{p?Z zC;^FxM-sVCMhdx3CD&+3Co;7_2=klBLI@dft&JR14wZ*%Du6WT13@B(D#BHjNVsAk zjALcf8>659|pLJd%3 zQkEvDDQbpVpyudX)DpEqt-;qE^3jHr<~!6DwL|UE_oxH<0d+*3!1q1Geisr;$3d@$ zx}t8VGw^jM`RI;%pq{8V>V^8CzNjDk_X_0q$AGUt@%2Xo&_IO!1K@h-`_ND5XEX>6 zMnlk0Gz@428lLE|zDA;7&?qz#jYeb8STqi3JX|{gVkQDj0-6jo1!yYJuRzn#G&DW2 zRNj2gM6=KgG#lKUM4iwYnG4s>BQzf^K-f1QXdzmJ7Ndn|30jJlq2)j;(TYR|{TEt| z)}U2rEn0`xqYXeC;hNPza0Uq4g0`YfXdB_#mdLXmc%ZLBJJ3$_8`_0-qdjOZ`W@{< z`+@d_i90~W9YlxFVRR53K}XRs^anbQ{zNB${)FpxkW~Ldf1{J=FYx~lokFM4|Iitr z|3UZ^iO1L(bQYb1(0M{QC)gKn^AU4qc1KpX2 zBBXT_T?6D6L2d<*I0y%&MMvF+JpGMsLy2#qJ8;!qbPwH!-v{U+dW8N%PtarZ6bL&w zGYLII3FvuZUgFUU2*04huh47s3cX3p&r1}KqL?&HS|*BlPI8ovc?(#2f~BXh1eAuM z<@(EvOA#9;PZ^k}C;(GKBv!2u-uH`il%E)Ab*mntKX5M44?|mi<^8xcd z^C6R!$;NyH1anKE%}}zB;r|mt*_j*+_T?npIVtX3fPTt+%H(G9FnO7L%xCbM7cl4n zna`R0%opGaGKH9ErZ7_ke!)c*1u90UI8%bbzT!Y78Em~JnV|KSVq6zK?#pCjOGk(; z!z@EVxtU0Aael+4UN(XZ+_FkBF%e>Yo)q9it5Mm35W_GmgBXr!k9bC81V#c9AVy{s zMrCA1V_G1cF&TrgfDDLXfM9OQlw)kh0e^X*3WTsM?E8wT$W&s!Vk$FLn6H^?OjV{j z5O%N&$<$zQ3&;O8iLVx*wF%W>>N40@kEzGt8mt2pD(f4N^$oa4{Yrht?OP(nH;53A zYk=;T!uMJYBV6t8)fz>J$J(c*H)a|$D?tl0fqnsk88*|5Y0flZS}@--EtytKYXA^&ND9hk9Xy!e6X$aG?UU^+8hn66AW zraRMv=?T<>VL)f)8fk%xGo|Gm06@jAO<#6PSt2BxW+uB*<+? z!ZC%J%KXYqVWt610dy)sF*c2v4t_iheKUz~CeZ=*$D-NH9A+*vo0&)SF^h>s z^9eFPfGl7ZGK-kS%o0-WrOaitj9JdCU{*4#NLg3IHF0P;6YQN=dg5atm9@+|W<9f( z*#ITn$ZTTPz%^T#t;{xN3#2oG+0JYRp1e+nJqk)n&As z*~9E*b~C>d{pcTPv3dmlwMd2D=&t`zH)`Ee!T=80BoLiGW1bV8zF=N4FPYcO8|Ez&#l8gpWt4`+ zHkFo5$Kv*ro_&YSz-DAKvG1~(!Iy!JMQOuu(DAY8eKrgG0sB7tA)A%W#(u=U$6iJs zv)DpDWoY2}!jcB_$R$W1F)r*k}o4AKCs; zGXvOx>>y|pKao=X#QscjflD-)AcH9+77b;GvBTM+fOTR=utR`rBtb?7kT^7q(kG1F zkmoM!FHpJ>>?pV@4)vts<4|)d1UpFV775b9_>!y9MOZ`Q09p-`^m-gWbvQVs`}cx0{4_ z2f}e^C#CZ}q}}ZCw41%G>ta#(*xyr#-xo1{KeWoAbh;$nh;FcU!l!v4qD@lM6KiK2wIQu7if;|EM$H8h&vUml1lKqR+=HKiA z^gs3#dz#%3|Nn#dGeBnvonz0l*mn*nmW^XCu(9k#b}6htFR_=|Wv~Xl!afDUE;7f} zRPg+h49~SBDNSVY`VOy{BIWAeWaRmCemxnU8%feU3?+nfjajVYo1nY(>@CoB9GXYf zz-{&pdzZb<-UH3vXCJVS*oW+YKo7wZ9+ic7<# zmxp_Tsxf)Fd|Y1Q!pDCG@t=XixxzZj&*9wVhji0&1t6DnOZl7&jv%;i`2xZPIjqHk zoPVq=M8bsv;b^WfSA>h^ib6Sxam6_}Je7mv>xhd*Uvd|K+-vceSQ^5m6T@XVca#k7 z+msDSKL)}+4si_U3#7-AFdGPS+*QPJ?%odW8{-gyGWm0xG zImCz@xEG@529eT`B1-VjF64+DxYwc6P(Y_jqCVm+z;uT0oHg7 znP81Akj2@Y!)j8~| z22_K?eNhdL?u%-1HMys#Cj8a}j@lfaht%f$V{RR8J*vwE_YCU+&o_kXa}7A`Ysld- zw;@N5xsABsZee}kY0N!CjkzWyw@slwn{my#7F;uM@vwW=l554a=2~)XI6Mx1$9)TE zTY|Nvuy)*Q)Qdwtyr z(w#yQP*;xLwd=w4c_b*9`)x2a09vi+#v2Jh;PXL%neRV zeSk+!JQ~UkKpB4_*e?_|iW|+1;YM*|iKfPZtnu6gZalbnG?Bzj zq~hYyByKV{g`31pC0h9vw1RapIwbAs+zf6eHyy5Y_fV&Sv{?k3MPUhOhUYq3r?V4F z71Z(^g3Kj(noDVL9zo_&h+ht#{lXps=el4Gxp**)EqXzc^cE6i5x1CI!Y!htFD1xQ z3R%W2=T>mbxRu;0(8>UIHMfT7Wi6?zwcI*VQ*J%2C)j!q7LPV^o4C!~MnLiWb_=%w z^tF{>TPZ97ZSv?V7H#M7>S8ah`0C$i(1auI1{d>cQDgP1TKSHiL!r^^mICYgf z#{Iz^<&JZIawoWx+~3?^-2Z^E%gDr|(?k!ap>%GpPC<#!5bO+vg_Cm@Dna~HWQKo_}9=n~Nn z#;$Nz!T%43XY&6*kA<(e#{J7(=WYO92VVcZk(-qN7V+OASKZ?9eG|B6z}?~Qa<{pA z+mqQfI-j1;z^CKi0ZPZ`XVUYyJz(q|J|p-u@pvYgiT{gS@h+d4e~*73 z=snJ|A_yX|Af!Z=j3znxqz_C&%~qLJRXa3^KCdc zKcDjPC=bE%P#8RslSEElkdqf&ekRf=n~x8Uvypf{ONIx}=e?tKBq{lmk%DLOSW1Cp zcDQd|AE(AB$oL5)(jt zJlv|`S(re&dp&rE#@&%dd~nvuhT+0%bAcCmi5Ga8S9q1zcpg&FdAuj5^Zq?KgO4Hm ze<>h%-o4q51nb0i=DYBn_~1Tl zSAuj6AaSU3h~!4R_Z~-gz6alv@6Pw)4>P^_K72ROWnY5zrLcaa=KAqJlA61W`jdL= z&ksnfw^%fY|B3&ZAH)yl&oD#yp-@u;p@hTu;ZWug{K&*IyF35C5DOW_k5057ckh2x z2pY5-EPYHWqz5&Mb=bpGQurz$KMu+`p2zpDCh!wUN!n)`~rR`!^9T5Y{C-Fi-_<(A?*r^G!46Yc0y^N4Pg_5}ZWi+r9p#VlfAB~7 z<3yjkm?Ln-O3zie)CZE3`UHQH|BFAt{|)tig!v!;C*s9=SV5f z^RbjB&WB*QhAv;4Q)#irx;3-U*A|4U66ji{1~5J|O5rlG}&;BT|O{pci_=KjxqE z@q7aOKPEiS`4{|4{yG1O$M?Wq^Ut6}ZwU5=!rl_CM+s>JJQk!CE~E59Iw6Dbj*wBv z1io~_OZ2XQ?J=|Po`6S(_k~#WfsjS`P{=A|6Fvf879k#G4$DJx#G_Ay>_QIV6CtO7 z?}p_PJ{3L&>A3}LRk?*c0)5XI`CuA-Ju{ixx~o3klIe zVWE&fx4R+)D?(ujC|aP~Q#>jv6cdUIMTHVVNdey*Dt-5Ge^FCqWcIRN)zr=BXR6-VMPNEWr?L z0pBZg1Rbs_N3e1fR$h3G$_o`dc0gMJU8b+V2E3)LNRUcGWub~tNeJ$Bd`*zADI@_^ z_GrGUfcHA83iMt_9k!a_x_DGwzoV@ zNIeSqM!@U#Zv=YXzKF&98Ff7QtS>YW8VdD=Mxg2YY-5n$RA?eJ18M^LY%a7Az7?7a zEkSmS{l0H<0)Sk}F*QAmJzBXJL>qSQsJ<6^02TgyF(S zAna^d+l(UR8U^KY^YaUobu__7Qy4rcnMBSQkTV9H4XgZM8yidNmPW=AWIU;%@l@ST zAjkv?nJ7#WCJPgVDNu{WxT$d6G~riaIuLg8Xa=;V8HxPZ%4R}%W@31j5Nda`ljLL$ zLFNkcg!#f;D%}MHSwJBRp}j2>+$a28dzu@<;1UX#s0ek_&GZh&ccG#Li3}@iWNrWt3kmAbu|9 z7rzkkY^$J%pEoKbMvH~TLLe8{UJ*#izf%y0qA7XwngDYa6_Y-hRE%Kod26wxSX>OQ zh)WTqQ~-%XB|;>BDtcGMrQtx|vSMj5Mm)^;#4jONh#)9{Fht9&$Pq2$^7BN`f+!L_ z<6RfQgWz(>q9UrIENbEzMh9I>;$>urrf7*aNOHB}u8$p(=W-y;)r-46E*F9ZwT1Ol z##0KoV);JMu zBH})(iAb*tni8z32V23v5JK1O&BYetw_G$Encc+hB%K~&SHOA_ ztS5yfpcW#%c7gZth?VsMD{Ce8hHGK(NbD#6DE1|7rIpxU>;rW^fM5eC%-e!+|IyE5 zi(V-Mi4-q3h+sdFmibenWEcCHU_Vo-#iAi1-sKu1;$5y*;$X-VjSMBoFv2s8@L(hk z^`Q37;M)(Rtiz$KL&XtL)|Y6c_=`A794(Fs*owQ-A1jU%$BPrhiKP8rgRu&)^%KxI zkM`oxgd}{y@hNFdO%icUt>9aTUMq=3Q%KIIB$jTf_^UV>N=T#AL_a#6@J=VZeiYt9 zBxRccWjl$ci8BK&4cDkw&aAM~XD30iobaeWCv(KP;yiJVIG<=~7E>N}9v2X70fj9T z7m16-CE`+I+smNOTrREQLuySNq7!uw3WiCX~ML9iVZ7KgS`vSZOMaVtEly$i+@eARAokGK=AqtU&h zAN`&1{tmn+kspQoo1|CUM|!or;{JfHN5G0F7>8qUKj)QvAW3>y^1(#(5J~S)VtRh` zFhLKK9Q$)Y%MIr3GCC?A6aNs8ipPmXL*EBwpX(|A9MDi`t(_20ihqeG#J{0cz&@M! zCurmp!A?=wX;PDC#Iu2#JV$Ekycip($@3lr*WCs2qIgNXAYO+4>MT4%X}bW+q@ToVk|Uw4@vIkKu$t+6l(2{h|Ey*zp&`zu;`Pp=+m%hd{{JrpwCFT zo{7&%XFOrR>rtQVuDnluOFYegb}&k%r-* z<6}{7DUXy_$}Q!S@NV8`Qhw=EkX%5*mRmsjT*5P{FQi>eA*rAgEftoEz<)sr*Q;0C zMJ2b-@uI~N(c)4Gsiag~qI;)O1S>^hFq@R<`Sh1kX{n6#rBqgmk?>xgPhzCvEWG?m z9JUx%;v`;TB|#E_qYNuavgBjS!$=c+)=U9C&|x(stc8U2kgyRFHbcS|gl*{pkVDmj zSIgz43es0nd8wj=_wFi5<=~pi1glJ8Rivw^iu5&A-*j!$WvCjGhiVY6F4d4~O4TL* z6LGajxKhiXvTtu5ha-D^vAB==eOx)OfYte)h$SoDpApGNscsxP_sKO0Eyv+l8| zkP~)yE(y{Q&mv>3hk2?!3K(pF593ZK#sPp~h4w7X2XM=SF^z+~-C*N}Z$*kP?k{mi%ZJ z!rO)L`cXJJLBh|7bd|=louzJ2>d>pZCy{v)b(X@TZc21r_6X5KPYCytdP{wzUV(D= zCE>n-a6h7lejYuz&qVg7%KoF&Um76&C=CR=yw47j9gD3N)A<|H3urv&; zErA^_y#=@+_c$j? z?jGkPX|gl{uA|W@k{_K)c&7sIN#sZ2EolkwXZ|W>CFnHCzhX%P_xOWxQ>1B-uTaU; zlca|w&qzdPlJsUKrsqdz5p)*Gu|F5I++g15`kM`caa?Xb3w}u<}D6!p0e(d_Da7?d!&65-Zk7W?S_&bAlLy4J18BJ4ogR*qlvvX z?seTc{UJ))kuY3%cKC;MT>4Y`LpmYh{lb&dU(zv1^^{v8-&{|95NJJz3)PD^K` zQ_@)&d%H5{r1MfN(0K`u%yA?xE)a+Jm|Rz$zaU+dE=d=p%Vea(tB@dZmNbO9E!~msO8-L6?n@7(hthpWV+8X^`cJwC*kgh{rm#5l zfRY6#B1w2R?TOSGu6YVq#-ar2ne<#rkX{fuFQs^p^NL`vDD1WLMtUo~mZD^LH!zKy zR!%3Uk<-iWZlE{b-N#ES)AN~kFHDS4s0f%)X5yMdn(EI*N$Uk>gD79dCg3YpD(Aba=!zK{#bh2$^fX!$Tx zSpFRHQG_5x0!UFA>$R9%oahzntpw3*Nx2l!EZ+Ys=|ONwO3P*BvT|uTMn1#%K$l<2 zml2W~&;tvSTs^q^e;mmr57Jysxch&62pZH6)<{85Dd5x<8SnoIas{!lEJ8`*P(ezE zap)OkVWIXU$=IH}HYO8HAvLX#ntmj?m`X4;5p!)(m+{`8F5|twN0K3%vIcsjQA_rt zHsQ4iuOEebf5c`Su$hy{lFP~dkrc0zgK;<3clsp(3uB3L5|Yb@j5t+CvM z*fZ|kn##C$YbrNO>?vYV3;A2QrQAYp1=ibIZX@57nuDyiL{?k59g+XN{2gHJ3D%y% z;!r&*r*PUAvCR%(n+@b2;L2FkN$xCnkvowV)mR7!Uu z#Vff7!FrPR+%r+Ki}fN{FDkWIL_ZtXKz5&vqp{w6Z@C-ntM?)0=>z5I&G#kc@pAP8 zu2}S=++Q9b|0ugp+PT<3f(=Z>a0=ch?c6@OKUK=hXpsC9Sl`d`;DGhHyB0%aJhly$ zhml_69E^DGzQxcaxWDJX78zQv!)08rbHsXbs4b0@@gC|(lGg_EFY+jP1aQ*m=m0u~ z@Qxw8eiWX-BYGMOdOC?l%i{uV`Y^QVU>uIYHsO^#K5Tju648l7j}sw1EZL7vBIqPf zE@-)!*Iy1igT$lwMYNE|Pol=6$xyn3Q;e8J{?`Kz;&mXDyT$ z$&2MB@?-EVlq0=~wE|8i@}J8R{@Lyos6Lz&loC%vif1aM2It|)%jFgFQh6B(jYaqb zl(A@~JQjZO`6f8LQeFkINuQ@&EidC&%d2I4Du}noUK6$-Uz>=olh?}|<)tN2tjwryX8IdEq1mqY&js=i7 zbeNJ6i;k1{;o9FDGN!^i&hmLVRxZQG$rt2{ z@+JAQ?0s85_`L81D8Z!&T*2ptugF*Bf8=Yj0wc-4q#yZLzAleJxF@**_Dc68?kP4m z!7^RnE#kW+-uvDoNtr%a8Bk(EBH@-EI*MS$xlfxf#5iG}Z`Gx!v%JB?Rcty(b8svG)K|kgEIs#AfPddMWw&{P?Ib7Ra!oN+CBT7k0 zMw%2k(x%9fE=7*?9*zvkJb11Ov<}TX5~F)37<*Hq~KbAU%}6g zzprFb@Y>)5r595deW+wrvMC=aAA-X?A1j|IPnhfqe$qXMl2gH}KmS{`xfJ@n+D{ey zynAjXkAhc({`YG0D)gJRxx@0%9Pqq{@|luf$qIWA1r&TvQvv03h3vyC6Pezqpi)SQ zRthTx!TBw>NWfQ=_=+mUl;R5Z7gKQmT|z0Tlu}A4Un)ajL@o^tl*QUswiKRy4(yWVpLUvXJb@T@QFdyl^UcL@u?d%mEgG>)sw^q<6sv|siV|Y zYAf}WZfs5vQt_wAGx1p8KLskBnQ zRf4PU)&yxCK;lqKh2Cvwqu^75+9-vnQ-Z!zT$gl>-!@Fj?Fjb0(q8GHd>=>$cE1Vo zLjdWhbW%DiU6ig$@H7~AuHD{~qiza*1FD;Hl#Idd+fUsI*4=}J&v_3j@8LOmCd1K7 zDaIyOdTc$e{e+j@I~m#d{H5=dKFM(OO_F*!h2AHLj_RlU2=>~a^fCjKfy6T1Geia{ z^eG}gk^bXnWiYW$_Y{#K3Vn{q&tW*|csLVI8Kw*e3x%_pN&hlJ8A)vO7wBI`DWjD! z%4GN-McVgR(nF3_#*wx*9(u?L%0y+7(i8qC1p0!>0pAqjo1#orepRr4D(Qcwk^X0z zr~g^XPlx_zx`G|<$0G5}NEO%2WVvQRe;1k_?#*t>vlZ-c`g0U~!s{F*c*5&k#dZE( zb{^rw*Ucxs`N{%ip@RL3l*P&tWvQ}E@t>rL&+}c7Y}uD5n`7KV;~cL@mTP60a`jW( z*~n^Tjj~EvODt-gvOc0kZ6NltQP~vHqBe%%@LJRsWh+?JW+?AA1;4Ge9n!-imTOVJ zh1k>%VpBUIMLg=dHnl6ns&sg8diCY~qq~ z8Cn!RhvABX&*H)-nRw@1S3%N0gsv(7D%f{j!6%AbS8gP^lAf#GRPae9HZa zXHg|6_&x97?nF(_-JN(wbo(rz+vi|4b%D=&#>@-Q=1W4al-COOy&*OC##3{RxX{yU zUIAwu5IondM5$@iw@O;IE=s3nP}8gL0Hs$Mlu^y3zN=b|e~Pu9(%exQD+ zW>vGP{?m9rQvK)gexSmaGLppmW8tJGHM^Qa{TR4%sh_Gj)m-3@g(QS+*~)qHAi z^qE>f&98nAlppdDMdGoge*q!a<_i*EK^5oS^%qj3)xv5awTN0&EvA-Gi>oDpu*27s zQt|brRD7OzF!W_&7{`?k$Sy;CWz@22jEem}6`wDU)UqfSgGVGrjfS)6S+yjp38w<% zGjCZB0$&XzIp#>3oXV?$IvXCemf+VNB^6awWmO|`b=6Su>CUDaJm1+O@@&;n^Dudt za%%8YXxkGL#Nk|H^((caS{_oaMAEIKR#vO1U#nHsYViAYpmfy(z8b_=L#?USQn9}# z$#u99ytWz~!^6ebi5Op39g3390Fs|C)Qcz$pBO(^`6gm~{gBiY)g8qfs*ThJYGb1B zCTi0p`ff(F+FWgsMBmLlF|@v0s;xlZ--6CstN5D$Z9p#8_jewRwNl+t$8QNq+9bY)%!J*ong7WJmK}T zJ7Vem`q`5r$KDhYuPbT)Y!FJ}gkCPF&XbU+O zu}$H7Y;Ny`+thkupX-6UFS(*235WaQ#9wN1U%dD`+1URj8+$6**we|zo=GF3T-{amn@E5*1ySVPc&LbMP%<2?^E-t*wF#&RXL=s5L)8mnF;Gx>I1%pmt;nN-!rFA8vh`L_=hRPKT09~zZBvhrx5?d6aSQy@2MIOE0zS%P6_py z`dodXzJxW)GjO;bUlIMhQeQ*-8=ws8TQy2cqovi}f)8_~*D`47w0AVTR?Dbm(psQ* zflMZ|_MY~>mPPwO%M1=qOVh}TP0OaWM;`%+%*Wa%T6QgmmQ(u}9IV;GaM1Daof<8- zmPgA4ym>XeTFa~D)1c0QV7;al&_36`(DH-B`NBO-!GNz2@fFgdwZa*O;0` z)up$l9Ic#YYvnckWt{R_1$W*IHGridIcqimGchG(6j? z#?*uy*3__rw_vr}S{<#HR##hx>S^C-c&0^vk*7BBL`OBy8fuNS`dVYc-8hlE32@WA zO-XE1t(jI&NMtJUzl_vz6vQ5j>K- zo)P5*Yo}L4p59^7>_cp@a0;8st3M76p5ogd_&2(Z*`O0Pi@`7mZ8oi^fA=ME6AS%}o+NL7S+> zXz5@E=8c`KP0^-mleEbskGOyQHPAm!Bfe?cbZv%){WG;$+H7r(HrHd>!Tq=CiRE#w1wI{Z4oKUVr@x;@-7Y=A1v!KZMn7r%DNP!uOwwz1+s9z>n|%kcCQM{5gfng zYdClFLDtWK60Z)Fcn$Hb(bj6~H0)nbO1yz8v1?1gGH)cg-;`LJ!Cog=pPM3Z_)EB! z_#4A<7MBbZy$!9A}?lD8!zIXE_MPloHau<7s6@XqKC&D|NrBP=~r z+^OL`)SVjM|J|i|$5wX~-yNv4J;b+1+pGPqVgEid>)5ZkJH(;&b%4w#4r+Lh*x!!? z>*`>bSbsh3CH0h^*bjUeLOY@z(vFg}j%j~TW0`k`bDT*0Q#(P8iryK{pJ6z>bB5^dETkBmot@LX^Ru&Ic)T2u?BRSeH665v*i>=F zrHbo9s<N_173u3=!z?D;{QV82_LfkTo<_&Mv^t*sq}9{uc=i*# zj-KJA*YQj(y`DkGGqqs3y(P-1XVTx*-_bMb(NR%A;YZqMSRcm;U1+UlWddmfz&ma}}YD;1-m{-rIf2QZr^Xqs{Q$YVb zq(;3xU4aN(bl%{b^LimYT7Rs40jU+%@j9yr$i?G*(2_&@vmz1rynR|;9gqEaLGDdb z3fuyU>i%_MG2$zx7uQSZ*k4k|vz=0Uum$*a;_czTB&8~?yRDekxwrJCBl6MZFAlkj zv<4lHN>Y1p;d^MjM^!R$j~bUOJlAlv8dm+m{o%u;J`aOd(i6?Zk)4`2vZE8}3Bxja z@EOB+R90UJ=gOAVV|4uFhfklyz-Lc&R)>#J>-fwcUKeyx=XFWP&+f@Or^h3OUR-m#B(<3{E($qJOR9CtIrOyO?Tvb-jjO4P4UGO>08glAdBzD-o@&*U{_h zwRQRwqk06ZM`3VwrB0s^6py~q@#&`D==ABPvzYq2>yoC|AZ#ugCZdh>#(ERI5tU** zYD%!C6xK{{uD8&?)m!TH7ctzE)*6T8ua%C^R&AxXhWahT`p=bXL(n!MXj{FV{=ME7 zQ2dmAd;L48>kb6#Kw$}}ohOZ0)KSMz5q8wwr|N&uX{3|hS;t5h!qWwKTm;t5dN;kh z-c|1b)Ky<4cOxwbV?Feq;P0j5r(b*N@Z3De=&kqB`|ACG`U0>2{e~YYe}CfdPp;~( zyYD^>&6xl^O6(wanMUlg1(|3JP!hAE9v;j-zmELif11aO{=j!wH`T7E&`M`^Jw^emvz_*C_ z7LhAl|6+ZKzEoeVFVmOnEA*B6YJHWy1_(P@jYsQ9U$+kWIyZM~!9vy(Y(0e~Ai>in zqN9?^-2ieofWuNZ>6`T}`X+s=zDn7qZ`U`1)ZYm98-?v4z3NVVSA<@5H|aI^=zAmd zs(Zr3)4l3GeZPJ{-=`ncH!Fwq!_ce#4!!CT9iQ%f6jBZLs>eM2>K|mxI1cFs$BaKc zqsH+t9Q3FX$yQG!V~2iQJ(&ugzf!^TcPe=PmkOR!so*(H@th$e#u@#r?w*HzjepUZN`A5H|Uj%FVmtg-= z*mWJBUwT8osk`Tw-qP_mlaih|ej_5E_w?&K`d$5=en-ErpHd#^5A{d-ZAjO9cKCl` zN4v*~=o9^^9m|> zOn3?dkBh*3*eGfgGm03+fr=PY)uINTZ)2>uQ3CuWiM~pLzVH>Lj4zGSMj4>ezzb*7 zkY`-VQvMj?k0DpZ82I^@=qR6o494&otic()AsCV&8Zr=eQ=!*ZNxi91Z*Hy>s7sAt z8ij?Eqk|kB+*CCIz4eR~k=Zei6gkSJ$WcDPQNh4x(pNCP zGWx(*+iI{C4SZ@f?t>~B_?rjS62E#-nY6CTf!0;Uz-QBAKDu?$E&gk;8gGlQN|0(s zb)$w+jcVUD2~v|nYLOOS%hTc)vABn!TYLhl?rEdp>m~TC0nW`+PhH2O+D09tu2I{l zXLMn{G3pz$n1)6JqY+R8V>#d0Xks)qni*$P`c~7!!>N;P5JXk}=ts zVoWlo5`F(_^kOoo(~RlHG;j=>O!^SKn=pfLW8X~Tn@O&7{j-eO#vEgoG1pkg&oky5 zOZgJ&0%M`EfH<5=IF3cYu?QTF?`ybsR+bn`jm5??qM_x+iU=B7NlL%USRFw_tHQSISd7rW0IA9zE>(~bl*Z(1+ z!$ZblqU9rCg?L~0sBz5r!#Hjn1&6s#7$=QCjlYO>{B8V?SjQ;?zhPlAr;RhlS>v2> z-Z%{oR#wCY;*1N#MlKro4G@vJWL!3`7*~ydj7#9)nfwS`G!ML!ZCp2Q7}tRRCTUGK zja$TCZbNIr&tlv$?i%-u`^Ft`IG?zcJP7z665m7Pk@25_{f|ISbkt+xiSg8UY{VP* z?bZb2nStMKEulUK{XHiROL}3vG+r4mjMoN!_vMZ8*1+$#(k(Pc*sm@}naO{5IWLzc zMUJ#7a->Uv^L;amnU~9KW;S7F zXyR{De_(!S;!z-2_ug8~W`1O5H9t1-cd$P;KQZx$60|1Tma>D)9E5V3xlHWK3G}I% z+stErYUVZZnHCF!lI znbpl2W>vGMS<9?#)-mfQ+H=r8gY%PGkjpv|`GTYHH)egaf!WZk2mFmp+|M;K{ry~H z6Tjm}_jA!vO$bjDv#E)D!e5~$4CQEUwlKdno0-i)qT36%H2wW?E8=Tqwl>?C*#Di` z)@)~fZ?;d;4uUhW)#Fy(4l3XB$6|-2MPEs|B>sC!mGC#MjsCXZ~nne}AHd0Uj;5b{Evb zKvI@LfjSTNVZk~d6p_bY-hL!Ekw?9sOnTgl%=dG$e1pw!qbMGaTbjWqLLy5alC1RL z7(FyuzF}c=G2Fzn`HT$ z>CRka5=*L$lgE;{GIrIH}{$QP3%8F`qG2u zp~Mm8Fqv6}Tbmq#RF4umX8vJf-*GatI&S_M=306tdV)dr_8_1 zzd`yjph$Cq{O0LoW<9~V(V0|nolO(^`1P@r@aa(peZuXZexzRtD=GE2EXk z!t2;)>yp8exJkF1~OakS?^m}tPd=_B7VR=Vjr>}TK~cCLpUQm4!vgu??z+^ z!|UgSdo0#R*2h*h>k|v_h-SC4T9;7{E2ovq%3*zK;n6s^mDkE+`p8 zxK#?${n9FJm9@%PF+kX1xjt(SoXF)%yuwGmYmW$q5;4YdQSaGSY?xee1mi8i5-py} zgG3N1fXEg;#Y?f4z}ov5TEyZLr&J4{-KANp82sc<8Q8_gr$=i^P+U)fM_Wlts6z>L zaIW5>Ec(2rSY%q3Wm~4@Sc{EvmSM%B@&qX#Kq?R``pT*p)`}_-%cyKs32Q}_!^8&d zrm9uVs%}-aYFKNGnpSq}Yp|PIR&B7GI*?k>Zt7B&Q;*c?H;`V?a_R?c2fp_aCf;vZ zu_&o_%2*9N77&i3VTv4$QsiiyB1e-HIhv-((agiqoLGBvs|B(4Z>>8_S*xYh%4%)3 z0ef!=4%@_cL}TAsZHY$P0aqdGd#ka zqzAE*o>nhnCB3az%nP=U)z|80{b=>K`hdHP1`ul)XbmE^@{{!r^f^CUgRLRfP-~d= zGq_kZFf0$v5sOAxBduSo5!NW{nlaiMV+{w{V@ZEA)*46bX1tY?&1+4tCR&rM$?!jc z^g7-)J0-D2;1)ldaeIxa1pC#RW=*$#4Ybf$G=m^B0!SR1=4o{^ty$J=Yo;~Fie|eo zbFFz+F}65c)|wCXF`qcB=>^t8Ymv3UT5OdDt~bUKD~9#4bl(@v73PX?(Oglu&sU5q z&Xwd!a7(RH@QYn63KzdDh4|$u#IHypeq{>rt5S$xO~tRVaQW6)Yrz7FaqGYWN^%jsxTX)#I>}G3=$4a~wu+7?T{bp^mc34@>oz_;kY8OFv1(4m=6ZR>) z+uGx?N7@?cd-!`n0v-Mx!uzcK)&Xmu<-ezYkc1Bg!f|LnWdnz-!`2b&kag6;-yu3? z{bAwn5S8SPL+OuO*u|nhtrONs>rd-13qNU4$o$*F-y*`}uD?%=LnkPi@Vzwav~|Wh zWu3JOo9C?mfsFG6IUhub%-Dd;IFRX;82p0Ng^*HSgzzQnvUSC}6e!hI622M;|6^UV z{Y zJ?p-8H<0=R5`GW}KP0(%=*i8Zko?@EauB&z9#QoYnd`q~xE_b8VY+-5tYvWK<}#Q| zMV9|08TmmAdYTMZe3)D$kXDvp`Fjy;hdfsbh=)Gp8EIe7tb1UC_u1!Gs09Y?9>$Ua9dKIXVw

F5RS5OyNj~p!CLT~GqiSIQ?(O^wo{?#s5Cad|B}Z35ZZKF zJF6KqUCi!a7Pq6JOeF|XB7l@6 znki*}8Bvp^B5(wCSH>=D$Jk|TpWV$wpu5tL4nuUuf?RjZ_3MtFGqMqQg7e1NOp%Z> zayH)k=WO0CZVOPVI8=nvN*u~Sg+l8`3{yW6!DL&pRa*|!2F&>hq6HA0)Q{n*AGeJK zYse&;vI15a9A|=dX+`Am>oX21o-#z9mL4yQPiCr|_$UZ^QC<=ZLNCHuiURYrCyI(`;vtv0B-e(f2l9UABkx$+JC)tAq}= zcb)J9sl$$t1H8ucuZTL?-ZfFjuv}iA$Tr$JS=$TRXqQy-bxkE-w^Z_VPbFWERPy!o z@b$9sda0M)+jiGWeMnoQ*QfIX>(joZckXNVOYFgagwfB(hFhf$um{?M>;d*q_ImSY zyFb|9V1f(|AVWy6H`E@MILZvS@w2{3SD`~A@_B7+r2UIM${uNtwzr#O?6LL;NOK&? z@3=sI$3uR@<<{G?O$gHyOeEMOd$K*no)l>Jv1lqmrUsB-$(S(Bo}M^n&#>{+#YxxS z^z3vd0&ZTnX|^GJAJAUxm3Yk~Rpf+XqC>#Bt|Uga#b7bVfLS9eS7 zrS>v=iM`zZ%UoeEhV)kwWMu$}LrbYXC^~ADjqhNrvQGm4YPc#6&7tBW%@&VaYwX|} zEHc;HRB^3K71#PyacxKy*Tz(FZKAj~+juRy+1_HiYtgMXUW?Le?c=c0#%t|uq^`CF z>S{Z*hbDLl-6YdM0vCrB^>~m1sICPNGRva2gg+k{Y=fm_iu>^~=FW49DxIo_mI|Kx| z6hJPM-sXy@xAE7dcfao{(cnLU-j=p5ude?|hLhGd*rBIXk;d1eY>ZWu#SX88B5~n0 zVvOak5tHL9%3g!fD=6=C7JrfAbC&)hMR7>$Upq1n{&E8533AM48ids7bsIlLaoxUQ zH@0uW94Zbqph^qhweze_XERpFjO><;pOwC4-?qzHckIRR{)k0g5r?9v5PZ4b#&?VE z+3wwhj$^ALDkBs%Jm{h$5Veq=uZdSvHR|FiL(N{l_RpMpQ$#`me> z?WqR7BEf!UKet~1JqKR*wCR@t-z(yKMXqrDukAPXTl=*g<)m@aI_aDYPI~7ZAnbC& zyCj(${FHqrrw5UK*Usp~qjw4RE`>!$C6$vIU=O9=P50=x(<$UVobaFfR zPE;PaI-ir*`3wj<{04M>2ft&UKk+Ibi?IR(E0BnN?zpJ;?XfSy=C5EPTF8lZ3Oj{7 zc_~7$A{18C!EdG)b2jqsJt_Q_ba4m2jb6gp&f|MhvxFw>Z;+!BNl^R-N3=t~mlcn~ zm9`|5wj?hb0z^Mlgb2V}rJ58KMPE!Zpy=vw(1WRg8u;vujf>_eGPD|I49Q?K<*pk!6 z`OazUv~ya6dx^d$_R`+zKp1TLB<9(8wmI6a;2 zPA>=F$Lj6$ak@bYeM#@s*Xc)W>qqFFJ`=Jz{ha~MKxYvA_lG_zv`_jeY@7c%5gqId zafUjBsrDU@h7oKSg(aXNA+2_}Gr}3^40nEU@H@A2tx-;Cp*Wk*84YzcnmDZ8G0s?L zoHNE5@8I`s-xw1dR`9X(8(I^ENy21dqA*3kcYUTp8=NNmDok{y!!LI6C_Klc6geiR z$T1~Fj;Sef{F)-iG>T)ogG)QznE@73jGIX;WSTI`neEJR<~qNEZx&d{Jg0%sP?+b; z53wV!g)DRyIg6cz&JqXT=UVD4fGd{~Y#D_ucUlPF3d@}pA(l(qE&bNtO30JEo;?Y)T zo3q{7>ip*5w|)zmI~@F8BJPL%{cHl-=8+nYb~(G9JP={qF1pIr|8G64}fI1hYw1A2IG-iqJv4weTX23og>as=P*^uV+1)yA%8f>oj;vFoD&XyzgaX- zI{1BRJl6Q%GfY57JY`5gyFFp}0=a9 z&N=6da~|l7Q(rsl;29Uj&O5Q-k8|)nwm7GtiLbceTy!oumw_$;um1$lE0q5#@n0oZ zU3Kv3pzx%cbItkJ`Nz5L+;DC>x12l9ZRai!cJ;M*bf0McK4{*})jg<<2LyXSVd3OF z1UV1E)z@eXe3Yan{v*g^=ZW*wc}(d&o*?lQl0a%A!BZ29SZ_@{@n}A>jXtBSG&0xo zWVl|0v3I(p*yfsX*ue=H5!=ekWKsyWs8`8wy$+L)H>4+eNK24(<Qg({?_?_#;yZCilU2eKp-LXCP@ehI+a%2Z>jGgX-y5UVnsva2!JW)Q2v z)P((74F1wsi>aQAXVhluFm;)F5bFXL{iQP550~uKCwukD8ODAL6U)RgF-!v{o@vM= zFpZc*rZGeeouIWeWw6(7%B;;d>}kT>pdAErSTK11HbhS|(9;Y;r|i<|iVD}y{KmaB z4!(oq9b$AaZDc}yoJpJABbzCZAsag)xr@ z$=IK13Tck10KX`6jPZ*y3w1Yu@QlD9A`*@W90PGs5+gGTBQaix5;HAZCbbzcFQdY~ z#^CR*8gnlP&+suiQ^0hFSO8oHWq9I!#IoOo>~|q&bz#s;uY>B!Jj!%qx-#9F9!yWB z7t@F7&GdzcVH(uw{^ZK{hbwPb)eo-50D=v$U^nTOwo(YEcOd8;2m!SYVg@rqm_f`? z27ebG#ykdEhZAhL1$&&-ml4bp_o-teO63IV)JWzj=4oam^9+N(4?hca>Pbj%6sc3A zLAx=24b&;?)7t1#S%D|E4QYczuJ~96fB7EE7{7dvf$JMU#xdg=M4ls@=YV4%u!<%# zk(tCyU?xMHz%0+6NUk_ylbI>7Kb65>pQkcuz@$;7bS!90} zIcpY!eu^E`3(RchMdk%&4)YT8GV=;EkD1H73K7F{xHb#Obyxt`!LVvRl+{9lEwo@a z>A^u)JXG&%p!YQhsMS7K@p{y-s-RxtzmFBYYk)0X5mw!b$(|pgR^358N-9eI>!Ed7+fpXhpiRw znke%g^FFh|QUh+#8ws}2f^A}OUg!hH^a>JOBR*sX!o1K&jNui;9Q_1p!$)D|ftv7M zP)*p7)+;}_xBr;Iw`M^M%yH%z^CN?A2Ap7yg4UA+J88jAk!x1S6ftD1Sjr5{X>w)GFh4OkZY^yF=1eIp z`}6hB%rDHZ%+Jhk489|9mKmR4%-o^RF*v(-9_+x`y}%621;#!HbCJ~QOJE7k@daj| zerN3SPnSyJ*;%Ex+{^cEk%5-`M;Uon%E&(1LQB?o3!w$%Ab=#X$t)tR2&WZr z3V;&15}n7MsoHupQZ4h!`q@&%jP3cby=2hE;ix<9veU zTd;6?7|;Wc8ldGS9cwF((w-={=Cf>IKCg5ZS0)x;CYDepmRKg1R3?^eVJR%mYbval zHRd%{*6_I&n91D=^SC&ZtC2dYmDEum^bzQF4f_ad9ksR{J*4dv5TrBv2-}72Y-uT7 z3DVVqJWATmqqeq#e%G+ITH6l%j%mBLH|ZL-V;pte33*=W&f+^%-C5%us%~Ia0O`T@ zWD)5_IK6;lAmE3C?ZftEd$au@_GZs#_aW^8v3_iS*dM^+dm01S4|4I0f$U@KAa*ds zLBNHt1jq^jLoEA4$^KAs)=(B#4{%V!*x~Hs>@ao&`vm(WJCc2xeTsbsB8KxY#u&w7 zk3NdM#~4;U3+-Vv!A4uKoAe#q$Ss`SF`#!01k^f?9nU_;j$IqNMJ zz3)4yx7l~t#q8Vc5_T!Oj9t#IWLL1OAYzDvo^cJiK5O9m7*?%@a$8HVwHE9qJ>J$H z!s%THde=cft@iuw`mp!icTJRikA0urV7bS_GYY{rTCh#zzWaggzH7E*`>&24l3Mdo z$vDK?$Jp!FNB3b`>(@>CJzMTef0k{N-Y~ySItKi;ue4Dr&N(*BH|8Ag$7_?mnYHR` zk&d3=Tcle(!6&)eq<_o?(hTOHZ*AlR7#q@l2*VY%+53j^^os`yG3LJ;WYl4@1Q8E%Y|uv-q6!J^LeL*mDGG z?oomrwO}{tAA`_k-{1|FH)=2Z`tS+w2|oE_<832gt7-Z^1SEmmvR^ zApfy+nlp{!A~?IZ<4P{8zhjCE&$4pB-vIXzSAi?fRpii@btSGGS4c;4xT--U7scV5 zk14Lo9KQX0$$8oNhw}>jzvK-4HYu75#i|gjDp!rG&Q;|C-z(K1NR1MtCO6Xal&2O~ zo3ndXzGw5TEV~q!*Co4D7rqhebLlR8BeuX*hcmwrD~(b2fsA?&WIX&pM*RmeVjjqd zEn&oQnAC}L1+EGZzzYM1p068nrswMf&UpLRqV-`CB!M2qihd|x!!`*jm8@SR){UCnY-NSF4;Wpgg@ zK4oc~7Wd(d4S6fGG%deb-tBD3;g@wS{dF*jykok=d55)r1qWZaajm%4Tr$^&Ys;l@ zNl;Sl2-2>0Acq<|=VcNH3GLMGxq=x5X^Z z-Woln@a(M8N+$b0<=fn4y!G0#44j-YaB|DQ$twe=QyDn<77oMV8@~+4a`?SUGZ)8U zzOY~8IqcV3xXhkSAZ00(^nW7sfB3b^{d+(uOb;j%OyRto$|)uNT_LRzL@PmjqzBY( zJ)qIP?0sMXsYjhldj3FbxA*;>OXXQ>ToLUpLkq<>tOM^?FLAcwtZf8*LULWWuFyUn z^9lWN$Dxgd57?{xEL5^Eu~mZX)qLbAaxddWfFnJWL;g;GiDo zrZ}fMM{v`L7n&w%T~fcMXE=A$Uqf(EBf>~c*$^oA(o5ip`<6h8zXYBv6KiCdSWlIS z^>mq7&yxIC{JZiG+ggU2zZr7lR7w>+wU=I z>ln`Pg;(8UuY>4CYi!AN9Y^-YapSq?INYDW{gHVkb0SylL65$ZT|awjI?;<(UTS?$ zr_}sZCN&QNtRqa?(`4?b$7pxApxt#z4Q+RmOXXSH-T3=l^%v;%X#ww9;oAm2L#|H? z_|6K?LeE)NU5{|+i}ewb_7o0#lquX)xZ_TPex-2SvkATzn=IYed=&Me><2>fo&&E{U@Uf||He1RLEHJhZ2*c|R9*ngSBk@?GT1@Vkm zxVhXs?p28MfD7KdvL<9r%$jf6UqJR3kh2zW6SI)Dkb8}Lom`W1xOUA_?w5{+RZF-V^fH1ivtT#rV%tgz;q)#Cy~`n>)|K2UZZ)@(TSG=H zYq=GmbsfRhS+MmSu88w4w}A^@!=<`yor!l#xxCjdLk8UD~fze>eXJ(T#+fT%89-H z?JbpOt$#P^U7Yn7q0;69ux|>?Zf!_g#Rboo?kA)B{r=JYKIl6F$amZU4v~X|a}YQN z;-C(3hq)u%A?|yKhqw>34wK#lvG2K~uz!s7Xvd&O!!v&1j&nb9Cm{X^Tv)3jYjf7e zStl*~r^x;(a@HyC<1Azqaz)%}u8=#!{lpb>KXbowzi_`n#PA`s`*Wm?pMy4TSalZK z-+6+aw_rEvRko4}r}qNry#N8VZb;h_GHY>>+vwTkxyW7Ows-uUd*8E>%v$`xUE%)Z z{^0%sWP8WUP)=6~aMSH_q^-c;P<|BBSadI71Fo3+uR-Q7I&A!)!e#!{>K&5_qbEeublrvYPgnLFHdjJ ze_S6=U-;kKgWd}%9-sdr_?sM#xH@`rJmvUYPagcw@!(8GM2Orf9qhqpiNPN0*Mmbm z<@q5V40wKc#zPNeRCpkx;sY6#9>|D%AS24csLW$-EA!STr@(X55RduXw8VME_H3|| z%-HO-&Deb9isrv^VJM`-%d0#xD z^_72yrii{`8NU?Lb$Dzkb$Fwt)aI>7UA`WV$isy5FmMb6zPjXN_*lL^9|y5M-#sga z$I&@raeM>VkLR&f#`BlR84dXaK9O$(F%h`%ES1$Wt8vL*6SCKYoMG%Y=Z5<7J+VD5%6oRE#u$%Ojps`A*-gcn39fa;# zqf>E3tkJ3CQjHa{o=cqw-+fF-ZO>0g#Q?wjmBQ%oKnDFlhVy|8*8>@;4`ig3Fw%L< zZ8~pV|I1i|&{+R#LTXi5;|ty~FZG!-gSS3U29K*3hk52a=rL9}*hu~tx{6$%w;j|6Sm%fFTOic^JD@!r+ggR2oW@o0QgkHgc< z(*y!OW%_uWGtePjyn|Kqgr5)5!?Ntpp`CcFC!HYW+Hfz2FTpeM_-`nWlcY=v_>-BZ zG7I=~neEa#^C@YCbQivLTAQ>keA~2?w03D-`A7Ljpx3gZ-FOr2PPpBHJJ4mKMRe=5 zz*8LbPb7sNkOFdg@&jE)NJ+EGP!gPaH@;U$>PP5qd~ZM6hos&IQn#Xg3EG#~Z`x$F zAGxOe{rLg>K)yf!7=JZ$5I>mj2RR)=upt&~DF3Z{uX`9joHu@x8Q|{a+LTd94-11A z(uMR0{t5m`egr>~|6k@){L}pR?#DszvqbN+{3w#vX#N?%#t>|b1uLQl@Yd2B%m1Bu zEpseCjvvI2hclrUrPFj1{Pvp0u7sE)5--jmh*4e`%dZHyKh4f3r z%9s3BzRbVE&w+HT=v>}J=MnBa!ZlF`^(rZgSD`HC^7Bbq*w0=NqI0ivE*~B>Q(DNU zy3^dR@vrk4Zk&BQ=FW64cK(^cLQ8lptPCZ=S9lSRS9od00DgJK(u@@u*lHI$k@+Uc z$(#OMzs0}JzX41u`VMcRiwSoza0j|f)IlvFxn2UfMs#UO`?(73Hy}d^)(*SoWg+Pu zq2J+``_UC7y%mt26{Gn z@oRW%S*<77dJFb0za?{P<_3Oyrt$0H15o&OZ>4mVvdxNo~= zyFcS!bkBxROn=UQ!SCR|Jc2`zwCBdm02x=?hCrUxwkDOz>jM@8-YZ zzvlPw=$rjpelNcZ>c@WmJN^K_pFhZt0pIb5_D7(8Il5^U09( zP{%1hT1e6>^rvT{MFcG(7MoUB^#*J!rcd){_@DUGd@=u-p9Vj5Qf| z_;neEPY`S+cliw&?`7QMH)fcA>hZpHF9gLpd(&2a;qv+~<&n5(53--Yybln^15 z6DXm)fF3~}68?kQSAk#^ELcSWTUI3@QV7?wq6BPNm4#>_T+6B)MjB{KRfTFob)l+I zLqK03HHE!=6`_!>O)RJ_)FD>Y6>0%ik6`sISP>l|So^a20=|D!UohUki4kIjhv7Uc z8Yh@&1Hx?p+`Uc{g{L+F-?fPsE_Fh*p|IC!gf-BX0y31WfwpAVoDh;8>PYmXjYxWp z{OOr!V}dp&7MoUB^#*J!rke;&g`aqb&`hwlrRIX!nhNO_0``tAg(Sh+J0=Te{|N74 zhh+rh!B?Y|fLCK_MuZT!M;LdAaCPkp-&(M?tni#RW#F_e11F^noOWg4v@ZjvgN;KA z*r(8f6Uw*}C=Lp7GFrKtPK@dp`l!Pj9casH0@CvG+2{MFY+DBTNE)fZoihqI~sSAL-Im@r5fEDVIO5Be#4A2|f_Z1gAO52L?jc~1*YcarN*{u#JN&j=VW#bF6(Gvg?puYm3xJIwHtkktZ}zWUF#kvtaGm=p^y$IAO8UPa}SVDcz}H31LTt) zAfIfJPZ2PGQ-rBd8(OALgWAw4b-FM^m?=CjtaVQprbBI*B}{TpcFz)Cu+K)mg3YyH^Msl1=iT#!S8esvT05=pIL!wg*5d_myij;ecwJa1 znD0C-BFBqLj=^)Gr5?N?yeYgTydk_T82%XF5%7JiR;i1j1QrVzz(1d`R9Ge~5ta*v zC-D`6u{OXow|V~oZaPUGtYA=^ddz6L$L1uSj}dL3&nn7eRLsi%l!6dXZ~bY^^h22bJt@ zIQ~Z1BYZ1-Q&N(9$?@Kj<9(!L_t{DoeLP#sc8{erORr`7E%mH))_3<|9SBont@(RT zc;AhokS<;S!TabBw1`9ZVI2-*#SzkT91+Z(5!=;z_d54_2r$woea82~6zDgvxQ+_A z)sI2ChDMm7SM;u&qDQFM1K*?XQ>)c^8+%J)PS0A z*ZgZpdZ^+{Qa&rL(%a zKd|U3qDR@v_JVLxxFlQC zwG_VDlNQp~g@1$_!gb-MfIdt9C)^UQLCUwu-TijS-Te;S-R*M$!6T8oVMikO{OG^J ze*z``YZ-OGFODK&5n^C;Qcf%{J|tEUD~k4hIWWgiME_^W9old)WaJzvqQ8vlE~u2@g3BR(vm_mTQyZOB^;!D1{}tcdj_ zPHZ3=cjnM~5?2Z%P%|2e31XtyP;4Zk=aI(ZEg>G#YD&^-DmqB|&BP{vH78he3sywe z6|GN>Ek*Pw(o!@$iX@52VhcFWinbC>v^C+j2JY2N6NT?oMf4uhMoc4UTk&e95k|l@ z3CLQBZNaW!%_$-2?RB;i+xgM*jPmZ1M%X=iO&0R#C1?VE0GmH2PYMpKCD&lNRSJ9ZOepIwSeFop3 zx`nMZ-Ti0}v8UKe>|v=Punq{pdRwqQBF@tF70vnY;2P6U#Cf{@qA>$*j@$yZrGHp? zpr-V+)s&?fokH$y14Z=CGEg+Uvpgma5(hx5x1xhZ6CFahLx6jPHc|<@CYmF#Lofpukf8)uN3m-j9+DpFc-)VUAnA?pr)Q#15cCORv1x@>Z@{)fdZhT2 z__R1ud`3i%E6<8gLf%FZY?K8XO|HTiajb|V*zhwo<48%57oQVx3>|)^W_(zV{n>n? zI7yr=P86qz=xb%F*jJbUHcclsO&4bnJ7cUu>OkeFi^cA#;9f$OvYQxYj?;StsJ0-`$MRIoEf^4dQ#^ zyW;yIdaBtdu7|6#iC~*7*azh9`Jwocf0Vje?BxnKr}|;3JbP>XMBE~76+aQTiRh(f zyZEX2F{Jw$vHG(Tt3L;;aYi}RYWrRCi?DZ$9e(snai{o|_@(9UP)P40*e(n9H5q5@ z7QgX7(d-eYy28yYTjwRd1)HtMd*OJWxL^EE+$WlIcL&Jvfs*5cBF<vdE3kJsCZ2LK|CrR7tt@xkK*@W!3lz$uwX^>Fh{dKAR=vnIEH0w=Tk=}kT)|@DtQeRbEuD3)Osw-|VqGW` z>tdN$m&(NY-NL#o;wRn_?mcK{71Z2QuA++;~OQ%H%UvlX=@3q{cX5OE1Kv%!o3IFfi4quQ2&bPQRZLq1E}A3#s5m`_eW5_ z12UB0sz7$lloXua5&Ete;YZ6!Xj?gH8r1IqTArZg0sSb~w8E+vxu(Sxq>54{se%+K zq0g8o=^?3*u1v7X7A#uAd4(!cRmqrFD6Pk?QYz1StqbYuQVpr5R9&hip}&~g(x+lI zNVhIYx2{x=*z&Mc2eA4Ct8c-I=!%kcjv-b;-!HL};rk^{Y9PhHc~&%DGSP;F+Yq={ zGfmV%B}nM!B|$nt&_v0cV>l1jCm@TL62Y!u&5c6R+v|*%8vD^EB)uk(o)v9M(5A#< z(+aCz^@xk_$j#jTS!>`OEW4;!SfAC#MUH#=_X69 zq?W+6qOBzpZ9}+ifIHA-q7JGpDc!bEx`?Jo=Byf)Za{_-EM2?ib|L8p>4{v&ST_B_u`czs<*%mE5CF~7)`df1nd4FwbMme{=hjdWA zq}rZ3o?cQ_&p*!I(slSIs;_j_`M0yLbj^9)`H!=o)L-fYSILSFkW6$S;SL1u5!ytH z=&R1a9nL{LMpAeTQb5k2l52Jy)~^o8P!gQ_0BLYY>ZoIgA00|kAL>utM28V{7_r~9 z$!dSV(n9)iX@vBI^tkk-l*f;ho|1+`PM;>&(-!O*3E#(iRvIPY`*@YzRov0;s_ts; z>Tb#v4g1CPXbIoW8zYUC@a??X?z-+e?t1Qr-Syp-U3Fj|-VYDMu*wVR@zQhB1Zli9 zQIhyc(qt*YJr1myO01bGO(Pagm!<$VgJ3f(SP?zK(px&HnG${(F;jY88Y#_!GvO_8 z=|yRdG+TO!=y?IOyiAanOORJ2{PJ(EG>_!+wiErv*!^0(D&g0E^Q8qOmk+_}l@(le zj~4U8Fs!~T;7x4lb!n0Gn)C+AWi8i2shECK!mj||f|T(~in^|cUG-dVOZ8nbu2|Ts z=fW=m-;wZ3z{QX@zL9;=iC?w_dskT;hGX}vvc&nKWPh{$MN7HC7=o0^Qh%8&la@LHiHj8>Dw7N>u@LIL+M_|zZw6*-W~Er{id)iJ14kCe8j3sxlI*9NDhGm`PO!A}y-UGBAfe~43)01)Qnr`h@6u)I59xR5igd{no%5%3 z2~z)yV1HS#BKoqexB6SEl~X(CZ>dfWo^?&SE?tH5tmr?IiQXXG8^9gtGEoP0Q~KR= z*>h96=t1;W3?-L5!J2P{q<4h=N4o7t?~wHFKzdg6E0~wV3pj09r$SC(fM)?xP zLo(*}A-RHVJZRBCC)s1qR`=NfLw*?oX0sX3(ChNRg> zZcFS)ky``Sj$rL9SP`9IxxR&T2N`|tb&w68d$jD7+rxQQ)Fqo}D&eLA_iCnzI;b=m zJqV@AQwf?boAXv}!52qB=91IFu3*g>A?fXPx@5N>%_Qk%LV8xzLr@Q~*tEi`7rCaz zSu);fv*nJm@hxHC&J{ck$}W{@zsCiaeU6M}zceF3wm*@bztKNFx`R`$av@PEL-pVMsH<*Vta(N zm@cw8`U&)%_UE=PrShz8tBBTYtt0$cHPHXz9Mq#SK9M~t8&722ZKGL-CvXGeQF!$VR>9gq9b5hV2y{?twM34%UB z>^E()>J3exU!Txv|{RNDd z&t>8n&&d=x;#~$Ahd)b2Bqz|N9y+ei5`wHlN1p;cF zC%-Dsm*>d~;Jz`4UntK7t*;U6H4FB-j2_e$$#2Mp2es*`xT*}UroJew1h2wd^4sz| z@>}v^8Nc~oBA-rw6Lc??(M#Gg&}4W?`zak)jk%D9*KAo>nQ=8=m4mP53K_rpUm+V` z{V#`W7eH3Zt7Jr06V7Vj7>I*fE3cE+%WLI#A+D9z!1dc8zbC&BdmH5e&VkNN@(1#V z^hflk5L<%ZiA^CWUd4~(&GN_caOa0`{wMMr`Xzb`oc9VnmmcAqON-!7!a;2b!4cgc zqqm6-aNatIxLpsnY=!-8GWs>xCXXYwZkIomKa)R)_?gAZFD&~z$o>v;)((;b@bxb5 zl)sX{ly}Kr%e&=o*9Z%SYtz<-@Yov)WOD9kpP`Wb~}|gFLV!uJ1G1 zwJ~*5>T1^~X?N)3q`v(qA5S-GqAwlSF2S1kV^|6H5JzmeL3?jo>_uzAbG(eRwI^j` zw)OI*9EuWEpl26OU5Kqh9=`-Y-B33N_4Ew*xI4k^%T-AeT z{3`z@pOw!+JPTa#^+ETf&%<61*f)+Y5c~o;%h7g59KVr`fM)sNNf(_XY&i`ak)Wd|UpXdvEq04$14IR2=i7~_xX zN+BIUYAUr9L~0XGZQvM)gQ~04Qyy08D)k}ORq80<-Aajt|8Ytix-H#6iC6l9_nm(9 z0D2((7(EE~`avyes5t0mbb^wo3F4MPus4?UT+J2q)!G75=}dQ_AE9?-b)_GryTRTgMBXAu zZkNU=*u%v@I`tsp_F>3nOW03RaGaH-JWom?S!t!TR@y*pU6TK{C3`7kFNK_8?6*_e zD;<<}3avO5my)WaD``pwL=1J58+4|Ebv9GEBO7Jx2Fyb+j|GEPVCxMWRH)u8(3=GT zwQfi|64F<6RC1JDrK4hu!m{D|29P|ZlY&S-;p79yK#J%bCGcGDpcsW!IE7Joh>Vg( zv!tvL;}rq+MMY9%MO3&9JYP|~imGT3Rp3g5n@(rYJ~&Dzy$2rYaO9wLa;C9gpmbIq zQ3{kUN>}AkrJK@2>8|vIh#?Kyd2gkU(pTxN98EK<>;*aQN3ebt>?YkuvG*RKdi#Uk z{t(izzYXeZ=B8ndFlJ0}dxnhY(9g~QD5(Jo2K1MMGc0yFaxfU)bXuobe{+B@P@VW& z#uBHo*1|xd!;U>hutCaTWr#9J39QL5lpsS(kYNh0$uL}b#$(KLPH~}Em37Wy`f&w4 z2aQlt(~Nn8p5QsCH}M@bBCG`MA8fhOXysLll}1j!u^|rXMJJXJjt-6g*#4eS5W&4C z$=;L7NaZO7_n%hQc-DHJQPzX6l6Xn^UBSIe5HAy*mz6)1E6Sgs_b-32ORoA;yrx`N{#O2h^KOuMQ~95Qd$)+K zx0Kt;9px@q3v=5LuR*lxzgMdMe{GWg6v`Xn{g2pGy2P-F@?w4+)KQ3sAeMvU@+3aw zt>DGIie4x+K=~KJ3N4dJt!Vq`o%>LaaBQiu1;K8^C|O zIt{(KRD!pmH__Y3+t}O0+tlmuHuE-zy(ZpxZ%YVC-WJ|uZz~9`;eQ)%TW>1mqEfu= zyq&1_aMZ!;gy8a~l5_Ap+M5m`!|R4J_L(mMb8V1wCV7@SzUV;^P z3%nzP^3vIomq$vNfxL7Hg9S7OcwNKr9u0$a3xjnJgY^i5^$dgcD#3btdwZt_3^p+gHYp4?ISe)>3^ug{o93P7{Uj)N0sE$x@B-M3Fxbox?D-I+ ziFZ~Q?1eDc>@e7iVX!%2u$RJMFNeWi34_fIgUu_!UiH4}{R&EI7iGPp%=a$v9- zlK6@DGfViy+bN<`M1I8QUN(Y@;3L=wAp%~}kB}pjlK)?LcX+?_?(}};-R1q-yW6`x zVvqM5@3-E)C1Ia;zxO+@vEA$aqy*pVJpjRu9P}OxLJoNk1tEvMhl7wK-XlTC_uivk zjCSqEyvKr&AG|*VA;-PPgODG+KL#NuyeEQ?lirg-$SLosAf(V+7=#pgi-M5T-qS(I z8Sj}OK}fN;I0*UK`*RTTi}#lxmlj$~7%Fv>c4Zz5nyx^4|2`_TC}EL8V5fMW#olMrK61NpMj2 zy#IRt^WO7PYJ^%&eMl`&VtMthw}M(xt)x~^Bh@Iivf4S)DvMUDs8!WywVHY{=6cMr zNK?OqnjQIKS_(OrdnOC1>8$9zsGbgVb@mCzS?RX^;qN&k&U7P zT9Tt$MYWDfj%pLtmV}t7x+>aPSFNW$tkzd!f^3D~rlOvYoE14VYFN~85{e>Y)i||* z8mq>udSpRlAk~G0xe%DHl2U-D$*5&f%cGV?t%zDl!uiOC#JYxRf_gbJQEj9)R-33z zRfpP4HSKJ!woqHD&DA7ge{@u`ilI^z71c`JAN3tNin)qEdkEiaZqgP}E)$sHpa82bEUat4@+V^(NJulF%TkBPo-PY7Vg=KgwDzFvCRnBJfW|n&l4bn^o^py=(PH zsy|x28wn?(a!JYLs(D1q>8S9f(@D)&8MTwj64pOauml{0t5KJtcy&zmvDJB1Af+>| zI))ojBH@dw6vUU+r>j3xT?RIm&XuT9)iGR(@~Wz;sb1Ab@~^7}>LY4r55ovd|hq|D;Ro+wWrS?{Ps(nambu0H(G1RPFxpF_XLXCf`DY>If1?sDsoe)sgB` zuzi|HpHZJx8`ZGNN0Hh-N~P4%>KLf&W7To$c+xWQ|8vkvv4u_`tO@Ewb&@(+of6b0 zrjqtMRh>rCm>$xGXOOdJKpHdQ%;#-we-@F=0@(}dZ1u$w=^TH`&YIYdIBRA^zEgQ| zWwS4N$#T}qaF)?ynCHBr&Q<5Buc)t*5mHC9N_=={rTQVSR2(%>IdqF>PJB( zzL`ijgN~0u@`4D+IQ6aH@Xn;`xk^+c_c zwe|oTOZ0{4!de(+MDJDisr%Kv>UX5f4yXs!!|EXt52@d(N7V1tqp&?jq(7*~)myc! z@*mX`>PhuS^%O~MQ*@zrv9RyCF!42&#M>J3nX4pe^xK4zpIyF`v;L;QU6qD*0#$3 zA|?BmdX>okhU;}ry{`VF-cWC<|5I<-uH9|*j(S(Ut==P+6-NK7V)!xoK=glVR-NoR z|EZLQ{d~td7`}^+(2yUYmDBLP7T}lH((7c@DX&o)=Hys(W*rO%qAO?>wMtqAEmFgK zTa;E=tD;4d7_B{|Rn@9#)nQwMNNZ}fwC;7R^4c2Sr)z7GY8|bz`gwipeY&nj?wT5< z*3%x=%Bl6W7%f&q2L6xJjDDm6VKva=wT4=PmIyqnjz&b<2y`?CNfUB5%CQG(N@Ptz z=FpmH%{5cn!ms&}x>&0pshbhmuS)+aX02{%IV%ayGEy_oNhbM8hWxaG{LHRlwWT$Y zwFX%mklm|km9-^W+iJ!LAq8x0XS20Ek+%mqlm(P?hf>RfCQRC|$r+Ms7m>S&JXK2z zlkarGNY^qnH#xiXx|Ks)(K?! zHW{pDUJu7vMfKW5ovU)bih0jx$T?>I*pMs45jp2CA3h{afyf1rk0z~41V;Efm_(S8 zUy}?x`+Z0ua)rpfnyQ7~i8R8{p!|H~?9xlnL2Y~(bGz~3Hc@MPu{FTx^5b*x2~kh9rC#IYh%T{19!Zwk2|yHe7pL8=*a+J*kb<%u(`F+SA%I+Ed!I zq&FO;VK^MQKeAJ7ek>E)DK5XU zE%!NXf;LH;Na93ov^H6rqD_VEG$Ng@&CvS9TIDmf=e1edOzj0?!E6mfrI_+DJ7d3! z-4(ktb_cQG>sSoYF`vbL9{WY?XMmywU&dl6A2UaLNqbqFqrF1Xn5)gx=4-E#_^S4z zwm@5`y$0LYiFA?nhITsEDu0ud`kT-S>uGPnm3upEKl~11y`wEA{qPdtS^Kx8+A?jq zwp3d|a`&bMa&xhV+=#+8S-G zwpv?9>|C$CtG%afAaR4XN_$`1sBMDn2Soaz_L25doK?PA`&j!#+pKLN7Hrips4-H^ z)wsXouEkxA`;%C3Jq|-b%;mU0;;zJ91{5v$D-MGcvt9dC`%K%eeNNK&LffJ3)V?I~ zOKqF>m9|U!8n(NM^c!uDR<(gu{w*n?Z=oDVt9zk*_G#h9#rp|szxJJmqQS}rs~W6mu&e==(pPty5F>L zW6iUKbyhp4;aKy0P%bWL7qv^;1?_i|i_02@-7!03y2W>o?-AcEzH2<@qGvpYy)m8R zABpc0-x*NM#iQ{UcE()M{?z``u4q?@oqucBw12egBwpA4&~9iqwg18P7Lnf8?r5Xp zt@69tJ?&rZuJ#|XfbwBD5pyi&RD5B4QT(a+6U2hk@feC?euzIF|6}|QfT9H_<1rkI zDd#Kid&pPLSHXvOEG6tGtE}M>jQmHGQ>w z<<#1~I=;HTdcKE!^?fluYs-oC;Rq?#7pcbiDyyzItE>T$H2_&W$c8qs${G?`Ly#qa zEI!^UOC)J0`Y5##q}|w-UK1i~0mzbpq~ZEk1A2r-YqP z?92BtzM~1p66z!dv@}diNK8y@nAj+>F$q-?Sz;&a<9xhN@QFdTI;ftBuO`e-=$Y6n zu{Q~Y36f9tDL%>P_4P{Voe)TM7Gcf;W==^dIH*?==O)fed?oSK#Q7xrlAsdnRG;QM zpWyT9zJiic?Cg8Q*TvV_*Ol1)Z^ENK47U@mC3N$BnD|j*H(z(($%Io0n-ekom(YXo zd-!?=@q78+OME}E7qD{@4kp}8*p!IjT0$RRUtd38A76ix*8#qPzCpgnNPNuK+c(%Z z#5WYS!-#aa?{VK7O{{VU^;eUtP5y53SCeZ^u9HwbaRe!g5xysg1!;-a@^DabO_3kh z^jedX31)f2+gweXG;P|naZ^XrW+Z%?_#`QjCw(J{mM;_SrScTHLQg?WdfN94T%Tus zqkN-DZNmR!pk`sM8cSGXedB!Neb4zO1YO68q|QzBO(J=l98&M5kh7;i8dKrSX}0=0 zoyewxY=&>9@A(qxEPu+sH^sXDd(-@cPL1*#nf3n#%UQGGETfj2=e$UyFM`fFzLy}y zmwm7J<`NG6pXb;7Dq+3qo9|oTTj+Z&C{M2w>Fc0l5lG&!+59Gvy$Q0nd~f^SDUmMr zYu@cZn|C|%6V5g|S7P%L%UMg|EW>8=oMlA1%(tAB;R;A|rEisQwJqH>q@344+g=N0 z+_9;(ZLcG;bs$?0vSf!<_Ab%=uD=y-0NwA|^uABz?}PkllKMtqgs%si2y>HP(+9w_ zU(FAR{6ixD$hSGn_57GHKK6a$+d|F`&E;0Y*b0nozzCIZC-Uu3`k#W_o|Dgr>@$#k z4ze$7xpYt+n`8gfv3aA!7aGrQY_^#lRngt^(w42-$Ufz`u2vYS^EfMAC%30a&~CxIH;%=n9HaZjS_R3qL4Tbj;*WM9~159xUKe@Jid ztDw`qin{f@N<>x(WRW0?vdJpz(RvlVvR+lMMgqLMmozD9a?-@4DM?dFfH$7>8hTB= zx?W4)-Qm{`XOm1OyknQNJZVMJvZR$st4Me=sW#!(*6RRwQPMXZ_H;1WbxBHf^?Lf* zq;pB_lTF+~WhQ%)vywBDvy(fLkd)jl8Exp6+&%fPq#nsVlY1riPVST3H@RQ3X_JHM zpFALWU~>QD$C3w;@V}&oNeU0^_4Rv6F?y^Xr#H~!^@e&vN!}9mMtWmCQEx(QXpr1g z$521HX0k({o;(A#&Gg@sE+@@Q#t@&}oba3LErR&)rB4dlJSt^$$_vS3QpTo?OBtW? zT*`!$i793t;0?%>$thD(CZ$YGnMT6A|GT9f>C3WgQQPTi%a z>P|h4@DC=Z>lpSW?@G?lPo$ho$fQ|b1Bp*+~ zuq!!R@2KbK*?KPFztAdA$1tPS#8#d3M(rB6>!jxs{Y~0onB9sYd`4%3_?+ILU3@zZ z*rZtKU}VaRH&>jipeUDqEW{2{Ho=okjJ?$x@hzOLQ+c3t&HiT-!nVHn!F z8{v1;y9e=m=quZ;YS#nUsJ~z9HSI9;YTZ-srT5l*>U{`*LF>LchPkc#5xk$?KM0@M zdVpTO{X^{s=mSZ*720E%(fTp{-**4Cdkj$2|5EFS_84Zi9;8o7o}4@g>N4VWlc$nU zGkIT!#Ysz&_I23bVM0mUc2M6D^gBQgknl&+U{Xs5>qGQKNpB=ADrxUS^&0dreW*TM ze_S7-KcPRVJE*7hr}U@wXUNe={aJk!gwgPSj6PN$uRo`cga3(iB0W)`pf{ph)A+?g z3f+#zcaY)jN@rDPHD?uPb*K4uGQ64V!8exIc>-@N8{A3yWPOT0NuR1u)2Hh*AUf%l zPV}F%#<|vsUzOlH*gw<1K>U@)<6?Te3%{cu@0#ceysKSI@1eh?_ri7`VI80k(ue57 z5D$S(i(GHG-gGT;z2&L_Iw!l{cGYxR<#*|O^j-R2zy3S)`qXz*H>9pleJ?fe^-(c> zhi>d_>}=v}>U21pIh(^40`;T^-yDD1V|{ZRZ7!x~>d))5^qKk#`fPok^F@7*{*u1K z`9T_fGqXAE<1`~T_)Sx$)8ou?W;(N-{ppU*TxX6m4`L2zUqUaTm(qR7mmt>fK>BvX z@1q8Ew0<8|Orx$l^vn7y`ds~GeV+a*Nq4@!0Mgx;wkiX?FRjfmRzt+K5Qn+3B!{_& zyN!~>wM0QnCVu(5Ez`QfD6)&`h5Bpy>-s`{k^TnJ{HFdEYR(wv#C4-5I46=TW|aLT zr&;=!GyllElKE%mUzzsO4=~`JZ~Yy8vHrHc1nL2n)l#Sl3!Dp`uR&bsbkfUo)UjM& zp_i^>rM?PutOkx<$2)1JmNOpI@{^|+-kZmI@UsW&LFgLS4r&dtXN|truWKE#2iK*x zu1bB*gZ1j1=X{91PO15+Ollqk2Q?=P>;6kwFK1zUcqOYQT;<^JdK}bx(h}C|?~<0V z0n)=SbFAOxEXcxkwIFMuO^>x!yr*MI@9P_(luA!&ll}p;xetMbZO%-|{tZy2tjMgW ztjbx@S!Ru_l4aJ(q1nT-hi4DXemuK|b42zN*)?r7_MPm-*-NsQW-qfz182kg_WEZ1 zWBnuj6G(rHz7=dp%u2{=1Ti7YNpI8f{O$UukcWVF>-nGQpTqfI0K;wo-x?D%h zYq^f)JEAoKT~@4g}J7(o%VLy|b73gXH zN80?Yj<@~aMBeGRDK*>|cX#MLqzBlef9vl7_L3d|zl^hf7gsI^d%1Es<#UW)E}*Ys zdZqM8_>!-D`W-qU2m6SGoWvZXk7$%L)rB6-{&oK6q+EAkub7^gGbv|s&Xk;~Ip~wW zGOQb)>x@pFmQy7)8UnnVpIRw3GPPoA6nx7WkegChZfb5?t}8b^7h7dUt~(c7C2BAk zLvn}a4$B>$`*;W=kcNZWr|;Ll)A#8IpnT^!59){DiY{;_X3fu8kTV~GlRm7Yo+JAA zq!+QKWYu$2KL&b!09LS`Pf9d>LoC{p`>o%iy~Jj$XUB7X%sG*BGUrszaR^w?juX3% z>p%MSogj9NqZ6}!&$*oQN6wX;-yt~ZgQOe|<{k>8)2i_#$;BzX(4UL@rCmhwavHcs zUaV;^&$XuhP+o<+ig}gtBJ=PX{SQ`cyXA_?y93)>a8J1Ay6*bNb;U-N#; zJDc}w-nl%iAsM;n^Kd=ATP{=Huv0>(#7>PmHST1W2ecQ{XY`-+V*QN%Gr3nD)PI3{ zWrMu9ym*Lld9bdrjyitR&+4V?IH#Wn9T$Lu_uGJuE+tytAeOw@=`Ft{Z+F7FeA_iX z@437Qc@y&{<&B4cce;zjo{Rb=zpmfGo`+ox^5*9)$Xl5A8vLIRR;}oSIbYFfWr#kj zw#&r!KlCep+e=UNPm+VbfNA6)km`s|)^vO2^v>y%(>JFdX>X6gcejJn2c`GV83Nlu z&}RFk_fH>?-Ys+nISzZS$A`S4|~j(TqD z|I_cM=azmO^xOd!j%TcT@=7#S%*OjwrR+$H#rFQUHj8Q1Ve7I+TJ>0?_k?`s56&Nw zKQw<>{vZfAg1SpAzpLN#r}(eG|E!rmDSvYQl>Djie-hXq&tgf&vkk-Ov}*j1vwy38hpt;tuVAkJFffg= zbKuTt|E9bbxl8xX>JxI8u3vzo=a_<6e_7y|8b{A@q>LIA#QSml!qj+9Xh=#W0VGDL z;B!Kea|hg46AKy@yskGcXi{JuZ~vaLFJlXjYY}bd&9#Vdjm^N?RWsbUTI>wBxyqw; z1FO~DQFv_C9vvtSy#{~rc;PbDFyBP+S(JFLf23(+gceNi2Zbd({F!iD|ZyrE|QDX zg0wK{+E>3Rn=i#;I=uj&veFAOh{hLnw?8LM+*bdhW$~GqN$R4fAj@Cp!h3Hlrn5;s z>sXKzlA_(GX)b9;c?F&P?FhYcm`|Pgq~$Omvp#j6P1~2o7T{9}SHMGiHojIip8bUa zd=3!-H=q4E_!7AnnN8?T_whgV+WjBg-#dbvoSR8|{4){ZzX}cg;x_cVR?6}hL6xJ* zQx8!UsESlo%6?Rdilm~b%2YH}h5DpTHL5yQgQ`i@qH4ntMb)9|LfG1-9>n^fF_wy< z>Qmcb-}oN~$hJ1Il<|Lin|LaL!tG~m8p7V^Z5qK*BJ6$9W_z0^u)n>HgZipXGuUfR zwV+y3NmMe`ifT=@q1sX@R6D9Y)d6H*x7iI$7@Wg?DwRg1QyG+-#7xRVWm1RRctGOu z|Iebbsg6_*l}lnC)rrcZ3fpu7MkoLOe2Sr1iUWkBcnTKnq$EoAZ{ZVON+nT)7Xmcu z?>0W*`TYNNs(|WDJwkQyZ@W^DQr$@GPW7O=Q}^2R0Co@m|DIGYsyEe#>g(V3qxyri zKQ(|FNDZYPqXtofNgPZKf#dpZhf%R@hf{HFAE!o8iEUf7eS#WE&7)%Ae}lG9QcqD& zQ_oP(QlqHR)EFwh?F?!rHI^Djji;WYCQuWpNz`O&3N@9QMoouvo~LF}FHp0o7pXba zOVrEME7V-7VcU6BLfiQueU<2K+_p*Eh16?Q)3(jqzD6}^`#QA zY8kbhT0yO(R#B^|HPl*a9krf%m)bzRN4-yNq&86>P#;nsQJblcsZXdaRNJ;&sg$;>8l{cf=AKHRb8?~BRh=kWK( z6qXiu5&TxV=ly8guAtZd$z8`T!nTG z{I!@o{`HufF}GrF$J~jz8*?w_e$0cIhcS<09>+Y1c^dO9=6TGEn3plHVqV9*f%X>s z+nBumyO<9#A7ehne2)1N^EKvM%=efW1VQroD1yVCUQXoBt+@NPScF4(xTi}*Bt%9O zL`5`2M-0S-W`Vbm-+UW!5f4d(Bt~M9BuG*u8Il}Hfuuz8`>BuuaFm6hr9;vq8IYQO zMkEthZQn=2NM`sbGz(J4&x+Lbvm@D%27XSYzMlihg~Y_t$ZzU52VWX#<_C9X!54QC__Yl3H&m2ITKE-^iby4- z3AD;cRiq~J9#;vEC`MF9svwPs|3<4J)gfL3sfAp}Ya?}#x=1~wKGFcJA<_tG8n2pw zZUoj4X^iwoMgcWLS|DwZNyt}3K*ylXkmkrW{J+tbNGpi9M%p50@pediqyy3s>4bDf zx*%PVZb(n)yCtA*NOz946Cy+oNMvfpy zkz>g52*qW7)SN(0A*Ycu$XVnZavr&WTtaRmmyxT;zsMEj8gd=Ef!suH!5vq@9as;L zCz1QF9wVP3`YG}Zj_eKc5%x0BZ;17zPsD(**Wzaih@ zM+btUD2C!Ffzr?u35Y;RltOv%K|F*qC=1Mi7tubbgvO#Ws-P;Wp*m`yHuOdUGEft> z(8S<_xP!W=2b>6e5;Q5AEOL)nN;D;!3QdQmM$@2a(cV!?kCs5opyko(XfreenjOuE zWH3evemwqZQDKXeG2VS_Q3& zRzqvReQ!0*v^m-uZGpB#TcJH8x((VEj;u4<1NJh| zozZq^d$a@E5$y!j4x(Mqu4p&3J7haYDw;)V`=b5O{^$U7AUX&gj1Gf-Z~_{P4nc>a zqrnI9;phl-B=9KkW6^Qwcyt0f5uJoiMyH{3Kuu1-lhG;YRCG4@AU++PfzAY;1%56% z51o%LKo_Em&`s!KbP2i?U4|}4SD-7=HPEk2Kr7Kz=xTH$_#nO(U5Bm*-T;0xx&_^e zZbP@DJJ6lzF7zLCH@Xk{-3e$nx(D5h9tI!8_oD~UgTRNtA489$C(-lhDfBdY20e?O zLoc8g(M#xMuz%4!*cJ3DdJWBuT}N-AchES06TJno+vr^s!zfI|Oe__44}F2&M<1XM z(MRZG^a=VDeTF_ya7DaCzoM_ux9Ds14f+m!kA6TuqMy*u=$8aF-_Y;y#tXqv?B^AT zV+2NGxv+$D(OBG-6v6~7&>@V$Sd7E?2nAPCC|(J0yOxY8n2Kqbjv1JRCBtmY!yL@T z5@Ct4SS$&a6ibe!z*1tVvGiCPEG?D}J_rqT1}q~SSr#lO_5}@e7AzCyV__^a_6txZ zh-SsIVcD@9P?;rK9n|Kxy;5 zx??@Co>*_J57rm!kB!6zU<0v1*a&PeHYC!9V#9!kW23Oqa8z@!Ww3{Vo`a3S#$w~J z@z`u^0yYtw9`zHkN!Vm;3N{s+2GL2_3~VOU%);hk^RW5YQfvXXFwz!bi-DI!#|=hZ zfvv<=VXLt<*jj8IwjOK)whP;cZNfHVJFzX;)=1liZ3o_g{exY{9%3)B4;Y2-#*Sip zu)WwmY(I7YJBS^^4r52+&)PBU5_TLrg`L1oVyCe)*jelxb{@NcU5u}|jQxvU!LDN0 z;v+Y(o7gSv0d^a^6KQv`d%*XxN0BrA7<&%tG4=#|iXB6qMWfHLm)I-pHTDL3i@n3% zV;?aL|Ac+TK4V|7Z`gPEj|GCGIF1uIiPN}(hj0dGaT(`uKGFnS1eS0W*Ki6?iTn6P zEYK-&9XD_jx9}9WjXQXfsCRG|_wYn`Vmub2E}j%m1~tj?RCsDU4W0>4i>HgU^mqo~ zjL~s}Q8VMe;92mjcy@d%o&(Q?=fr=-bK`mNym&tRH#|RB0lXAm5HExm#!KQw@S>4c z3@;8`0xyj>!Q0_o@xJ&NybN9uFN^<<|AGIBm&4)1BfLCb0snE8SHkPzmGNqL6}&25 z9j}4c#B1TT@j7_j_=@^?1H2*L2yYx8X^J<)o8xWq7I@1@YlXK4ZiBbSgTFZ)@GhV_ z;2rT!@b%)((P$UE8{Qr7f%n9F;l1%bct3mu-X9-?55NcFgYhBwP<$9Z93P2~!bjs{ z@#*+Dd^|n@pN3DwCq>$1d8%?z8l|z z@5T4w`|$(#LHrPY3_pw?!H?p{@e}w-{1ko~KZBpc&*K;Hi(r@V%lN~^_y_zW z{t5q#f5E@v-|+8XF$4||bU+D=P>47ph!8;%6hRX*5om^B2`S<^f+qw*1U&#@!Apcj z=!8L-gau|3PJHixwh5O=MEpi1CQ=fyL=qw?STZ7ce4iY2G9m?$ibzeQA<`1*i1c6? zh%k|n$VB)=z9?lTvccCUvJhE`yhNb06FG=H5ucOzmB>Zp20Z}D0X`3rpC~{SB;eN_ zL}8*xd|w1~VWKEeoG3w*BuasmCdv@M$17Mw03v0HKZrjG_&OHxH&LFbPE;T&5|xO` zU{#2!@qJa$RfuXt4WcGdi>OW1A?gzK!0HnX;`;`m>k|!$#zZTBATfxTKopS{5lw)a z60QAaL~~FrK(!>Afp43D18POICfX1K{u6Bn+q8x1_R!i90sYV&h>k=jqK)65=nT;= zknc)#1JxZ=527phpVZp#N%SIm6K(x|L?5W>3)w+LJAX7W7-$I5-X97y4Eo{32v8$I zjUtAFFCqQpfW{DGi4J~oX&h`f9`bpmZvF&fA~BhmP8<=>i?_sYA|-{TiNqu#kM#d# zrVvvhH;tGEmzW1OpIAUFiC2q2F94fQEF|_4A#ySBQeru=nvls9 zWKMEAxq?hXEhUx_gT?e< z)M-#>h?C&2Cg6b166c8X#1-NK`E+(VAN>!#A8saLNS4e)aggCj4w^uU5RpiQR7s80NrN;=i?m6H zbjiffy9vl8Ju(rQ9DERuC6kazfs=txNv0xGlWEAbWI8fEnSsnmW+Hv^7wG*2?$lu8PU_iSE zi;;hl#mN$6NwO4Knk++>C4VRXAcLzZxXup}<;WW3Ut|UHZ?ZgDk*q{kCaaKD$!cWv z1T{6uT4Zgq4p}!rtUlR*Y)DQe6VBC`j2pEH*^&%&6S67UjBHM}h)^(JlX%sVY)!Ty z+mh|b_GAaLBiWhkOLifPfWDl|@*^BH=_96R`{mB93AaWQvm>fb5C8tF6aB>73 z**J1C>}8w<-1K37#E4hi>Ol~0+Dvq|1 zm&t47J@Og(mE2AqAa{^E$z9|>lF!MHo9r7*&WWOckMuQpKqfR7t87RhlY8RiOT+DuOCY z{Z9P>K8OXnG|-<^IqEOSmj@qYDo~ZFDpXaf8d!B|2UUZrN!6mZQ?;o&RD-CmOVxu| zeX1eVpBhY!rY2L1s76#9sxj4sYDzVuno}*PmQ*XMHTCmvVq2;w)sE^&wWm5zov6-K z7pg1Ojp|PINKn&@>P_{b`cnN8#0F3UsX^4X|CwtD756uBC^d=-^iXOTHJlnjjf_xm zMGTEsqo^^|SZW+Ko|-^Sq$W{QsCm>>Y6dlpnoiB6W>K@LIn-QgKDB^aNG+z8Q%k6& z)G}&oM6aM$!jY|~w!mHndOfv@T1~B?)>7+$RzY+FwUOFHZHDanNX4Q^?M~_%^?-U# zy{AZe7j=aChuTf;q4rYysQuId>L7K9I!pyWd-oBCsiV|I>KJvBI!>LSPEn_+Gt^n? z9Ce<$5MOhNx=j5`U7@bVN3K&hsGHP%>K1i7((X`qf$vcdBcnc|o`HHqJ*J+(Ie8k5 zKBHbxFR545Yw8X4mU>5hpiufF^@aLGeWt!r->C2KD;k8xXq+Z!ik4}b4$%xP(JakH z8cz$rBCXIWP0}goO!NgR&?#t*)@g$_>EyIU+jMNy+q6Twv_~hR6GPOYlh8?_CK;WQ zPDQ7tGtz13w2_vMP7j^9g6A^jq4Uze(fR2DbV0fh zU6?LH7X>Ru|4A38OVB0hKj>0)=}0R>mj(WvE=RYbyU@Mq!Sp2hFS>f`(Qyb3kwCUi}@4qc0`P1mLC(e>#DbVIrk-8jCYDcy{2PPd?2#z$JyZRoai zXSyBTKGHhS9f3R1UFqPJ?MC+k)s5~>_n@1TJ)_ZHbRW7e-H+~151@nRG7X`}!E>QT zz>}ed(jh;hKr)NakOnMgZYQFl^g?dK0~w-brtvw?^7F zdOPrr=(xeCyXigjUV0yWfIdhcqDzDh!=33z>0|V9Ft|^ZK1rXVPtzCaGxXU=J4c@f zzCd55KhS6>6p}+pLjTft=_~YA`Wk(mzCqulZ_&5uJMri59{q~GPd}m`&=2Xy^b`6i z{fvH2zo1{nSG=a*&~NE?^!xb8NBR@}8Ge28h5kwh?Hm0aI3|RJf^&|CXi#_vWau_Y zMWb|x39;~$DLy2G#E=wHLS9G>86hpChs=-_vO`YD4J8UC4#kF&!n3uKg_4I-gwlml zhEhdZ>QEZsw2>!oB?;vV6%W0ngC_{&i#&xZ9LgNZ8~P=bCG>05X9;BuWea5w4AlzN4s{3rzwZR=h3bbIK&B!1#-S#m zo_;sjI(U!U6y7&Bj>dZVanuBI%|gvXEkb?$uHaijeJhBy4z&T*7F4@XYuN55_3_(> zI)plg`uSbJcY>adXP$z$MWb08-H3r(~(CAQHKQ=TjG(ObXpAeb| za}V-k`Q&_3A&n3g_6moD2IAz-IhUl>{xS`7V?&{9xC{iE>|=w+ehp%tN(p(Eh`cg!`RwV`#8 zSr2|=XcLTi7`EOF^KFds584FrEupQUZJ`nVA@JLwb_c|EhIWDa2h{G+PT1}z4fpqi z_J;O_M*0WA?}w@bP_-!(%y2Ap9QO7^=v?Szgnn=w9S@xfoerIWna+Yg4pkRI7ekjq zmqY)Cu7s|Ju7$3LM)^-dqy49$vHq+7&<)7lgxs~ztbubU(1*}CpI|dw`GQ&ma z2geb^@QlES%s5{H&p?&RXpGJnjLBGx%{Yw9cubP0PsAiclbgxIA-YkIz_27(}n5DbY;3R-I>Y$c%}!_ zi|Gw#X)rU@9|^}5P@wxTeVKkte`bn5h#9~PWQH(9nPJQ{e*`l;y2U7FB-H)JjAq6# zW0`T0Hi?JGl!YW%x4xb3z60kS^hcZJad7W<6j1U5vndh)nO)>;W~2z_Vy-opScyG9~?(FnA^-9<}S>15Bv?N zddNIt9y6WTC(Kjk8S|WZ!MtQ%G4G=OHS>mf%XDPpV(%gTfiYN@t;IHC3$pM3hd#ou znLaU}nJ>&&<{R^!iD3~IWq)3?I4iLPOSACbD>lS3EX#5%&kC%VphjjDR%JC-PY^R% zi?!LgeofY4)3BLYkB$3#lZZ_o(TUi^Y%H6Eo#!WGlS1xCJ_Y1cvZ>hAU}@QOY^Rijk>}*yRzFg1dWPfFI!LsXlqS<`xZ)|?HK$Hryh1lY3VYUcc zl%4O_V2iOO*phG#%CQUmN^lGT1-cYlnk~bYWf%B=!o&6dVEC@lbMC6L)!6EhR-3KE)@AFl_1Ok&L$)#7mTkf|XPdIk*cNO{wiVl&ZNs)>+p`_m z&TJR9E8C6j&h}t?vc1^eY#(;9KaySIk7AekT`*yHR8$eaX!nmq$!-i58t!hENr z{DaOw{2Y6py}+*Z?|{DuwU;1vnf({k6;M~%%dp)~TH{}1ud_GUb^dMeH=*hlRGndi z86L0?VQ(L?FWAQs`oVGZkbS~FWuL)J&%r;0s@Lpb{|&qoddt27|2+W*^p1Vceqg_` zA0hWC>OZqzAoi7w;RgE%_Z>1QH^_&t^+osxVI0m8+6F8LHl|fzHNd=W@XKIl<@RazkAnt{|5e>T<<%9OdTn zaldi-VeA6nb3;|a<1ES*8bzr@xb1#4X{L!Z}#W{o`+jV+bhF z%edv-3T`F0%U{E-;#PC(xb@ryZnwXQ+Zf$q3%42ST5(&sZQOQlN2KlM_HcW-ecXQT z0C$i(%$?d-cbB`z z-RB;1kGRL&6YeSZjC;<#;9hdCBJDNzhI`BH@!xarxR2Z??lbp=`^J6eVtAfMc$CL@ zoF{mary`B!Lp;Op^*Ns91zzMOUgi~Ed-r-%|;}h{o_@sO?J_YadDfx7K zDn2!z1|BStp3lH%+$vZ27E)HMtsxAsLlA+pqlZ` z`4)Ujeuv*G8gB#fwtPFjJy<8cGv9^p%6H?t^F8=p{1Co3-;eLZ_vQQZ1NedbAbv1E zlpn?qha(utPvJ-LqxmuX0e><-mLJED=O^$JfhO@&`Dsug3CDd`s1%_XwGx=Hk zL0=GN^K;;%gE{L-@_mA_wjrA1N=e$5Pz6I!XM?2^C$R|{3-r4e}+HH zpXV>|7x_#4W&U6O3V#*s8h@R?!QbSM`nUO8{9XPYf1iKAKjI(rPxue~Q~nwMoPWW; z1bfB5=HKvd`D6Zj{vH33|HOahzwlrAZ~S*YMnDBjzy(4e1xkPiG7GH02?u>%5CvIK z1XVcUJAx+Yf+3iK1!N0x=f)FUI3tz#L_%UAR!9nT213EJ9Wxn~+_|A>;)6RVXav5^@W9gp+gfLPV1^Yin zm@bSJ#tGwv)BZGJf-q5-Buo~j08JHU2s5E#t#HoY0ec)!pl1oQg*n1p;f%jpm?z8^ zmO)z}EEEf$lsxNt%^DVzd3EnF4O2xooKxOebC!9tt;vo5C&Ows1$dE8G+A3lHMY@FU@!@K|^zJQ1D> z&xIGlOW~F9T6iP8jjwnwd=NegpM=lxk*~rx;k$6rC&d^M5m6Bnaq*&0h;-z0qL3(n z3W3s6=uhnN#8N{d(gisD-#pg{jB<`Q#@dBn?p zNinaOPb>oMH!;6hKrAT!>lYRaiABX?VsXfq5KD<=#Imp?)>XfP_=otXSWf&){2Qoz zbnHs-jQA`5TOoKxd{wbpq}32>inYYrVjZ!rSWm1kHWV9)jm0KnQ?Z%YTx=<}5?hOX z{Wjox!Dnaf#SUUeu@gKAzN^?x>@M~Ydq!F>vA5Vq>>JS)xdCDie<1wsc#zm1{8;fP z2Q)++Dh?CJh{GW_BI-wqqaZd~90yyD7so{a{y`JPN#bPjus=ndCQcV;h%?36 z;v8|VI1g;TxIkPeE)o|<^ipw|xLjNzt^`{pu8!|lgI*=B5!Z?9#SP*{uubANakIEZ z+$tW9(suD5afi55+$A0o1HD_^BOZ+Sz2ZJ`zjy%jK4K5}gW^%~n0Q<~A)XXZiKoHN zh-c&bv!Kt2=fn%*Me&k&S^QVLB3>1*fn67G#P>HqUl(tRx5Yc+UGbiHUwj}w6d!>- z7N3hx#HZpj@oSV`h;PJ~;w$mB_(cr#Tk)OvIpW`oAHIEC6`!7@){8Kq3|eJ0QuC11)c{UT+NvP#*c z>{1RXC)lr2uJ}F|=wGGWQaK`!DpHNXw!qjrob-$>xSlOi)Czn9bwqV1uogVYgJCs3WG4zR^fy6tz7x=P)oyM9lpJJj@m zn!ZwBQ2nGnQvWE$aU2bh215 zN^u-Vo24z%Rv3Mov_sk{?UMeHc1wGt15v+M+9&Oou0-h|#1BcwrGpWFSUMsdl^*;5 zO2?!V(n;xj#GjH*OJ}4f{yFKa)X6^~U4ZyS>5_CA?3#34x*^?^Zb`SLJJMb0ssB=X z=D(7j`|tik_aJv4a(AQ$pdLz(q{q-dk)DEj<}>mS3iLDSIoJy+B>(S-ucbF&FQm89 zd$6Z4$20!}%oR|;Kj@?MN%}0k@M-xAM887SH|aa582Oox$lqX#pY+^EWlYB9mp&yE z@>8FL8dhdOaWW(GQHtX@5@b=9b0Lgo&udhP6?Ju{_Q_wq?OZwrIORj8Npt`9IyRMFjqhU z{~%uu%bDf3em?mZh-QJRta3I`*+J!yv%(fX>5ZRL{#DK;zw`6TxuGTx)D(~lfGQ~G zmkUKHj^n7XTtqGkqZgA)$R*`ca%s7YTox9w{6qdzE+%V zBfhd+MXoBp_nXMoXC#0)1Y-0CrJ+`JXZVm9KzZl&{Ly!D3(zBzyzr3Mk+obW^@1-Y}8>Kjoqj&Oq z`2&ppQT{A{k-y5{ND) z6lhL?j~Ns}N&TNOWF`0`D5|1^5ikcCHejxR0{%g!Vkx#lhf^sIL|v%zltiEsgNju= z*y1Nq;Ur2@C7BWmr&N+dO$w+5&q!d<|@FJxMWJ@V^ zl_p9XrH?XDnWYp{iYp}`S5hghR8-0+<&?6@@5&#_pUPj#-%5FKGTtW;5|D%F(g zkyb;gsnk-~a2=(#QctO`G*B8Ujg-bpQ>B^GTxp@SR9Y#mm9|PdrM=QY>8Ny4IxAh2 zu1dE^>#p=rdMaGFx6(`LtMpU)D+824%3x)PGEo_-3{!?HBb1TKC}nh{jZwxb;}kwT zK^d=1QYI@?l&Q)zWx6s$nW@ZH<|uQOdCGicfwE9pqO4bzDyx)b%5r6evQk;CtWnk~ z>y!=3MrD(-8ElKPRoSL&SA_6RWry;QvRm1s>{a$D`;~*rA?2`gL^-M)Q;sVql~c-T z<&1JxIj5XgE`S9q=v;)ii2$ z)v}AK3=pd-VNT^i37|xk1Ft6FfFxB`6*bj=q8e>;4b@aFRSl<9ZHPLM zcU2EmB2bA{7yM6B!m(-+HL0qFQ>e+HCOKr&sCqcFnieRXYJ}4RWq>}TnhBH-Dy(J% z@7X^&pkLH1su_0etgu}+$V+yjaCS9^`m35(J!QSHa;P~~(M}l2rRIiM9yOo(o0?xO zpcVuxq!w0-$E%{C3xgF>i>S5KxoR=s5^5<}zImytY9-W?>P{OLw4^}U# zqc&6@nc5sw3s5c9X5c#~;DB1Gt<^SaC$%l)+C_bPwFAUDs$F2qu4-q< zbc5C<%0H;P+C%NBI^nryFUa;*$Ez=OIowBe!->Lu!S_@9!&x2xYM|N={O|-E&>(fN z>V=1?L!fFXyrLtZ@1qWf%8}4Us3X+4ev~>|9RpX&SalrCkvM$Oykg!oUz-!u*zguB zad@jW5u%gS(|Q|YvN}bbs!mgTESxc#b++ zpR3MOyJwidFp(%tF{0l3)KbC7O9INvP4~`E>~BmE7eu%YITjeR$ZsAS2uud zRJW>|)XnM^Rng*Tn|fKjrruMZsbAIY>H&3!x>Mby{-f?z_o#c-ed_+m>lIu#_0)sv zS@n>5R6VR7QIDy|)f4JT^^|&AJ(Hm3oO)ippk7ojC5Zj2UQw^Yo6dxDU59HS81;sF zR}J(H^`?4Dy{+DfP+aCm&0Y1r`apfCK2jg6Pt>RCbM>S8LVc~iR9~rY)VJz8^}YH* z{iJ?Yzo_5f*5B`HjD~2E7HCYvwXg7n4N*G-H#6cIqY)aZQ5vmpt!bhGAu^%vx6M7cGmHP0Oz3&~j?Q>Q}iF)a2EI)wA+z1ro#x zX@#{ST4yccT*b7wQHyJ3v_KcvN@yjuQd;Q<1@je;S7o%{HCPBsE2sUX{jHVPDr&X0 zN?KK|vQ|Z_rd8K!Xf?H3S{<#fR!?i7HPISsjkLyEr-*K$V{hHE3V zkzk{=DcWdlj5b!Atc}ygN7@8!BJd<_s+8S+be9d}ogSJuIq-~CmY}K}D z+qFH~4sB8K zjp!Kth4xZ=rM=d^Yj3o-+UKZ$tG(0SYag_a+9!y<)4ph5q2`;0=%|kAAsyF=NF#L$ znAYRQ4Myd3UKeyxmvkB4lBv3;>$(AE>PdA=w{=HPqPw~mX^Hg2z_EHVJ-=R3|5LB5 zH`0^q8TAx;NoxUSdTqUqURSTLH_#jEjrF#A6TPY4OmCw%*IPtd zOT86vYrUP`9*${%J_`0Q&;#@idPlvJ-dXRjchS4*y`#RX-c9eW_t1Oly&&36@1ysH zntu8~eULs_AE^(~hep~keK_!l=(xeCWAtR()FR z?Njw>`gF+506$BgttSbug{_01PIKTV!K`R3X*iB%LvF4~bg z)Dloj^+mAVPf8wMrZ3l5=qbai!LNj>RZuls4`$h*Z-hPGq;Ix1>nHSeRuGAg#YZ;k zR(Ok^Dx4;~9n=m`JM~?l{sFaH-wFOe0uE@8zE@8j-mmY|?eM|K)(4>K5VV8(K|QWN ztRK;j>Q4BWejH{>8!m1BVU{-&Sts>$VZuxsCe2e2J)@u1&*|s&3;IRzpX$%_ z=lToS%eZ#~fqJFC*5AN>z182tP46G{&-zFGlm12js(;hJ>oEpmpvKQFaDy;NgEHs@ zF~(pG&PX4wZ19F_q&Gw(ZhXlwB3d$JLork%Ls&O7$oAf#&1S`BU8ANQNSo<6o#`@8s2`EgX0P)&_#@*Mlqwf;fG5ZC5)0r8KbQ6 zyOB8z%dbVZ_{%5Pj*l27rHXB=vt;RNEd-QeKX&g3o8GDR>jNQgwW1q3#IA9z! z4n?y^jHAXe<9L)#7$=Q$#wp{pamL6Rer%jI&Knot99%cDhws8M1Qh6t#wFvj@vo6B ze9gFGTs3YOH;r3Hj_@7hc65t-#$BkJX52R(7!Qp{k@n1ZZoDvF8n2Ak#v9|E5o5kL zJ{cd3kH%-?i}BU?W_&jg6E!iDGSh`=_LN$_d{4u~;XlQR{Qhnx`gqA5X4HZ|B% zH&w_O(BSPeX$%b9EauW-YMVW;3&n zS=X#*<_zfVChGrwPF;Ek;xjD?7XwEa2nOn^kW@odd*~)BfwlUk9?acOO2eYHu z$qfE&?WT4zyO;yau4WIjo7vs$Y4$REn|;i_WN&t<{_xytU_Va>K~y%A2yGeN6lkqzA$4QH&4Kg>nF^U<|*^EdB*%Le9k;?o;4GP z^N0U6FPN9i%jQ+{nt9#4Vcs%tn|I8+<~{Si`M`W=J~AJhPr#m<&&=oM3$sA@mHE=eK%t)#6qotVcfzj(xNQd$`=k#FVYfQu~rf*sg=x1Zl$nNTB)qmRvNIhR#q#WmEOu= z6$xjtGFq7|-wIoqfqt>FS$|nItR_|)tGAWi%5UYca$3Jyxvbn)9xJbv&-%^!@v0ZF z%2)-hB32=*uvOG5W)-(eSS77eR_XYPvexewEIwZ z3RKOi8Tlz)%c>8mmQ~xTW7V~?gzH7)4ItjoYGgGAYic#Knp-WbmR2jPwbj<@Znd*I zTJ5b4Rwt{o)y3*+b+dX{J*{4_|9!0CR$r^1)!!->9%c=&23mux!PXFg#CJ767#Est1-AafMj;VA#0W7cu&gq1&h z(mHLOvCdlOtn=0d>!NkZx@`SxT>-ml-L$S**R31Y^C;c2?pe33JJwz6nHA{!)&uKl z#6PqiS&ywJplhoSz(2KKTCc3v)*I`s_0D>4eXu@SpRCVdU#uAWtM$$LZV7fAAvR&7 zu<9mm^LC&~o3goxr|po<*evMUDg~aiWm~aTTel5cvx8474EQ`K_*6)j^n7JUCFKt zqgS!3+12eDc1^pMUE8i>*R|`}_3Z|BL%WgPEb1HEP3)$2?No_pp0{^@Wu;``ZKTf%YJKusy^c zY7euA+av6e_SmQ&WskPU*o&ex4&vkODfYOCpI}e4C)pLk3+>7FRC}5|C*r5uGwhjm z#qex<7R)!*o(u7L_I!H**iv}4m%;12++G5HV*(Cng}u^VWpA)oLvBsfueH}fY`wh+ zw%lxQgv=Ibo1*-Kw%Xh5?RKT`b9)D5_u7Z-EA}o}k8?M~_SpOE{q_O-pncdrVjs1S z*~je@_DTDcecC<)D`uXv&qvw?`=Wixt{nc?zHDE$ui4k_8}?26mVMj4W8by!+4t=S z_QObfWIwi_*j2*M?5Fli`<4CLeq+D0-`gMTkM<|~v;D>XYJao8+weG12XRmbb8x3> zn1lzc&`!u<9M<6+-Vq$pksR59Us*V+qd7XfDX$i`91~Xf4c7TB9ZuxLI@QDJob*l( z=Syq}Ckaqer$#uLlN?kEP&L9So&Tj&PHHEOllFfq(mOT7zc?9Sn~YAaa3&xh`mmEZ z60a4m70%)W6sNK}*__(p@3Gl|AIA;|=Wud5xtx4X=h%L+qhgoEu8+MIo74H#c@+Eq zWpX=tAeR@`5Uw4Y->DQ^0M-&N1nUeJiL5tV%qbPGN`MYl9WLq=cj`NXoRYw$ow80j zXOuJ7+30v~Mt7lG+9~7YjQ#&IzdL_G?oa10r)=!sPKns^P6el;Q^~39RB@_0)ts8p zS4%+Eoa#;urylqqUdyTN)B&ywzJXIGTr9SDY(uBH(+H?>)HiXOIt`s>P79}QxFyuI zg1)uW22@*6b;IqP|E2a$2dAS`G`16D>xB!(_nn>k;jYg0M4uB`iBl&okhn$SE>44R zme~5?tg+pkG_l>Cog2|v5jDaM6vzDKPb@sAk*Jz6z<~;a2kgP zI+qd+at1rw+@0xQNN2P&#u@93bH+Op zoQcjPXRP&OyfDhs`oSDun;Mw5kIrE(b&O&FAv)EbUEOnMS%bgX@ zYUo!apcT$aXO*)7d=Ovbtaa7_uLr-$+3aj_w!+HJ!TQcS!FIuV&v9!&yKWrqb8b8L zo#)Pbco@ij=cIGMIp`d64m(Gjqs}qsxN{=%x&+rvW9O7})j92)bIv$to%7BG=c04T zx$OMwTuD%K&AIN}aBe!c62$H}cb$9i7cAji58yQqMt$f!a{~R)dE`8Ho;Xh<6qor? z^UQhSymVeUubnr}Tj!ng0Uqx7(fQ(hay~m>oo~)}cziJ8VlM6yF6FW=?S@>&wIiBy zd6#rm*K)5ofmU6?6tRY29?d>A`1meK+imaWlKWxLMq+ZZ@#&ZZ0>6o74T(9UZ0IZe_Q+ zTOZb}?&juki@JH;eC}^=to@zZG%ky_ox#Tih+>mT*hDrQI@aS@(DM z5BE>ET!Nav-STb)x1w7qL9B{f)ve}^aud!~!;KrYrd!VqbWOLGTidPU){RgwU(I+` z&u!o~bQ`&i-6n2RSXsM;+X+_JZUgITw}$n%+qv!C4sJ)cv)je(>UMW~yFJ{VZZCIa zME7y~!jTPjN5EbNda&Ei?e7k72fBlR`ayJvJJcQK4u|aENJY0u?O1o5JKmk(PIM=^ zliexqRCk&?-JRjihJHo@n&HlLXSoZ&2k|-XTz4MueDI6h#qJV!sk_Ww?yhiGx~tsP z?izPJ^lK8(8h5R`&fNk&h;MK=x|@JEgWvA%aCf;E+<)Bt?rwLFyVu?49&iu3hup*N z5%;Kj&OPIv2X)Ln?w$Z2!~%T;=%jnfJq`J@;DgLL_o92rz3l!AcE!!>U3IUy*WEnc z4fm#d*NyYH+}jYl;i~BV}&38A(Lp;>Oyr0_<9_dkDZZF|nAusNVU_8+aG~=-z z=kcBpq2P*O;+5#hp5m#V=INf{nV#(>_Z%;g=X#!(*o*a&cuBovUJ5Uzm&!}yW$@B^ z>Adt_u87X)Wr8Ei>iz0{bpxH%^S!W_+55%I0^~z9o0r|o;pOx)LN2a4sLkh9_ZoUF zybfM}?>Dccm)|Sk74!;ug}owPQLmU++$-S)Kg-}Kyi#68ueA5OSH>&r{o(!TmGl1c z{`Sgy72<0ud6m5?URAGJe58g~)2rn*@M?Q?BCW1h54gV9C^BkeuQ{m3UK6h=oRenJ zXmhWn*UD?{wei||?Y#D0N3XZn$?NKM_PTi8yzX8Pucz0`>*MwH`gsGqQQkmrkT=*H z=?(FQM%plMIPeH>v^NHhYOc2&_At>P_>e zLv(Uvh4u-sLi;>ezkPwX%v}oJ`tPf|JZvAI4O?oalB_|W|tM%U3OWt-F13#2n2$=CxPJZ8r&_o zySqyuIKiFZ5E3MK&;)`83HE>0y}dKDEL<+R``&&2zw7s@XZ5P8ySk^V>UB+36rK)` z#$)hUJUt$dXTTHiOgIC>BzURgG@cE|tw7);$P~`v99&Z_ybyjn8{%`}JTBlOF5x+G z8CP&K2`jjYYq*XZxDT`{o``1$n;dveGR+|-VYywkHKfd z{`QTmeeP@Hb?~})J-j~N0B?vpPkv*(J>CRwjyJ`d;Vtl%cq_a$-Ue@rw@bF@fPaa1 z#5>`glQmuOZg_XRFWv+1X@y>RZ;<=o{c-eb8h{T5ZU8)J zZ^w7wJMsPaE_}BY_TYO#-j|dw%Jn<^5Pldxf*-|?;m7e4_$mB*{51Xp{v&<{KZ~Eo zo0~u3EzFGMIlP(amztRu@RsIO;t+9?I7i$g9>HG^F5<1s;Y3Sw1aS$!f?vh2;XmWo z@f-L}{1)EYe1f+zf5+RIf&6XI-vRwi{1?2f`73@GzX$X;{627POowk{KENO1kMPI% zAAkeXKgFNn&q4nJ@GJZ^{sw=GV}y$cA>4$AXm7?69nADZM>CN3f<6@VZX%5M(o92y z6A?foi74PYm=52;OiQFAqKOzHBjCXFaqs~=1LzX~XC|@`S&3`}P7nl1Py|gdR$vK^ z;0eLvB|;_?LM1dpCpww=iO%LnL{~GAH$d+Ly+)Ws7c-H_PUHZZlgI^JXVc+3o4JWR zL|!5vQ5bMw`T|5j;$zSk0$hYBN)#h15ygoTL`k9)QJN@2l(j-RqC8Q7sA%z(i7G@@ zq8jlj@fq;a zI%0si9zr0$nCNdV0o`(9DOfB6%N3wmNqkK#CkC1hKhRu7tR}u8)(~p}+u8x<*I==M z*hp*w{bs;hh^@pnVmq;e*lC4b#BO2_@g1?3*k^_P!~u{G5{HQ+#8KiHahy0ooFcv_ zP7^;6KN4q%v&4DgC*lHek+?)$Caw@yiEG5qR=7^wAZ`+OiCe^NE8HP|0r^+rH{w3= zfOtqeCY}(#6QSf&;u-OrctQL@yd+*(;WhDwcuROmjC5HcgmjZ{VcFm?G7T9{Mv#$Y z6q%MxN5+t`WO_1=j3+aY31lWRGns|VN-}^0$BG0=k`(A^z#Pew0x6O*sgNqEkveHu z!AF{8BAMOdbCFHV+%O_-Y~~?zl7Uh_GC%nd`3YG7VhbkWkI6!yD@+z8i;-Qj6er7& zWyx}6d9or|iL6Xk0jNq=BR?fSBR{wJ8srybO|lkQ8=wwZH<{K2z7AQBY(O?78O^AipF#lAVBWlBGT1&SZD82icSC zMfN89kbMFAk^Pftf8hI(1IVw)LF8a^2sxA-Mh+)O0E{F@CDT#Bk0eKvW65#kcya$s=aUP_r50XDE+Q9`OMq{ZWdY!& zApaomlgKAk z5Bx&NJ9M9{XFebYn-9rHFIk#)?M=q5wuX{rMCnUP?m%%u`l z8S<$@Rinn4-%?wsT@=sF=BiTnxj;=t<39Hp^*L3YszH50)ud_x)CQrbZW@D-e)r=ZQ4T5tT6QmgdauaHTIUI0Ph;L4{pjuL`s5Zd0r+QL7r~y>R6xxoK zwiQ^m2D|ZQTdEyE2kJ|(>_l~@x&U^#E>u_0bfdZh?G7=)`S*Yl20-XV^``m&cDO!N zU(ocU`UCAjeFZR>8bS@FhQT=oUd>2q6g8Tf16MQ#U@SGBnoLchW&k&injNH>KurYQ zBuGD*no3Qhrc*PiS%BwK^QigM0%{Splv+l8^Nzd#;ulhjsU^uVbCYeCQ!A*I)YsH1 zYBjYS(yyh~Q5&d@z;B@@ng{hQ)K<8I+oO6Ikx(qfKs4IZagWXTSpNDWRAcrf|Rq7hGUcW-I zOfL2+^)q#yx{-uY+)b*SVaMHq9Bxy0s9P5Ph5D7cOWmV>qwZ4=03K4$sn^s>@pI)1 zrKa+bdPF^@(&`VX+FZE)hKt0KpKp8(IK>(_P`S8u;4HaOQ_Om=y19p z(Eju@;E^tpj-u0oHXYy?dWsoKr>8T}ndoeA#x$KLNSEExq^INPsip*WGMxx~JlH1C z86jk%rEYXBb@SAZ@^e+;pO z0LO!U5xOW{j4nZ!1YCx$PM3u=<>*RuW%|<~zC2w4;wvVfMJ70pN^}*vDqW5KjQ$*O z4JfZBU5l|`id%9aHnmTk{u&M`E_34(tH=tX9eFM4?$c^b{bn_Ha6S`@# zreTsrGe}v7ZcVqL+XB2>Y6rRl{UzOz?nHN{yU<+$cYyQhLHDG4(|v#+KtH7i!rlLh z9!lS#@6z{ENQ3CXU^9ds2JvG64~IG&L64+I(W61i1G=&F1bQm{J$(kaarAilJkXzj z3yhgaPogJ7+!Vmm=o#j8dImjaZUNv$^kRAmz*2ezy^{W#UPbQ#99YU~`Wt$U71l!8>*)3LT6zP$k={gavG90v zGyN^V7J4h_chI}(ZS;1K>}MBQZ3XR4(C?&o0qmgn(+B8-^kEB6Fu$V@0UV}}fc`jr ziatso1Id1Jk<}5z(0fE@eBG7dbX*n zFX$qito}j2q$iuN=-2ccD`t*a#IR#NR^QUSITsVc#4@2wdL{!~*!aY7F>cU!7;iES zWx|*kCL`EHFp*3qpkYiJCft(30EaSBOj;%#q>2XY&o7RNXEK0g0^m$cW+n@4zL1G| z%f4YVGg+BzRzP~3VIUsq3CM$FD2A{w&2Y?549^IRn1m&!kRdaBD21uU$qd55?S#r` z&`xNK&KOKyCI?U-a3+(;WM^sw@i~~BOdcj5*yLvN1I`I{xq#0JIXJ2FGaoSpn2(M8 zjAneq6l6YT3MF9_Q9!DtVN= zN?E2HQ=SPm$}-ROa!duLB2$TJ#58BxGhYkinMuslRCOp-W#*ZVQl&9c<_TN4GM_@O zp8>AU%r%!X%b0JN0>bA^5y3Ct*NX^WFg2N4Ol_tPQTH(~y}4bUJWI)0k<( zGzDEVz%Ag+S}-k{R!nQA4bzrs2e<{CYX{~_rX%weGn5&|jC)7!$aDgW&S236xUNh$ zraRNi!t=}?OizGbOmEQlV+JyPn7$yPw_kl4S@i~Of6(`51_1P91~G$~Apj}M9L@}9 zMld6pQOsy&3^Nw+a5$R@%tU4~GX?k=%uHq$GuOiN&DqQxfVs>(&@W_`F!PxOAffkP ze;QfM1MMQvFJcx0EQI=7&MYvG>C2d%`f_Fk)agp*Yi5I^e6PyP{(VSwal-~ zM&>TFP2aAsVb+1Xp4pI0H!_=;Uzi7Av4y$DJOsLl+01-vNt*y~1=z-HXD%|knM=U! z0DdR4E1B+Q_Am#S;}E-_IRSVNvzOTi{2mCqnS)^c9neEb+#%*LbA&kxHpc*;U`{fp zmX!)|86*$6f=fO`td=R~uyY&;tSG=@#brf1{W1U4h9u-SlS z0xmO~h0V%V4&uS_%*w0^HYBS7#=(vN9%m_*W;s@38K5jkJS(swK!)U8HCAU0HYcQ8 zqwB1XHQB@@oSn_V=4SJ=*#UA`As2gv$^&`lW%IEgv3ZhccD4Xpko}k~#1>{hVT%A1 zWy`P?*~Y3{57oo;qHHm?IQtFI2)!6vf-T9GVr#MW+3sw8eGEG>6%9&JnoVP+$e^dp zuPj@REe|IhU#_hhTotw|TaEpc{fzybtqxEF;0v}UI|b-e z;E<*^TZgR+x_W>cunpNJaAt^hgwTjxY_GH+4k(0 zY&)Rs*fwkjwiDZ#?ZS3ty8&(h*Vu#Y$@XH0v7^}0DWqO(Z;0&!v3-H-$M$ExVu!K= zfDQy{5IdM10x+B%!Hxt7&S?}omL12AXD7gUOkgLmlYyVcPG@Ja^Vk_cXM!}Fox{!r zSiml1mzcHG1+1bjWEVk=EoPUnOD+9U^Air~bE(Uq23N2v+3V~#>j5^f8`;zBR`v(rHnE%8Z`m!$bSt}!-N_z+ z*d6Raz}p~xJMh~eY-M+Wv>WK2ByJD8m)*zi2RO)n#~xz82YL#)!|W0EXc9&-$Jmor z%yCG6f;|r56v#iaXV|j<=h>gwOYAlF0?>;fU1qPaR{{LjdXv4y-e&KxzW~0=-eZ4b z9|L{DMsWAo`|JZNJY*lSUM`$_2>RD7k{$wm1k&#SPuXYebM^($KiHS-D*%jp3%vjr z7s9zY58zO4B^$=20gCWLHj<0tqPaLOEzopaBp1WQa_Ir$xdd*R$#5)}iObAo;j(hs zIGiImyAMlp6i0LS;U~S!!2v-zJDaIA&N^mR8@>~(F0#_1nDXuhE zhAYdJ3y814tu#fo0#}i%#8n2nuT24@DqL0WQ|>eFbFMm9gZqN3$<^X&a}Bwv$~0xV z(pRyirkpPH0;vwit97}0Tz&34&Y=ysENWIY*sc-Rn5)Az;hI_XR0Qg)6xWn9gl1fG z7z4K8T5_$pwp?qjjTPE)?YXX8FK!Sw47d*5ms|s(JvWN0Ep*^Ia-F!&+;56QQG6F} zB-o+&3rb+xZd`Y+2c+%^_$}Rw>%;Zs`f>fa0f7BI&#$se+stiC z!kf8O=10nA?ptmPx0TxkF{{lObu-sR+0O0Yc5-KuV|H_UxV_v#ZXdVb3j4Wl%mct5 z;tq31xTD-L?l^aXI|-UyfKPGXbEmlBz@=x3a?jqMa zh`+>L;p*}A`AZOg`JGfhbJvq~XOr`~#ogxaaKCWBa=&qRxqDW)&pqIta({4dfIj5v z@Q=91+!OB8Wcq;nopZrPuD0DQ%kmlb9Bf~3FS%FTYwj(F@vpfM-p$wMJ-n9>O~N63 z7+;M~!-w+`NjQ>^;?wf!_-H-`mN!Yy$MG5YN=Y=1Ut?AUO+250PvElxx7N%DI6t3> z&&+4x6O&`I@i-+t z@VB`9{Ccwrz1nfYN*!zARskugI6@ zD_G$({&T)MUxWXGugTYfv$(<4=Ns^v(2#G$H|A|zpKrpeLR0=6*Nkt@H{}s-!MEgF z@vZqbd|SR9-=6QlcZHJ{1W`DbB=zJ^@jvqCAm&S65jygn_|E)LekAV|=I{&n#r(UP z4ty8>8vk>O^xgRGkV6l?7r)V5!>{Ex^IQ3z0n%Z<58s#X$M@$4@B{g;_(A+&C}$PF zn*Rn$LDFD;2>-Rk4daIcKY|~{&of8!WB76WL^vbAG#zxC%xQqfLd-aRJit720^mvf zWPS=i74-g;Gx(YOEPghmpUcnV=kp8sMSzz8EajK+%lQ@jO5ov)%|n3K!TGP}H}D&Q z|JK|EcsIWZu4Oa-Ex<-|3*c@1c76xHGr)E?zs39xEcftx`F%jQng;>z=MV5l_@n$W z{v>~#KVgOM`P2Ll{2Bf%;PU`K@fY}u{3ZS>f0@5xg`fHBye!<{Z}PWz8~@DT<|W|{ z{|o;sZ{xfCJ^nZTKL3D!$UovA^H2B}7AAxz{5BH@{5$`Yf5yLtnC)gf$Qgv^VDkt6 zl7Gd=2~O-A{wFDqSRq8%VY-126~crxLbwniL<&(tTF|`a(+cT?Xdy<371Dzy zgRs-g1~@^;C}aY<%ghQmvyep~1xlcy>0<=e3h7is@CmtvkAzQ!8o-%CqR<*>UEs0{ zIfR@-sOnG@n@gx=#kLg!)8!HJ3i%*qe!#1lkA#B4$3h{Yui& zA(RAKN+>Oq5z1S5oLN>V2T)$90Q$;8HKC$V2_(BkM^+U;TLtu0gsK3Qh0lb~h3di= zLQTN6p=GKk)E62GO)R|IY$P-WXd*Nf>Ip4`HbOI@IY_8g8O0&1rl4&J`j$c~fEGeq zp`FlP@ThHt^U5=&ozOw}Qs|h3QCuhCoMOjy7P<&s1siu0x(hvooY?Af~$A!((Hfg(bP}&8W-NGJWukcF{zfagN zY?QtO+k?UaajA3&xcy*zKyWnsh3|y<;vv9?g(JdI;fWB)9}|8Q&OkZe2`3ToC4p7a^}p!e!y2g|7%#g=@mk!gb+> za8tM?+!lTlri(MgnPNrJmL3VE#Bw0r7VZdh#9xG8g}cIA!7cU>6T~bco{HwSa8IZ% z)<}{5zVHBYcqlv;4w#ZCi#{>C_$Wa7MEqTNDm)XO3onE}gqOlA;Wd=Qi-IUZDM)%P zyb(By!$g<(MhFo-;z83ZhKgxmA%1s&6briV%oxC75R*m>2RLX(0FDyVis{5?(EC%S z7vscuF$1K}C}t8fi&@2NfMGhfNQ#t5iwq2e2{_|JW?{gJc)(OeP1J!uY~}%+S2W;S ze4+_(z)S?3L(D1W5_1RG<`s{ag}^eOm|y$|=uz`yzy-vDViB>ZSWGM_78grcp|n^= zEGw25D*&zpP+6=ZRu!v>pNpT0pIM=X_=PxItSQzKYbW6vVjXdoSXZnk)=$C>#D-!c zv9Z`hY$`Srn~N>PwiccM+%a=J;Fe-5v9_ z-Ni0qS1WWCPng|+?IiUjiN|ekBeP2a7|1A1Uo%ZgH`+ zQ(PqN68DIE#eGQ_#q1XsS}_Np?1SQW;sFaE5)X?<#G~Rd@wj+GJSmJi}Kr2Y!i$91zif6>T;zO~E!Z>6Z1BDeG6 zPvQm0?;_yK;`e4|DU0M2Y3Wk3lvBDYUK4*7uZuUto8m3;ws;51OOP_c9~7e`B;65z z5r2iHM()4@4Zm3N_r%}C`=EOO_>p+pd@MeJ-_J8ROGkPOn%~7A%-2BQ0QVGPpNlWV zKg5^fE5J{aQayv%Cn6@fq!7t1c>q5WL!~e&jT9k80)|D!rF2rX6eGn->7_WpX{8Ks zCRwDc(vM~ixEcw#Y|{58E)fz5a#rb#DO!42q9j^kBydGY&>;crx0Ap^mK5o%nH^{% zaH^z9x}`g3nwHLxd=QgYl%-r!ZYhtHSNaHWJ}JKy3Q8YKg``iUB7lnl6qia!C8bhQ zX{j9GGE!M9RDiouQK}?WmgY+prFpkV}TnkjgUrKx=ZF5OE*dy4Kc&Tq0&TYk~CSGB25Q8Rhnjn z+0q=Sj~Y-ri=;EcPH~yETv{QmvhYT8rSvtxDrvQ}Oj;{#kiL=DfP~t>g95Tz4cc{} zUni{xSSvYgt<%zeD{Yas0;JUMkakErrCri)X^*s5IskZ|wBHKbpw&Gr9g(stha_G( zEFG1ONyn2gia8;1R?JChr*KO8UOFiud|LWJ`cXO~ot4f>=cS*d3)0V0v=Rf$Q1+8; z>9*8e?k)F|FGv@qOVU)JBjk(HW$B7^ReCDDlupRmWJ+e`cQq)*HEFn&VtR_)u1hzh zn~>ivz;~p}W5=ppO3N>QBo~0Pkn~u3BKm9Cm$K-0)?B}{h7A+lTc$X+=V zFeW=`!sQ4#Qch>#O=gsw79gD*Er-K0dl}>yITj?lluOB_ zyM|OSu(DcCP_h zwE%4!(6^D>0<@ODlsn3uLhK|2}rljSJ@ljQ0040$Ge zZk!Ga`pl4L$+P7-Nf^bmAqR1M&1O{*YX|nilu!ZuL0|I@_KoLyivXk*sZ9u0(k|e3%X{R#@&OC)F!#y(0S?FqL4R02CVwX%0?F>pAghC*Jp%e8@=<`p z@=5uW{Jnfy{z3jxJ|mxnJU0P8C!d#pk}t>?F%1(fIgRhm!HZn z@*4~9G+)WD0p7@OK_8-oDwyI@-pF=O3t7DdtsC@i#RCwcq*20^2qjWU2RKSe zYlZYmoRXkqR5B@9Equ?+tYiVms$>H_sjv#J5FnvmW(^TpWdkh*dP;%m9>5EVs7Q)x z;onSIL5Henp!X>`6kRbuLZgo}0ke z%BO%UD%F)5%6hT3Qb&1c&W7B&D6@dCtJG8KgH%U(WOh!{Hc%QWjg-bp6Q!xrOlhvP zP+BUjtg<@+ZLPFX+A8go_DV;<9h5Jv@YtLQId)aLDczMP<_zF_C_R;-N^hl)(hs1& zGC&!qe5DLg1}THBFiaV)j8H}@qmso)ELN6)ez~$rS*k1p$@a%0t0kaa0s0lnN`U3cYULZ{ck>L;L%^+3)+*~P z-Ba^BOSfLxplnn&DVvpVl`YCvWt*~H*#WRq*`@4O_9%Om1AzA_`>k+TIieg@PFQ%o zc}zJDa6&n$99B*%XO&aR_aNDIfviq~_6N}ap!^7MS~;&Q;Vvi_l#9wG<*7w4D_4}O zij9%}nsN{HKP%Uj8%g-4a!a|b+);i}epT+mGjLydpgdF_DUX#W%J0f#;koic`9pba z;b-Pchn&nbmzc$4uD*0ZuPlY0BAwA3h;Tc?s!^@Ul& zqJ`B@)FKx5hglqGQMH&_N-eFHRm-bofR+KNoLWJxs8)h}s;br0&()ghr$9dgsk&N2 z{Q{tlT34;7)>j(=ZlE@_LSwaw+D&b#c2|3=ebk|9Q;?gf&D9p_w?TYMwUydc9R`+d z)Zu_zfn94$YNfVS+o@lwUDWnKJAl+t?WA@FNSSvJwWrz(Af>*q+E?wT_E!g}1J$q8 zA%F*|gRRgT${L}LRL28-p^Z>SsiV~~NqDR}PMxLBQ^%?kAdIuMe1U8iMmu>rY=`ksB6{R+8yl|t)ga2o79q8 zIgnPUE7h;n=Rm*ER;sJi)#^9ue)X`rK)bEpRqv;wK`GX#HLMhkQslNyU9WC{{5AsK ztiCe6TBw#*+my@~(zd8u)oto_b%(lB-KFkU_dsbOnp^W|_bq9Ux>wz&J_G%8;86Sl z^`QD4=nersqP{kds>jrmaE1S^@{^+&)blGB`1&#M>J ztLjfcFR16#OX_9y3c%0mb@hgNQ@sWFhf|gmswTwVBX&JOET2>AI>ZuVLskst)pgDn~G+JXcR^zmg#2i2ci$|2x zL`~9E&8Nvg6_7Md*9-ttOVqN1T`n!RmQO3FwQ<^bZGtu*@I-Br6((tuHE-e+kfs7RO`ERG(&lM1 zfX)PIwl+tb3$RdIq%GE#XiK$a+H!3LSdRg`Qu|t4rLES!(bfQ7p>5DMYTs(xwM{@b zgS184s%-<n`pSA1SEx)vq4Y*6M9w^{=B18 zI<3nVWpq~ObQ>c*uS=j8bWv}lAgt<|?$dMVI#2^7Q%}^h1LV?k>v{Bi$uz5;U;jui zpcm9X)(h!{^-uI7dMSO8MP~tDR8NyQ6X=(~71N9BCG_%^HaxL|rR}Ac1pCr@8NIAt zPHzv`v8td~)GO(g^?HD-=vA#yRj;N;Bz_9gXTW`~SJ!Lmb@duRzW}M0UR$pN&_Hjf zH_{vHP4uREGrc)jm)F|?ZK1c+Tj{O!HhNpY&Gn9YC%voQQ|}D43rOAc?s^Y^UV3jm zGI1c#@xb-b`|ADl{`vqtDsddpuPh$X0s0_)us%#5r4Io*6r|z$2z?~LXnl-67VIYI z6ZI+j41E&N$skSDr|HuHX6tkGx%xbPA>jG?0xK-hm+C9@)%r4^%k?GtO8skn6~J13 zoxVxms&4?g5v0xfxB3=Kx4uW;Z{dmNUVR_He*FOG59`PE@AN|;*>ft8)dA2R z0sRsED8OO;gnm*_n|KfCS>R6T-|MF>UAn|GmhK1rNBx|BUjIqI3HSog=)~*#4ZuHx zbWy*gUj`bJcn$Cs{VK%V)NetGU-aMf+xi`l?AaM8{VmY`3i@C5y8yrF5A=unBmJ@d z6z~)McPqTm|IlCRZ!8>}_)32b@J4^DztG)AnBg))j5j)(tx=mpR&PP;0lmlY0=SJd zMz|4S$UxHr7imNpX_GLDNoT}bG10IE(MAQ~I3wQ3U@(9afTmBR4BE(OkVYmWvymkU zCm2}`!pLUeMqwUd8thp}DHw{u89Yd+ZKJv6dPaSK21X;}8>2hWhQ|5;xslP>Xlk@H z78%QdYhtVhJHMux(HwLwj8;Z#qm9wlXlJxHIskkL(9!4&p_8#NiSJ@`1*x0S!^n`> z)97XNHfBS4Um1ggG)V4cBqS~{`T+Db`a#H;ILqj73@`>7GeL(^BR#Sl1Yw9V%oqiD zs4*j18g7gLd89Gg7z4V=5I+{UOo@|>amILKf-x~EW~?#Am}*RenCZqGW3DmJm=9-T zpW9+%iLumJ23NDfSZRE1tO9P0u@>rQo#Db@`C}`5YiuzXY=^Pa*k$Z7ZW-aeZw&-n zjBMDq28EG;ZQv|U#ByR1aJjI(Ms94salkleoHQ;Q-x-ID!^RQgsBz3VZsY-rQ^xnk zY2yduN8^HV#yD%7GtL_ygZ`p%$+%)%HLe*y8`q5+#!aI*cH6jP{9-&dT)tn8yT(1^ zH{-tXz<6ks2Ky(*@5WQ(nep6sW4ti_FkTw3jH;l2Yhb<*pWEm0d3~Y2Fkc#9Z7jkU z>5KBE^`-Mg`(k{tzVyC0U%W4aFTt15*9g*N_D#bw`w(=)x&t>38;jAt36_rcfiujf z_*9?f(|v|-Drik#qA$BIhi@}x0&hcJUp}AcD&i~VtL4j+Do8G*FYYVl`_xy$SJIc) zSK3#`SJqd-SI$=+q>8>uzRJETzN)@zAbsZh+*jRK!}o=+CP=k?b$qgwzMik1uc5Dz zFPEz!(1yNd0A*dxd=*_SyzP7~z3qLiydAyWfbR}`CkQ=2?ho<+;Cn+D3j8qGOKbwr zi9nBfyZWv}xZ!=_H6nfk?R~I%>Fwov<((e>V|e zXdd1+ypOM4cwb-3@czE`;myOlg4_+{jv$W;@9LWtKE$^me3)-x_;BC6@I`|>1fasCYUFz%7GKQY=hc)qnaFuJnduy-%o%Xc;C zR#-dnZiJnCpGG~3icA}pHgDQ|X{$$}-D1)Ht#hqCS^I-?p;D%c7ypk z*1wxeeDc04Xm6IRus4gdvkC#Sv#*M?PYT*m#J|gjvtx&U-;KZ>HE44-?5#07Zcg%Q z>S(R?C9v|kvs(IckkMM^Xhrh03HJKpXl3sL*81IO?eG|DEp9&H6KjR-(hyVJS|7Vo zLOEDH+FrXFts#BfS}7W(SOzOOqm`X6fbOidF7wZ@j&tA|$hl!{dIq%q zFItiJn6=6-T2=Q1tf_kzR>{4W(VgjS#(@cF#apya?dPy+>>yajb%M1v>oi!K6|KA) zxW*@L`q%UHuhdyMdHqbZ%4SJeW7An7vkb@`tW_-0DwUmJMM<>IBw9gohP9^Sj?DI| zj^|;u#(S_1qZd|IY@InHteiL@vwvMgXN^O&b|G4|@7b_V_P`z`!S&Hqo< zf!ykV|1uR3mIeHq!GJlyXx59Iprg$I2RvcGGC@WjpJCwd7@p)$ussANVMJRh zX}oBU0PRr~8cEsXB{WXLl8!qLlSExuAOuF<3KXy#SUAXD2w?#JL&bC}hXJlZE)4Tw ziIz0fHNq9{!m-R)2GH6^?obSi1Sun8Hj-ILtrf_Gu*SkbLasj zf1aFC4Xg(6Kf$%fCFzQSof%p%$=Vl?Dml>ym^iD}QmeW5C;BUC{uQ}33i-rUKcrB= z71F6>TQ(pbMb}Qnre=_?a#D(luKRHRK24DxnU)9L{Nyw|hVFFWf$n%OS6|ore*b>k z;n)<>Q)9WuDR4N(edJo!rY@t-_YM+Cf12qajE;CiFt+Xw0@lvXc_gvDjMASZo7? z-0rUKm9CCqD_vb7ApMCDq@56YC3I|(9g_X2>-$-Y3qIQvkpW_e$Lnc|A$b4s!u`tK z7(?Jc9=IR78)68Y!!FUr*`xDBUrB=?4$JDU4XK@@UFg=(aV`YUBVI*Za$gF#2FK>l zQB>03yI&)*xt@{O0tlwNgL|>7QP^TvGYCk3Ctbru`=EZjWt;A|gNINg>hP3-UwjGk+ zSFh~@B3R&iS#LqRMGT1g2W(!?N1iSig7+UjPj*iy41xa;JhJCY41se5K1n5_%SPW$ zgCGv0-3{ITlwCvjhE8!IND~^ebKY5sCOq*liJeqD~O3tBpK1fCaqb^;H_HqhqX_okg9u+2#=U0}Yc^Z1`=d){q% z{OwL^r4|TidF*xvSz&*bAD67dOOC6vr{5l^lpg5&^yIBq%bWE zy(b;np@F|Rq_f|=POPIrzW~@g75Z$>L4|Q)sK@2VSW=H>rN`;>pdQX*=m!NJPXET~ z*&ul}^mhD5;B$zysh?yAWi= zVc22_2fQ`~);e6+L3o?CN@LTd*pJ?&7?&2&@4<38cFw{%lB}0o^aALlw5Ws_$bEhM zLeR#f#jr!5=hNF18QRv_AZANEhCPGcE|1*?oAGW8yAGpv`z$X+2cP8yhzUGPJ6%r5 zrF9HC_jkt{s4V+;7d*D|Khww}_?U|=^3KJU!rAO|RZ6qZRSjyug(2;H(A&qqwxqdO z1@{=YQ`$ZkD(iL#(yj}|!sc3KBU}M$2VHaEH3gpuZ`I%vu(50Kba-$L{su9DHE7oj zmkzb1OOl^o3|oqw4POe_f4?6>%HTVWG(6S?Y91XopuWy|(S5Z`HsSsUK4I4&KCoo; zWMbG+(8j|vl@~%_Y3K=Yo-C9zDrrT)`5_HDoGV4w9S2XgAMAKPo{L?NdLFgPJ3oGv z7tsptHE=f@#>~YKt>E4Qct*q=&>sw+i_P%{*Q=9{|5@T4RSG&0JLZ{)orF-p-P^s^ zH70DWYXStM|1kt$iCxCgp7yH12{cAbWR^cO;qc0uTa z(5aYhhveY&|M;C6Z0|__Z;za(WBI+)v4Rkay9c|sx*mjWbv=QA^p``Bc1h@y(CL_M zhh(ST9Qu#j4#zT>{=Yl|_SeKrtd4gkRv$ts_b~SkS4^56u6PJYe=P)QSA;$for&3Y zNcN{i++^&LXEOFXghK9q?hUThVH;fQARzs@5Tu>_n*B7fi1&dtMmVjZvWXjx1ZVTQKzC; zc~?Qt&hGK;gr1$<-?RJFP9M7#^zx^s-{(R!u(!S5dppXL_IA|Lc+@*x@Aag`(xSfk zCU0!|x!5M}3g|61gC3-zJk3)#&&4wNcK0rmu~x zoMR%kB64Ts8Bd=0Gaf_>xX(lHSB#m6AzHxwGvI0w2SHycd?I$xvm$a*^cl|?(Aw6e zKx^CDy<(@AJOg^oozky$AsX0UzUWyUc_eamWWo50o+FV5BT-NKilu`u`VTJkT%wW*4G?BLUuK>G=hQ`ZD4 zf{GX#hss(Njj+G0j@J3wW!Wi_ridG*2(Fpt;2}T|eAxX1^mO_G`5OF(GbZ$FY!3Ha ziLt-?`NxU2k0GU{BgcDwih0);`(upX8|!!}(3rA7^w$~uUjV})rG20>2zU87Eb@L_ z(AU36BB@X4l!swCBM&C99v<5l=jc#s9t%luMaUd+j<~qZCiVE<&3so!RTvn`mHWR1IJyZz2%~sMU{*C zGQPC8SyZDaG$JeKT@pJTE9Wf?rIdk^Z5U9z11_KlGnr*ch>{` z%?#5qL<8+NLQZ`XxCky{VjL=Kb2P&KvW8gaYnNrGM4IAmlp5-UsJ>3Ul{Jw7Ixk1l$_UOeK{9j5vkkUTTlaKpY{d~5HFs3}osblc8*ICdu1&f5q|sRkw6I#fE83KomwP`N+EHuW~kh)Vt;wv>AU z=wN99?374T%8gPubz;-W*p&)T>A|j`w&Q#7@RM-sezCZYbH-q0$SvJyDaf z0dc6@R$_}(}Y@<*(WdLBF6+co2GFQP-hdojWLTkLFXg14-@ zPE1+%WbkH;iRcN^y71XpPw(oey-~xxW8;T|es5G+_b8w>VrFBA275UQfZt<$`rR%> z$9mUAorqc&H93B)_e9i@DEJ$0#L76^egdQ{>n`g)9W@(U8;43b7(Lu;=RX|ET><3= z=8wEAW!)&pqtOW4IU@hdYRhs0Tt!*8oj1~yb)(YCx(`DBPcqEL5JlEbxv$}B$0W6= z?NfRG+BN9!Um)(!^)d2#?#KM&83w<^ygS3-MvVV!y7R_E_q0Ux`V9Wr1?eEA|1Q(+ zs&Q!|e~$~CS&$}@4tjSY>%x&&5j!-+LY!ZpZ14^fSlhw1`;KV}MWBc4!zly(G9R`KEF#d-f33&@ z#r*xAb>R7@cEA1S^1kQn?|SG`EA`!3D7ICKc_^ujdi>}6A@1Fp{A*8{^KAXA?^58i zl3M-vukC$zsxBY3zM^?d&{Aj^fWcL9A zb+(t`-`|T1EEB^5-)MnzYA~z!g1$64-?1hc`{tzl**Q1%SLc%+=esnL z4}(7}_^`h%XZAW@pOG8~zMZ;^O@?_R>W9g2oLgeg4Nh$k(4AJm-iW^B|x9 zrpSYyW1c18T}Qrm6MXNE&;OWbj^%SlwzdZyy@SStAy5CFcRck9m-v-^_?|&lMZ&;a^~#9rEzoxg$CFNa@*6@FI_XU+|A3n&3sA zeJU!(o8XNBA3E}|cLoo;oZkH2V&KC?9^R?o;dMOA`Mm`!&n~jH z{j{h>c@T#D$+zC|D}UIl?Dr7o_ac9CxA>I)g$eUa*Bg%bNY_K1> zuyuCGk8J0TVH`-ikI++p$l`Kiw%UcBQ+Na_2m<3&Et{op}Fw2l|~ zK8HkA_tx=t1urA=L2d;fq~rSpzoq4SM7Fjk5w)T*X^_YB@jIT+5Boj+KFm5^v$y zwcAg2@Ev=<^q=+^1@_th*)#WF)rV6C_Ju!e>Ce$_?|Ez^sQk0S*MhVjypjLDS>czx z7^eN{S>Y7)e|N#G@YmM-;)jrS61=S*K;PIt%bO`?h2Mslz?sE%x?+$^hg4>Tvv9S6 z|FjLP9mEgCdU=LoeW4Ya>navD*Hy~;e=X8>2fcj+&kg>wIS$1XcPqD(>s%Mgb8iUJ z4h%gJIy9+dB-_{ip;_LjM*e{Mv1{bt|2?8-(k$=a;BMG6PwbKlryq(>9akmsPw(lT?G8M6^=QB?q48i-4GM@6DUKj%Z@rkFH zrw4|>IqVW`Tq*j~=(}kU#9^GfiQAvDXXwGu=`I8@k@1nY+_&KQ_-h9$$??7U=M(tz z3a$YpIX=(SXAt!BC$+fX^eG|(#5SJxp1~M`_aBWt%{*UW2>eGaPhHOd41sglCEECl z=(^Dl(jbV#M0azyKV{$0BcZcg2r@-xi~Pm?%YRJ`I@f_{;Pc3WoN`8Jpl#}W)uiY0 zLr;F0RGv%!Q<+-1l>(ki`>8}$F@O0*0B<_!)?>gbs=iUAgs7Ycv?mFjcgUETJtkUc=|^6j6|*KXb&Ab6dUas0PlYW zwF#CErGT~;ELz5O@idDRVh4FLWkjV5v5LDZ=nrKWiXj?kuR>1s5~3oaA{xh`^2SCZ zjC?8n^76uYqr)llF7!z3R`Q?iwRC{_nZJHuekRKKL#-r=O?h-n+yUM92bRWNn-jl)K)@F*a-&3}Q=&l==Dbf`m*v@8x zE%ySi5b@O`V={IOx(|UC)gsemY?&NeDH6-rAX!3o|H*sye=)j7z4E|*@rQqfsP_;& z=l1>f_S5CB>-SswZ4ejOKS}M2rQh25=Hb`a+~4n?q*m^`{S(_NxQCM3na+zjEsGE%F3f1SPWuK(RWQfj5X+efmk zf_q7+y>DmGS327T{%qo@X}bT_W1cgFF`PBD;8cc^W|v z6!lNjL;uw28~5_mvihaS*1q4!8&xa}^@umV(=+z>i~W66f1lV+KiVo4^@bDUQ}%{? zc~CDnCLy>t+{=Ud!c<0-d&XFV{khk(&JOjs?c9-^+VN-lqJin`-$cr%25HN9GROO$ znwZEkK`p&@W< zf3Ja7i083gfc<{wZ6NJ{{>x?yo_G;;jx7#jQTttT1%9V4kVB!D$YZa)7&ZgA=kR;9 zb9oZJ8T04j&%w4|ZOsb`_5(VFu)qZRgzfTBS*agr?!FyKBz!ycVf$R*yiqfcp;2$gE|pB~jg4OJZGQwQyuL5kr<}

A-tF5hd zt#$28i!h2qtFxh;f~#~YmMscxBR4D-;UaD{@3CD3!bRMPFblGywMs)@Yn6s{wqBR*D@UPIe*|phqDJ{Y%4z1{hatbc9xi#(y)Y;=3TQeQ26jjAr#ryxU_Z

  • M-)*}5s{*RC`C|IR8&Mnlp@$s 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 s{lihkM => """ """; - -# ############################################################### -# Default documentation for fields -# ############################################################### -# Syntax: -# add(fields) = ( . => """ """, -# . => """ """); - -# ############################################################### -# Global group settings -# ############################################################### -# Syntax: -# glob = { -# groups = ; -# add(doc) = """ """; -# add(extra) = """ """; -# glob = { -# -# }; -# } - -# add(doc) = """ docu for all functions !!! """; -# add(extra) = """ extra comments: @ref s_rand !!!! """; diff --git a/matlab/mtoc/config/mtocpp_post b/matlab/mtoc/config/mtocpp_post deleted file mode 100755 index 002c61daba43bb0a3a2cff7aef514cec6178012b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90916 zcmeFae|%k4l{S7aB@j`(QBfoQARtH(loUaOrE-f+In_o2QmqO$TEOTC2Bb`ItRyrC z?&;w`{Bbn`YE+cy>u81{7#S|6r6#c=24o;QI0naY&ULCrMoVCnywCHjea^i%Y3cZV zzwbZq$9!_m+H3E%*Is+=z1RME_WjY5zj^B5OeVW5lj%JmlgT^>PwzpQjNf}CT$#*^ z@OXGOZtPffX4^a4&N+*|wSU=KbgvlsOd)}d8_#XqaBeM$;x{bf;&8~8sl!w6qHNrF z`Gq}~SK=?`*NY60)6NN_A%0+moJemqx^d%&FW>y$52s#>`K_KYeru>4{3WpOmwdRQz{UI;{=@jS9Ai<4*XS?hWDfBA@a31i|C0AP|Hb_B z&&sMij&(v2ul`#s>&A_nFZtW|Uvk05_g`|+hd{WPU)QsZ-+7J=@x?r<`ET5KVb{is zE_=_#7n<*4ep5N)*Xkk>M_btEAM0@eX>Z)PdeynBs>o1ZB~pX)OX{=3S&zv#(dEXC zmwa&Jhp)W&y&wAE#_r26+b6$-T?YKBu=uVkJQMCDNs7XUP4av^op|>3H;~U}9^Omfv*Fi-=UI48 z-2CCoPW<5e-+ST(SAOsU;QV_r9?J2W*Z=GdzdrJ`m7j0=;@`b!`<_1^4L{YHLm)Js zS0*?h|C*5%#_N8r(DcQ0s0W5w5%j>7t)z|gHywZctCznTLcIUIlB>0bzJ%hcrGM*{ zAHMv;i(h?)ivsFQ<`sA(AN02Y?&BQ30XYATufn_J^Vip3HSkvr{8a;g)xcjh@K+7| zRRe$3z+W}+|BeRMd%?eZTX%PQ;mY7CEVV?ZeErExCVul|CUZAO#9lBN{|X2KzKQ^% zCo+s{B1Cp6Y~>$MA=)81X!a=RiEauzn6w4S8F4 zcAkGv?ugZyjPl#Jd*u0y@5jGeSeWz=n_F05i3X(xxV&Jr9$>;hnBEiqK?dpwyufb+ z^g;uH-(;9T;5Q2!6$nOK3=;@OTMZKkM)Sl1e}g;?1b(Ms0)gLUm_Xon0GssJ;Y~^V zN_26)o7?&-yhDWq+r6+zLT~cGT_iK_I+kM~it;@R3k!Y9CMQ|B>*|+F57W>C#OYz& zsP5D!;2HSc#KK6PK~x|X_`UM<9X4Q|g9e5S42vWZWe|Y&P+zZj>fMS>wLWzMZ*#~8n-mZEKS3rY4e-0+1knb=2y%Bkr|%am(mqsvNj8S`bN#ENMxO{0>ahp{LDXxQ z0E*Hu18eJ%&k;l_*D~ldB7q?4GE5+dx(y?MG~1uddYg7RF3232L4qa`IS@4y3!-6* zDS%{!6$OIgm|+4zaojM1+#M(N{Yp88iBOJ*l|(cIkY=0+lATq&NFaz}!vun8&M*VY z_#h%B8Fvh(j7T7erVSGaq8Y;osxwY5$hd(>!kxN3*UvSL3IB8uXUY*8Yo*|OA5#Q@ zz%Ljk5cmVaB7qxdBX$(KVy^vfnRSJLGF(FzDLz`VAEmn z`>(RnHEl=6v)E)jhXkTQf#QHtD9JM%rDy!Dadsq^Qwii2%DIxx+^4qpKAJ5=tnB44 z!XvmJLe>Kdq63W)k@(r*&{`UhQ+Y~BycRgH`D|dIpsrT15B5njBRN~g5 zC!->SSX@s%9sDeJrm@72EX&s*j@%BAV50w$bq<;&dQQze^p5T-)1)m_=iFf%;OZ^~#QhI%oQu!DVEgvsz zhZfKsP59R$Cd|nD2v)MoFGQC?*qq;RFtic*1q84{u+XEr{@=3|`g5uRkvxN_{=Y<$ zAqahkHJE3kfhGgZB8fza)+A5%hh40gLDWet{SczUYXjU#f2iPal)>l^84{n4)oBIm zW!cCwb@jOF!n-7Sl((c?EosA|4)gTn={szLd3GA;GSDrONR((n@}$Te6b5aO!W?cQ z=iyF7R&Y4VV2I3+_`18QMTTl6U=&fqRsdNr9I<4Fm8|cual-@xe@a*+fG)%^0oY)} z2$ou?R)~>yXhe{Yd5a(rM43NOUIQpt!wjfHYeA%>Lvsvfje!7qM8gEoBN|3f-Jy{S z%1R*cheYH+G)xS3*OC@MO%YZU2#T$S2?WKwVFXKi@@8c+Y63_zP6Qe6RJ=$4rr9t7 zm}bKaDC3QYlw{m7@Qg?R11iG=V0R58sLnXKAmaui30LXKzl)rwJ^6^#;Aj4*2m&y8 zh6%u83yTDTsL?QiAZjv%Yz!vxUG z3?o>opiH%bh(JNJ7C`{j+%N%DbHfZsLG!yS3UUm_j7R|E7{dfGjxkIiRnW9~3ZP*d zCV;5~_@g2Tpbaa`07iJGAXkHWEKC6NE5igZGBk`}Up={LJ2IX{HC1$qjE5>GP;3yF zl03svddB}g#-98SR04Vw^yGBb^yIAUm7cr+EQtCgc!@~75**By_LZ0_+2oR*oI+Vo zPPuA&a%)7cCzrTMPwvc&GL-b%t}ZEei60Tylam>Ga)N@-UU8e=d(>NI3;us0SFX!` z0pvqxOOqb|@Q!Ljzeinc(zQD}+Whz{HFVp=E*%3Dz|a;6s=BoX%uHkJ)_T||FpE~( zC>b^p=fM)baiV#tu>8>@mkbv6Emf{Ys`4LSUOQem6UjrcV+q`N;XkAXH(u~0o?oC` zQQg}i0OaYFl#x6!=`hg;@^?+t7oG z5{8UeK#|8O3??-c=GPDgUL$Z@9S=(i4o4Xbkr@)7cYC$S7%xb`D58d~061RAD+Rw* z$@&iKFiZeLDPfTSthr$V==TgGSZW7{Ax7G55`oYKiy#n01BMBpU=1^%ZgU8cl5W#6 z=r$q&45$nfz(Rsy1l8Rpxxm^91b&N%9Ee(pL9Z5505wHeQ6MNz872@Erwt=mI$ju5 zCZhp>G~+~&@j1nd1Ym#-6TqWcW9VEr8_jwX##YIh6$h+nVtmD9V*NK*2JV*Yar;l zEKC4lh6$iz8BYNWS_~6FGc$}}se-zz6+{FI>b3|15YR9IRCB`&NI_jy3UUlQBND*y z(l7yx&kPet71U{-0%+KV2>>$%37`!t%m7qk3UW25-ogYh#xzU-6JNs!_BCEGZAZql z*kZMEL?9X#C=MyTl03svdd8z894}lzC9tCp<*XSmu(DUi3yr{n=rChMBt8@z%$D|* zm@3(%;{^(3;|0o9GhVPpslBe&m7V~U1kT>9oBoZasj6B)-dPtFdB|vIn|{2SG|!axjSRwF-5=H`;ml`I3%o%1t-DU$K zCEccDkTD_w45$nf2%-kV2&%hHazVE#fRUF(b|6y5G2gV90;nm%iUL7#gJA+evC}Yu zrQ?NqWirYDq!}lIjQ1*DB!IPP!vrvFGR%N7KL4MUj5`KhMkIhG3Bv@?pBP3^opEyE zv``?DaFy}G&!LBOyxb3PK;2=CNXdA?G4PB?00m~40J=lN1S;L3d3F+@12jwk;}t2$7eIHYFav=< zX4S>&i9aGm8$g(00;pJ42m%g9a7P7r+?PFoD1y zHjH3j;|0@pWIT&%s)3(pJXAS>VvD$xBXZ*fiJOcUoS9LElCgoSOUhm1N5qX6$PD8J zf`ZRpahu+I)LUY_@MGkvb{s&H9$);`>b_}@^-Z;OwE1yc4c#`eTQpwav4g5^t!lh5 z$TG)-LPcLPY$Bfhih81{MDsRYcrLy|Su$QwkGU7Az6BN*jXEzEpI19xnB+iV!oStP zwFcft;8q(ZrIeRG8-hgs6apaSFr|#@em5iOh9q+&Ps|K0BO~(k9X4*BQwF9D%!nis zC0dX?*%gl=MO4QTfcQk{lnEvZ!0182;V6UA6*DCM;w{yAobZ{cDmCkXfh3RSEa}*i zHZ02gw{kTvPv2qn=GkDN(LfVH7>N@7sXQt25QV{Tg2H^%%4`7cWb~k5?7EREEHWhi z?U$=XhH8f?U=&fqRva8XbXu|-l&tTtZo>qyttc!KfNeKS0KK4L1WT=A0b-=x1`+Dn zA_$-a3==@X8fHM9XfGlqqX)-ez=#B(55oko-E9~_btg(L=tKpuFfJkoq7B4g9xbK- zYKpL;Kv0Yg69|fPh7l|sJ@hD(Q8z%EaU#fg!=ETbB!HDx!vrvhGR%N7-i1g>#vOxs zB^(K0fx@B)pm#BhpgQB^f{Yu8BwS_m@FDO^M-QE3j-Eh#44|19CVcqtZ8knTFKvZ!iVr70(wym|+5_SjJNTJPi{-Gc$}}se(GH6+{FI z8n6fgsOE+Vpqd+IKnn7z6yz9m8Ib@6n}!Ksv}TwE12hifk6X9B8fza#ZGxrKHjPVaGUY$<8QQ-(hox31C@LSR??8 z{1lTg0F!1I!O{j_4>8jIfe7_%5d=^Ih6$ix4KtwrA%jRs|KJ!j7?A+7E=H11KyKfT=W00MlB-2$oiZS!v@$m>RGqg0siHUfAC21?>gE0^k53 z9$EPr=G}FJM(%Gg{sJf-!vruxHcTM3^4;bsfR4a00StmnD*{2=pcmr7;S{SDRv-Lv zDbxVM3==>FGGz;Zn_&WIPKFUIRm8LuvC0%d1j#m-XacCth6$iL8)iVNnW|EaV-Q=4 z0;qY0384EoOdwTF#<&Wg0UIWO;f|?B0Bu!a1~4!(1-Y6sEY`jN22_R#U_fOU!M-{` z({^M$i)xD)Suh@|mOwEtE+u*5KoYQc&1cyG?xYgfbA@u&bbzeXl@4$WSPFiuxQtlEzBCZ1@GfbZetoPfd zKCs|(invX0Y}M017yNsW+1kk{&3Js&XR3`mHpNL@OGi5&Kl$lex?7jh4U_JHMRb!( z>842c^%}Zu5-**a*W-lWgs-Xj)0_ZkAJ(@A;TFJ&_)IWFGZ;(CR0UhU9Gn(S%*$I) zREzyiTk5xOUHrCKtqUczNAvYXijzXN?~Qn0TKQBh@kd}&tG*`iDJ4u_)e*l~n+NS< z(8LHn$N-#(SAq#NF;c6E3l(?epMHSA=#=}WaKLinQz9Hr2*H|e@S5_XL$9Vm58B7U zc4~pAO@|UX|Zlpeg)DN`O zr;+-MQXfd@EcILNtx5gVBy}tX&8Y@I1_Ab19m@50Tjme8cH+rNMUia>ssoUum}WbV_&gAseqK<;VomCcjXLI; z#U3Ayg%EX)v3uw@nz5j+#mf(>X;lQz$I!H)QFB! zjBjEui_!d+*@?EFAA-2EUerFVb2hL8LHlrg82N;4G?7UAl;@+x@pH1r!3bVMQp>is zpJTK%1p4_x+v8ri9=uQT!Yf*HcW-$-*mAQMJiIz=1K2#UI+#HCz`_HCwj0W?f-uZ_ z+f>lL(~GuD$EPJKLRO~Y8-bZ>+6(cUDLH8xCDE3VZEZK??w*T(NJ(U|+Ir)O6!C3r zHShK9WS*f0{k=MOL_bjXec9IC4e<-f4hoIuKzC7Xc;9Z^RDsY^D+eCPQ(*H z@6^4GR2xB3J`34{5j1kYi?Ko*lwv)?FoD`(XfhnbD7a=M?1q3oStIpfR1!RHM9$8F z6{T$qkx;2<mfaqA;ERTc)qcZ^Uit9-}%CN~bt^WdklWW*#XS~TZ zO%65_POfPY8WlO1^sr_M*6SkGt#vqQMGW(0g0Sv8`0{P-*6{}TbV51s`PLGjE%0d% zwy)i~y>T5DhQSmu@<|MA6Ie10*gIMm9(tnUZ?6YwJ$<=GxSr#)zBM?toS z=fr0P&uUmZnXjkR?IFC@#_idub(p?e{3Zcya<4-b7B;yqxUb+p3}qmJDXFPAPBk?_ z=`uhlXZcU|7RVTXlf7*9$R+B)p~Lq;bHOfe>yAcfl~KLm1>V-}c`vwrm<@dEM1vPR z9)CUrzkbA1_c9Bu1<@mTmtV8g4~4p%Y`2cr!;%}%iTL%`B|QaVpa^{h)F}~I&AucU zA!N^SCZs7p@8yZ1i*9GeHex{vB&-V{X@(Zif(5WbVVHp1*$dqUUzAr^Q6MOKh6x0= zC6CWe;kjg)Kxf#}^?_kaw$~C8c=~O3C@&gfKHg=Sb__Nc0|CsD4HLi|*)V~`^6YzO z-tmV@m<&`h$mo$~Pp{wofN0^+Gi>Q4Ea8(>?D}0i=_gE46P~O zVZpuda=|{`h%u}@9J)!VMMF|ZiJmqPOd=@Oq(1{P{!RKf!-WhclE$xqXz}@Ym5+h% zqI|pT#90!xlBg9#OGnu%ysGu$QwTh=36BE^(KnRTGI7fhg!4N6%c*p3A$WOMoVgZo@k!LImz zG;Yv!BiAU@|>iP@48qqAf> zwk+VgHbPP_gzex6YznDQK_`>`PP`AKK48(PB%IKe|AQ5OdW0QKTX!AELE}Sl6NHm~ zD=#ndZ6nj4Ba_JXG2GDvte9#$^w6)d7H{3o);p*jf&=*?4DQ2@PyVFEa> zWf;NIKB56hr+owwNL^5bNC1bi3==?|GR%M$3+oXn=_4G2P9qXne730D!UWPjqQ^W1 zY7ac2vQa23YnW_9q>M`Q0CzUY#7xAyw5MNU~h0P>B3@xVAH6u>|-!9p7{&l3&tv{6$*1Jkbl6$-AUWm!yYi>0?v40 zk{4X3frjrzhwdN9W?`9j7h#HvPzqbc+)31#pW;S}W<1ySp_god8GZ^BrvO z+s^JMh=`f-zvc`nDUH9d9i?h<8@B#B8x$Wzu5&(QN)@aehmoWSRyCEs0G3`<_c`pP zLDqwABzXQt&S9fdy08X|%1GLEo6c-qll$(o)~wz1drz8bf)w7Sx3{i_@7b$iBZQv? z{O)Jf13wLTUx{z2=lWKfMOammxl_ds+ay{lBw2E<2T@WZ==N6V7;DSjp z(tydbQGP`gx6qC_A2${3z==4p$v%WDU;xX5h@w1|kAliWpPGUArp9S@R_KG^XCW@~ zghfUHtz!YXa@%w^Rzgm02u2s{_{16=XOx089!kaAAH{_o_j}oCuZ~NjD26!{ZK7zp zp=ic(ioVw}W_;YoBSm3O(S47KkH#DGjE*m27+w0NN3Fg^Qj_$Bi}h`hI1rDJvjCRrE(l?y0P*XvG$ek~-}g&N)ADs)u$ z<^RR-LvJ2Nc#$!JhjQON>{3@2!P%{;?;VG=*wrk~7L=bO&L?4#1_ti}pDI$=fL;F+ z8_*CG_hSRLFd2*qA({spQey*lJQYlD{dFFSn}Q_>iaTi!u3ck-WwDxwY%s*zbXdX5 z?pXMRD?xXoLTV?P%REWA&%i%6R00>qlWH2QYKHaL0J-jdHIy9x@uQ%EZAN-Yos$^~ zz2@BAm!k5N7!==UWFrY#`7`_B?uDzGQ03yHMI25ezI(qB&u;RL!|Du%fji<^i_VW= ztjZiw72P(uD*8xOfojEu#A+X&eOjMp2*KH>d7E~G=l11MJ<7kxRg|+&HW;Gqhs{k@ zY@S|@@Du#zrTps`EBTB!JMjW(=B3-P%5Y6w>(}KtQCTWm^E1~y^0D7NX)%rr( z+l!ti?w(cWW^?^Vv-w*sMUU6|E!5L9O@iK30+1x4hu&$f8}^Xa{o%TSeBiZ~pE^_& zdzG>L@Y4m}Srz#7#k#+|e&m15$yHwL|Moq}&MW8FYT=Y`LJO;|R5|o4(t_G^r5sUn zI+{Jqsuw1_<92xqyW-DUyzV4knpl0`6MIYS7k`h$9_&$KT}dQsfS-R>MXO77v!8nW z9FwCnW!Yie5|e|$mbG0~!my(??5`G|ap2RG zS_^CF&Z@Y3Go7fEQ{RAwZu?tlXhV`6R|JsnUr>VOE&FHVlg-~4n~sD{`Stscco8F> zWD&hIVs?KK!)oKY)uQLq=sOPCx6aC6sR~?L@X3hHZ0AW=+ z`q2LqZoS5>CFSZi8Ve$8Fv5u(<+()H6 z_v@$>FWoE!H6>gZRm!-2-(?I7t%|#|istEyx_raalJ2K04=_<16H&{njN9I|Uw-+b z(EX_QmdmAxhLroi9h{c!WWMEaHqq%CuNTv+RO59Ny}n-KrSa=$%u7YJkJ9+TFiPWw z4@-#pLq@m0 zle4RsEKaEW%BsMgb$0L>s@0zd-{0_5IB(?8yV$=f}b+bj~Z50;v_WeY) zeBFMjRK^=Ds`JI+lp8=ZD2|Mb7i1Xsx9x{J`j{_#6=J=`BJzR_i}=$0MGS4^{bGyA zYd|dGFOkhe6-!(7lBOD+@_eR$U$^o%mawT*;QiLREq`f(w|oT&o^J_`Bng&Jcv?2q z9Zxy?w4{r(Xa)FRA5sQ~lk{8$s|t`o@x4Yil#rE+%l;yDqExB|^!1j>!IayXe=#=> z=wD|-4Ww-Nvny2@SURAOPC5M+D0%)8Wx9|={0mC;R$G%!ZS)c=9G&vWmytx#lITs6 zC||Ijh*lgIShSubT2*dbdty+$(#X0KvhqRu;g0e|0bpEgeebmvzblPT%UPAyQq3Vg zr$Rd~mgYLsh*2eC=(TL!{;Ocu*77s3Y`Fm>Sk%NjcP$V6cv0Q4GGumPSk>(Q>2&FT z3A$WsiF!$*mEuZ!k%o#Gd!Nchr?ipPQy-N4`K7G(+nzBgcv=-%0e;yMXkC;*ReNGk z{81xoNyy6i{cvAYfD;zKIgO9#ZdvI99R8BYgT~i#-WVPF*o_qZ|IT_Zmzq%^dt2-A zAISNKC2}RTYck&iuT1%CTwburckO!hClHE&s$rnpw_M?Sn49PN(F0`4Czg|3I?@$> zo}vC*7(J>x>n3usMP)8de)r^#zNZkB$z6Y}QM_s|6u(6^%DC0hPvRR}>pB>1v8ewj zzm+pzWRUHHfAa%yG?XNqMf44clKse*;w|n-|lPqLksm46JH` zRs4bXD@XIVrp+a8_r}%k^OCe?pLZ6VCjCe7Mu#NLku{cf<-fb)Pmk(--d1FzqVkFh z${*mejM60|2xQl>Lb*iZqY%?FjTGU!8RRTWw>uX9qATHyZIaaY-qd%6`D!=+^YFzc zmp{Z~jx$U*Jl$KdWj^qCGhP4fpbh25`6`}%()mY4-IOu}4Rb!l=n13ww?6x7DOJ)c3?AMxPCq@mynw~0oleC$E7 ziy1cK49~PWl$NQFabM;2dti+ zHMhC|Ym}$2lVj+U3~N<%U%=fL5xQ$5bntA_KN=Kluze-EFo;Zl%YZm`Fz`hpnWVpE zKyS^{0>#yD8BA(Vlx$#>sKWTTi>N3ajgc)wj#^CV9y4Yd?q3Isgb;ac9e+NKvSgHP zyJ!X#iYbQx~t1cSW<8} z+C(bsVFJVpn-b%x+~MpK1&l^1XZ&1rK}fp+gvKH&$^Q>YBrmFa{L`R`v<-|x8Zr5X z2_|(5h^hr{>$iY06I=k@ptRP& zp$Ka4PeFyUFatW_F%OHDv`)uhRteiRDz*2g;Nvh06G&UAmVxOuyc+V+`B29Fv z5+VqiR=h|6pW7KGfY0p=GoZ7}vxt;@IO!OS8IeFTq_L3=%Fx0Hs!ubM3o>pXQtb$f zL(~*>$|$P6GZHYG7SL^t@zi@&1IH?wy3*!_1kI*9VIor1gDh9(1N#{v5sZpde7zzmO z%pPvMwvlUtfkpy+vBEY!>GoA7)&}G8`i-^LtZwTkAw*>NQYQDJ_)Ux+mYM}diN!yC zcP4XNR#v-E{5I5=>;KN7j9T|X%vmbp>hpZqZAP8c7x_+H7ohtd!sL38PNm>3ONqLK z6$S9!vta^3vBxlirS@%D#TgArtX0rB)Jg;;H>`M(z|-H0qH~gnl-M`Npw}1(peHd* z07D|f1XBApXr2NXt{5f&<3*uxuGM54SD0_WUHTZ=We!CFoOsf~%~1h-d}5dYK0Yyw zV5xl@P!h!gKx*HJure#2U0qRoX9W)2S(pLYw_Zd_?3-h-!H5KK@Y^r}9Pu-ZpxVBX z3o>pX@|5vno&Y*!6xp{P2^e(?6uSW8_V6;)WD$&!~x7Sg;J4Oj$-@?{efP(g5ea2ZQrNeO zi5Q{>&iE5d>OvNBVw0s$SX6+!4FyLeje3j#ztOS8qHV<>T{Xk1M^ra@J(9)mJ%Q3} zDsP>bGA`pvp(G4niU5nVh`-3N&DN7c-5Vk?(s&PJ8RHNWO!~6`PHGo0&j4I7C9){P z1M(QP>vI}+4^Oe)eO#lkVuN9gh7l|^Y#l1c$OA}QF%hWGQ@lt3_aPW2fXgKeGoXQ6 z9+48m<`^^?kpL{eVFEa_XqZ51*jmj~023+01b|T}Oe{>cPKEgfBA1C7c@_nN;;gVy zfnYQ?OaSfGFoLCqtyM|r!rW9PEtYHpPQO9v0#9>Ohx_QP8Icmh<`~Q?;s43SBk6B# zkqa_zz}=8kWbHtuYopmw3(hC4BAmNkFaq;PSDgcV)XL1Clh z0K+yQtVA9#Yz1O{0yTzBR4aTkN)4O*62nF$F>Fc>pG=6eiL%TvT-0fv0xJ}j8a9(F zk_Z@fkuWiAhPW@w>;i^BUyi`IOo7lDwuj+_VIzPI7zc=VV6h1*%2cU{Zg};Os;@*) zW7w2dXt&tNu#JGCV%WNY;SfF}hb1EM!rK-ZHb&rI#jwqwFoWnpHSZFUco!m}%t=~f z*mQveMwKk~Nxy|>G4aP{p^8~l09vl9g}Z#h-#{!XjVv*2Uthn-u>CxcJtC!z5^31F zj9I5rC<(*w{{V}d5r6pu_}Wo}HVlmyusoIXAIA*ei|RgO%$#u>FfIjxNxv6>YK7!3 zV2}Z!T_S@qjbQ>DfUteLHmeVt#PFsjR_P-0OEI?i)w+s8ScbtnauVLbm)G1 z!rj(WbW!t`XL5kmY86%@4_K`hVE7c2p6js50g$_vlP1VU#5-O=I0#L`+*Yn5ql#_- z!oTuioSfn&bGTV$bN=%zt`f0NvXiy{4$zmK8(|t{KhDZh{sDc_gF8%vbDF56UcvE?5065s6>PczvZrN4ZapTjSp* z6HxFuecPt@9?dp!>&s^#dhKm4G&AuD$fR-)JTpp0A0R@jb zDw@u?R0$qcFjhZ775^SPae@EABz^m2B#h{hoeH#A>KMjT6&4a8{&tlIMTx(fc$6Da z+f`YbkT^ac!@GRuQa`ZPGUUZ+#G{rHl}}4*UWM7dOZcdi6=GSbCdGVk8g$oEj=HU2 zvB<|PrAxGNClsgRs4kQEmc_o(%k`=68!6M(*cC;;-obKtGi0*g!%FzDxZIaJT8*Fd z&x3BEmQ1{ce9+XZKcTAzaI--C=XKz}b!S8MeFS~G?YlvlFIYyk^q|I>2D(DHG`6W(b%(^YHO;*@EKT1b!4hk!G8hB@O0eOZ`wX35PGhNYiyKHVJ? z;aZ$MuE(0InJl|RqUs~qwjnQU8wz{sk=I#B6_g&qJM{TJW(2BU*@RclKOFDG@GVO& zx?q2uj8aWe?qam$el&S5bC;MEm0ev^f-g@WgG}b}1~0q{9#7%=hY?)!FtR$lW&o*8 zwqan0u_!JI!Br-ITD^j|uh=zgSiwcgIMgvFFv_7#&fjAq5<%xYGtu^N6WHNKBHU{- z{^qo>_)FYvv$$C0uB(wHY;=AeVccYSP0qEzc#P}*C4PmV;w+NZwV)98v<7pB%lnwPJOKG&!m zmg)sYGXlkV&U8>J0Jx$f2Eebj&<`6&pja1tu9<|BeiH!yQ0(%-R+@2W6&P(Hz*koU zlSrC6C|Z&x09HI1*7pR%(!rcb<2N9v{5_N{svu=S@)A&kqG@?ZIM=>FHLUdVjqu@A z6FvefbSG!-`j<)ocR^>QpyT+-CSB!cGH_B6C=B9C4b^FKmWqKE0ZDS#KWv1O*Ad!W z76P!uz&j&QoR?(gDGS+&7NamM=`$>t!~vKUx&$%T{}J+a-_}MEYaBQ=5lrgh9=NCt z`9Pr-uDTTzn=MS(Bq|WIlCT(K26)@lq)(YGP)-Oeb-xX#Sgc<4X|_(1-1Sdz1Q%&Z z0nDhbZADcG-V}uSLt*{5&>At0YaiJLXV9jhTwy)-&FRF=ZB@7WAVLg0rU8Tvg~goA z-MxM?{`i{eL|h%H>h&OEUU@U%x;EV0sO-DDnG6J@8$>CPyW<75Kipn@g>E|S>N3?7 zAQ`NR&7S~m8fNp(PLPx_%Vym$a(C&IT^NeVxv5^Dw%<(Wojpb(5F~dq+0DV&MKc+O zoQ}~}y5&DH_bL|lGTQ4axC%`0za8dfYAp5_5lR! zYR`Su$4S2*RIJ7N>Qtt15I)#hV{BaR0uXOrjqnZ-8M)GSLGMHm%sHcM-&)QF{%MNc ziZ`z11Y;=xAN|6^AJ6$$CthXtDdo2VMKzL70}MQ-&c>LD369RaLF%m>v)@m;rNvwPQxrIw(} zNP3t7?6VjriY=|V95`dJmMU=mu|?Qm5jrXny2$M3h8)8FuQLHUmhc#>0L5Y+Dw3x1}%DWea$xx+DVD)5c8TX)f$`x@<-ISQ9Zz4V(n*pTGs$w`I?Qj;o~AHOp7xHz{06IqeY}f@(*+zzK~k7+V@O+WQdg~^=BzqtXTesq zjpZUo3AUi*TpqWc;iThwn3~OCsH5=SakyFqUu&)(cH)BZinif28H@ZKOOry)Zo?`` z`*iN^C-Jq&!gRV0qmhHHOM`{mb!(d~e_gFY;toMZ4WXSd723~KZvP7+_VA}3J?8iiQ~yOe%QuR{h#GmS>zfE^Q+9e z{5Fx`KJtq)_M**?e}`=vn;EzWRag3AfpQ9K6v==ppQ*Z^cwUzY zpYqCXgkv!){wxS9Eh4@8tl!dYu?32@xuAVFiURfB);K5QO(0M_7kSHEJP0pq`t6Uz zYlkt=TXcVKipqf0} z3$R>sMWgC$aB|KIp0G>yw!j|X3cI%H_>9+86a<5UF-+ffYX!K=`M&s%2!^8bPEm2t z{!1uo+Af+Smx=g+ghL`~9A6CSovQAQUq(^)RzIyivo7-j=+nJrXJT4C8Be{oQnvjl z`SgC|rbNFi*e`*~B}k(*8Gqj-ZCYHU2>K?2aA$S=&v4|9Nb1Wz(m!Kg>8JGZ^QuTN zM|1VBBatsW^v;W!2VQ3gokQAPrvS%Nf+SAwJ#+OAg1w9qvN(=}!-$x1*2z7+1E~MF zZ3JTkdEf)Hyjyfak200(e})3MmP?(y5EylzFrSUzM{lgKId&g6cKW!Abw2dED(uEn zcH{A-jE88JD)jg=%Qjl8^M>SC!_O7r=i~t9^jYMOe$j%l7 zMGbOnMcRvu1ZRdUAva>0A%-LJ0;5yLMV^uKEY8Z)Vb6h1M1>oc&EK%I(KWGmYC&sQ zxIge;f}>ASfk(~q16)2zjOWVz)(vX zB%iObb~1hPkawo6@P^+~XThLTim@xWBBFDsLpJ(*XnPZn&wgiYiGNPcmrZ3u0D z2(5U^n`|4ml(auHEgPN8Gec;UThP4coK1QCEQWwFAS@~LXJbv|Cxdiy{WdTWLi>OR zc9&|~GDe;K2?JVHnDTRxplrxU(EbeskHlL~f~qtgv+p-Hm>Cm)|+@UsFL=N z`^yu1NDKJWmPN+oMW98`vNwDE6vpkc*T+=x>)Sm5slajs1GHfbe=xp!1JFU-l?q(} zY#vzXXhG*&*QMyg2!v|3j0J7i#Xm;!h%?{?*YtwTw(CEEV()!4TWG)43*`lEjDruJ zRz&PYBLddD(9wD{e#&g~K;at3*w%I{$CuOFgdLq-jj`tHQ+MS03#>&U?!#v>Ot#G+ z4K%a1X$PkSWo>ynz-zEl819i4z(fO&5+ z-))lRp?<89x*9AcxT6 zs{BNsGE+W!NxYu!_!lRpgk2MT>d~y`f|!@-4*+oUx@!E;w|ksnLCM;-opef$KNZar z25|Erih$8sd<6%41w({|TOi!WP<&eofG9rJl#ey(8o14SDLwe{$1;MCwctVNZ9(aM ztaZ=T${RnvM=|93k!|GL%vp8su5&bLkbnI9ZSP>SRaK7M1(%>;+5yK@(#lE{#~6 zVSt>!7C~doEyj#*UYvm?^XvlaB3Se4-pJgc$iH{JqJ7iqw zRf_hbjJPm>)VQrX_|d{SE%@Ctp|*D3cV!PqQ9ZGmgHJ^nB>aZw0HB1JGkN&j1&5|6 zwuiwJ22N+UxPj882wJ{ptQR2IffBYHYF`=se~cLXLv@~m6Nd%p1M9+`7E9#w;wWq_ zW;a1;fK<@OZO1c3+l|4Vi6U)dBITE0037<%H+Opu2>k2l)J{{bzk?KbKtb=VF?NHv zl9lUc>!}LiJ`j3M^TNg#Xc(=0173#8YWJq-0Sw(oW;Y-+$ZQ9I%Pf76S^6MTUBui* zOeoAU+k;e@+3&qlne8MsM%(c90?X`)NH$e`Pfw+gz_pwCx?V-I$;vhNE!+3T+Bm|> zY?xXD!?27vP%@+2?TJ(t3mYNA)0ZfRa>yhHFfNnyK_=+~aX3apCiBEXVV22GB*#q7 z9;ZxNc#*zICf}rP)YYz@p7;}>{7wPFVle|hO9h7C=_Pg__I`Sh_PR*375x@VqY}4) zF7BV84EX6%^K2%DqgTYRRtm5dRTN*+OClUI)W#{;;wLsFPSI1y1K0t|%foM#hkgSg zK0+C=nHSDGh~R1pmK0DOWk%jDE?Xzr#{beRk`_h?ix!B6j+Jf;|0fHlRA~P7lGe7D zaKK5PI8J1w14cSxtSp9OMbYDKh1cEmZl;*QO5=RaH}h-;DA|{bpFnf4_1M1Md8(iL zYwZo|!n%!!+a6}HCQBQ~iA7L+3Er6T5%KyyCZhpK8;IJ$87MU)Y;+#t=lY$!AhJJu zVexFoz@|xHgjmTTVw(V!^6=!AnuQUw+Qr(z+I2ND1bboOZZF-M&{@dd@&`~xCQVNd z$m+=^*}Zp%x$j_6#tq=Jk9OcBwa!Fmqrrue(ua**Of}D8W4ADl8%)yJNjmciNowIX zHFb^sD-d9;fLE}??CEyQg?o&TgzuYVD3YT5NW&wr1J7#%T&-T3RT`|>Gjic0TQ?iV zvY?L4$L*FpDN(CPrX?=c#wj52t1MiZ#V#jP=Moshrx|{z!s}YkTUy0bA^LW*KAnG0 z{OM!x+gwZ6v(TTax!3J^mvK-Ed~<#dnV$5&0XH0(@((e;q8Ve1 zJ}HXcwcY9OFdVk-v=e0U4x(Y*@k(;#VFLC8y|4!4!z{AF5{aiptpvitb&$62Q8prX zfT{ms$#3c7TQ)|w<8xk^Z$agfV9BC@?c}Xk)tu`;89eTWK1nVb*OPXwq-S+DiI;zM zpzEE?Zb!+F|60_K_ktVNAXy>MG|ls4`E}u%{JLdpI#KQ#Y$g-(5~~|(j4Cg_;RXgN zM54tI47u&o2KT+g;!52iCGp6Kx!muoZfEU7(5i*|SLw#S)xkr7f3s9#`);9Iboxl% zJMIb8!Ggko;fDc?%tT+?6MymL$jS1f9P0&{raNlmLb;FRBU#AH}~vio0w) zE{TLy5BBuAx#udCQ&MTza3Pw3d{d>;fQZQ55I{x-C?M|BUATn={kRas>*uoeVvtH3QODbA+@uLF;V zzMZBXgS{yLu)-W)N5hv|`a=nR(DkIW>BfxfF6DRpucoBw%HMn<{A>)XL>vH!69S}}LV zN(O)Olgo_Q<_@ujXzNi>5PtPg$ELKt)wD;+qQt7k>KyZxe@T`RPD)2nFO!Rk1%SJ0 z=z_;l(U~~LXw&Pk%Y+Kw#9An?cKkA5`xJhpuc2M1*qY@#j@Hlwm-ks*z=ZN8GZZRW zqnlwRP$++?(hju@U=5}K_mGwj`I%a{WeDjSJ5KJgp2|f={)Qx$SF!l%t;prCys;+WZ+>V;^YU$1`s^ye-v;2fspwDRgYl?WI!yS=65k^K>TMPob36# z(YgC*&w-CX`$+)1LtqBZX+jHQ^7j3hT-zUoglMI^;-gSMP??!%YR&sl;$tjz4jNoV zu(b`h#1YDHnxHky_2D@UG@LFy3_+P}%ECGX_oW>UeD5O0|~Na7CQxQ{FxxB}s$($KO| zrR#p--ECL|?x3>$YA33TB`WK0_MwqJY60%Rvh0It${n3{Am5nZ)|xMr4n_nNY^WM7SFpqO5du?+o-4v5?#mxpx0+&JwjLJaUkQ|+gc}DX_GG`>}??U_T zf-Tg#wH1ftw%QpIGv3(rfSAcgIL1g`5St-(s^Grw^rB8-K8{(Xtu7?Fs?*@dswR$! zSnpD@D-)P?bB)@K3KQ{tFRCh*9uz{cOUl9)Cc0mKiG4LGFt?#Mt}FsT%Ba+@u<@Bx zxvydWJPfrB`+28B`~#w4-N|PWtbD0}eb0}n5(v^^Kauz+q>k)-nrk)e!||ObMF}{N zS~Tjm>Zeh62aLZ5Ia52V6JEeZP#Cnv>#Qwft%0$yEHR7#U6r1(}}p4za*3Pra(-Qdg)`QjEDl4wNV1bq$&a|7)Xqr-yt2w3!FbpE+c zQU>}Ip8CU}N9u{D1-7pX4m;Wl&O5pu0@G{N!UKVSF2wCC@r{x}?|41lNv%lrm)965 zys`sDxHwrP9Nu8NC6}r~OBx0_-yvn)K9WDm^<| zO(Q|FpqLRDotG?bt;&UI%w*x*<#P8_RNKTki)A3OiNAvZ%Ok z0vqFEAm7NZ3x5u;+#N-Nph%se^4AP)qG?4kaO$cCyD2m&Vz<~`Sv$peVhj-|2Am^_ zGjyNnxN9PmVbZxyfOK#+g);1c{|(@w-Wc$4WcRL`-)BZW+ABabZGXTHB083Zunt4oFd!aOszR&sb#wVy;HLUB z*>*Eo@i*YN?yP4S*!i8l-E&m5jgW&;o9idzXq&17F5h;)S+p>)+Et;!?t@_IL=nBA4mD%Ij5y? zNX3&S0W~3y>&L=2l*DB%-llndqY78DibCb90N~{$!hoAU4X*_w{*ZyAO?LA7uLje~ z$kTF>e!v7aiBtnf8P7SmARVI}n?I&$6p#j{0Ptirtbxkz+{`kG`FCN06M zxg^DUP(;ZkJx_c5BucTyeWa83QZc)V!qaO=05L zS7KD_t3S1uYLpx~(qNtEqms&GV|w=uVp^p-DPvNlY8Pfyv8uyav36k54Hb*tODfiJ z5U+d@4WeX(a2W7u%~d#C61M9ADpg;VA*lt)>f#2;q}4@pTB~C6Z?xp;y(Iasrzu-X z_aMErio4aEMAP*uYFijbqeq3#s1R!g-|j3Jx^oJYo=I`vK=W9NwU$(Uu>`lval*9- zPm-HefIHcy){Gwlv+wbO(FYZ%1**^?9_-9RHp6KtBgndB~bI#Jop_+1cOd8g##j$Pzhltv2vD96GCjMGhpUpLj#1aEj%eArMXW;cbQ5f|G`Q^2PGmpt zExQsnx=MfijHIH&2B$w17T4W6n#x`xjWIJlF1xfF%(}p=JMHm<%~R+!#=&d`k*Pg0 zgCA!f=-fH=fRv^fW_hCR)@+#zXK*Bk-NsE@OTRD3gEtC&^PEO8`#x`84?2`0cA{{= z`2i&3TBJUAnXWx={A{2GvmgJZKLj{gj=0KGX46(n2Lo2PUBtI)-u5K*{ovtj%0t&jd0KkV}B6QKzVv2#4(XWGK(Zea>1 zOX8Pc;xHbx-Icqxl~yCF`=g8p+D!6OFCg6-zXnx3cLUZe(&$^`mZ0r+`+ltaCj^M@ zWSE>>e^f))8nk^Q*Z+Gojc%Boss%LV>{1Rpm1yacEDLik$} zykPjM1n)8YiFLPULBuzg)x4zzu{H2hxpr^5G?&Znt5{F z9-Ax{7QujR?*^`tMm#IMvV(<|e8uJNGB_2d2J_15YVG>hE9eiNU*7l7TfFYkYB%j10yp7B8t?ofk^)~A-Hy0OUXu`>R}W8lrAz)mhS)4c%)!E@9ByIWol>!Hc>~*3w#4EK6M>Rs zlTBy0n&OTW>e#pHQ$btZ9M|?=ay1ZO^bW+q~6Si^r1HnUg zW*R6quSl@{W0M^NK9K7aJ@pWO8?C>(xo#=MJR4{oT=Wu>?79?vT9!%ZkU&QYQ}>D{aM)q3*`{K7HU=v~lzo8H&y zeZAfTdVfOiPwD+>y$AKaQSY1e{=D8pdVfjpTlD^_-otu-UGHz`{Y||`^uA5++x5Of z?=ija)cY>I@78-~M{fOTG zs`sqkztH=adjCrASnpr!y<6|!>OH6TV|xEV?>&0Y>;0tOf71IYy)&rs3&$LwH@~`E zIOZU|>-9cF?`P@#Y`q)wey-k!>HPw|`Ss<(F^B8@61`uBH*9zG(#@x2GOOOYaLk2( z16}ki{|b$?3tRu0@*{L@rRxE@{(-J3x*nqIZn~bNi|dV*U#yCCtXc2$z^s*U7P7zL)R!>>^!=D zPuEFworwNJKMv9LA-Y~hzuV~IcT-*SbR9z13UplM3|)Uq*BqztiWsXC-Ji0zX*Hv_J?%s7H zT_2+h>dTb51Z1ucq%JPWblpa}x6$=uy4KP48@kS*>zU7htCg;o)Aee)-bmNcbiJLf z!|A$&t_HfUrRyNN2I=BQQ(a%7>oK}+rz@uG9=iUOuKVeFkglK8wTrIb&^1oipXj=S zu4f(s7qomPt$cCTnjhd zm02dUxSG1+A06xP3=;Vw9!QZH!;}3wUT?>vKh!<^E!^}iKeOzXy3IgoRxgJ7GDo83 z5$Pjx8VU_{Bhv+#U4_?uK=hYGFjiiT>=i(c10n_kK)5RZ8%VZnInqyA|I#5hvZFc( zi#=q05@7b3cyR+ve}4u-1|s8%$VMPy{ZBwr*6+b0Ys&gC$Ua^?$k$N&WE`Rk2=uxFJtAA>u_x80${}X8#7Sl=b-tCTo%TKvLE( zKC(*c9|1|FzWOLbKG}kZw;XG%rvbBH!|QY_B^>f?HnLkub`um$2LD9lE7&w+PVWcI zejTr;faq`dL`zg;(+&}Xd50*`CU7|)5v1NnfLsJbI=BjBo#Rm#6ml0ndEqAf%UIhU zbjV3Wo&v&bD`XXX((-w`LlnZpqzn`8_3gj4s=9ZZfTexX#*|NV!_R-Htsm}33z+a>JWZs7`7XMR3t^q>6 z3i&zYeJPNek=^X!cmdD6j|gY6{{=*U&xIDKC&l?La>h6x1()oTxZ;p;&Lpw`vbmRL(emd_Ks|!RzHfN<=o} z^%Efad&>(fKZ;$of4F`5cj6Ak@x35V;Kq1kWhs9)~E- zgANh5LqYqWAiDz6E_*g!%#T8z2jnCm`fCG1ZX!Dy$SE!$Q`;W?S6^n?&DmoSNNOJ? zRL4`_s9c%P68Q}fd?%548;WPGlz&<8ORAB)8F$E z%yd=&W{$+`IP=eB-o%JN#NaF-Y5RrNw`B2cLB2zUT0VgU~*WRs70Ks$$-jQv?Nq#!NJD+ z0X*5a*^{!b$)21&CHva!>$0ucQ?swnuE?I2U73AD_VjE% zyDEFe0S7c6aKLc~9Pr8m4mds)>Hkp&isb+E-#+6qAbIx2{qq0a-nGC-QJwoUyE_R9 zghs7m)#8FFvOpjS36BsW6{ALcL=vs?aM;~!l7)R`cLPMP8f`(WTBEIs^%Z$O~<-q>18E3NTXEwgr{sjbu2bzA+LB#2c5D zawq9-g*UBwSwasWnCwUfrj(RSsx4_nWXXBSc?gnXs;MohE@=&si7)QMZKP&^eB*3`C!ss-{I=1JozWIUy8eTG@eU8i9dGoAv* zQ^I)aFrHGOq-;~+1r2v8vy|VISt<_QL(MYqg2HN6GKq3kvy3u}KXh}{l~He6x~y!j z&NcZEmJfq*;WBI)7!*iTPUeDumB;cSp%$~wYpM7Mf&7F&b`rE}Q15NjIP`D`(3WKHY>b|Ds%E@Bt6rED2yvzK7C zV+HdvAM>+jNVhGlm9;^vT*X2x%pxqxVvrZ(EWwiOGIlw;0&-3lyPAEIeT%JQ>)ADI z1G|=en_b7QXE(6#up3!7MAe(wciHzK5#P#g!-CfBkX!F&_plyzFT0Q3&mLqCvCZrU zYzyXSkFX!HN7**^1p6_2iv2753HvE~jy=y_VE@KmWC^N*O69bkWAe`X)B0rnwQcmBdYVjr_l*k9Q}_9^=t z`#bvw`wYu0|Ac@zm@GEYB1tV$G^Jhnrg95J%BPf{E5BAgfY+lOUvtcM)H{|s!j3B) zH#zQeJnneWvD7Z&Ytr?^U;`kEmPKN7bj*pQz8NJJpxf*VR4hZ`JqJzo>s#U6_AaF9+IU z9@=3cWZtou%N&XJcRbqOiD-8xq0OC)_BIu5Z3f!eOti7tXkqiP&3y{m)&?+{eO+0`WL9Jmc>t&Cz$Jvuu`Fxr^!=7b3*iN=9qb>XjivWAk2B`k` zqwfENTK^C={xNF%Q`GclsAUDxwhBqR08(}lB`K%9+X{qs}9m-neQsoMz zOSxM4ma<;CR=G~OLAg=6N%@{~tFlq~zH+B>x6-5BtK6?VsBBiYC_hxTD!s~M%HztD z%68>xUwyrle0c}00u=~LcN-c;UFb}M_7eahQPzw#HfHm5^%uQqd(S)GaJ=IfaQxFT*7;TEH0L~Lz4Ibxt88IU^@^-lFcwHK{#m-?!@TYX3U zgL**yv-)3Xd!MV0JXc;}UUA;IyzzM@c~kPr@@8AxBi=y$a><9R+(EYdHi$E`T^e%I za~d}LhlTz}++lO+Ub+8412G3Vwm`>)V+u2_0%5}WsKP=;U_8>LjLLU7lu?;iejXZg zKGNcJIGsqP6I0qjE=*7VN3ZPR51uZWsG(g*mx8;nibR*o{!+W-OoYI_+;-^ItYQp&-T5F;OnL;+2_oaon(*i*e78Z)ouNGmPFGBBH zgt5K|qkR#^`y!0^MHusoFzOef-z~z(UxePb2%~=y`rsl+07dADix;Jx(}-cx%Z8FjJ&+>FgzF+JdiRx7#%#2G<5R|z)9lpK<4mpelhqG&{EJc&~nfv zAPuwvV^I=obnO_C z*5cj?B6KMz0lEzIFQCgoSAebrb%Cw|T@Css=v$z5p!J|@KpQ~Ug1!y94s<=}2GDmn z-N<=2o^Jw?B-9RBs2yWoJC}#rAra~5c42b|yAu-99h~msvXcIM4>#%I_f7nMFTdZ1 z`~BSg1H#vX-25Sa-^}kn;P);3{xHA)kl!ES_pP}9i2Lp3_eX)ZVcgw@(f0|Y>PgU# zLEAx3VI*#cWY&)HxE=CYJ4WSpNNGE8e-5-0^gL)6=tVBKk!?GB3G@``XIzeZnY{x3 z3(%{e*Fb%s*FkT9ehGRL^zWdzK)(X*2K@(U4`?4o`(K0J2K^_fAM_6BH=y5w-Ua;* z^d9KN^QKjcS}Cq9G>`5~l=5AmLnB>4$sN&0;% zxQ^s~zr|FSIi)RetBcP}KCHH%XSMxjR$HcIi&x2N>&R;B%xde(YO7|o&C6<=pVf9$ zR@;KCwuM=3M`yJilhw8;tL<2eZ84-&`=7OC9AWX9Ue^cNk%QRktIKA$nQiizL451} z#$~lVHmmJd2C=1e?x9k3g2{!oJieOMc6?Ub6D_uGMgL6@Tw=E(W1VR7AyY1GYfSHD z%Jw9LCy~xeOj!DtZoZb)c2ZW`lWlD$n@nXq)_6T8S6168gV;{RJW7VBbLq3xVs5sT zex{pTNx#x|MpoOhthVL0wiPB*8P9%JiJ4$(A7vJv=Ygs)FEszE`0mPlzP~abGsgKA z`-Q+(U#Hoa)!CSxZewfvO&mH0^ti{ zYYfzh)dWl-(yqOxwZpxTXy`|4UVE{6Q-s;ZmoU>9d_9p|n^zR~iWSAt#)@LTLaxd& zKS-@AP7&*hdoe$lKj_Nh-n5lPT32l1>p^l|aS2~pJcfrpPNCIBA7uJ*3au}WQ(7U( zk5fX(YlKx`_mX~6R_}mhPEvX&r1Q%lmCMz}t1{Lb$rr6RR$||iR+PFSXMYbUeZ`Ta z>bu0c<0i54_<*tYI14_vaQ}~B)#*oAcX|fvkdJXG_z6h7PeT6P&St|^r9!Kca}<(z zpM$jfJS5#0AnE=aB;1!F-ToYs?JpqJ&cWVmA0*m0AkDtX*DHU;c0+>Q0~=j_eH*fC zKVP*x0;QwyHQ_?k!FbRl$gc-kk&?@LCe@q1qB#ot$Ej6KtP;=~0rr^pG8J*oVEP{O zKFkdDJ?678_mjKK4G3j9JC?7A9fv#ZHIKvZ621ydYryt9&yy7oR?Vot*LR@FPr0%R zbFZ075_2M|n=tNG%5|6tRVv?Q4`XGI%CS;;iqY=$0+iG|kldqw7A5prTI*-1eST@n z{oZx!49$a-lZ)4g=T2#+CK}}<`1xticqFY5^QCrN0G%9$R|#(spU$hE;|-Movs<7O zCqSic1~)eJHcXDxue0*mCKINff&&VMMuD_D;~e<}Ubi8j;@TioGQucB+iTTQGNwfc zac{*Nf$K^@X&Pi~w5ondWk-)AE4%9`TE4(_GA;KqED%5uX#QN>)kT=|Q_%Ie|3AMh zJ?*e^JE&i>kYpbs6hJb5W6_Tf{hNsOp!DAq>?)X_oG$4{}R8y@|}fG(eDoZD^><`GlARa_rcuIM=eQrhko;O z`q96Y)gasWz*jt)=Iyc;l@S{V8PwFzUm==pBlI{xw@kB5habuzK zf`qp>)y9bsP}PGQJ!VYRp;5U))&du3NnLyv1$^eqJou#;3A1Q<)cc2#r1#$KO;RjDSGA zk39hmnbzh@K-8=!3k*iXzS3Q+ur3VhF99q7)$PU&7vUHV8^nIi*p4$8D+4cE1pocO z?a&^72X@ynHs`mD4crLbeefN(!v0-oe)cf747?lsPQsfQ+Xp@Xe!}lyelIjr!F%q* zAxz>AF?ImF>osT?zQ@>#J{;}<@BbxZA#f+sGKRUaa!fg4$^dQyRZLj`tF2Ur0PzW^U}pkSzD|WkI5g^4O=R6x}29b zoXu_W@3;%FSlV;TLvi~ujglz0qjfHW>=x#|N9pEe7JKP#C{FcF;ZuDR?>|f6U+KI7 z^-tzKC_CaSr1`5l9(YCP{isjw4)slZ2kMnL)i3cF+5>TF7sSi%);YBoZr-EwVu9WF z=$P6G*;9KXPVI*{wM*jNcj|mVgjX!m7ejj{`+gD5z?nLx_Dkk>?$P;!g7*skwBWl0 ze^v0^g8xSF1A>1dIHnFf+(N;>EckfACktL7c#Ytv3*I3362Y4Vj|je2@T&#CUhrE5 z?-6{9;ExObtl%#R{)XWD1bBvXbqTzF|a{Q9;|7SG6uwMf z{~+n>AxCM~fJmo&?`1zg=OyWqT)Hb0{Y0`P zWx|f{TyhLzZlW!^ChU!L`YC69w#ljV2jcNM^5n@FBU^~OVPsvlN6 zMQRV`?{K*7{bp}w@VnWO={Ls$h3}1GdMMh5k@PviXhK6{3(Riu216b30Ni$)%e1M! zF=^ypO%gZ#)zjm$H7hGLlf#*a}<0ncrDrPMhX^B#rN3AIC*{JmF)@!HfLRnxH ziB7Dghc#4Ul&Z1+BUz0rj2QHd2fGC7t;z_^a3bo}u$U|METY()7-YLbuZ-1YTBM>< zlc^JX7ddo0UAyzYKnQj{K$Cr1vNsP?FEp|c?9$TgZNLlI|Ea7Sp4H|M4EYn5`1ym@ zx^}G=!(L1@;td6po!aU$yqJGCd>x5?S;I@^U#0omR%xvr-gq;4Z1IPpi2$COJ44NI z(Bad3!ANr;&boG|nyVQ5sBbq~ZF!EnOXtTo7U=b3ysEkGV|DcyNqF2oh#|MCy80Z# zYon!Q4Ye!QwOJINd|izIa!!6)3G$+|hA?#M4?1|h^+xbg)<%FjiuXA6p?i_oFsv|! zFl-gszkhfq=Xyh-sNajNG`@d`zBr8SqW}#bP}PZVHU~P3+=c>k+q?;_#p@3w>lUBe zSf=S+I*a$(yY5H?>|Ad*yEb}}Sr`ft5B2gazC$=_OqS1#cSMrGa6s?07FN_PUeu_O zo0kkXe5Z6o8K0%0&kQCh-*~B5yzdZ)$phVe2m=lC`sqIuA$nujr))N|zWDL&ID!DG5`n;~*1EOmD2&&0Hkn~Z8-qPMosk5Z41_|h`s=-U1pZH}(Eaw> z`prI2&xCAT$KRaoBk*0-66#2_iH>O`{%PZ$_pKq`Z;c>`SqW%D#5?I_Xq?F#ZQsa4 zk!^MN2!fEc&_4pdp+sGf3Sht4Z;sM5dVW4M42;OPO!q;PZ!xgxeN36&8K~)sC@KHrqRq6AziSUX!ySMR-}B+YN8VTm_}6 zkKwviV{&dX5Dy0Qgpb->+V9VkMQ??k`Qog$Ks7iT+kTFtmX|ZIS{B20t^U^1??mQBoFNmyxWUMU? zB%q2!#v8eDnp92GH?rs<3oclrk0gW5WDr;zOcLlNAmwfr-_9ZLRmgS+hom(`=p~M* z3n$da5%s-K!zo`4@;P14#B0Hz<4m#}FLa_gw-Aic8Hth$Mv=44$bF((@+fw>_~v*pIjf*rtj zJcNACZdP)fqV&VlkV7MECD(Ee8_l+KKlYy8s!j0y#P{;5-^m*d$f+)TzZ`H^l$!%ZV!tC)T&jG2=>j#s*v z)T5~rnDoTRBh+uWeV@>)f!YtpQqSfXI3oX2pC~G@)HixfV5xWXzQ9u7NI?R4{6f#i zCot)u!6|!7V5yfxADJfiQorYPD3lSF`ayRIjN=d7|FZ&1J*2-0EcJ-SqaMk>)I(Y# zu+%5ILtv?&@VdZKUnF1E{TFZ2`tEcH9i6nKHq^SE7Lsqb+>V5#>}GD`O^^*y`-_wCZ- zzgu9b@3BWsUJ3GwC-N&vy}?GL+FpK2Of($C7nnr-;Dy3t|Z|t0#EMO@iPLG z&Lr8tDlq9z5`ITu(xD{$k@Wwrj`Q*AQhcOSN%qGHOuChXO9dt!OTr5TCS6OyjRKR- zCE*reVt0)H;B)U%{ddy{{uXW1&S)XTh6;2xo0bO88E zNGR#uQhNVN82+XH)l?`95tjO6D+HGMOt%nL(SHhksh0(o`dyz3EcK0MA|8A*6EW%W zoxw5mB%$9+@t>oo1HGhvE!Qzw@#|p=e#3%4wcv4jxQ72q3%<&NU$fxjj^gPu{I^>0 zHVgiT1)mMYTzEG8-(0ds!jU5VUlx>rPXXbz!Fg!?1L^1@_!sCg6yj@5`iJ4s zt#V!7$>8YH`A5-5fkPbNpOG#FrwhXcCXqOS3AzNNfmVRLARovNY6b;BEudCV8z=}` z30egTfx@5&s2vmsB|u3~2WT~@6Lcx)N>CT*TcCBIYd{-7*Mhzcx(;+b=myY@pu0i$ zfO~1KkgL0Q4YeIcPIz3+Q3c4?&NBwt{{H>IFRtdJMD;^f>4V(37AagSLa7 z0zD1-SI{#cTnnzk?P}0BLF+-^0eu(rJrJ%gqFd=nJQKeSw~e6NK@Wj`0HXA$pgfQZ zbhuvsZ|c>}Qo1~$1Qtr{P7GoPPm}w(#^QOCR#(?JOQT)DR1lR|^$FSUB-YVJU`8-a zT2mPXG+RR`q@ECI#bO+m>x^~4w9t`2TO|R6YuTjC8rNKH@gho!dGiK&K`NTcks2Ol zd3gi!+Mu8DG%~cN$>+-qxV${=Bw^hbjUhAq*<#%c%dAvOrcCkW z+*I8`yF$a>2NVEsI1uZkajZPIcLXASsN_&_Wu7F>UZD-NXBOtsiv4EnpvIu0L#x>O zVa~wh53dL3=lWoDH&1X5Xz4bnytkhRh% zDdr02Y2fVyyMlAI##8n9gf0xN*L&msw)%$CeSSafm?A-Sb!g?-n8Y7EqNO!KkwvSR z6-QA-!QijTh};@E-!2|*qu|N_DNmWB9aj@Gk9H>IS6z<`TmBS? zNrK`wGV^9BVOV0}84DMRt!9k9pntqX z!h#M6gWY{S;AO_`r-N?tN*S3ngeH-TsieO4yI zV=(l&a}>JxVB6FrU!B~fCf(D*VR|YfQCoH|hQ4@Xg_!51Ql8V8ucyIKlFzMUx6kbm zeXD(Lhw@~~Em|Z7Bzy?S$gNbCz#B7cO>L8(;nbL+eleWVGSoMQQ7wj_;iS&$X{ZuL zcaW)$qH#?Xs}c~67l%&QIxVzpO4c^c?6j-XoEalxO0Ak~2i2$6Ef0|*^`Q*RwAhd_ z9A~azpkuI1_oa?ERHwgi!qRo7zj$CNW$7;(mOi_Omh_EYGpDEHxtz(x##pXJ=Ce=99B2bF0pJhK;3(W;@xAAD`uiMxeit9FZawXf6zuZ*S_3+F(>J~E@u32Fnjxwa`?A@;} z+bO-S~M2|hlx?4^yK(;=9?h^&f&-;SYX= varargin{2},varargin{2} >= varargin{3},...) -% -% Parameters: -% varargin: chain of input parameters @type sym -% -% Return values: -% fun: a >= b logical value, negative for false, positive for true -a=varargin{1}; -b=varargin{2}; -fun = a-b; -if(nargin>2) - fun = am_and(a-b,am_ge(varargin{2:end})); -end -end diff --git a/matlab/symbolic/am_gt.m b/matlab/symbolic/am_gt.m deleted file mode 100644 index cd2b420334..0000000000 --- a/matlab/symbolic/am_gt.m +++ /dev/null @@ -1,17 +0,0 @@ -function fun = am_gt(varargin) -% am_gt is the amici implementation of the n-ary mathml greaterthan function -% this is an n-ary function, for more than 2 input parameters it will check -% whether and(varargin{1} > varargin{2},varargin{2} > varargin{3},...) -% -% Parameters: -% varargin: chain of input parameters @type sym -% -% Return values: -% fun: a > b logical value, negative for false, positive for true -a=varargin{1}; -b=varargin{2}; -fun = a-b; -if(nargin>2) - fun = am_and(a-b,am_gt(varargin{2:end})); -end -end diff --git a/matlab/symbolic/am_if.m b/matlab/symbolic/am_if.m deleted file mode 100644 index 729df34153..0000000000 --- a/matlab/symbolic/am_if.m +++ /dev/null @@ -1,24 +0,0 @@ -function fun = am_if(condition, truepart, falsepart) -% am_if is the amici implementation of the symbolic if function -% -% Parameters: -% condition: logical value @type sym -% truepart: value if condition is true @type sym -% falsepart: value if condition is false @type sym -% -% Return values: -% fun: if condition is true truepart, else falsepart -if(islogical(condition)) - if(condition) - fun = truepart; - else - fun = falsepart; - end -else - if(logical(condition~=0)) - fun = falsepart + heaviside(condition)*(truepart-falsepart); - else - fun = falsepart; - end -end -end diff --git a/matlab/symbolic/am_le.m b/matlab/symbolic/am_le.m deleted file mode 100644 index 49fda85721..0000000000 --- a/matlab/symbolic/am_le.m +++ /dev/null @@ -1,17 +0,0 @@ -function fun = am_le(varargin) -% am_le is the amici implementation of the n-ary mathml lessorequal function -% this is an n-ary function, for more than 2 input parameters it will check -% whether and(varargin{1} <= varargin{2},varargin{2} <= varargin{3},...) -% -% Parameters: -% varargin: chain of input parameters @type sym -% -% Return values: -% fun: a <= b logical value, negative for false, positive for true -a=varargin{1}; -b=varargin{2}; -fun = b-a; -if(nargin>2) - fun = am_and(b-a,am_le(varargin{2:end})); -end -end diff --git a/matlab/symbolic/am_lt.m b/matlab/symbolic/am_lt.m deleted file mode 100644 index 53b3006bd9..0000000000 --- a/matlab/symbolic/am_lt.m +++ /dev/null @@ -1,17 +0,0 @@ -function fun = am_lt(varargin) -% am_lt is the amici implementation of the n-ary mathml lessthan function -% this is an n-ary function, for more than 2 input parameters it will check -% whether and(varargin{1} < varargin{2},varargin{2} < varargin{3},...) -% -% Parameters: -% varargin: chain of input parameters @type sym -% -% Return values: -% fun: a < b logical value, negative for false, positive for true -a=varargin{1}; -b=varargin{2}; -fun = b-a; -if(nargin>2) - fun = am_and(b-a,am_lt(varargin{2:end})); -end -end diff --git a/matlab/symbolic/am_max.m b/matlab/symbolic/am_max.m deleted file mode 100644 index 54ba55fd0b..0000000000 --- a/matlab/symbolic/am_max.m +++ /dev/null @@ -1,11 +0,0 @@ -function fun = am_max(a, b) -% am_max is the amici implementation of the symbolic max function -% -% Parameters: -% a: first input parameter @type sym -% b: second input parameter @type sym -% -% Return values: -% fun: maximum of a and b -fun = sym(['am_max(' char(sym(a)) ',' char(sym(b)) ',0.0)']); -end diff --git a/matlab/symbolic/am_min.m b/matlab/symbolic/am_min.m deleted file mode 100644 index 3059eccaff..0000000000 --- a/matlab/symbolic/am_min.m +++ /dev/null @@ -1,11 +0,0 @@ -function fun = am_min(a, b) -% am_min is the amici implementation of the symbolic min function -% -% Parameters: -% a: first input parameter @type sym -% b: second input parameter @type sym -% -% Return values: -% fun: minimum of a and b -fun = -am_max(-a,-b); -end diff --git a/matlab/symbolic/am_or.m b/matlab/symbolic/am_or.m deleted file mode 100644 index 0c5d484232..0000000000 --- a/matlab/symbolic/am_or.m +++ /dev/null @@ -1,11 +0,0 @@ -function fun = am_or(a,b) -% am_or is the amici implementation of the symbolic or function -% -% Parameters: -% a: first input parameter @type sym -% b: second input parameter @type sym -% -% Return values: -% fun: logical value, negative for false, positive for true -fun = am_max(a,b); -end diff --git a/matlab/symbolic/am_piecewise.m b/matlab/symbolic/am_piecewise.m deleted file mode 100644 index 07b2f6205d..0000000000 --- a/matlab/symbolic/am_piecewise.m +++ /dev/null @@ -1,12 +0,0 @@ -function fun = am_piecewise( piece,condition,default ) -% am_piecewise is the amici implementation of the mathml piecewise function -% -% Parameters: -% piece: value if condition is true -% condition: logical value -% default: value if condition is false -% -% Return values: -% fun: return value, piece if condition is true, default if not - fun = am_if(condition,piece,default); -end diff --git a/matlab/symbolic/am_spline.m b/matlab/symbolic/am_spline.m deleted file mode 100644 index ff33784f66..0000000000 --- a/matlab/symbolic/am_spline.m +++ /dev/null @@ -1,14 +0,0 @@ -function splinefun = am_spline(varargin) - n = nargin; - str= ''; - if (round(n/2) - n/2 < 0.1) - error('Input arguments of am_spline must have the following form: t, t1, p2, ..., tn, pn, intss, dudt'); - end - for i = 1 : n-1 - str = strcat(strcat(str, char(varargin{i})), ','); - end - str = strcat('(',strcat(strcat(str, char(varargin{n})), ')')); - str = strrep(str, ' ', ''); - str = regexprep(str,'\,([0-9]*)\,','\,$1\.0\,'); % Black magic of sym - splinefun = sym(strcat('spline', str)); -end diff --git a/matlab/symbolic/am_spline_pos.m b/matlab/symbolic/am_spline_pos.m deleted file mode 100644 index de3f9e2c49..0000000000 --- a/matlab/symbolic/am_spline_pos.m +++ /dev/null @@ -1,33 +0,0 @@ -function splinefun = am_spline_pos(varargin) - n = nargin; - str= ''; - if (round(n/2) - n/2 > 0.1) - error('Input arguments of am_spline must have the following form: t, #ofNodes, t1, p1, ..., tn, pn, intss, dudt'); - end - for i = 1 : n - if (i < n) - switch class(varargin{i}) - case 'sym' - str = strcat(strcat(str, char(vpa(varargin{i}))), ','); - case 'double' - str = strcat(strcat(str, char(vpa(sym(varargin{i}))), ',')); - otherwise - error(['Input argument ' num2str(i) ' of the splinefunction seems to be neither a symbolic nor a double. Please check, if it is correctly defined.']); - end - else - switch class(varargin{i}) - case 'sym' - str = strcat(str, char(vpa(varargin{i}))); - case 'double' - str = strcat(str, char(vpa(sym(varargin{i})))); - otherwise - error(['Input argument ' num2str(i) ' of the splinefunction seems to be neither a symbolic nor a double. Please check, if it is correctly defined.']); - end - end - - end - str = strcat('(',strcat(strcat(str, char(varargin{n})), ')')); - str = strrep(str, ' ', ''); - - splinefun = sym(strcat('spline_pos', str)); -end diff --git a/matlab/symbolic/am_stepfun.m b/matlab/symbolic/am_stepfun.m deleted file mode 100644 index 3cc3052572..0000000000 --- a/matlab/symbolic/am_stepfun.m +++ /dev/null @@ -1,14 +0,0 @@ -function fun = am_stepfun(t,tstart,vstart,tend,vend) -% am_stepfun is the amici implementation of the step function -% -% Parameters: -% t: input variable @type sym -% tstart: input variable value at which the step starts -% vstart: value during the step -% tend: input variable value at which the step end -% vend: value after the step -% -% Return values: -% fun: 0 before tstart, vstart between tstart and tend and vend after tend -fun = heaviside(t-tstart)*vstart - heaviside(t-tend)*(vstart-vend); -end diff --git a/matlab/symbolic/am_xor.m b/matlab/symbolic/am_xor.m deleted file mode 100644 index 93514c6c50..0000000000 --- a/matlab/symbolic/am_xor.m +++ /dev/null @@ -1,12 +0,0 @@ -function fun = am_xor(a,b) -% am_xor is the amici implementation of the symbolic exclusive or function -% -% Parameters: -% a: first input parameter @type sym -% b: second input parameter @type sym -% -% Return values: -% fun: logical value, negative for false, positive for true - -fun = am_and(am_or(a,b),-am_and(a,b)); -end diff --git a/matlab/tests/SBMLTest.m b/matlab/tests/SBMLTest.m deleted file mode 100644 index a3e3c1f783..0000000000 --- a/matlab/tests/SBMLTest.m +++ /dev/null @@ -1,109 +0,0 @@ -% Run SBML tests from sbml-semantic-test-cases - -function runSBMLTests -exedir = fileparts(mfilename('fullpath')); -cd(exedir); -fid = fopen(['./SBMLTest_log_' date '.txt'],'w+'); - -for iTest = 21:1529 - cd(exedir); - testid = [repmat('0',1,4-floor(log10(iTest))),num2str(iTest)]; - if(~exist(fullfile(pwd,'SBMLresults',[testid '-results.csv']))) - try - runSBMLTest(iTest,fid); - catch error - fprintf(fid,['Test ' testid ' failed: ' error.message '\n']);; - end - end -end -fclose(fid); -end - - -function runSBMLTest(iTest,fid) -cd(fileparts(mfilename('fullpath'))) -curdir = pwd; -testid = [repmat('0',1,4-floor(log10(iTest))),num2str(iTest)]; -disp([' =================== ' testid ' =================== ']); -if(exist(fullfile(pwd,'sbml-semantic-test-cases','cases','semantic',testid),'dir')) - cd(fullfile(pwd,'sbml-semantic-test-cases','cases','semantic',testid)) - try - if(exist(fullfile(pwd,[testid '-sbml-l3v1.xml']),'file')) - SBML2AMICI([testid '-sbml-l3v1'],['SBMLTEST_' testid]) - elseif(exist(fullfile(pwd,[testid '-sbml-l2v4.xml']))) - SBML2AMICI([testid '-sbml-l2v4'],['SBMLTEST_' testid]) - else - return; - end - amiwrap(['SBMLTEST_' testid],['SBMLTEST_' testid '_syms'],pwd); - catch error - fprintf(fid,['Test ' testid ' failed: ' error.message '\n']);; - return - end - load(['SBMLTEST_' testid '_knom.mat']) - load(['SBMLTEST_' testid '_pnom.mat']) - load(['SBMLTEST_' testid '_vnom.mat']) - load(['SBMLTEST_' testid '_kvnom.mat']) - [t,settings,concflag] = parseSettings(testid); - options.sensi = 0; - options.maxsteps = 1e6; - eval(['sol = simulate_SBMLTEST_' testid '(t,pnom,knom,[],options);']) - results = readtable([testid '-results.csv']); - eval(['model = SBMLTEST_' testid '_syms;']) - adev = zeros(size(results{:,2:end})); - rdev = zeros(size(results{:,2:end})); - amiresults = results; - for ispecies = 2:length(results.Properties.VariableNames) - ix = find(logical(sym(results.Properties.VariableNames{ispecies})==model.sym.x)); - ik = find(logical(sym(results.Properties.VariableNames{ispecies})==model.sym.k)); - if(~isempty(ix)) - if(~concflag) - vol = vnom(ix); - vol = subs(vol,model.sym.k(:),sym(knom(:))); - vol = subs(vol,model.sym.p(:),sym(pnom(:))); - vol = double(vol); - else - vol = 1; - end - amiresults{:,ispecies} = sol.x(:,ix)*vol; - elseif(~isempty(ik)) - if(~concflag) - vol = kvnom(ik); - vol = subs(vol,model.sym.k(:),sym(knom(:))); - vol = subs(vol,model.sym.p(:),sym(pnom(:))); - vol = double(vol); - else - vol = 1; - end - amiresults{:,ispecies} = results{:,ispecies}*0 + knom(ik)*vol; - end - adev(:,ispecies-1) = abs(amiresults{:,ispecies}-results{:,ispecies}); - rdev(:,ispecies-1) = abs((amiresults{:,ispecies}-results{:,ispecies})./results{:,ispecies}); - end - rdev(isinf(rdev)) = 0; - assert(not(any(any(and(adev>settings.atol,rdev>settings.rtol))))) - cd(curdir) - mkdir(fullfile(pwd,'SBMLresults')) - writetable(amiresults,fullfile(pwd,'SBMLresults',[testid '-results.csv'])) - clear all -end -end - -function [t,options,concflag] = parseSettings(testid) -fid = fopen([testid '-settings.txt']); -T = textscan(fid,'%s %f'); -t = linspace(T{2}(1),T{2}(2),T{2}(3)+1); -tline = fgetl(fid); -T = textscan(fid,'%s %f'); -options.atol = T{2}(1); -options.rtol = T{2}(2); -tline = fgetl(fid); -tline = fgetl(fid); -if(~strcmp(tline,'concentration: ') && ~strcmp(tline,'concentration:')) - concflag = true; -else - concflag = false; -end -fclose(fid); - -end diff --git a/matlab/tests/testModels.m b/matlab/tests/testModels.m deleted file mode 100644 index 479dcda199..0000000000 --- a/matlab/tests/testModels.m +++ /dev/null @@ -1,214 +0,0 @@ -% Run AMICI Matlab tests using pre-generated models and test setup - -function testModels() - % disable specific warnings for these tests, some tests are supposed - % to produce warnings - warningreset = warning; - warning('off','AMICI:simulation') - warning('off','AMICI:CVODES:CVode:TOO_MUCH_WORK') - - % second-order currently not supported via rebuild_*.m (GitHub Actions) - % only via wrapTestModels - ignoredTests = {'/model_jakstat_adjoint/sensiadjointemptysensind', ... - '/model_jakstat_adjoint/sensiforwardemptysensind', ... - '/model_jakstat_adjoint/sensi2adjoint', ... - '/model_jakstat_adjoint/sensi2forward', ... - '/model_jakstat_adjoint/sensi2forwardlogparam', ... - '/model_neuron/sensi2forward'}; - - model_dir = [fileparts(mfilename('fullpath')) '/../../models/']; - cd(fileparts(mfilename('fullpath'))) - addpath(genpath('../../tests/cpp')); - addpath(genpath('../examples')); - % wrapTestModels() - - cd(fileparts(mfilename('fullpath'))) - hdf5file = fullfile(fileparts(mfilename('fullpath')), ... - '../../tests/cpp', 'expectedResults.h5'); - - info = h5info(hdf5file); - for imodel = 1:length(info.Groups) - modelname = info.Groups(imodel).Name(2:end); - - if(~isempty(regexp(modelname,'^model_neuron'))) - model_atol = 1e-9; - model_rtol = 1e-4; - else - model_atol = 1e-10; - model_rtol = 1e-5; - end - for itest = 1:length(info.Groups(imodel).Groups) - testname = info.Groups(imodel).Groups(itest).Name; - if(ismember(testname, ignoredTests)) - continue - end - - display(testname); - - [results,options,data,t,theta,kappa] = readDataFromHDF5(info.Groups(imodel).Groups(itest),hdf5file); - - % rebuild model - old_path = addpath([model_dir modelname]); - old_pwd = cd([model_dir modelname]); - rebuild = str2func(['rebuild_' modelname]); - rebuild(); - cd(old_pwd); - - sol = getResults(modelname,options,data,t,theta,kappa); - compareResults(sol,results); - path(old_path); - end - end - - warning(warningreset); - - %% begin nested functions - - function sol = getResults(modelname,options,data,t,theta,kappa) - theta = options.theta; - options = rmfield(options,'theta'); - kappa = options.kappa; - options = rmfield(options,'kappa'); - t = options.ts; - options = rmfield(options,'ts'); - if(isfield(options, 'sx0')) - options.sx0 = transpose(options.sx0); - end - ami_options = amioption(options); - if(~isempty(data)) - ami_data = amidata(data); - else - ami_data = []; - end - sol = feval(['simulate_' modelname],t,theta,kappa,ami_data,ami_options); - end - - function compareResults(sol,results) - if(results.status<0) - assert(sol.status<0) - return - end - - for ifield = transpose(fieldnames(sol)) - if(strcmp(ifield{1},'diagnosis')) - for jfield = transpose(fieldnames(sol.diagnosis)) - if(ismember(jfield{1},{'xdot', 'J'})) - checkAgreement(sol.diagnosis,results.diagnosis,jfield{1},0,1); - end - end - else - if(~ismember(ifield,{'chi2'})) - if(~isempty(sol.s2llh)) - if(ismember(ifield,{'s2llh'})) - checkAgreement(sol,results,ifield{1},1e-4,1e-3) - elseif(ismember(ifield,{'x','sx','s2x','y','sy','s2y','z','sz','s2z','rz','srz','s2rz','sigmay','ssigmay','s2sigmay','sigmaz','ssigmaz','s2sigmaz','x0','sx0'})) - % TODO: reimplement this as soon as #264 is done - else - checkAgreement(sol,results,ifield{1}) - end - else - checkAgreement(sol,results,ifield{1}) - end - end - end - end - end - - function checkAgreement(sol,results,fieldname,atol,rtol) - if(~isfield(results,fieldname)) - assert(isempty(sol.(fieldname))) - return - end - expected = results.(fieldname); - if strcmp(fieldname, 'sx0') - actual = transpose(sol.(fieldname)); - else - actual = sol.(fieldname); - end - if(nargin<4) - atol = model_atol; - end - if(nargin<5) - rtol = model_rtol; - end - if(~isempty(expected)) - assert(~isempty(actual)); - actual = actual(:); - expected = expected(:); - assert(all(isnan(actual)==isnan(expected))); - actual = actual(~isnan(actual)); - expected = expected(~isnan(actual)); - assert(all(isinf(actual)==isinf(expected))); - actual = actual(~isinf(actual)); - expected = expected(~isinf(actual)); - try - assert(all(abs(expected - actual) <= atol) || all(abs((expected - actual) ./ (rtol + abs(expected))) <= rtol)); - catch - warning(['The assertion for field ' fieldname ' failed!']); - end - end - end - - function [results,options,data,t,theta,kappa] = readDataFromHDF5(groups,hdf5file) - data = []; - t = []; - theta = []; - kappa = []; - for igroup = 1:length(groups.Groups) - group = groups.Groups(igroup); - [~,name] = fileparts(group.Name); - eval([name ' = hdf2struct(group,''' group.Name ''',hdf5file);']); - end - if(isfield(options, 'sens_ind')) - % adapt to base 1 indexing - options.sens_ind = options.sens_ind + 1; - end - if(~isempty(data)) - if(length(data.ts)~=size(data.Y,1)) % no idea why this goes wrong only _sometimes_ - data.Y = transpose(data.Y); - data.Sigma_Y = transpose(data.Sigma_Y); - end - if(isfield(data, 'Z') && size(data.Z,2) > 0) % don't ask ... - data.Z = transpose(data.Z); - data.Sigma_Z = transpose(data.Sigma_Z); - end - end - end - - - - function matlab = cpp2matlab(cpp) - dims = size(cpp); - if(sum(dims>1)>1 || length(dims)>2) - switch(length(dims)) - case 3 - matlab = permute(cpp,[3,1,2]); - case 2 - matlab = transpose(cpp); - otherwise - matlab = cpp; - end - else - matlab = cpp; - end - matlab = double(matlab); - end - - function s = hdf2struct(group,hdf5path,hdf5file) - s = struct; - if(~isempty(group.Attributes)) - for attr = {group.Attributes.Name}; - s.(attr{1}) = double(h5readatt(hdf5file,hdf5path,attr{1})); - end - end - if(~isempty(group.Datasets)) - for data = {group.Datasets.Name}; - s.(data{1}) = cpp2matlab(h5read(hdf5file,[hdf5path '/' data{1}])); - end - end - for igroup = 1:length(group.Groups); - [~,name] = fileparts(group.Groups(igroup).Name); - s.(name) = hdf2struct(group.Groups(igroup),[hdf5path '/' name],hdf5file); - end - end -end diff --git a/scripts/downloadAndBuildMtocpp.sh b/scripts/downloadAndBuildMtocpp.sh deleted file mode 100755 index 692936442e..0000000000 --- a/scripts/downloadAndBuildMtocpp.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -# Download and build mtocpp (Doxygen filter for Matlab) - -set -euo pipefail -set -x - -SCRIPT_PATH=$(dirname $BASH_SOURCE) -AMICI_PATH=$(cd "$SCRIPT_PATH/.." && pwd) - -MTOC_CONFIG_PATH=${AMICI_PATH}/matlab/mtoc/config - -cd "${AMICI_PATH}/ThirdParty" - -# download mtocpp? -if [ ! -d "mtocpp-master" ]; then - # mtocpp requires ragel - if ! command -v ragel &> /dev/null; then - echo "ragel not found" - - if [ ! -d "ragel-6.10" ]; then - if [ ! -e "ragel-6.10.tar.gz" ]; then - echo "Downloading ragel ..." - wget -O ragel-6.10.tar.gz http://www.colm.net/files/ragel/ragel-6.10.tar.gz - fi - # build ragel? - tar -xzf ragel-6.10.tar.gz - (cd ragel-6.10 && ./configure --prefix="$(pwd)/install" && make -j2 && make install) - fi - export PATH=$PATH:$(pwd)/ragel-6.10/install/bin - fi - - if [ ! -e "mtocpp-master.zip" ]; then - echo "Downloading mtocpp ..." - wget -O mtocpp-master.zip https://github.com/mdrohmann/mtocpp/archive/master.zip - fi - # build mtocpp? - unzip mtocpp-master.zip - - # patch for xml support for postprocessor - sed -i.bak 's/== "tex"$/== "tex" || file.substr(file.find_last_of(".") + 1) == "xml"/' mtocpp-master/src/postprocess.rl - - mkdir -p mtocpp-master/build - - if command -v cmake &> /dev/null; then - echo "Building mtocpp using CMake..." - cd mtocpp-master/build && cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && make mtocpp mtocpp_post - if [ $? -ne 0 ] ; then - exit 1 - fi - else - # No CMake on ReadTheDocs :( - echo "Building mtocpp without CMake..." - cd mtocpp-master/src - sed 's/@MTOC++_VERSION_MAJOR@/1/;s/@MTOC++_VERSION_MINOR@/5/' < config.h.in > config.h - ragel -C -T0 -o confscanner.cc confscanner.rl - ragel -C -T0 -o mfilescanner_parser.cc mfilescanner_parser.rl - c++ -I"$(pwd)" -o mtocpp.cc.o -c mtocpp.cc - c++ -I"$(pwd)" -o mfilescanner_parser.cc.o -c mfilescanner_parser.cc - c++ -I"$(pwd)" -o mfilescanner.cc.o -c mfilescanner.cc - c++ -I"$(pwd)" -o confscanner.cc.o -c confscanner.cc - c++ -rdynamic mtocpp.cc.o mfilescanner_parser.cc.o mfilescanner.cc.o confscanner.cc.o -o mtocpp - ragel -C -T0 -o postprocess.cc postprocess.rl - c++ -I"$(pwd)" -o postprocess.cc.o -c postprocess.cc - c++ -rdynamic postprocess.cc.o -o mtocpp_post - cd ../build/ - ln -s ../src/mtocpp mtocpp - ln -s ../src/mtocpp_post mtocpp_post - fi -fi - -# generate filter -echo "$AMICI_PATH/ThirdParty/mtocpp-master/build/mtocpp \$1 ${MTOC_CONFIG_PATH}/mtocpp.conf" > "${MTOC_CONFIG_PATH}/mtocpp_filter.sh" -chmod +x "${MTOC_CONFIG_PATH}/mtocpp_filter.sh" diff --git a/scripts/run-doxygen.sh b/scripts/run-doxygen.sh index 3b11b7e74a..f77457ff96 100755 --- a/scripts/run-doxygen.sh +++ b/scripts/run-doxygen.sh @@ -9,43 +9,31 @@ AMICI_PATH=$(cd $SCRIPT_PATH/.. && pwd) OUTDIR="${AMICI_PATH}/doc/build_doxygen" mkdir -p "$OUTDIR" -# Set up Matlab filter -cd ${AMICI_PATH} -scripts/downloadAndBuildMtocpp.sh -MTOC_CONFIG_PATH=${AMICI_PATH}/matlab/mtoc/config +cd "${AMICI_PATH}" # Generate doxyfile -DOXYFILE="${MTOC_CONFIG_PATH}/Doxyfile" -cp "${MTOC_CONFIG_PATH}/Doxyfile.template" "${DOXYFILE}" -DOXY_WARNING_FILE=${AMICI_PATH}/matlab/mtoc/warnings.log +DOXYFILE="${AMICI_PATH}/doc/Doxyfile" +cp "${AMICI_PATH}/doc/Doxyfile.template" "${DOXYFILE}" +DOXY_WARNING_FILE=${AMICI_PATH}/doc/warnings.log # Replace some template values sed -i -e "s#_OutputDir_#$OUTDIR#g" ${DOXYFILE} sed -i -e "s#_SourceDir_#$AMICI_PATH#g" ${DOXYFILE} -sed -i -e "s#_ConfDir_#${MTOC_CONFIG_PATH}#g" ${DOXYFILE} +sed -i -e "s#_ConfDir_#${AMICI_PATH}/doc#g" ${DOXYFILE} sed -i -e "s#_ProjectName_#AMICI#g" ${DOXYFILE} sed -i -e "s#_ProjectDescription_#Advanced Multilanguage Interface for CVODES and IDAS#g" ${DOXYFILE} sed -i -e "s#_ProjectLogo_##g" ${DOXYFILE} sed -i -e "s#_ProjectVersion_##g" ${DOXYFILE} -sed -i -e "s#_MTOCFILTER_#${MTOC_CONFIG_PATH}/mtocpp_filter.sh#g" ${DOXYFILE} -sed -i -e "s#_LatexExtras_#${MTOC_CONFIG_PATH}/latexextras#g" ${DOXYFILE} -sed -i -e "s#_GenLatex_#YES#g" ${DOXYFILE} +sed -i -e "s#_GenLatex_#NO#g" ${DOXYFILE} sed -i -e "s#_HaveDot_#YES#g" ${DOXYFILE} # Fail if no replace was made sed -i -re "/WARN_LOGFILE(\s*)=.*/{s##WARN_LOGFILE\1= ${DOXY_WARNING_FILE}#g;h};\${x;/./{x;q0};x;q1}" ${DOXYFILE} -# Generate latexextras -cp ${MTOC_CONFIG_PATH}/latexextras.template ${MTOC_CONFIG_PATH}/latexextras.sty -sed -i -e "s#_ConfDir_#${MTOC_CONFIG_PATH}#g" ${MTOC_CONFIG_PATH}/latexextras.sty -export PATH=/Library/TeX/texbin:$PATH - # Run doxygen doxygen "${DOXYFILE}" #cleanup -#rm ${AMICI_PATH}/mtoc/config/latexextras.sty rm "${DOXYFILE}" -rm "${MTOC_CONFIG_PATH}/mtocpp_filter.sh" # Build pdf #cd "${OUTDIR}/latex" diff --git a/src/returndata_matlab.cpp b/src/returndata_matlab.cpp deleted file mode 100644 index 296b41940e..0000000000 --- a/src/returndata_matlab.cpp +++ /dev/null @@ -1,471 +0,0 @@ -#include "amici/returndata_matlab.h" -#include "amici/defines.h" -#include "amici/exception.h" - -namespace amici { - -mxArray* getReturnDataMatlabFromAmiciCall(ReturnData const* rdata) { - mxArray* matlabSolutionStruct = initMatlabReturnFields(rdata); - return matlabSolutionStruct; -} - -mxArray* initMatlabReturnFields(ReturnData const* rdata) { - int const numFields = 22; - char const* field_names_sol[numFields] - = {"status", "llh", "sllh", "s2llh", "chi2", "t", - "x", "sx", "y", "sy", "sigmay", "ssigmay", - "z", "sz", "sigmaz", "ssigmaz", "rz", "srz", - "s2rz", "x0", "sx0", "diagnosis"}; - - checkFieldNames(field_names_sol, numFields); - - mxArray* matlabSolutionStruct - = mxCreateStructMatrix(1, 1, numFields, field_names_sol); - - std::vector perm0 = {1, 0}; - std::vector perm1 = {0, 1}; - std::vector perm2 = {0, 2, 1}; - std::vector perm3 = {0, 2, 3, 1}; - - writeMatlabField0(matlabSolutionStruct, "status", rdata->status); - - writeMatlabField1( - matlabSolutionStruct, "t", gsl::make_span(rdata->ts), rdata->nt - ); - writeMatlabField0(matlabSolutionStruct, "llh", rdata->llh); - writeMatlabField0(matlabSolutionStruct, "chi2", rdata->chi2); - - if ((rdata->nz > 0) & (rdata->ne > 0)) { - writeMatlabField2( - matlabSolutionStruct, "z", rdata->z, rdata->nmaxevent, rdata->nz, - perm1 - ); - writeMatlabField2( - matlabSolutionStruct, "rz", rdata->rz, rdata->nmaxevent, rdata->nz, - perm1 - ); - writeMatlabField2( - matlabSolutionStruct, "sigmaz", rdata->sigmaz, rdata->nmaxevent, - rdata->nz, perm1 - ); - } - if (rdata->nx > 0) { - writeMatlabField2( - matlabSolutionStruct, "x", rdata->x, rdata->nt, rdata->nx, perm1 - ); - writeMatlabField2( - matlabSolutionStruct, "x0", rdata->x0, rdata->nx, 1, perm1 - ); - } - if (rdata->ny > 0) { - writeMatlabField2( - matlabSolutionStruct, "y", rdata->y, rdata->nt, rdata->ny, perm1 - ); - writeMatlabField2( - matlabSolutionStruct, "sigmay", rdata->sigmay, rdata->nt, rdata->ny, - perm1 - ); - } - if (rdata->sensi >= SensitivityOrder::first) { - writeMatlabField1( - matlabSolutionStruct, "sllh", gsl::make_span(rdata->sllh), - rdata->nplist - ); - writeMatlabField2( - matlabSolutionStruct, "sx0", rdata->sx0, rdata->nplist, rdata->nx, - perm0 - ); - - if (rdata->sensi_meth == SensitivityMethod::forward) { - writeMatlabField3( - matlabSolutionStruct, "sx", rdata->sx, rdata->nt, rdata->nplist, - rdata->nx, perm2 - ); - if (rdata->ny > 0) { - writeMatlabField3( - matlabSolutionStruct, "sy", rdata->sy, rdata->nt, - rdata->nplist, rdata->ny, perm2 - ); - } - if ((rdata->nz > 0) & (rdata->ne > 0)) { - writeMatlabField3( - matlabSolutionStruct, "srz", rdata->srz, rdata->nmaxevent, - rdata->nplist, rdata->nz, perm2 - ); - if (rdata->sensi >= SensitivityOrder::second) { - writeMatlabField4( - matlabSolutionStruct, "s2rz", rdata->s2rz, - rdata->nmaxevent, rdata->nplist, rdata->nztrue, - rdata->nplist, perm3 - ); - } - writeMatlabField3( - matlabSolutionStruct, "sz", rdata->sz, rdata->nmaxevent, - rdata->nplist, rdata->nz, perm2 - ); - } - } - - if (!rdata->ssigmay.empty()) { - writeMatlabField3( - matlabSolutionStruct, "ssigmay", rdata->ssigmay, rdata->nt, - rdata->nplist, rdata->ny, perm2 - ); - } - if ((rdata->nz > 0) & (rdata->ne > 0)) { - writeMatlabField3( - matlabSolutionStruct, "ssigmaz", rdata->ssigmaz, - rdata->nmaxevent, rdata->nplist, rdata->nz, perm2 - ); - } - - if (rdata->sensi >= SensitivityOrder::second) { - writeMatlabField2( - matlabSolutionStruct, "s2llh", rdata->s2llh, rdata->nplist, - rdata->nJ - 1, perm1 - ); - } - } - - mxArray* diagnosis = initMatlabDiagnosisFields(rdata); - mxSetField(matlabSolutionStruct, 0, "diagnosis", diagnosis); - - return (matlabSolutionStruct); -} - -mxArray* initMatlabDiagnosisFields(ReturnData const* rdata) { - int const numFields = 25; - char const* field_names_sol[numFields] - = {"xdot", - "J", - "numsteps", - "numrhsevals", - "numerrtestfails", - "numnonlinsolvconvfails", - "order", - "numstepsB", - "numrhsevalsB", - "numerrtestfailsB", - "numnonlinsolvconvfailsB", - "preeq_status", - "preeq_numsteps", - "preeq_numstepsB", - "preeq_cpu_time", - "preeq_cpu_timeB", - "preeq_t", - "preeq_wrms", - "posteq_status", - "posteq_numsteps", - "posteq_numstepsB", - "posteq_cpu_time", - "posteq_cpu_timeB", - "posteq_t", - "posteq_wrms"}; - - checkFieldNames(field_names_sol, numFields); - - mxArray* matlabDiagnosisStruct - = mxCreateStructMatrix(1, 1, numFields, field_names_sol); - - std::vector perm1 = {0, 1}; - int finite_nt = 0; - for (int it = 0; it < rdata->nt; it++) - if (!std::isinf(rdata->ts[it])) - finite_nt++; - - writeMatlabField1( - matlabDiagnosisStruct, "numsteps", - gsl::make_span(rdata->numsteps).subspan(0, finite_nt), finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "numrhsevals", - gsl::make_span(rdata->numrhsevals).subspan(0, finite_nt), finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "numerrtestfails", - gsl::make_span(rdata->numerrtestfails).subspan(0, finite_nt), finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "numnonlinsolvconvfails", - gsl::make_span(rdata->numnonlinsolvconvfails).subspan(0, finite_nt), - finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "order", - gsl::make_span(rdata->order).subspan(0, finite_nt), finite_nt - ); - - if (rdata->nx > 0) { - writeMatlabField1( - matlabDiagnosisStruct, "xdot", gsl::make_span(rdata->xdot), - rdata->nx_solver - ); - writeMatlabField2( - matlabDiagnosisStruct, "J", rdata->J, rdata->nx_solver, - rdata->nx_solver, perm1 - ); - - writeMatlabField1( - matlabDiagnosisStruct, "preeq_status", - gsl::make_span(rdata->preeq_status), 3 - ); - writeMatlabField1( - matlabDiagnosisStruct, "preeq_numsteps", - gsl::make_span(rdata->preeq_numsteps), 3 - ); - writeMatlabField0( - matlabDiagnosisStruct, "preeq_numstepsB", rdata->preeq_numstepsB - ); - writeMatlabField0( - matlabDiagnosisStruct, "preeq_cpu_time", rdata->preeq_cpu_time - ); - writeMatlabField0( - matlabDiagnosisStruct, "preeq_cpu_timeB", rdata->preeq_cpu_timeB - ); - writeMatlabField0(matlabDiagnosisStruct, "preeq_t", rdata->preeq_t); - writeMatlabField0( - matlabDiagnosisStruct, "preeq_wrms", rdata->preeq_wrms - ); - - writeMatlabField1( - matlabDiagnosisStruct, "posteq_status", - gsl::make_span(rdata->posteq_status), 3 - ); - writeMatlabField1( - matlabDiagnosisStruct, "posteq_numsteps", - gsl::make_span(rdata->posteq_numsteps), 3 - ); - writeMatlabField0( - matlabDiagnosisStruct, "posteq_numstepsB", rdata->posteq_numstepsB - ); - writeMatlabField0( - matlabDiagnosisStruct, "posteq_cpu_time", rdata->posteq_cpu_time - ); - writeMatlabField0( - matlabDiagnosisStruct, "posteq_cpu_timeB", rdata->posteq_cpu_timeB - ); - writeMatlabField0(matlabDiagnosisStruct, "posteq_t", rdata->posteq_t); - writeMatlabField0( - matlabDiagnosisStruct, "posteq_wrms", rdata->posteq_wrms - ); - } - if (rdata->sensi >= SensitivityOrder::first) { - if (rdata->sensi_meth == SensitivityMethod::adjoint) { - writeMatlabField1( - matlabDiagnosisStruct, "numstepsB", - gsl::make_span(rdata->numstepsB).subspan(0, finite_nt), - finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "numrhsevalsB", - gsl::make_span(rdata->numrhsevalsB).subspan(0, finite_nt), - finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "numerrtestfailsB", - gsl::make_span(rdata->numerrtestfailsB).subspan(0, finite_nt), - finite_nt - ); - writeMatlabField1( - matlabDiagnosisStruct, "numnonlinsolvconvfailsB", - gsl::make_span(rdata->numnonlinsolvconvfailsB) - .subspan(0, finite_nt), - finite_nt - ); - } - } - - return (matlabDiagnosisStruct); -} - -template -void writeMatlabField0( - mxArray* matlabStruct, char const* fieldName, T fieldData -) { - - std::vector dim = {(mwSize)(1), (mwSize)(1)}; - - double* array = initAndAttachArray(matlabStruct, fieldName, dim); - - array[0] = static_cast(fieldData); -} - -template -void writeMatlabField1( - mxArray* matlabStruct, char const* fieldName, - gsl::span const& fieldData, mwSize dim0 -) { - if (fieldData.size() != dim0) - throw AmiException( - "Dimension mismatch when writing rdata->%s to " - "matlab results (expected %d, got %d)", - fieldName, dim0, static_cast(fieldData.size()) - ); - - std::vector dim = {(mwSize)(dim0), (mwSize)(1)}; - - double* array = initAndAttachArray(matlabStruct, fieldName, dim); - - auto data_ptr = fieldData.data(); - for (mwSize i = 0; i < dim0; i++) - array[i] = static_cast(data_ptr[i]); -} - -template -void writeMatlabField2( - mxArray* matlabStruct, char const* fieldName, - std::vector const& fieldData, mwSize dim0, mwSize dim1, - std::vector perm -) { - if (fieldData.size() != dim0 * dim1) - throw AmiException( - "Dimension mismatch when writing rdata->%s to " - "matlab results (expected: %d, actual: %d)", - fieldName, dim0 * dim1, static_cast(fieldData.size()) - ); - - if (perm.size() != 2) - throw AmiException("Dimension mismatch when applying permutation!"); - - std::vector dim = {dim0, dim1}; - - double* array - = initAndAttachArray(matlabStruct, fieldName, reorder(dim, perm)); - - std::vector index = {0, 0}; - /* transform rowmajor (c++) to colmajor (matlab) and apply permutation */ - for (index[0] = 0; index[0] < dim[0]; index[0]++) { - for (index[1] = 0; index[1] < dim[1]; index[1]++) { - array[index[perm[0]] + index[perm[1]] * dim[perm[0]]] - = static_cast(fieldData[index[0] * dim[1] + index[1]]); - } - } -} - -template -void writeMatlabField3( - mxArray* matlabStruct, char const* fieldName, - std::vector const& fieldData, mwSize dim0, mwSize dim1, mwSize dim2, - std::vector perm -) { - if (fieldData.size() != dim0 * dim1 * dim2) - throw AmiException( - "Dimension mismatch when writing rdata->%s to matlab results", - fieldName - ); - - if (perm.size() != 3) - throw AmiException("Dimension mismatch when applying permutation!"); - - std::vector dim = {(mwSize)(dim0), (mwSize)(dim1), (mwSize)(dim2)}; - - double* array - = initAndAttachArray(matlabStruct, fieldName, reorder(dim, perm)); - - std::vector index = {0, 0, 0}; - /* transform rowmajor (c++) to colmajor (matlab) and apply permutation */ - for (index[0] = 0; index[0] < dim[0]; index[0]++) { - for (index[1] = 0; index[1] < dim[1]; index[1]++) { - for (index[2] = 0; index[2] < dim[2]; index[2]++) { - array - [index[perm[0]] - + (index[perm[1]] + index[perm[2]] * dim[perm[1]]) - * dim[perm[0]]] - = static_cast( - fieldData - [(index[0] * dim[1] + index[1]) * dim[2] + index[2]] - ); - } - } - } -} - -template -void writeMatlabField4( - mxArray* matlabStruct, char const* fieldName, - std::vector const& fieldData, mwSize dim0, mwSize dim1, mwSize dim2, - mwSize dim3, std::vector perm -) { - if (fieldData.size() != dim0 * dim1 * dim2 * dim3) - throw AmiException( - "Dimension mismatch when writing rdata->%s to matlab results!", - fieldName - ); - - if (perm.size() != 4) - throw AmiException("Dimension mismatch when applying permutation!"); - - std::vector dim - = {(mwSize)(dim0), (mwSize)(dim1), (mwSize)(dim2), (mwSize)(dim3)}; - - double* array - = initAndAttachArray(matlabStruct, fieldName, reorder(dim, perm)); - - std::vector index = {0, 0, 0, 0}; - /* transform rowmajor (c++) to colmajor (matlab) and apply permutation */ - for (index[0] = 0; index[0] < dim[0]; index[0]++) { - for (index[1] = 0; index[1] < dim[1]; index[1]++) { - for (index[2] = 0; index[2] < dim[2]; index[2]++) { - for (index[3] = 0; index[3] < dim[3]; index[3]++) { - array - [index[perm[0]] - + (index[perm[1]] - + (index[perm[2]] + index[perm[3]] * dim[perm[2]]) - * dim[perm[1]]) - * dim[perm[0]]] - = static_cast( - fieldData - [((index[0] * dim[1] + index[1]) * dim[2] - + index[2]) - * dim[3] - + index[3]] - ); - } - } - } - } -} - -double* initAndAttachArray( - mxArray* matlabStruct, char const* fieldName, std::vector dim -) { - if (!mxIsStruct(matlabStruct)) - throw AmiException( - "Passing non-struct mxArray to initAndAttachArray!", fieldName - ); - - int fieldNumber = mxGetFieldNumber(matlabStruct, fieldName); - if (fieldNumber < 0) - throw AmiException( - "Trying to access non-existent field '%s'!", fieldName - ); - - mxArray* array - = mxCreateNumericArray(dim.size(), dim.data(), mxDOUBLE_CLASS, mxREAL); - mxSetFieldByNumber(matlabStruct, 0, fieldNumber, array); - return (mxGetPr(array)); -} - -void checkFieldNames(char const** fieldNames, int const fieldCount) { - for (int ifield = 0; ifield < fieldCount; ifield++) { - if (!fieldNames[ifield]) - throw AmiException( - "Incorrect field name allocation, number of " - "fields is smaller than fieldCount!" - ); - } -} - -template -std::vector -reorder(std::vector const& input, std::vector const& order) { - if (order.size() != input.size()) - throw AmiException("Input dimension mismatch!"); - std::vector reordered; - reordered.resize(input.size()); - for (std::vector::size_type i = 0; i < input.size(); i++) - reordered[i] = input[order[i]]; - return reordered; -} - -} // namespace amici From 2d548670276523ec112bb989efc6018d096ec745 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 1 Oct 2025 15:31:29 +0200 Subject: [PATCH 2/2] more --- CHANGELOG.md | 9 + CMakeLists.txt | 2 - README.md | 24 +- doc/AGENTS.md | 3 +- doc/CI.md | 18 +- doc/about.rst | 44 +- doc/conf.py | 3 - include/amici/interface_matlab.h | 72 ---- include/amici/spline.h | 23 - include/amici/symbolic_functions.h | 115 ----- python/sdist/amici/de_export.py | 36 -- src/interface_matlab.cpp | 649 ----------------------------- src/spline.cpp | 297 ------------- src/symbolic_functions.cpp | 229 ---------- tests/cpp/wrapTestModels.m | 108 ----- 15 files changed, 32 insertions(+), 1600 deletions(-) delete mode 100644 include/amici/interface_matlab.h delete mode 100644 include/amici/spline.h delete mode 100644 src/interface_matlab.cpp delete mode 100644 src/spline.cpp delete mode 100644 tests/cpp/wrapTestModels.m diff --git a/CHANGELOG.md b/CHANGELOG.md index a84ed524f3..38807fd6f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ See also our [versioning policy](https://amici.readthedocs.io/en/latest/versioning_policy.html). +## v1.X Series + +### v1.0.0 (unreleased) + +BREAKING CHANGES + +* The MATLAB interface has been removed. + + ## v0.X Series ### v0.34.1 (2025-08-25) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a973b3309..e94d2cd7c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,7 +172,6 @@ set(AMICI_SRC_LIST src/edata.cpp src/exception.cpp src/simulation_parameters.cpp - src/spline.cpp src/solver.cpp src/solver_cvodes.cpp src/solver_idas.cpp @@ -213,7 +212,6 @@ set(AMICI_SRC_LIST include/amici/solver_cvodes.h include/amici/solver.h include/amici/solver_idas.h - include/amici/spline.h include/amici/splinefunctions.h include/amici/steadystateproblem.h include/amici/sundials_linsol_wrapper.h diff --git a/README.md b/README.md index ec8dd4339d..2fcd8e36ea 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## About -AMICI provides a multi-language (Python, C++, Matlab) interface for the +AMICI provides a Python and C++ interface for the [SUNDIALS](https://computing.llnl.gov/projects/sundials/) solvers [CVODES](https://computing.llnl.gov/projects/sundials/cvodes) (for ordinary differential equations) and @@ -12,13 +12,12 @@ AMICI provides a multi-language (Python, C++, Matlab) interface for the (for algebraic differential equations). AMICI allows the user to read differential equation models specified as [SBML](http://sbml.org/) or [PySB](http://pysb.org/) -and automatically compiles such models into Python modules, C++ libraries or -Matlab `.mex` simulation files. +and automatically compiles such models into Python modules or C++ libraries. The generated model expressions along with the corresponding sensitivity equations are transformed into native C++ code which allows for a significantly faster simulation. -**NOTE: The MATLAB interface is no longer supported and will be removed soon.** +**NOTE: The former MATLAB interface has been removed in AMICI 1.0.** Beyond forward integration, the compiled simulation file also allows for forward sensitivity analysis, steady state sensitivity analysis and @@ -53,7 +52,7 @@ constrained optimization problems. * Generation of C++ code for model simulation and sensitivity computation * Access to and high customizability of CVODES and IDAS solver -* Python, C++, Matlab interface +* Python and C++ interface * Sensitivity analysis * forward * steady state @@ -66,11 +65,10 @@ constrained optimization problems. ## Interfaces & workflow The AMICI workflow starts with importing a model from either -[SBML](http://sbml.org/) (Matlab, Python), [PySB](http://pysb.org/) (Python), -or a Matlab definition of the model (Matlab-only). From this input, -all equations for model simulation +[SBML](http://sbml.org/) (Python) or [PySB](http://pysb.org/) (Python). +From this input, all equations for model simulation are derived symbolically and C++ code is generated. This code is then -compiled into a C++ library, a Python module, or a Matlab `.mex` file and +compiled into a plain C++ library or a Python module, and is then used for model simulation. ![AMICI workflow](https://raw.githubusercontent.com/AMICI-dev/AMICI/main/doc/gfx/amici_workflow.png) @@ -79,9 +77,8 @@ is then used for model simulation. The AMICI source code is available at https://github.com/AMICI-dev/AMICI/. To install AMICI, first read the installation instructions for -[Python](https://amici.readthedocs.io/en/latest/python_installation.html), -[C++](https://amici.readthedocs.io/en/latest/cpp_installation.html) or -[Matlab](https://amici.readthedocs.io/en/latest/matlab_installation.html). +[Python](https://amici.readthedocs.io/en/latest/python_installation.html) or +[C++](https://amici.readthedocs.io/en/latest/cpp_installation.html). There are also instructions for using AMICI inside [containers](https://github.com/AMICI-dev/AMICI/tree/main/container). @@ -89,9 +86,6 @@ To get you started with Python-AMICI, the best way might be checking out this [Jupyter notebook](https://github.com/AMICI-dev/AMICI/blob/main/doc/examples/getting_started/GettingStarted.ipynb) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/AMICI-dev/AMICI/main?labpath=doc%2Fexamples%2Fgetting_started%2FGettingStarted.ipynb). -To get started with Matlab-AMICI, various examples are available -in [matlab/examples/](https://github.com/AMICI-dev/AMICI/tree/main/matlab/examples). - Comprehensive documentation is available at [https://amici.readthedocs.io/en/latest/](https://amici.readthedocs.io/en/latest/). diff --git a/doc/AGENTS.md b/doc/AGENTS.md index 71905769ac..ae92f4334b 100644 --- a/doc/AGENTS.md +++ b/doc/AGENTS.md @@ -4,14 +4,13 @@ This Agents.md file provides comprehensive guidance for OpenAI Codex and other A ## Project Structure for OpenAI Codex Navigation -AMICI is a python package that uses SWIG to generate python bindings to C++ code. There are also a deprecated matlab interface to the C++ code, which will be removed at some point in the future. +AMICI is a python package that uses SWIG to generate python bindings to C++ code. - `/binder`: binder configuration - `/cmake`: various cmake utility functions - `/container`: docker configuration - `/doc`: high level documentation, all API documentation is automatically generated using doxygen/sphinx - `/include`: C++ header files -- `/matlab`: matlab interface - `/models`: pre-generated c++ models for testing - `/python`: python source code - `/benchmark`: helper scripts for benchmarking diff --git a/doc/CI.md b/doc/CI.md index e7d7dc71aa..00e34abfdc 100644 --- a/doc/CI.md +++ b/doc/CI.md @@ -49,12 +49,6 @@ To run the SBML Test Suite test cases, the easiest way is: Once the test cases are available locally, for debugging it might be easier to directly use `pytest` with `tests/sbml/testSBMLSuite.py`. - -## Matlab tests (not included in CI pipeline) - -To execute the Matlab test suite, run `tests/testModels.m`. - - ## Model simulation integration tests Many of our integration tests are model simulations. The simulation results @@ -69,17 +63,7 @@ This code is to be updated whenever `amici::Model` changes. ### Regenerating C++ code of the test models -Regeneration of the model code has to be done whenever `amici::Model` or -the Matlab model import routines change. - -This is done with - - tests/cpp/wrapTestModels.m - -**Note:** This is currently only possible from Matlab < R2018a. This should -change as soon as 1) all second-order sensitivity code is ported to C++/Python, -2) a non-SBML import exists for Python and 3) support for events has been added -for Python. +*To be updated* ### Regenerating expected results diff --git a/doc/about.rst b/doc/about.rst index a52a79e521..6d3298eb32 100644 --- a/doc/about.rst +++ b/doc/about.rst @@ -2,17 +2,13 @@ About AMICI =========== -AMICI provides a multi-language (Python, C++, Matlab) interface to the +AMICI provides a Python and C++ interface to the :term:`SUNDIALS` solvers :term:`CVODES` (for :term:`ODE`\ s) and :term:`IDAS` (for :term:`DAE`\ s). AMICI allows the user to read differential equation models specified as :term:`SBML` or :term:`PySB` -and automatically compiles such models into Python modules, C++ libraries or -`.mex` simulation files (Matlab). - -In contrast to the (no longer maintained) -`sundialsTB `_ -Matlab interface, all necessary functions are transformed into native -C++ code, which allows for a significantly faster simulation. +and automatically compiles such models into Python modules or C++ libraries. +All necessary functions are transformed into native C++ code, which allows for +efficient simulation. Beyond forward integration, the compiled simulation file also allows for forward sensitivity analysis, steady state sensitivity analysis and @@ -31,13 +27,12 @@ Features * Generation of C++ code for model simulation and sensitivity computation * Access to and high customizability of :term:`CVODES` and :term:`IDAS` solver -* Python, C++, Matlab interface -* Sensitivity analysis +* Python and C++ interface +* Sensitivity analysis (first-order) * forward * steady state * adjoint - * first- and second-order (second-order Matlab-only) * :term:`Pre-equilibration ` and :term:`pre-simulation` conditions @@ -48,16 +43,15 @@ Interfaces & workflow ====================== The AMICI workflow starts with importing a model from either :term:`SBML` -(Matlab, Python), :term:`PySB` (Python), or a Matlab definition of the model -(Matlab-only). From this input, all equations for model simulation are derived -symbolically and C++ code is generated. This code is then compiled into a C++ -library, a Python module, or a Matlab `.mex` file and is then used for model -simulation. +(Python) or :term:`PySB` (Python). +From this input, all equations for model simulation are derived +symbolically and C++ code is generated. This code is then compiled into a plain +C++ library or a Python module, and is then used for model simulation. .. image:: gfx/amici_workflow.png :alt: AMICI workflow -The functionality of the Python, Matlab and C++ interfaces slightly differ, +The functionality of the Python and C++ interfaces differ, as shown in the following table: .. list-table:: @@ -67,44 +61,30 @@ as shown in the following table: * - Feature \\ Interface - Python - C++ - - Matlab * - :term:`SBML` import - yes (:ref:`details `) - no - - yes (<=R2017b) * - :term:`PySB` import - yes - no - - no * - :term:`DAE` import - - no - - no - yes + - no * - Forward sensitivities - yes - yes - - yes * - Adjoint sensitivities - yes - yes - - yes * - Steadystate sensitivities - yes - yes - - yes - * - Second-order sensitivities - - no - - no - - yes * - Events - yes - yes - - yes * - :term:`pre-equilibration` - yes - yes - - yes * - :term:`pre-simulation` - yes - yes - - no diff --git a/doc/conf.py b/doc/conf.py index 11c15dc949..78e8534768 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -241,9 +241,6 @@ def install_doxygen(): "INPUT = ../include/amici", "BUILTIN_STL_SUPPORT = YES", "PREDEFINED += EXHALE_DOXYGEN_SHOULD_SKIP_THIS", - "EXCLUDE += ../include/amici/interface_matlab.h", - "EXCLUDE += ../include/amici/returndata_matlab.h", - "EXCLUDE += ../include/amici/spline.h", # amici::log collides with amici::${some_enum}::log # potentially fixed in # https://github.com/svenevs/exhale/commit/c924df2e139a09fbacd07587779c55fd0ee4e00b diff --git a/include/amici/interface_matlab.h b/include/amici/interface_matlab.h deleted file mode 100644 index 21fd89c412..0000000000 --- a/include/amici/interface_matlab.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef AMICI_INTERFACE_MATLAB_H -#define AMICI_INTERFACE_MATLAB_H - -#include - -#include -#include - -namespace amici { - -class Model; -class Solver; -class ReturnDataMatlab; -class ExpData; - -namespace generic_model { -extern std::unique_ptr getModel(); -} // namespace generic_model - -/** - * @brief setModelData sets data from the matlab call to the model object - * @param prhs: pointer to the array of input arguments - * @param nrhs: number of elements in prhs - * @param model: model to update - */ -void setModelData(mxArray const* prhs[], int nrhs, Model& model); - -/** - * @brief setSolverOptions solver options from the matlab call to a solver - * object - * @param prhs: pointer to the array of input arguments - * @param nrhs: number of elements in prhs - * @param solver: solver to update - */ -void setSolverOptions(mxArray const* prhs[], int nrhs, Solver& solver); - -/** - * @brief setupReturnData initialises the return data struct - * @param plhs user input - * @param nlhs number of elements in plhs - * @return rdata: return data struct - */ -ReturnDataMatlab* setupReturnData(mxArray* plhs[], int nlhs); - -/*! - * @brief expDataFromMatlabCall parses the experimental data from the matlab - * call and writes it to an ExpData class object - * - * @param prhs pointer to the array of input arguments - * @param model pointer to the model object, this is necessary to perform - * dimension checks - * @return edata pointer to experimental data object - */ -std::unique_ptr -expDataFromMatlabCall(mxArray const* prhs[], Model const& model); - -void amici_dgemv( - BLASLayout layout, BLASTranspose TransA, int const M, int const N, - double const alpha, double const* A, int const lda, double const* X, - int const incX, double const beta, double* Y, int const incY -); - -void amici_dgemm( - BLASLayout layout, BLASTranspose TransA, BLASTranspose TransB, int const M, - int const N, int const K, double const alpha, double const* A, - int const lda, double const* B, int const ldb, double const beta, double* C, - int const ldc -); - -} // namespace amici - -#endif diff --git a/include/amici/spline.h b/include/amici/spline.h deleted file mode 100644 index d6f6b24b01..0000000000 --- a/include/amici/spline.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef amici_spline_h -#define amici_spline_h - -namespace amici { - -#ifndef EXHALE_DOXYGEN_SHOULD_SKIP_THIS -int spline( - int n, int end1, int end2, double slope1, double slope2, double x[], - double y[], double b[], double c[], double d[] -); -#endif - -double seval( - int n, double u, double x[], double y[], double b[], double c[], double d[] -); - -double sinteg( - int n, double u, double x[], double y[], double b[], double c[], double d[] -); - -} // namespace amici - -#endif /* amici_spline_h */ diff --git a/include/amici/symbolic_functions.h b/include/amici/symbolic_functions.h index 0e8f558eba..ba6bf05b74 100644 --- a/include/amici/symbolic_functions.h +++ b/include/amici/symbolic_functions.h @@ -114,121 +114,6 @@ double getNaN(); */ double sign(double x); -/* legacy spline implementation in C (MATLAB only) */ - -/** - * @brief Spline function - * - * Takes variable argument pairs (ti,pi) with `ti`: location of node i and - * `pi`: spline value at node i. the last two arguments are always `ss`: flag - * indicating whether slope at first node should be user defined - * and `dudt` user defined slope at first node. All arguments must be of type - * double. - * - * @param t point at which the spline should be evaluated - * @param num number of spline nodes - * - * @return spline(t) - * - */ -double spline(double t, int num, ...); - -/** - * @brief Exponentiated spline function - * - * Takes variable argument pairs (ti,pi) with `ti`: location of node i and - * `pi`: spline value at node i. the last two arguments are always `ss`: flag - * indicating whether slope at first node should be user defined - * and `dudt` user defined slope at first node. All arguments must be of type - * double. - * - * @param t point at which the spline should be evaluated - * @param num number of spline nodes - * - * @return spline(t) - * - */ -double spline_pos(double t, int num, ...); - -/** - * @brief Derivation of a spline function - * - * Takes variable argument pairs (ti,pi) with `ti`: location of node i and - * `pi`: spline value at node i. the last two arguments are always `ss`: flag - * indicating whether slope at first node should be user defined - * and `dudt` user defined slope at first node. All arguments but id must be of - * type double. - * - * @param id index of node to which the derivative of the corresponding spline - * coefficient should be computed - * @param t point at which the spline should be evaluated - * @param num number of spline nodes - * - * @return dsplinedp(t) - * - */ -double Dspline(int id, double t, int num, ...); - -/** - * @brief Derivation of an exponentiated spline function - * - * Takes variable argument pairs (ti,pi) with `ti`: location of node i and - * `pi`: spline value at node i. the last two arguments are always `ss`: flag - * indicating whether slope at first node should be user defined - * and `dudt` user defined slope at first node. All arguments but id must be of - * type double. - * - * @param id index of node to which the derivative of the corresponding spline - * coefficient should be computed - * @param t point at which the spline should be evaluated - * @param num number of spline nodes - * - * @return dsplinedp(t) - * - */ -double Dspline_pos(int id, double t, int num, ...); - -/** - * @brief Second derivation of a spline function - * - * Takes variable argument pairs (ti,pi) with `ti`: location of node i and - * `pi`: spline value at node i. the last two arguments are always `ss`: flag - * indicating whether slope at first node should be user defined - * and `dudt` user defined slope at first node. All arguments but id1 and id2 - * must be of type double. - * - * @param id1 index of node to which the first derivative of the corresponding - * spline coefficient should be computed - * @param id2 index of node to which the second derivative of the corresponding - * spline coefficient should be computed - * @param t point at which the spline should be evaluated - * @param num number of spline nodes - * - * @return ddspline(t) - */ -double DDspline(int id1, int id2, double t, int num, ...); - -/** - * @brief Derivation of an exponentiated spline function - * - * Takes variable argument pairs (ti,pi) with `ti`: location of node i and - * `pi`: spline value at node i. the last two arguments are always `ss`: flag - * indicating whether slope at first node should be user defined - * and `dudt` user defined slope at first node. All arguments but id1 and id2 - * must be of type double. - * - * @param id1 index of node to which the first derivative of the corresponding - * spline coefficient should be computed - * @param id2 index of node to which the second derivative of the corresponding - * spline coefficient should be computed - * @param t point at which the spline should be evaluated - * @param num number of spline nodes - * - * @return ddspline(t) - * - */ -double DDspline_pos(int id1, int id2, double t, int num, ...); - } // namespace amici #endif /* amici_symbolic_functions_h */ diff --git a/python/sdist/amici/de_export.py b/python/sdist/amici/de_export.py index a8e8764956..90ca718319 100644 --- a/python/sdist/amici/de_export.py +++ b/python/sdist/amici/de_export.py @@ -248,7 +248,6 @@ def generate_model_code(self) -> None: ): self._prepare_model_folder() self._generate_c_code() - self._generate_m_code() @log_execution_time("compiling cpp code", logger) def compile_model(self) -> None: @@ -317,41 +316,6 @@ def _generate_c_code(self) -> None: CXX_MAIN_TEMPLATE_FILE, os.path.join(self.model_path, "main.cpp") ) - def _generate_m_code(self) -> None: - """ - Create a Matlab script for compiling code files to a mex file - """ - - # Second order code is not yet implemented. Once this is done, - # those variables will have to be replaced by - # "self.model.true()", or the corresponding "model.self.o2flag" - nxtrue_rdata = self.model.num_states_rdata() - nytrue = self.model.num_obs() - nztrue = self.model.num_eventobs() - o2flag = 0 - - lines = [ - "% This compile script was automatically created from" - " Python SBML import.", - "% If mex compiler is set up within MATLAB, it can be run" - " from MATLAB ", - "% in order to compile a mex-file from the Python" - " generated C++ files.", - "", - f"modelName = '{self.model_name}';", - "amimodel.compileAndLinkModel(modelName, '', [], [], [], []);", - f"amimodel.generateMatlabWrapper({nxtrue_rdata}, " - f"{nytrue}, {self.model.num_par()}, " - f"{self.model.num_const()}, {nztrue}, {o2flag}, ...", - " [], ['simulate_' modelName '.m'], modelName, ...", - " 'lin', 1, 1);", - ] - - # write compile script (for mex) - compile_script = os.path.join(self.model_path, "compileMexFile.m") - with open(compile_script, "w") as fileout: - fileout.write("\n".join(lines)) - def _get_index(self, name: str) -> dict[sp.Symbol, int]: """ Compute indices for a symbolic array. diff --git a/src/interface_matlab.cpp b/src/interface_matlab.cpp deleted file mode 100644 index 8ac331c484..0000000000 --- a/src/interface_matlab.cpp +++ /dev/null @@ -1,649 +0,0 @@ -/** - * @file interface_matlab.cpp - * @brief core routines for mex interface - * - * This file defines the function `mexFunction` which is executed upon calling - * the `.mex` file from Matlab. - */ - -#include "amici/interface_matlab.h" - -#include "amici/amici.h" -#include "amici/edata.h" -#include "amici/exception.h" -#include "amici/model.h" -#include "amici/returndata_matlab.h" -#include "amici/solver.h" - -#include -#include -#include -#include - -namespace amici { - -int dbl2int(double const x); - -/** - * @brief The mexRhsArguments enum takes care of the ordering of mex file - * arguments (indexing in prhs) - */ -enum mexRhsArguments { - RHS_TIMEPOINTS, - RHS_PARAMETERS, - RHS_CONSTANTS, - RHS_OPTIONS, - RHS_PLIST, - RHS_XSCALE_UNUSED, - RHS_INITIALIZATION, - RHS_DATA, - RHS_NUMARGS_REQUIRED = RHS_DATA, - RHS_NUMARGS -}; - -/*! - * Translates AMICI_BLAS_TRANSPOSE values to CBLAS readable strings - * - * @param trans flag indicating transposition and complex conjugation - * - * @return cblastrans CBlas readable CHAR indicating transposition and complex - * conjugation - */ -char amici_blasCBlasTransToBlasTrans(BLASTranspose trans) { - switch (trans) { - case BLASTranspose::noTrans: - return 'N'; - case BLASTranspose::trans: - return 'T'; - case BLASTranspose::conjTrans: - return 'C'; - } - throw std::invalid_argument( - "Invalid argument to amici_blasCBlasTransToBlasTrans" - ); -} - -void amici_dgemm( - BLASLayout layout, BLASTranspose TransA, BLASTranspose TransB, int const M, - int const N, int const K, double const alpha, double const* A, - int const lda, double const* B, int const ldb, double const beta, double* C, - int const ldc -) { - assert(layout == BLASLayout::colMajor); - - ptrdiff_t const M_ = M; - ptrdiff_t const N_ = N; - ptrdiff_t const K_ = K; - ptrdiff_t const lda_ = lda; - ptrdiff_t const ldb_ = ldb; - ptrdiff_t const ldc_ = ldc; - char const transA = amici_blasCBlasTransToBlasTrans(TransA); - char const transB = amici_blasCBlasTransToBlasTrans(TransB); - - FORTRAN_WRAPPER(dgemm) - (&transA, &transB, &M_, &N_, &K_, &alpha, A, &lda_, B, &ldb_, &beta, C, - &ldc_); -} - -void amici_dgemv( - BLASLayout layout, BLASTranspose TransA, int const M, int const N, - double const alpha, double const* A, int const lda, double const* X, - int const incX, double const beta, double* Y, int const incY -) { - assert(layout == BLASLayout::colMajor); - - ptrdiff_t const M_ = M; - ptrdiff_t const N_ = N; - ptrdiff_t const lda_ = lda; - ptrdiff_t const incX_ = incX; - ptrdiff_t const incY_ = incY; - char const transA = amici_blasCBlasTransToBlasTrans(TransA); - - FORTRAN_WRAPPER(dgemv) - (&transA, &M_, &N_, &alpha, A, &lda_, X, &incX_, &beta, Y, &incY_); -} - -void amici_daxpy( - int n, double alpha, double const* x, int const incx, double* y, int incy -) { - - ptrdiff_t const n_ = n; - ptrdiff_t const incx_ = incx; - ptrdiff_t const incy_ = incy; - - FORTRAN_WRAPPER(daxpy)(&n_, &alpha, x, &incx_, y, &incy_); -} - -/** conversion from mxArray to vector - * @param array Matlab array to create vector from - * @param length Number of elements in array - * @return std::vector with data from array - */ -std::vector mxArrayToVector(mxArray const* array, int length) { - return {mxGetPr(array), mxGetPr(array) + length}; -} - -std::unique_ptr -expDataFromMatlabCall(mxArray const* prhs[], Model const& model) { - if (!mxGetPr(prhs[RHS_DATA])) - return nullptr; - - auto edata = std::make_unique(model); - - // Y - if (mxArray* dataY = mxGetProperty(prhs[RHS_DATA], 0, "Y")) { - auto ny_my = static_cast(mxGetN(dataY)); - if (ny_my != model.nytrue) { - throw AmiException( - "Number of observables in data matrix (%i) does " - "not match model ny (%i)", - ny_my, model.nytrue - ); - } - auto nt_my = static_cast(mxGetM(dataY)); - if (nt_my != model.nt()) { - throw AmiException( - "Number of time-points in data matrix does (%i) " - "not match provided time vector (%i)", - nt_my, model.nt() - ); - } - mxArray* dataYT; - mexCallMATLAB(1, &dataYT, 1, &dataY, "transpose"); - auto observedData = mxArrayToVector(dataYT, ny_my * nt_my); - edata->setObservedData(observedData); - - } else { - throw AmiException("Field Y not specified as field in data struct!"); - } - - // Sigma Y - if (mxArray* dataSigmaY = mxGetProperty(prhs[RHS_DATA], 0, "Sigma_Y")) { - auto ny_sigmay = static_cast(mxGetN(dataSigmaY)); - if (ny_sigmay != model.nytrue) { - throw AmiException( - "Number of observables in data-sigma matrix (%i) " - "does not match model ny (%i)", - ny_sigmay, model.nytrue - ); - } - auto nt_sigmay = static_cast(mxGetM(dataSigmaY)); - if (nt_sigmay != model.nt()) { - throw AmiException( - "Number of time-points in data-sigma matrix (%i) " - "does not match provided time vector (%i)", - nt_sigmay, model.nt() - ); - } - - mxArray* dataSigmaYT; - mexCallMATLAB(1, &dataSigmaYT, 1, &dataSigmaY, "transpose"); - auto observedDataSigma - = mxArrayToVector(dataSigmaYT, ny_sigmay * nt_sigmay); - edata->setObservedDataStdDev(observedDataSigma); - } else { - throw AmiException( - "Field Sigma_Y not specified as field in data struct!" - ); - } - - // Z - if (mxArray* dataZ = mxGetProperty(prhs[RHS_DATA], 0, "Z")) { - auto nz_mz = static_cast(mxGetN(dataZ)); - if (nz_mz != model.nztrue) { - throw AmiException( - "Number of events in event matrix (%i) does not " - "match provided nz (%i)", - nz_mz, model.nztrue - ); - } - auto ne_mz = static_cast(mxGetM(dataZ)); - if (ne_mz != model.nMaxEvent()) { - throw AmiException( - "Number of time-points in event matrix (%i) does " - "not match provided nmaxevent (%i)", - ne_mz, model.nMaxEvent() - ); - } - mxArray* dataZT; - mexCallMATLAB(1, &dataZT, 1, &dataZ, "transpose"); - auto observedEvents = mxArrayToVector(dataZT, nz_mz * ne_mz); - edata->setObservedEvents(observedEvents); - } else { - throw AmiException("Field Z not specified as field in data struct!"); - } - - // Sigma Z - if (mxArray* dataSigmaZ = mxGetProperty(prhs[RHS_DATA], 0, "Sigma_Z")) { - auto nz_sigmaz = static_cast(mxGetN(dataSigmaZ)); - if (nz_sigmaz != model.nztrue) { - throw AmiException( - "Number of events in event-sigma matrix (%i) does " - "not match provided nz (%i)", - nz_sigmaz, model.nztrue - ); - } - auto ne_sigmaz = static_cast(mxGetM(dataSigmaZ)); - if (ne_sigmaz != model.nMaxEvent()) { - throw AmiException( - "Number of time-points in event-sigma matrix (%i) " - "does not match provided nmaxevent (%i)", - ne_sigmaz, model.nMaxEvent() - ); - } - mxArray* dataSigmaZT; - mexCallMATLAB(1, &dataSigmaZT, 1, &dataSigmaZ, "transpose"); - auto observedEventsSigma - = mxArrayToVector(dataSigmaZT, nz_sigmaz * ne_sigmaz); - edata->setObservedEventsStdDev(observedEventsSigma); - } else { - throw AmiException( - "Field Sigma_Z not specified as field in data struct!" - ); - } - - // preequilibration condition parameters - if (mxArray* dataPreeq - = mxGetProperty(prhs[RHS_DATA], 0, "conditionPreequilibration")) { - int m = (int)mxGetM(dataPreeq); - int n = (int)mxGetN(dataPreeq); - if (m * n > 0) { - if (m * n != model.nk() || (m != 1 && n != 1)) { - throw AmiException( - "Number of preequilibration parameters (%dx%d) does " - "not match model (%d)", - m, n, model.nk() - ); - } - edata->fixedParametersPreequilibration = std::vector( - mxGetPr(dataPreeq), mxGetPr(dataPreeq) + m * n - ); - } - } - - // preequilibration condition parameters - if (mxGetProperty(prhs[RHS_DATA], 0, "reinitializeStates")) - edata->reinitializeFixedParameterInitialStates = static_cast( - mxGetScalar(mxGetProperty(prhs[RHS_DATA], 0, "reinitializeStates")) - ); - - return edata; -} - -/** conversion from double to int with checking for loss of data - * @param x input - * @return int_x casted value - */ -int dbl2int(double const x) { - if ((std::round(x) - x) != 0.0) - throw AmiException("Invalid non-integer value for integer option"); - return (static_cast(x)); -} - -void setSolverOptions(mxArray const* prhs[], int nrhs, Solver& solver) { - if (mxGetPr(prhs[RHS_OPTIONS])) { - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "atol")) { - solver.setAbsoluteTolerance( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "atol")) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "rtol")) { - solver.setRelativeTolerance( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "rtol")) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "quad_atol")) { - solver.setAbsoluteToleranceQuadratures( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "quad_atol")) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "quad_rtol")) { - solver.setRelativeToleranceQuadratures( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "quad_rtol")) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "ss_atol")) { - solver.setAbsoluteToleranceQuadratures( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "ss_atol")) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "ss_rtol")) { - solver.setRelativeToleranceQuadratures( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "ss_rtol")) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "maxsteps")) { - solver.setMaxSteps(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "maxsteps")) - )); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "maxstepsB")) { - solver.setMaxStepsBackwardProblem(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "maxstepsB")) - )); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "lmm")) { - solver.setLinearMultistepMethod( - static_cast(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "lmm")) - )) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "iter")) { - solver.setNonlinearSolverIteration( - static_cast(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "iter")) - )) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "interpType")) { - solver.setInterpolationType( - static_cast(dbl2int(mxGetScalar( - mxGetProperty(prhs[RHS_OPTIONS], 0, "interpType") - ))) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "linsol")) { - solver.setLinearSolver( - static_cast(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "linsol")) - )) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "sensi")) { - solver.setSensitivityOrder( - static_cast(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "sensi")) - )) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "ism")) { - solver.setInternalSensitivityMethod( - static_cast(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "ism")) - )) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "sensi_meth")) { - solver.setSensitivityMethod( - static_cast(dbl2int(mxGetScalar( - mxGetProperty(prhs[RHS_OPTIONS], 0, "sensi_meth") - ))) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "sensi_meth_preeq")) { - solver.setSensitivityMethodPreequilibration( - static_cast(dbl2int(mxGetScalar( - mxGetProperty(prhs[RHS_OPTIONS], 0, "sensi_meth_preeq") - ))) - ); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "ordering")) { - solver.setStateOrdering(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "ordering")) - )); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "stldet")) { - solver.setStabilityLimitFlag(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "stldet")) - )); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxsteps")) { - solver.setNewtonMaxSteps(dbl2int(mxGetScalar( - mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxsteps") - ))); - } - } -} - -void setModelData(mxArray const* prhs[], int nrhs, Model& model) { - if (mxGetPr(prhs[RHS_OPTIONS])) { - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "nmaxevent")) { - model.setNMaxEvent(dbl2int( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "nmaxevent")) - )); - } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "tstart")) { - model.setT0( - mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "tstart")) - ); - } - - if (mxArray* a = mxGetProperty(prhs[RHS_OPTIONS], 0, "pscale")) { - if (mxGetM(a) == 1 && mxGetN(a) == 1) { - model.setParameterScale( - static_cast(dbl2int(mxGetScalar(a))) - ); - } else if ((mxGetM(a) == 1 - && gsl::narrow(mxGetN(a)) == model.np()) - || (mxGetN(a) == 1 - && gsl::narrow(mxGetM(a)) == model.np())) { - auto pscaleArray = static_cast(mxGetData(a)); - std::vector pscale(model.np()); - for (int ip = 0; ip < model.np(); ++ip) { - pscale[ip] - = static_cast(dbl2int(pscaleArray[ip]) - ); - } - model.setParameterScale(pscale); - } else { - throw AmiException("Provided pscale has invalid dimensions!"); - } - } - } - - if (prhs[RHS_TIMEPOINTS] - && mxGetM(prhs[RHS_TIMEPOINTS]) * mxGetN(prhs[RHS_TIMEPOINTS]) > 0) { - model.setTimepoints( - std::vector( - mxGetPr(prhs[RHS_TIMEPOINTS]), - mxGetPr(prhs[RHS_TIMEPOINTS]) - + (int)mxGetM(prhs[RHS_TIMEPOINTS]) - * mxGetN(prhs[RHS_TIMEPOINTS]) - ) - ); - } - - if (model.np() > 0) { - if (mxGetPr(prhs[RHS_PARAMETERS])) { - if (gsl::narrow( - mxGetM(prhs[RHS_PARAMETERS]) * mxGetN(prhs[RHS_PARAMETERS]) - ) - == model.np()) { - model.setParameters( - std::vector( - mxGetPr(prhs[RHS_PARAMETERS]), - mxGetPr(prhs[RHS_PARAMETERS]) - + mxGetM(prhs[RHS_PARAMETERS]) - * mxGetN(prhs[RHS_PARAMETERS]) - ) - ); - } - } - } - - if (model.nk() > 0) { - if (mxGetPr(prhs[RHS_CONSTANTS])) { - if (gsl::narrow( - mxGetM(prhs[RHS_CONSTANTS]) * mxGetN(prhs[RHS_CONSTANTS]) - ) - == model.nk()) { - model.setFixedParameters( - std::vector( - mxGetPr(prhs[RHS_CONSTANTS]), - mxGetPr(prhs[RHS_CONSTANTS]) - + mxGetM(prhs[RHS_CONSTANTS]) - * mxGetN(prhs[RHS_CONSTANTS]) - ) - ); - } - } - } - if (mxGetPr(prhs[RHS_PLIST])) { - model.setParameterList( - std::vector( - mxGetPr(prhs[RHS_PLIST]), - mxGetPr(prhs[RHS_PLIST]) - + mxGetM(prhs[RHS_PLIST]) * mxGetN(prhs[RHS_PLIST]) - ) - ); - } else { - model.requireSensitivitiesForAllParameters(); - } - - /* Check, if initial states and sensitivities are passed by user or must be - * calculated */ - if (mxGetPr(prhs[RHS_INITIALIZATION])) { - mxArray* x0 = mxGetField(prhs[RHS_INITIALIZATION], 0, "x0"); - if (x0 && (mxGetM(x0) * mxGetN(x0)) > 0) { - /* check dimensions */ - if (mxGetN(x0) != 1) { - throw AmiException( - "Number of rows in x0 field must be equal to 1!" - ); - } - if (gsl::narrow(mxGetM(x0)) != model.nx_rdata) { - throw AmiException( - "Number of columns in x0 field " - "does not agree with number of " - "model states!" - ); - } - } - } - - /* Check, if initial states and sensitivities are passed by user or must be - * calculated */ - if (mxGetPr(prhs[RHS_INITIALIZATION])) { - mxArray* x0 = mxGetField(prhs[RHS_INITIALIZATION], 0, "x0"); - if (x0 && (mxGetM(x0) * mxGetN(x0)) > 0) { - /* check dimensions */ - if (mxGetN(x0) != 1) { - throw AmiException( - "Number of rows in x0 field must be equal to 1!" - ); - } - if (gsl::narrow(mxGetM(x0)) != model.nx_rdata) { - throw AmiException( - "Number of columns in x0 field " - "does not agree with number of " - "model states!" - ); - } - - model.setInitialStates( - std::vector( - mxGetPr(x0), mxGetPr(x0) + mxGetM(x0) * mxGetN(x0) - ) - ); - } - - mxArray* sx0 = mxGetField(prhs[RHS_INITIALIZATION], 0, "sx0"); - if (sx0 && (mxGetM(sx0) * mxGetN(sx0)) > 0) { - /* check dimensions */ - if (gsl::narrow(mxGetN(sx0)) != model.nplist()) { - throw AmiException( - "Number of rows in sx0 field " - "does not agree with number of " - "model parameters!" - ); - } - if (gsl::narrow(mxGetM(sx0)) != model.nx_rdata) { - throw AmiException( - "Number of columns in sx0 " - "field does not agree with " - "number of model states!" - ); - } - model.setInitialStateSensitivities( - std::vector( - mxGetPr(sx0), mxGetPr(sx0) + mxGetM(sx0) * mxGetN(sx0) - ) - ); - } - } - // preequilibration condition parameters - if (mxGetPr(prhs[RHS_DATA]) - && mxGetProperty(prhs[RHS_DATA], 0, "reinitializeStates")) - model.setReinitializeFixedParameterInitialStates( - static_cast(mxGetScalar( - mxGetProperty(prhs[RHS_DATA], 0, "reinitializeStates") - )) - ); -} - -} // namespace amici - -/*! - * mexFunction is the main interface function for the MATLAB interface. It reads - * in input data (udata and edata) and - * creates output data compound (rdata) and then calls the AMICI simulation - * routine to carry out numerical integration. - * - * @param nlhs number of output arguments of the matlab call - * @param plhs pointer to the array of output arguments - * @param nrhs number of input arguments of the matlab call - * @param prhs pointer to the array of input arguments - */ -void mexFunction(int nlhs, mxArray* plhs[], int nrhs, mxArray const* prhs[]) { - if (nlhs != 1) { - mexErrMsgIdAndTxt( - "AMICI:mex:setup", - "Incorrect number of output arguments (must be 1)!" - ); - } else if (nrhs < amici::RHS_NUMARGS_REQUIRED) { - mexErrMsgIdAndTxt( - "AMICI:mex:setup", - "Incorrect number of input arguments (must be at least 7)!" - ); - } - - auto model = amici::generic_model::getModel(); - auto solver = model->getSolver(); - setModelData(prhs, nrhs, *model); - setSolverOptions(prhs, nrhs, *solver); - - std::unique_ptr edata; - if (nrhs > amici::RHS_DATA && mxGetPr(prhs[amici::RHS_DATA])) { - try { - edata = amici::expDataFromMatlabCall(prhs, *model); - } catch (amici::AmiException const& ex) { - mexErrMsgIdAndTxt( - "AMICI:mex:setup", "Failed to read experimental data:\n%s", - ex.what() - ); - } - } else if (solver->getSensitivityOrder() >= amici::SensitivityOrder::first - && solver->getSensitivityMethod() - == amici::SensitivityMethod::adjoint) { - mexErrMsgIdAndTxt("AMICI:mex:setup", "No data provided!"); - } - - /* ensures that plhs[0] is available */ - auto rdata = amici::runAmiciSimulation(*solver, edata.get(), *model); - plhs[0] = getReturnDataMatlabFromAmiciCall(rdata.get()); - - for (auto const& msg : rdata->messages) { - auto identifier = "AMICI:simulation:" + msg.identifier; - mexWarnMsgIdAndTxt(identifier.c_str(), msg.message.c_str()); - } -} diff --git a/src/spline.cpp b/src/spline.cpp deleted file mode 100644 index 719af0a86e..0000000000 --- a/src/spline.cpp +++ /dev/null @@ -1,297 +0,0 @@ -/** - * @file spline.cpp - * @brief definition of spline functions - * @author Peter & Nigel, Design Software, 42 Gubberley St, Kenmore, 4069, - * Australia. - */ - -namespace amici { -/************************************************/ -/* Legacy implementation of spline functions */ -/* adapted from */ -/* CMATH. Copyright (c) 1989 Design Software */ -/* */ -/************************************************/ - -int spline( - int n, int end1, int end2, double slope1, double slope2, double x[], - double y[], double b[], double c[], double d[] -) -/** -Evaluate the coefficients b[i], c[i], d[i], i = 0, 1, .. n-1 for -a cubic interpolating spline - -S(xx) = Y[i] + b[i] * w + c[i] * w**2 + d[i] * w**3 -where w = xx - x[i] -and x[i] <= xx <= x[i+1] - -The n supplied data points are x[i], y[i], i = 0 ... n-1. - -@param[in] n The number of data points or knots (n >= 2) -@param[in] end1 0: default condition 1: specify the slopes at x[0] -@param[in] end2 0: default condition 1: specify the slopes at x[n-1] -@param[in] slope1 slope at x[0] -@param[in] slope2 slope at x[n-1] -@param[in] x[] the abscissas of the knots in strictly increasing order -@param[in] y[] the ordinates of the knots -@param[out] b[] array of spline coefficients -@param[out] c[] array of spline coefficients -@param[out] d[] array of spline coefficients - -@retval 0 normal return -@retval 1 less than two data points; cannot interpolate -@retval 2 x[] are not in ascending order - -Notes ------ - - The accompanying function seval() may be used to evaluate the - spline while deriv will provide the first derivative. - - Using p to denote differentiation - y[i] = S(X[i]) - b[i] = Sp(X[i]) - c[i] = Spp(X[i])/2 - d[i] = Sppp(X[i])/6 ( Derivative from the right ) - - Since the zero elements of the arrays ARE NOW used here, - all arrays to be passed from the main program should be - dimensioned at least [n]. These routines will use elements - [0 .. n-1]. - - Adapted from the text - Forsythe, G.E., Malcolm, M.A. and Moler, C.B. (1977) - "Computer Methods for Mathematical Computations" - Prentice Hall - - Note that although there are only n-1 polynomial segments, - n elements are requird in b, c, d. The elements b[n-1], - c[n-1] and d[n-1] are set to continue the last segment - past x[n-1]. -*/ - -{ /* begin procedure spline() */ - - int nm1, ib, i; - double t; - int ascend; - - nm1 = n - 1; - - if (n < 2) { /* no possible interpolation */ - goto LeaveSpline; - } - - ascend = 1; - for (i = 1; i < n; ++i) - if (x[i] <= x[i - 1]) - ascend = 0; - if (!ascend) { - goto LeaveSpline; - } - - if (n >= 3) { /* ---- At least quadratic ---- */ - - /* ---- Set up the symmetric tri-diagonal system - b = diagonal - d = offdiagonal - c = right-hand-side */ - d[0] = x[1] - x[0]; - c[1] = (y[1] - y[0]) / d[0]; - for (i = 1; i < nm1; ++i) { - d[i] = x[i + 1] - x[i]; - b[i] = 2.0 * (d[i - 1] + d[i]); - c[i + 1] = (y[i + 1] - y[i]) / d[i]; - c[i] = c[i + 1] - c[i]; - } - - /* ---- Default End conditions - Third derivatives at x[0] and x[n-1] obtained - from divided differences */ - b[0] = -d[0]; - b[nm1] = -d[n - 2]; - c[0] = 0.0; - c[nm1] = 0.0; - if (n != 3) { - c[0] = c[2] / (x[3] - x[1]) - c[1] / (x[2] - x[0]); - c[nm1] = c[n - 2] / (x[nm1] - x[n - 3]) - - c[n - 3] / (x[n - 2] - x[n - 4]); - c[0] = c[0] * d[0] * d[0] / (x[3] - x[0]); - c[nm1] = -c[nm1] * d[n - 2] * d[n - 2] / (x[nm1] - x[n - 4]); - } - - /* Alternative end conditions -- known slopes */ - if (end1 == 1) { - b[0] = 2.0 * (x[1] - x[0]); - c[0] = (y[1] - y[0]) / (x[1] - x[0]) - slope1; - } - if (end2 == 1) { - b[nm1] = 2.0 * (x[nm1] - x[n - 2]); - c[nm1] = slope2 - (y[nm1] - y[n - 2]) / (x[nm1] - x[n - 2]); - } - - /* Forward elimination */ - for (i = 1; i < n; ++i) { - t = d[i - 1] / b[i - 1]; - b[i] = b[i] - t * d[i - 1]; - c[i] = c[i] - t * c[i - 1]; - } - - /* Back substitution */ - c[nm1] = c[nm1] / b[nm1]; - for (ib = 0; ib < nm1; ++ib) { - i = n - ib - 2; - c[i] = (c[i] - d[i] * c[i + 1]) / b[i]; - } - - /* c[i] is now the sigma[i] of the text */ - - /* Compute the polynomial coefficients */ - b[nm1] = (y[nm1] - y[n - 2]) / d[n - 2] - + d[n - 2] * (c[n - 2] + 2.0 * c[nm1]); - for (i = 0; i < nm1; ++i) { - b[i] = (y[i + 1] - y[i]) / d[i] - d[i] * (c[i + 1] + 2.0 * c[i]); - d[i] = (c[i + 1] - c[i]) / d[i]; - c[i] = 3.0 * c[i]; - } - c[nm1] = 3.0 * c[nm1]; - d[nm1] = d[n - 2]; - - } /* at least quadratic */ - - else /* if n >= 3 */ - { /* linear segment only */ - b[0] = (y[1] - y[0]) / (x[1] - x[0]); - c[0] = 0.0; - d[0] = 0.0; - b[1] = b[0]; - c[1] = 0.0; - d[1] = 0.0; - } - -LeaveSpline: - return 0; -} /* end of spline() */ - -/** - @brief Evaluate the cubic spline function - - S(xx) = y[i] + b[i] * w + c[i] * w**2 + d[i] * w**3 - where w = u - x[i] - and x[i] <= u <= x[i+1] - Note that Horner's rule is used. - If u < x[0] then i = 0 is used. - If u > x[n-1] then i = n-1 is used. - - @param[in] n The number of data points or knots (n >= 2) - @param[in] u the abscissa at which the spline is to be evaluated - @param[in] x[] the abscissas of the knots in strictly increasing order - @param[in] y[] the ordinates of the knots - @param[in] b array of spline coefficients computed by spline(). - @param[in] c array of spline coefficients computed by spline(). - @param[in] d array of spline coefficients computed by spline(). - - @return the value of the spline function at u - - Notes - - If u is not in the same interval as the previous call then a - binary search is performed to determine the proper interval. - -*/ - -double seval( - int n, double u, double x[], double y[], double b[], double c[], double d[] -) - -{ /* begin function seval() */ - - int i; - double w; - - if (u <= x[0]) { - i = 0; - } else { - if (u >= x[n - 1]) { - i = n - 1; - } else { - i = 0; - int j = n; - do { - int k = (i + j) / 2; /* split the domain to search */ - if (u < x[k]) - j = k; /* move the upper bound */ - if (u >= x[k]) - i = k; /* move the lower bound */ - } /* there are no more segments to search */ - while (j > i + 1); - } - } - - /* ---- Evaluate the spline ---- */ - w = u - x[i]; - w = y[i] + w * (b[i] + w * (c[i] + w * d[i])); - return (w); -} - -/** - Integrate the cubic spline function - - S(xx) = y[i] + b[i] * w + c[i] * w**2 + d[i] * w**3 - where w = u - x[i] - and x[i] <= u <= x[i+1] - - The integral is zero at u = x[0]. - - If u < x[0] then i = 0 segment is extrapolated. - If u > x[n-1] then i = n-1 segment is extrapolated. - - @param[in] n the number of data points or knots (n >= 2) - @param[in] u the abscissa at which the spline is to be evaluated - @param[in] x[] the abscissas of the knots in strictly increasing order - @param[in] y[] the ordinates of the knots - @param[in] b array of spline coefficients computed by spline(). - @param[in] c array of spline coefficients computed by spline(). - @param[in] d array of spline coefficients computed by spline(). - - @return the value of the spline function at u - - Notes - - If u is not in the same interval as the previous call then a - binary search is performed to determine the proper interval. - -*/ - -double sinteg( - int n, double u, double x[], double y[], double b[], double c[], double d[] -) { /* begin function sinteg() */ - - int i, j; - double sum, dx; - - i = 0; - - if ((x[i] > u) || (x[i + 1] < u)) { /* ---- perform a binary search ---- */ - j = n; - do { - int k = (i + j) / 2; /* split the domain to search */ - if (u < x[k]) - j = k; /* move the upper bound */ - if (u >= x[k]) - i = k; /* move the lower bound */ - } /* there are no more segments to search */ - while (j > i + 1); - } - - sum = 0.0; - /* ---- Evaluate the integral for segments x < u ---- */ - for (j = 0; j < i; ++j) { - dx = x[j + 1] - x[j]; - sum += dx - * (y[j] - + dx * (0.5 * b[j] + dx * (c[j] / 3.0 + dx * 0.25 * d[j]))); - } - - /* ---- Evaluate the integral fot this segment ---- */ - dx = u - x[i]; - sum += dx - * (y[i] + dx * (0.5 * b[i] + dx * (c[i] / 3.0 + dx * 0.25 * d[i]))); - - return (sum); -} - -} // namespace amici diff --git a/src/symbolic_functions.cpp b/src/symbolic_functions.cpp index 37e10fde62..609d44ef70 100644 --- a/src/symbolic_functions.cpp +++ b/src/symbolic_functions.cpp @@ -6,7 +6,6 @@ */ #include "amici/symbolic_functions.h" -#include "amici/spline.h" #include #include @@ -106,232 +105,4 @@ double pos_pow(double base, double exponent) { return pow(std::max(base, 0.0), exponent); } -// Legacy spline implementation in C (MATLAB only) -double spline(double t, int num, ...) { - - va_list valist; - - double uout; - double ss; - double dudt; - - auto* ts = (double*)alloca(num * sizeof(double)); - auto* us = (double*)alloca(num * sizeof(double)); - - auto* b = (double*)alloca(num * sizeof(double)); - auto* c = (double*)alloca(num * sizeof(double)); - auto* d = (double*)alloca(num * sizeof(double)); - - /* Variable list type macro */ - /* initialize valist for num number of arguments */ - va_start(valist, num); - - for (int i = 0; i < 2 * num; i += 2) { - int j = i / 2; - ts[j] = va_arg(valist, double); - us[j] = va_arg(valist, double); - } - ss = va_arg(valist, double); - dudt = va_arg(valist, double); - - /* clean memory reserved for valist */ - va_end(valist); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, us, b, c, d); - uout = seval(num, t, ts, us, b, c, d); - - return uout; -} - -double spline_pos(double t, int num, ...) { - - va_list valist; - - double uout; - double ss; - double dudt; - - auto* ts = (double*)alloca(num * sizeof(double)); - auto* us = (double*)alloca(num * sizeof(double)); - auto* uslog = (double*)alloca(num * sizeof(double)); - - auto* b = (double*)alloca(num * sizeof(double)); - auto* c = (double*)alloca(num * sizeof(double)); - auto* d = (double*)alloca(num * sizeof(double)); - - /* initialize valist for num number of arguments */ - va_start(valist, num); - - for (int i = 0; i < 2 * num; i += 2) { - int j = i / 2; - ts[j] = va_arg(valist, double); - us[j] = va_arg(valist, double); - uslog[j] = log(us[j]); - } - ss = va_arg(valist, double); - dudt = va_arg(valist, double); - - /* clean memory reserved for valist */ - va_end(valist); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, uslog, b, c, d); - uout = seval(num, t, ts, uslog, b, c, d); - - return exp(uout); -} - -double Dspline(int id, double t, int num, ...) { - - va_list valist; - - double uout; - double ss; - double dudt; - - double* ts = (double*)alloca(num * sizeof(double)); - double* us = (double*)alloca(num * sizeof(double)); - double* ps = (double*)alloca(num * sizeof(double)); - - double* b = (double*)alloca(num * sizeof(double)); - double* c = (double*)alloca(num * sizeof(double)); - double* d = (double*)alloca(num * sizeof(double)); - - int did = id / 2 - 2; - - /* initialize valist for num number of arguments */ - va_start(valist, num); - - for (int i = 0; i < 2 * num; i += 2) { - int j = i / 2; - ts[j] = va_arg(valist, double); - ps[j] = va_arg(valist, double); - us[j] = 0.0; - } - us[did] = 1.0; - ss = va_arg(valist, double); - dudt = va_arg(valist, double); - - /* clean memory reserved for valist */ - va_end(valist); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, us, b, c, d); - uout = seval(num, t, ts, us, b, c, d); - - return uout; -} - -double Dspline_pos(int id, double t, int num, ...) { - - va_list valist; - - auto* ts = (double*)alloca(num * sizeof(double)); - auto* us = (double*)alloca(num * sizeof(double)); - auto* sus = (double*)alloca(num * sizeof(double)); - auto* uslog = (double*)alloca(num * sizeof(double)); - - auto* b = (double*)alloca(num * sizeof(double)); - auto* c = (double*)alloca(num * sizeof(double)); - auto* d = (double*)alloca(num * sizeof(double)); - - double uout; - double ss; - double dudt; - double uspline_pos; - double suspline; - - int did = id / 2 - 2; - - /* initialize valist for num number of arguments */ - va_start(valist, num); - - for (int i = 0; i < 2 * num; i += 2) { - int j = i / 2; - ts[j] = va_arg(valist, double); - us[j] = va_arg(valist, double); - uslog[j] = log(us[j]); - sus[j] = 0.0; - } - ss = va_arg(valist, double); - dudt = va_arg(valist, double); - sus[did] = 1.0; - - /* clean memory reserved for valist */ - va_end(valist); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, uslog, b, c, d); - uspline_pos = exp(seval(num, t, ts, uslog, b, c, d)); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, sus, b, c, d); - suspline = seval(num, t, ts, sus, b, c, d); - uout = suspline * uspline_pos / us[did]; - - return uout; -} - -double DDspline(int /*id1*/, int /*id2*/, double /*t*/, int /*num*/, ...) { - return 0.0; -} - -double DDspline_pos(int id1, int id2, double t, int num, ...) { - - va_list valist; - - auto* ts = (double*)alloca(num * sizeof(double)); - auto* us = (double*)alloca(num * sizeof(double)); - auto* sus1 = (double*)alloca(num * sizeof(double)); - auto* sus2 = (double*)alloca(num * sizeof(double)); - auto* uslog = (double*)alloca(num * sizeof(double)); - - auto* b = (double*)alloca(num * sizeof(double)); - auto* c = (double*)alloca(num * sizeof(double)); - auto* d = (double*)alloca(num * sizeof(double)); - - double uout; - double ss; - double dudt; - double uspline_pos; - double su1spline; - double su2spline; - - int did1 = id1 / 2 - 2; - int did2 = id2 / 2 - 2; - - /* initialize valist for num number of arguments */ - va_start(valist, num); - - for (int i = 0; i < 2 * num; i += 2) { - int j = i / 2; - ts[j] = va_arg(valist, double); - us[j] = va_arg(valist, double); - uslog[j] = log(us[j]); - sus1[j] = 0.0; - sus2[j] = 0.0; - } - ss = va_arg(valist, double); - dudt = va_arg(valist, double); - sus1[did1] = 1.0; - sus2[did2] = 1.0; - - /* clean memory reserved for valist */ - va_end(valist); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, uslog, b, c, d); - uspline_pos = exp(seval(num, t, ts, uslog, b, c, d)); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, sus1, b, c, d); - su1spline = seval(num, t, ts, sus1, b, c, d); - - spline(num, static_cast(ss), 0, dudt, 0.0, ts, sus2, b, c, d); - su2spline = seval(num, t, ts, sus2, b, c, d); - - if (id1 == id2) { - uout = (su1spline * su2spline - su1spline) * uspline_pos; - } else { - uout = su1spline * su2spline * uspline_pos; - } - uout = uout / us[did1] / us[did2]; - - return uout; -} - } // namespace amici diff --git a/tests/cpp/wrapTestModels.m b/tests/cpp/wrapTestModels.m deleted file mode 100644 index 80d8d05936..0000000000 --- a/tests/cpp/wrapTestModels.m +++ /dev/null @@ -1,108 +0,0 @@ -function wrapTestModels() -% wrapTestModels calls amiwrap on all test models. currently necessary for continuous integrations -% to yield meaningful results -% -% Return values: -% void - - amiciPath = fileparts(mfilename('fullpath')); - amiciPath = [amiciPath '/../../matlab']; - - %% EXAMPLE STEADYSTATE - - cd([amiciPath '/examples/example_steadystate/']); - - try - [exdir,~,~]=fileparts(which('example_steadystate.m')); - amiwrap('model_steadystate','model_steadystate_syms',exdir); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - %% EXAMPLE DIRAC - cd([amiciPath '/examples/example_dirac/']); - - try - [exdir,~,~]=fileparts(which('example_dirac.m')); - amiwrap('model_dirac','model_dirac_syms',exdir); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - %% EXAMPLE JAKSTAT - cd([amiciPath '/examples/example_jakstat_adjoint/']); - - try - [exdir,~,~]=fileparts(which('example_jakstat_adjoint.m')); - amiwrap('model_jakstat_adjoint', 'model_jakstat_adjoint_syms', exdir, 1); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - %% EXAMPLE NEURON - cd([amiciPath '/examples/example_neuron/']); - - try - [exdir,~,~]=fileparts(which('example_neuron.m')); - amiwrap('model_neuron', 'model_neuron_syms', exdir, 1); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - cd(fileparts(mfilename('fullpath'))); - - %% EXAMPLE EVENTS - cd([amiciPath '/examples/example_events/']); - - try - [exdir,~,~]=fileparts(which('example_events.m')); - amiwrap('model_events', 'model_events_syms', exdir); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - %% EXAMPLE NESTED EVENTS - cd([amiciPath '/examples/example_nested_events/']); - - try - [exdir,~,~]=fileparts(which('example_nested_events.m')); - amiwrap('model_nested_events', 'model_nested_events_syms', exdir); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - cd(fileparts(mfilename('fullpath'))); - - %% EXAMPLE ROBERTSON - cd([amiciPath '/examples/example_robertson/']); - - try - [exdir,~,~]=fileparts(which('example_robertson.m')); - amiwrap('model_robertson', 'model_robertson_syms', exdir); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - cd(fileparts(mfilename('fullpath'))); - - %% EXAMPLE CALVETTI - cd([amiciPath '/examples/example_calvetti/']); - - try - [exdir,~,~]=fileparts(which('example_calvetti.m')); - amiwrap('model_calvetti', 'model_calvetti_syms', exdir); - catch err - disp(err.message) - cd(fileparts(mfilename('fullpath'))); - end - - cd(fileparts(mfilename('fullpath'))); - -end