Optimization
NRV includes an integrated optimization layer, enabling direct tuning of input parameters to optimize the outcomes of simulations.
See also
Tutorial 5 — First optimization problem using NRV
Optimization Problem
In NRV, an optimization problem consists of two main components:
An optimizer: based on algorithms from third-party libraries such as
scipyandpyswarms.A cost function: a function \(\mathbb{R}^n \rightarrow \mathbb{R}\) to be minimized.
To evaluate how specific parameters affect simulations and overall cost, NRV defines a nrv.optim.cost_function class composed of:
A filter (optional): a Python
callablefor vector formatting or dimensionality reduction.A static context: an NRV simulable object (axon, fascicle, or nerve) used as the simulation base.
A context modifier: generates a modified local context from the static context and input vector.
A cost evaluation: evaluates simulation results to compute a scalar cost. This is a generic, user-definable
callable.A saver (optional): stores intermediate parameters or results during optimization.
The optimization problem is managed in NRV using the Problem class. A typical setup looks like:
import nrv
my_prob = nrv.Problem()
my_prob.costfunction = my_cost
my_prob.optimizer = my_optimizer
Once defined, the problem can be executed by calling the instance:
Note
Additional keyword arguments (kwargs) can be passed to override optimizer parameters at runtime.
Tip
Parallel execution is fully supported by Problem and cost_function. Optimization involving nerve or fascicle contexts can be parallelized. See the Parallel Computation Guide for more details.
Cost Function
The first component of an optimization problem is the cost function. NRV provides the cost_function class, enabling users to customize cost functions using three main components:
A static context
A context modifier
A cost evaluation
You can instantiate the cost function directly:
my_cost = nrv.cost_function(
static_context=my_static_context,
context_modifier=my_context_modifier,
cost_evaluation=my_cost_evaluation,
kwargs_S=kwarg_sim,
kwargs_CM=kwarg_cm,
kwargs_CE=kwarg_ce
)
Or define it incrementally:
my_cost = nrv.cost_function()
my_cost.set_static_context(my_static_context, **kwarg_sim)
my_cost.set_context_modifier(my_context_modifier, **kwarg_cm)
my_cost.set_cost_evaluation(my_cost_evaluation, **kwarg_ce)
Warning
cost_function cannot currently be saved using the save method due to the custom nature of the cost_evaluation component. This feature is planned for a future release.
Context Modifier
Context modifiers are callable objects that modify the static context based on the input vector. They may change stimulation parameters, electrode configurations, or other simulation features.
NRV includes several built-in context modifiers, all inheriting from context_modifier.
Name |
Description |
See Also |
|---|---|---|
Modifies electrode stimulus using interpolation or waveform generation from input vectors. |
o02, T5 |
|
Specializes stimulus_CM to configure biphasic pulses using user inputs. |
o03, T5 |
|
Specializes stimulus_CM to configure harmonic pulses. |
o04 |
You can also define your own modifier:
Note
Custom context modifier classes should implement the __call__ method with the structure shown above.
Cost Evaluation
Cost evaluations compute a scalar metric from simulation results. These are also callable and typically subclass cost_evaluation.
Benefits of subclassing:
Supports algebraic composition of multiple evaluations.
Integrates cleanly with NRV’s optimization framework.
Name |
Description |
|---|---|
Counts spikes per fiber during simulation. |
|
Counts activated or non-activated fibers. |
|
Estimates charge injected by one or more electrodes. |
|
Estimates energy injected during stimulation. |
Warning
cost_evaluation and its subclasses currently cannot be saved using the save method.
You can define a custom evaluation:
def homemade_cost_evaluation(results: sim_results, **kwargs) -> float:
# Analyze `results` and return scalar cost
return cost
Or define a class:
class homemade_cost_evaluation(nrv.cost_evaluation):
def call_method(self, results: sim_results, **kwargs) -> float:
return cost
Alternatively:
def __call__(self, results: sim_results, **kwargs) -> float:
return cost
Filter (optional)
Filters format the input vector before it is passed to the context modifier.
my_cost = nrv.cost_function(
static_context=my_static_context,
...,
filters=my_filter
)
Warning
Filters are not recommended and may be deprecated in future versions. Consider integrating input formatting into the context modifier instead.
Optimizer
The second major component is the optimizer, which defines how to minimize the cost function. NRV provides two built-in optimizers, both subclasses of Optimizer.
You can use either of the following styles:
res = my_optimizer.minimize(func_to_minimize, ...)
# or simply
res = my_optimizer(func_to_minimize, ...)
Name |
Description |
|---|---|
Interface to scipy.optimize.minimize. |
|
Particle Swarm Optimizer using Pyswarms. |
Tip
Choose your optimizer based on problem type:
Use
scipy_optimizerfor continuous problems.Use
PSO_optimizerfor discontinuous problems
Warning
Pyswarms support may be replaced by scikit-opt in future versions of NRV.