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 scipy and pyswarms.

  • 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 callable for 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.

NRV optimization structure

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:

res_optim = my_prob(**kwargs)

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.

Built-in Context Modifiers

Name

Description

See Also

stimulus_CM

Modifies electrode stimulus using interpolation or waveform generation from input vectors.

o02, T5

biphasic_stimulus_CM

Specializes stimulus_CM to configure biphasic pulses using user inputs.

o03, T5

harmonic_stimulus_CM

Specializes stimulus_CM to configure harmonic pulses.

o04

You can also define your own modifier:

def homemade_context_modifier(X: np.ndarray, static_context: NRV_simulable, **kwargs) -> NRV_simulable:
    local_sim = nrv.load_any(static_context, ...)
    # Modify local_sim based on X
    return local_sim

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:

  1. Supports algebraic composition of multiple evaluations.

  2. Integrates cleanly with NRV’s optimization framework.

Built-in Cost Evaluations

Name

Description

raster_count_CE

Counts spikes per fiber during simulation.

recrutement_count_CE

Counts activated or non-activated fibers.

charge_quantity_CE

Estimates charge injected by one or more electrodes.

stim_energy_CE

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, ...)
Available Optimizers

Name

Description

scipy_optimizer

Interface to scipy.optimize.minimize.

PSO_optimizer

Particle Swarm Optimizer using Pyswarms.

Tip

Choose your optimizer based on problem type:

Warning

Pyswarms support may be replaced by scikit-opt in future versions of NRV.