Skip to content

Python-Julia calling patterns

This page documents the internal bridge between Python and Julia. Most users do not need this — the standard from quanestimation import * handles everything automatically. This reference is for advanced use cases that require direct interaction with the Julia runtime.

How the bridge works

Python is a thin wrapper over Julia. When you call Lindblad(...) or QFIM(...) in Python, the call is transparently forwarded to Julia. Most API calls work exactly as you'd expect — you pass Python data (numpy arrays, lists, strings) and get results back.

Direct Julia access

The QJL singleton provides a handle to the Julia runtime:

from quanestimation.base import QJL
QJL is initialized once on first access and shared across all modules.

Calling patterns

Constructing Julia objects

Python constructs Julia structs by calling them as functions with Python data:

from quanestimation import Lindblad, GeneralScheme

dynamics = Lindblad(H0, dH, tspan, decay=[], dyn_method="Expm")
scheme = GeneralScheme(probe=rho0, param=dynamics)

Bang functions

Julia functions ending in ! (in-place mutation) are not valid Python identifiers. Use getattr to call them:

import quanestimation as qe
getattr(qe.QJL, "optimize!")(scheme, opt, algorithm=alg, objective=obj)

Explicit type conversion

When Julia cannot infer types from Python data (empty lists, ambiguous shapes, complex numbers), use juliacall.convert:

from juliacall import Main as jl, convert as jlconvert
ctrl_bound = jlconvert(jl.Vector[jl.Float64], [0.0, 1.0])

Return values

Most results flow back to Python via .npy files written by Julia. For functions that return values directly, _unwrap() converts Julia arrays to numpy arrays.