def create_function(name, inp, out, options): codegen = options['codegen'] if options['verbose'] >= 1: print('Building function %s ... ' % name, end=' ') t0 = time.time() fun = Function(name, inp, out).expand() if codegen['build'] == 'jit': if options['verbose'] >= 1: print(('[jit compilation with flags %s]' % (codegen['flags'])), end=' ') fun.generate(name) compiler = Compiler(name + '.c', 'clang', {'flags': codegen['flags']}) fun = external(name, compiler) os.remove(name + '.c') elif codegen['build'] == 'shared': if os.name == 'nt': raise ValueError('Build option is not supported for Windows!') directory = os.path.join(os.getcwd(), 'build') if not os.path.isdir(directory): os.makedirs(directory) path = os.path.join(directory, name) if options['verbose'] >= 1: print(('[compile to .so with flags %s]' % (codegen['flags'])), end=' ') if os.path.isfile(path + '.so'): os.remove(path + '.so') fun.generate(name + '.c') shutil.move(name + '.c', path + '.c') os.system('gcc -fPIC -shared %s %s.c -o %s.so' % (codegen['flags'], path, path)) fun = external(name, path + '.so') os.remove(path + '.c') elif codegen['build'] == 'existing': if os.name == 'nt': raise ValueError('Build option is not supported for Windows!') directory = os.path.join(os.getcwd(), 'build') path = os.path.join(directory, name) if not os.path.isfile(path + '.so'): raise ValueError('%s.so does not exist!', path) if options['verbose'] >= 1: print(('[using shared object %s.so]' % path), end=' ') fun = external(name, path + '.so') elif codegen['build'] is None: fun = fun else: raise ValueError('Invalid build option.') t1 = time.time() if options['verbose'] >= 1: print('in %5f s' % (t1 - t0)) return fun, (t1 - t0)
def export_external_ode_model(): model_name = 'external_ode' # Declare model variables x = MX.sym('x', 2) u = MX.sym('u', 1) xDot = MX.sym('xDot', 2) cdll.LoadLibrary('./test_external_lib/build/libexternal_ode_casadi.so') f_ext = external('libexternal_ode_casadi', 'libexternal_ode_casadi.so', {'enable_fd': True}) f_expl = f_ext(x, u) f_impl = xDot - f_expl model = AcadosModel() model.f_impl_expr = f_impl model.f_expl_expr = f_expl model.x = x model.xdot = xDot model.u = u model.p = [] model.name = model_name return model
def _make_jit_function(f: cs.Function): """ Compiles casadi function into a shared object and return it :return: """ import filecmp import os gen_code_path = 'ilqr_generated_{}.c'.format(f.name()) f.generate(gen_code_path) gen_lib_path = 'ilqr_generated_{}.so'.format(f.name()) gcc_cmd = 'gcc {} -shared -fPIC -O3 -o {}'.format( gen_code_path, gen_lib_path) if os.system(gcc_cmd) != 0: raise SystemError('Unable to compile function "{}"'.format( f.name())) jit_f = cs.external(f.name(), './' + gen_lib_path) os.remove(gen_code_path) os.remove(gen_lib_path) return jit_f
def test_generate_casadi(self): model = pybamm.BaseModel() t = pybamm.t a = pybamm.Variable("a") b = pybamm.Variable("b") p = pybamm.InputParameter("p") q = pybamm.InputParameter("q") model.rhs = {a: -a * p} model.algebraic = {b: a - b} model.initial_conditions = {a: q, b: 1} model.variables = {"a+b": a + b - t} # Generate C code model.generate("test.c", ["a+b"]) # Compile subprocess.run(["gcc", "-fPIC", "-shared", "-o", "test.so", "test.c"]) # nosec # Read the generated functions x0_fn = casadi.external("x0", "./test.so") z0_fn = casadi.external("z0", "./test.so") rhs_fn = casadi.external("rhs_", "./test.so") alg_fn = casadi.external("alg_", "./test.so") jac_rhs_fn = casadi.external("jac_rhs", "./test.so") jac_alg_fn = casadi.external("jac_alg", "./test.so") var_fn = casadi.external("variables", "./test.so") # Test that function values are as expected self.assertEqual(x0_fn([0, 5]), 5) self.assertEqual(z0_fn([0, 0]), 1) self.assertEqual(rhs_fn(0, 3, 2, [7, 2]), -21) self.assertEqual(alg_fn(0, 3, 2, [7, 2]), 1) np.testing.assert_array_equal(np.array(jac_rhs_fn(5, 6, 7, [8, 9])), [[-8, 0]]) np.testing.assert_array_equal(np.array(jac_alg_fn(5, 6, 7, [8, 9])), [[1, -1]]) self.assertEqual(var_fn(6, 3, 2, [7, 2]), -1) # Remove generated files. os.remove("test.c") os.remove("test.so")
def load_model(model_folder: str, model_name: str, compiler_options: Dict[str, str]) -> CachedModel: """ Loads a precompiled CasADi model into a CachedModel instance. :param model_folder: Folder where the precompiled CasADi model is located. :param model_name: Name of the model. :param compiler_options: Dictionary of compiler options. :returns: CachedModel instance. """ db_file = os.path.join(model_folder, model_name) if compiler_options.get('mtime_check', True): # Mtime check cache_mtime = os.path.getmtime(db_file) for folder in [model_folder] + compiler_options.get('library_folders', []): for root, dir, files in os.walk(folder, followlinks=True): for item in fnmatch.filter(files, "*.mo"): filename = os.path.join(root, item) if os.path.getmtime(filename) > cache_mtime: raise InvalidCacheError("Cache out of date") # Create empty model object model = CachedModel() # Load metadata with open(db_file, 'rb') as f: db = pickle.load(f) if db['version'] != __version__: raise InvalidCacheError('Cache generated for a different version of pymoca') # Check compiler options. We ignore the library folders, as they have # already been checked, and checking them will impede platform # portability of the cache. exclude_options = ['library_folders'] old_opts = {k: v for k, v in db['options'].items() if k not in exclude_options} new_opts = {k: v for k, v in compiler_options.items() if k not in exclude_options} if old_opts != new_opts: raise InvalidCacheError('Cache generated for different compiler options') # Pickles are platform independent, but dynamic libraries are not if compiler_options['codegen']: if db['library_os'] != os.name: raise InvalidCacheError('Cache generated for incompatible OS') # Include references to the shared libraries for o in ['dae_residual', 'initial_residual', 'variable_metadata', 'delay_arguments']: if isinstance(db[o], str): # Path to codegen'd library f = ca.external(o, db[o]) else: # Pickled CasADi Function; use as is assert isinstance(db[o], ca.Function) f = db[o] setattr(model, '_' + o + '_function', f) # Load variables per category variables_with_metadata = ['states', 'alg_states', 'inputs', 'parameters', 'constants'] variable_dict = {} for key in variables_with_metadata: variables = getattr(model, key) for i, d in enumerate(db[key]): variable = Variable.from_dict(d) variables.append(variable) variable_dict[variable.symbol.name()] = variable model.der_states = [Variable.from_dict(d) for d in db['der_states']] model.outputs = db['outputs'] model.delay_states = db['delay_states'] # Evaluate variable metadata: parameter_vector = ca.veccat(*[v.symbol for v in model.parameters]) metadata = dict(zip(variables_with_metadata, model.variable_metadata_function(parameter_vector))) independent_metadata = dict(zip(variables_with_metadata, model.variable_metadata_function(ca.veccat(*[np.nan for v in model.parameters])))) for k, key in enumerate(variables_with_metadata): for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(CASADI_ATTRIBUTES): if ca.depends_on(ca.MX(metadata[key][i, j]), parameter_vector): setattr(variable, tmp, metadata[key][i, j]) else: setattr(variable, tmp, independent_metadata[key][i, j]) # Evaluate delay arguments: args = [model.time, ca.veccat(*model._symbols(model.states)), ca.veccat(*model._symbols(model.der_states)), ca.veccat(*model._symbols(model.alg_states)), ca.veccat(*model._symbols(model.inputs)), ca.veccat(*model._symbols(model.constants)), ca.veccat(*model._symbols(model.parameters))] delay_arguments_raw = model.delay_arguments_function(*args) nan_args = [ca.repmat(np.nan, *arg.size()) for arg in args] independent_delay_arguments_raw = model.delay_arguments_function(*nan_args) if delay_arguments_raw is not None: all_symbols = ca.veccat(*args) delay_expressions_raw = delay_arguments_raw[::2] delay_durations_raw = delay_arguments_raw[1::2] independent_delay_durations_raw = independent_delay_arguments_raw[1::2] assert 1 == len({len(delay_expressions_raw), len(delay_durations_raw), len(independent_delay_durations_raw)}) for i, expr in enumerate(delay_expressions_raw): assert ca.depends_on(ca.MX(expr), all_symbols), \ "Expression to be delayed can not be a constant" if ca.depends_on(ca.MX(delay_durations_raw[i]), all_symbols): dur = delay_durations_raw[i] false_deps = ca.vertcat(*[ var for var in ca.symvar(dur) if not ca.depends_on(dur, var)]) if false_deps.size1() > 0: [dur] = ca.substitute( [dur], [false_deps], [ca.DM(np.full(false_deps.size1(), np.nan))]) else: dur = independent_delay_durations_raw[i] model.delay_arguments.append(DelayArgument(expr, dur)) # Try to coerce parameters into their Python types for p in model.parameters: for attr in CASADI_ATTRIBUTES: v = getattr(p, attr) v_mx = ca.MX(v) if v_mx.is_constant() and v_mx.is_regular(): setattr(p, attr, p.python_type(v)) # Done return model
def load_model(model_folder: str, model_name: str, compiler_options: Dict[str, str]) -> CachedModel: """ Loads a precompiled CasADi model into a CachedModel instance. :param model_folder: Folder where the precompiled CasADi model is located. :param model_name: Name of the model. :param compiler_options: Dictionary of compiler options. :returns: CachedModel instance. """ db_file = os.path.join(model_folder, model_name) if compiler_options.get('mtime_check', True): # Mtime check cache_mtime = os.path.getmtime(db_file) for folder in [model_folder] + compiler_options.get('library_folders', []): for root, dir, files in os.walk(folder, followlinks=True): for item in fnmatch.filter(files, "*.mo"): filename = os.path.join(root, item) if os.path.getmtime(filename) > cache_mtime: raise InvalidCacheError("Cache out of date") # Create empty model object model = CachedModel() # Load metadata with open(db_file, 'rb') as f: db = pickle.load(f) if db['version'] != __version__: raise InvalidCacheError('Cache generated for a different version of pymoca') # Check compiler options. We ignore the library folders, as they have # already been checked, and checking them will impede platform # portability of the cache. exclude_options = ['library_folders'] old_opts = {k: v for k, v in db['options'].items() if k not in exclude_options} new_opts = {k: v for k, v in compiler_options.items() if k not in exclude_options} if old_opts != new_opts: raise InvalidCacheError('Cache generated for different compiler options') # Pickles are platform independent, but dynamic libraries are not if compiler_options['codegen']: if db['library_os'] != os.name: raise InvalidCacheError('Cache generated for incompatible OS') # Include references to the shared libraries for o in ['dae_residual', 'initial_residual', 'variable_metadata', 'delay_arguments']: if isinstance(db[o], str): # Path to codegen'd library f = ca.external(o, db[o]) else: # Pickled CasADi Function; use as is assert isinstance(db[o], ca.Function) f = db[o] setattr(model, '_' + o + '_function', f) # Load variables per category variables_with_metadata = ['states', 'alg_states', 'inputs', 'parameters', 'constants'] variable_dict = {} for key in variables_with_metadata: variables = getattr(model, key) for i, d in enumerate(db[key]): variable = Variable.from_dict(d) variables.append(variable) variable_dict[variable.symbol.name()] = variable model.der_states = [Variable.from_dict(d) for d in db['der_states']] model.outputs = db['outputs'] model.delay_states = db['delay_states'] # Evaluate variable metadata: parameter_vector = ca.veccat(*[v.symbol for v in model.parameters]) metadata = dict(zip(variables_with_metadata, model.variable_metadata_function(parameter_vector))) independent_metadata = dict(zip(variables_with_metadata, model.variable_metadata_function(ca.veccat(*[np.nan for v in model.parameters])))) for k, key in enumerate(variables_with_metadata): for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(CASADI_ATTRIBUTES): if ca.depends_on(ca.MX(metadata[key][i, j]), parameter_vector): setattr(variable, tmp, metadata[key][i, j]) else: setattr(variable, tmp, independent_metadata[key][i, j]) # Done return model
def load_model(model_folder: str, model_name: str, compiler_options: Dict[str, str]) -> CachedModel: """ Loads a precompiled CasADi model into a CachedModel instance. :param model_folder: Folder where the precompiled CasADi model is located. :param model_name: Name of the model. :param compiler_options: Dictionary of compiler options. :returns: CachedModel instance. """ db_file = os.path.join(model_folder, model_name + ".pymoca_cache") if compiler_options.get('mtime_check', True): # Mtime check cache_mtime = os.path.getmtime(db_file) for folder in [model_folder] + compiler_options.get( 'library_folders', []): for root, dir, files in os.walk(folder, followlinks=True): for item in fnmatch.filter(files, "*.mo"): filename = os.path.join(root, item) if os.path.getmtime(filename) > cache_mtime: raise InvalidCacheError("Cache out of date") # Create empty model object model = CachedModel() # Load metadata with open(db_file, 'rb') as f: db = pickle.load(f) if db['version'] != __version__: raise InvalidCacheError( 'Cache generated for a different version of pymoca') # Check compiler options. We ignore the library folders, as they have # already been checked, and checking them will impede platform # portability of the cache. exclude_options = ['library_folders'] old_opts = { k: v for k, v in db['options'].items() if k not in exclude_options } new_opts = { k: v for k, v in compiler_options.items() if k not in exclude_options } if old_opts != new_opts: raise InvalidCacheError( 'Cache generated for different compiler options') # Pickles are platform independent, but dynamic libraries are not if compiler_options.get('codegen', False): if db['library_os'] != os.name: raise InvalidCacheError('Cache generated for incompatible OS') # Include references to the shared libraries for o in [ 'dae_residual', 'initial_residual', 'variable_metadata', 'delay_arguments' ]: if isinstance(db[o], str): # Path to codegen'd library f = ca.external(o, db[o]) else: # Pickled CasADi Function; use as is assert isinstance(db[o], ca.Function) f = db[o] setattr(model, '_' + o + '_function', f) # Load variables per category variables_with_metadata = [ 'states', 'alg_states', 'inputs', 'parameters', 'constants' ] variable_dict = {} for key in variables_with_metadata: variables = getattr(model, key) for i, d in enumerate(db[key]): variable = Variable.from_dict(d) variables.append(variable) variable_dict[variable.symbol.name()] = variable model.der_states = [Variable.from_dict(d) for d in db['der_states']] model.outputs = db['outputs'] model.delay_states = db['delay_states'] model.alias_relation = db['alias_relation'] # Evaluate variable metadata: parameter_vector = ca.veccat(*[v.symbol for v in model.parameters]) metadata = dict( zip(variables_with_metadata, model.variable_metadata_function(parameter_vector))) independent_metadata = dict( zip(variables_with_metadata, (np.array(x) for x in model.variable_metadata_function( ca.veccat(*[np.nan for v in model.parameters]))))) for k, key in enumerate(variables_with_metadata): m = db[key + "__metadata_dependent"] for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(CASADI_ATTRIBUTES): if m[i, j]: setattr(variable, tmp, metadata[key][i, j]) else: setattr(variable, tmp, independent_metadata[key][i, j]) # Evaluate delay arguments: if model.delay_states: args = [ model.time, ca.veccat(*model._symbols(model.states)), ca.veccat(*model._symbols(model.der_states)), ca.veccat(*model._symbols(model.alg_states)), ca.veccat(*model._symbols(model.inputs)), ca.veccat(*model._symbols(model.constants)), ca.veccat(*model._symbols(model.parameters)) ] delay_arguments_raw = model.delay_arguments_function(*args) nan_args = [ca.repmat(np.nan, *arg.size()) for arg in args] independent_delay_arguments_raw = model.delay_arguments_function( *nan_args) delay_expressions_raw = delay_arguments_raw[::2] delay_durations_raw = delay_arguments_raw[1::2] independent_delay_durations_raw = independent_delay_arguments_raw[ 1::2] assert 1 == len({ len(delay_expressions_raw), len(delay_durations_raw), len(independent_delay_durations_raw) }) all_symbols = [ model.time, *model._symbols(model.states), *model._symbols(model.der_states), *model._symbols(model.alg_states), *model._symbols(model.inputs), *model._symbols(model.constants), *model._symbols(model.parameters) ] duration_dependencies = db['__delay_duration_dependent'] # Get rid of false dependency symbols not used in any delay # durations. This significantly reduces the work the (slow) # substitute() calls have to do later on. actual_deps = sorted(set(np.array(duration_dependencies).ravel())) actual_dep_symbols = [np.nan] * len(all_symbols) for i in actual_deps: actual_dep_symbols[i] = all_symbols[i] delay_durations_simplified = ca.Function( 'replace_false_deps', all_symbols, delay_durations_raw).call(actual_dep_symbols) # Get rid of remaining hidden dependencies in the delay durations for i, expr in enumerate(delay_expressions_raw): if duration_dependencies[i]: dur = delay_durations_simplified[i] if len(duration_dependencies[i]) < len(actual_deps): deps = set(ca.symvar(dur)) actual_deps = { all_symbols[j] for j in duration_dependencies[i] } false_deps = deps - actual_deps if false_deps: [dur] = ca.substitute([dur], list(false_deps), [np.nan] * len(false_deps)) else: # Already removed all false dependencies pass else: dur = independent_delay_durations_raw[i] model.delay_arguments.append(DelayArgument(expr, dur)) # Try to coerce parameters into their Python types for p in model.parameters: for attr in CASADI_ATTRIBUTES: v = getattr(p, attr) v_mx = ca.MX(v) if v_mx.is_constant() and v_mx.is_regular(): setattr(p, attr, p.python_type(v)) # Done return model
def _load_model(model_folder: str, model_name: str, compiler_options: Dict[str, str]) -> CachedModel: db_file = os.path.join(model_folder, model_name) if compiler_options.get('mtime_check', True): # Mtime check cache_mtime = os.path.getmtime(db_file) for folder in [model_folder] + compiler_options.get( 'library_folders', []): for root, dir, files in os.walk(folder, followlinks=True): for item in fnmatch.filter(files, "*.mo"): filename = os.path.join(root, item) if os.path.getmtime(filename) > cache_mtime: raise InvalidCacheError("Cache out of date") # Create empty model object model = CachedModel() # Compile shared libraries objects = { 'dae_residual': ObjectData('dae_residual', ''), 'initial_residual': ObjectData('initial_residual', ''), 'variable_metadata': ObjectData('variable_metadata', '') } # Load metadata with open(db_file, 'rb') as f: db = pickle.load(f) if db['version'] != __version__: raise InvalidCacheError( 'Cache generated for a different version of pymola') if db['library_os'] != os.name: raise InvalidCacheError('Cache generated for incompatible OS') # Include references to the shared libraries for o, d in objects.items(): f = ca.external(o, db[d.key]) setattr(model, '_' + o + '_function', f) # Load variables per category variables_with_metadata = [ 'states', 'alg_states', 'inputs', 'parameters', 'constants' ] variable_dict = {} for key in variables_with_metadata: variables = getattr(model, key) for i, d in enumerate(db[key]): variable = Variable.from_dict(d) variables.append(variable) variable_dict[variable.symbol.name()] = variable model.der_states = [Variable.from_dict(d) for d in db['der_states']] model.outputs = db['outputs'] model.delayed_states = db['delayed_states'] # Evaluate variable metadata: # We do this in three passes, so that we have constant attributes available through the API, # and non-constant expressions as Function calls. # 1. Extract independent parameter values metadata = dict( zip( variables_with_metadata, model.variable_metadata_function( ca.veccat(*[np.nan for v in model.parameters])))) for key in variables_with_metadata: for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(ast.Symbol.ATTRIBUTES): setattr(variable, tmp, metadata[key][i, j]) # 2. Plug independent values back into metadata function, to obtain values (such as bounds, or starting values) # dependent upon independent parameter values. metadata = dict( zip( variables_with_metadata, model.variable_metadata_function( ca.veccat(*[ v.value if v.value.is_regular() else np.nan for v in model.parameters ])))) for key in variables_with_metadata: for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(ast.Symbol.ATTRIBUTES): setattr(variable, tmp, metadata[key][i, j]) # 3. Fill in any irregular elements with expressions to be evaluated later. # Note that an expression is neccessary only if the function value actually depends on the inputs. # Otherwise, we would be dealing with a genuine NaN value. parameter_vector = ca.veccat(*[v.symbol for v in model.parameters]) metadata = dict( zip( variables_with_metadata, model.variable_metadata_function( ca.veccat(*[ v.value if v.value.is_regular() else v.symbol for v in model.parameters ])))) for k, key in enumerate(variables_with_metadata): for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(ast.Symbol.ATTRIBUTES): if not getattr(variable, tmp).is_regular(): if (not isinstance(metadata[key][i, j], ca.DM) and ca.depends_on(metadata[key][i, j], parameter_vector)): setattr(variable, tmp, metadata[key][i, j]) # Done return model
def load_model(model_folder: str, model_name: str, compiler_options: Dict[str, str]) -> CachedModel: """ Loads a precompiled CasADi model into a CachedModel instance. :param model_folder: Folder where the precompiled CasADi model is located. :param model_name: Name of the model. :param compiler_options: Dictionary of compiler options. :returns: CachedModel instance. """ db_file = os.path.join(model_folder, model_name + ".pymoca_cache") if compiler_options.get('mtime_check', True): # Mtime check cache_mtime = os.path.getmtime(db_file) for folder in [model_folder] + compiler_options.get('library_folders', []): for root, dir, files in os.walk(folder, followlinks=True): for item in fnmatch.filter(files, "*.mo"): filename = os.path.join(root, item) if os.path.getmtime(filename) > cache_mtime: raise InvalidCacheError("Cache out of date") # Create empty model object model = CachedModel() # Load metadata with open(db_file, 'rb') as f: db = pickle.load(f) if db['version'] != __version__: raise InvalidCacheError('Cache generated for a different version of pymoca') # Check compiler options. We ignore the library folders, as they have # already been checked, and checking them will impede platform # portability of the cache. exclude_options = ['library_folders'] old_opts = {k: v for k, v in db['options'].items() if k not in exclude_options} new_opts = {k: v for k, v in compiler_options.items() if k not in exclude_options} if old_opts != new_opts: raise InvalidCacheError('Cache generated for different compiler options') # Pickles are platform independent, but dynamic libraries are not if compiler_options.get('codegen', False): if db['library_os'] != os.name: raise InvalidCacheError('Cache generated for incompatible OS') # Include references to the shared libraries for o in ['dae_residual', 'initial_residual', 'variable_metadata', 'delay_arguments']: if isinstance(db[o], str): # Path to codegen'd library f = ca.external(o, db[o]) else: # Pickled CasADi Function; use as is assert isinstance(db[o], ca.Function) f = db[o] setattr(model, '_' + o + '_function', f) # Load variables per category variables_with_metadata = ['states', 'alg_states', 'inputs', 'parameters', 'constants'] variable_dict = {} for key in variables_with_metadata: variables = getattr(model, key) for i, d in enumerate(db[key]): variable = Variable.from_dict(d) variables.append(variable) variable_dict[variable.symbol.name()] = variable model.der_states = [Variable.from_dict(d) for d in db['der_states']] model.outputs = db['outputs'] model.delay_states = db['delay_states'] model.alias_relation = db['alias_relation'] # Evaluate variable metadata: parameter_vector = ca.veccat(*[v.symbol for v in model.parameters]) metadata = dict(zip(variables_with_metadata, model.variable_metadata_function(parameter_vector))) independent_metadata = dict(zip( variables_with_metadata, (np.array(x) for x in model.variable_metadata_function(ca.veccat(*[np.nan for v in model.parameters]))))) for k, key in enumerate(variables_with_metadata): m = db[key + "__metadata_dependent"] for i, d in enumerate(db[key]): variable = variable_dict[d['name']] for j, tmp in enumerate(CASADI_ATTRIBUTES): if m[i, j]: setattr(variable, tmp, metadata[key][i, j]) else: setattr(variable, tmp, independent_metadata[key][i, j]) # Evaluate delay arguments: if model.delay_states: args = [model.time, ca.veccat(*model._symbols(model.states)), ca.veccat(*model._symbols(model.der_states)), ca.veccat(*model._symbols(model.alg_states)), ca.veccat(*model._symbols(model.inputs)), ca.veccat(*model._symbols(model.constants)), ca.veccat(*model._symbols(model.parameters))] delay_arguments_raw = model.delay_arguments_function(*args) nan_args = [ca.repmat(np.nan, *arg.size()) for arg in args] independent_delay_arguments_raw = model.delay_arguments_function(*nan_args) delay_expressions_raw = delay_arguments_raw[::2] delay_durations_raw = delay_arguments_raw[1::2] independent_delay_durations_raw = independent_delay_arguments_raw[1::2] assert 1 == len({len(delay_expressions_raw), len(delay_durations_raw), len(independent_delay_durations_raw)}) all_symbols = [model.time, *model._symbols(model.states), *model._symbols(model.der_states), *model._symbols(model.alg_states), *model._symbols(model.inputs), *model._symbols(model.constants), *model._symbols(model.parameters)] duration_dependencies = db['__delay_duration_dependent'] # Get rid of false dependency symbols not used in any delay # durations. This significantly reduces the work the (slow) # substitute() calls have to do later on. actual_deps = sorted(set(np.array(duration_dependencies).ravel())) actual_dep_symbols = [np.nan] * len(all_symbols) for i in actual_deps: actual_dep_symbols[i] = all_symbols[i] delay_durations_simplified = ca.Function( 'replace_false_deps', all_symbols, delay_durations_raw).call( actual_dep_symbols) # Get rid of remaining hidden dependencies in the delay durations for i, expr in enumerate(delay_expressions_raw): if duration_dependencies[i]: dur = delay_durations_simplified[i] if len(duration_dependencies[i]) < len(actual_deps): deps = set(ca.symvar(dur)) actual_deps = {all_symbols[j] for j in duration_dependencies[i]} false_deps = deps - actual_deps if false_deps: [dur] = ca.substitute( [dur], list(false_deps), [np.nan] * len(false_deps)) else: # Already removed all false dependencies pass else: dur = independent_delay_durations_raw[i] model.delay_arguments.append(DelayArgument(expr, dur)) # Try to coerce parameters into their Python types for p in model.parameters: for attr in CASADI_ATTRIBUTES: v = getattr(p, attr) v_mx = ca.MX(v) if v_mx.is_constant() and v_mx.is_regular(): setattr(p, attr, p.python_type(v)) # Done return model