invode.utils

invode.utils#

Functions

load_matlab_data(file_path)

local_refine(best_params, ode_func, ...[, ...])

Perform local gradient-based optimization to refine parameter estimates.

invode.utils.load_matlab_data(file_path)[source]#
invode.utils.local_refine(best_params, ode_func, error_func, fixed_params, param_bounds, method='L-BFGS-B', verbose=False)[source]#

Perform local gradient-based optimization to refine parameter estimates.

This function takes a set of promising parameter values (typically from global optimization) and applies local optimization techniques to find a nearby local optimum with higher precision. It uses gradient-based methods that can efficiently navigate smooth objective function landscapes to achieve fine-scale convergence.

The function is designed to work seamlessly with mixed parameter sets containing both varying (free) and fixed parameters. Only the free parameters are optimized, while fixed parameters remain constant throughout the refinement process.

Local refinement is particularly effective when:

  • The objective function is smooth and differentiable near the optimum

  • Global optimization has identified a promising region

  • High precision is required for the final parameter estimates

  • Gradient information is available or can be approximated numerically

Parameters:
  • best_params (dict) –

    Initial parameter estimates to be refined. Should contain values for all parameters required by the ODE function. These typically come from a global optimization phase and represent a good starting point for local search.

    Example: {'k1': 2.5, 'k2': 0.15, 'alpha': 0.8, 'beta': 1.2}

  • ode_func (callable) – The ODE solver function that takes a parameter dictionary and returns model output. Must have signature ode_func(params) -> output where params is a dictionary of parameter values and output is the model prediction that will be compared against data.

  • error_func (callable) – Error/objective function that quantifies model fit quality. Must have signature error_func(model_output) -> float where model_output is the result from ode_func and the return value is a scalar error measure (lower values indicate better fit).

  • fixed_params (dict) –

    Dictionary of parameters that should remain constant during optimization. These parameters will be included in every evaluation but their values will not be modified by the local optimizer.

    Example: {'temp': 298.15, 'pH': 7.4}

  • param_bounds (dict) –

    Dictionary mapping parameter names to their allowable bounds. Each value should be a tuple (min_val, max_val). Only bounds for free parameters (those not in fixed_params) are used during optimization.

    Example: {'k1': (0.1, 10.0), 'k2': (0.01, 1.0)}

  • method (str, optional) –

    Optimization algorithm to use. Must be a method supported by scipy.optimize.minimize that handles bound constraints. Common choices:

    • 'L-BFGS-B' (default): Limited-memory BFGS with bounds

    • 'TNC': Truncated Newton with bounds

    • 'SLSQP': Sequential Least Squares Programming

    • 'trust-constr': Trust-region constrained optimization

    Default is ‘L-BFGS-B’ which provides good performance for smooth problems.

  • verbose (bool, optional) – If True, print detailed information about the optimization process, including refined parameter values, final error, and success status. Useful for debugging and monitoring convergence. Default is False.

Returns:

A tuple containing the refined results:

  • refined_params (dict): The optimized parameter set containing both refined free parameters and unchanged fixed parameters. Has the same keys as the input best_params.

  • refined_error (float): The error value achieved by the refined parameters. This should be less than or equal to the initial error if optimization was successful.

Return type:

tuple of (dict, float)

Notes

Algorithm Details:

The function implements a parameter space transformation to work with scipy’s optimize interface:

  1. Separates free parameters (to be optimized) from fixed parameters

  2. Creates a wrapper function that converts parameter vectors back to dictionaries

  3. Merges free and fixed parameters for each function evaluation

  4. Handles exceptions gracefully by returning infinite error for failed evaluations

Error Handling:

The wrapped objective function includes robust exception handling. If the ODE solver or error function raises any exception (numerical instability, domain errors, etc.), the function returns np.inf, allowing the optimizer to continue with other parameter values.

Convergence Criteria:

The optimization uses scipy’s default convergence criteria for the selected method. These typically include:

  • Gradient norm tolerance

  • Function value change tolerance

  • Maximum iteration limits

  • Constraint satisfaction tolerance

Parameter Bounds:

All free parameters are constrained to remain within their specified bounds throughout the optimization process. The optimizer will not evaluate parameter values outside these bounds.

Examples

Basic usage after global optimization:

>>> # Assume we have results from global optimization
>>> global_best = {'k1': 2.3, 'k2': 0.18, 'temp': 298.15}
>>> fixed = {'temp': 298.15}  # Temperature held constant
>>> bounds = {'k1': (0.1, 10.0), 'k2': (0.01, 1.0)}
>>>
>>> refined_params, refined_error = local_refine(
...     global_best, my_ode_solver, my_error_function,
...     fixed, bounds, verbose=True
... )
>>> print(f"Refined parameters: {refined_params}")
>>> print(f"Final error: {refined_error:.6f}")

Comparing different optimization methods:

>>> methods = ['L-BFGS-B', 'TNC', 'SLSQP']
>>> results = {}
>>>
>>> for method in methods:
...     params, error = local_refine(
...         initial_params, ode_func, error_func,
...         fixed_params, param_bounds, method=method
...     )
...     results[method] = (params, error)
...     print(f"{method}: error = {error:.6f}")

Integration with global optimization workflow:

>>> def optimize_ode_model(ode_func, error_func, param_config):
...     # Global optimization phase
...     global_optimizer = ODEOptimizer(
...         ode_func=ode_func,
...         error_func=error_func,
...         **param_config
...     )
...     global_best, global_error = global_optimizer.fit()
...
...     # Local refinement phase
...     refined_best, refined_error = local_refine(
...         global_best, ode_func, error_func,
...         param_config['fixed_params'],
...         param_config['param_bounds'],
...         method='L-BFGS-B',
...         verbose=True
...     )
...
...     improvement = global_error - refined_error
...     print(f"Local refinement improved error by {improvement:.6f}")
...     return refined_best, refined_error

Handling optimization failures:

>>> try:
...     refined_params, refined_error = local_refine(
...         best_params, ode_func, error_func, fixed_params, bounds
...     )
...     if refined_error == np.inf:
...         print("Warning: Local optimization failed to find valid solution")
...         # Fall back to global optimization result
...         refined_params = best_params
... except Exception as e:
...     print(f"Local refinement failed: {e}")
...     refined_params = best_params  # Use original parameters

Performance Tips#

Method Selection:

  • Use 'L-BFGS-B' for smooth, well-behaved problems (default choice)

  • Use 'TNC' for problems with more complex constraint handling needs

  • Use 'trust-constr' for highly nonlinear problems or when high accuracy is needed

  • Avoid gradient-free methods unless the objective function is non-smooth

Numerical Stability:

  • Ensure ODE solver uses appropriate tolerances for the refinement scale

  • Consider parameter scaling if parameters have very different magnitudes

  • Monitor for numerical issues in verbose mode

Computational Efficiency:

  • Local refinement is typically fast (10-100 function evaluations)

  • Most time is spent in ODE solving, not optimization overhead

  • Consider parallel local refinement of multiple candidates

Troubleshooting#

Common Issues:

  • Optimization not improving: Check that the starting point is reasonable and within bounds. Verify the objective function is smooth locally.

  • Numerical errors: Reduce ODE solver tolerances or adjust parameter bounds to avoid regions where the model becomes unstable.

  • Slow convergence: Try different optimization methods or check for ill-conditioning in the parameter space.

See also

scipy.optimize.minimize

The underlying optimization function

ODEOptimizer.fit

Global optimization method that often precedes local refinement

lhs_sample

Parameter sampling function for global search phase