Source code for biogeme.expressions.jax_utils

"""Define various items for Jax

Michel Bierlaire
Tue Mar 18 18:28:07 2025
"""

from collections.abc import Callable
from typing import TypeAlias

import jax.numpy as jnp
from biogeme.profiling import JaxExecutionProfile
from jax import jit, vmap

JaxFunctionType: TypeAlias = Callable[
    [jnp.ndarray, jnp.ndarray, jnp.ndarray, jnp.ndarray], jnp.ndarray
]

_VECTOR_FUNCTION_CACHE: dict[tuple[int, bool], JaxFunctionType] = {}


[docs] def build_vectorized_function( the_function: JaxFunctionType, use_jit: bool, profiler: JaxExecutionProfile | None = None, profile_name: str | None = None, ) -> JaxFunctionType: """Build and cache the row-wise vectorized version of a JAX function. The returned callable applies ``the_function`` observation by observation, vectorizing jointly over the rows of ``data`` and ``draws`` while broadcasting ``parameters`` and ``random_variables``. """ profiler = profiler if profiler is not None else JaxExecutionProfile() function_name = ( profile_name if profile_name is not None else f'vectorized_function_{id(the_function)}' ) cache_key = (id(the_function), use_jit, id(profiler), function_name) cached = _VECTOR_FUNCTION_CACHE.get(cache_key) if cached is not None: profiler.add_note(f'Cache hit for {function_name}.') return cached def row_function(row, draw, parameters, random_variables): return the_function(parameters, row, draw, random_variables) mapped_function = vmap( row_function, in_axes=(0, 0, None, None), ) if use_jit: compiled_function = jit(mapped_function) def vectorized_function(parameters, data, draws, random_variables): return profiler.timed_call( function_name, compiled_function, data, draws, parameters, random_variables, ) else: def vectorized_function(parameters, data, draws, random_variables): return profiler.timed_call( function_name, mapped_function, data, draws, parameters, random_variables, ) profiler.record_build(function_name) _VECTOR_FUNCTION_CACHE[cache_key] = vectorized_function return vectorized_function