Skip to content

🇬🇧 English · 🇪🇸 Español

Ejecución en cualquier entorno: iPad, Juno y el navegador

El núcleo numérico de puremacro se basa en NumPy, SciPy, Pandas y Matplotlib puros, lo que lo hace importable en un iPad o en un navegador web mediante Pyodide y WebAssembly. No obstante, ser importable no equivale a ser inmediatamente utilizable: cuatro servicios que un sistema de escritorio provee de forma transparente están ausentes en una tableta, y este módulo resuelve esa brecha operativa.

Diagnóstico inicial: ¿qué puede hacer este dispositivo?

from puremacro import runtime
print(runtime.report())

Salida típica en un entorno Pyodide:

puremacro runtime
  host       : pyodide 3.12.7 (wasm32)
  device     : tablet
  network    : js-fetch (call runtime.enable_browser_network())
  parquet    : unavailable -> use puremacro.runtime.store / pocket
  threads    : no (1 cpu, unknown)
  writable fs: yes
  backends   : numpy

La detección se realiza por heurística del sistema operativo, el entorno de aislamiento de iOS (/var/mobile/) y el agente de usuario del navegador. Cada campo puede fijarse manualmente mediante variables de entorno si la detección automática discrepa: PUREMACRO_HOST, PUREMACRO_DEVICE, PUREMACRO_SOCKETS, PUREMACRO_PARQUET, PUREMACRO_THREADS.


1. Sin sockets de red

Bajo WebAssembly / Pyodide no existe una pila de red TCP tradicional, por lo que las librerías requests y urllib fallan. El navegador, no obstante, sí puede realizar peticiones HTTP mediante la API nativa de JavaScript fetch. Una única llamada encamina toda la infraestructura de descarga a través del navegador:

# requiere: un kernel Pyodide / navegador con acceso a red
from puremacro import runtime
from puremacro.fetch import fetch_xrate_monthly

runtime.enable_browser_network()
fx = fetch_xrate_monthly(["MEX"])

Para revertir al modo tradicional, ejecute runtime.disable_browser_network().


2. Puede no haber motor Parquet (pyarrow)

Como pyarrow es un extra opcional (puremacro[io]) y sólo las distribuciones recientes de Pyodide (314.x) lo incluyen, puremacro.pocket empaqueta los datos en cartuchos portátiles .pmz autoverificables con firma SHA-256 que llevan consigo su propia procedencia histórica:

import numpy as np
import pandas as pd
from puremacro import pocket

# Un DataFrame para transportar (en la práctica: el panel recién descargado en la estación de trabajo)
panel = pd.DataFrame(
    {"USA": [1.0, 1.2, 1.1], "DEU": [0.8, 0.9, 1.0]},
    index=pd.period_range("2025Q1", periods=3, freq="Q"),
)

# En la estación de trabajo:
pocket.pack(panel, "panel_g7.pmz", source="OECD QNA", vintage="2026-09-01")

# En el iPad (incluso en modo avión):
cart = pocket.load("panel_g7.pmz")
panel = cart.frame()              # Comprobación de integridad por SHA-256 al leer
print(cart.provenance.vintage)    # '2026-09-01'
print(cart.summary())

3. Suspensión de aplicaciones en iPadOS

Cuando el usuario conmuta entre aplicaciones en iPadOS, el sistema suspende los procesos en segundo plano, interrumpiendo cálculos largos. puremacro.longrun ejecuta el trabajo en bloques reanudables y almacena puntos de control (checkpoints):

import numpy as np
from puremacro import longrun

rng = np.random.default_rng(0)
Y = rng.standard_normal((120, 2))          # sus datos del VAR

def iteracion_svar(i):
    """Una replicación bootstrap: devuelve aquello de lo que quiere las bandas."""
    idx = np.random.default_rng(i).integers(0, len(Y), len(Y))
    return Y[idx].mean(axis=0)

job = longrun.bootstrap(iteracion_svar, 2000, checkpoint="svar_irf.ckpt")
job.run(seconds=30)  # Computa 30 segundos y guarda el avance en disco
# ... tras responder un mensaje o reabrir la aplicación:
job.run(seconds=30)
bandas = np.percentile(job.result(), [5, 95], axis=0)

Cada extracción \(i\) utiliza deterministamente default_rng([seed, i]), de modo que un cálculo ejecutado a lo largo de varias sesiones fragmentadas genera resultados bit a bit idénticos a una ejecución continua, condición indispensable para publicaciones científicas.

n_jobs recurre a un bucle secuencial

Bajo Pyodide no hay hilos del sistema operativo operativos, y allí ThreadPoolExecutor o bien se niega a arrancar o bien no ejecuta nada. Cinco motores de bootstrap aceptan n_jobs — wild_bootstrap, wild_bootstrap_var, block_bootstrap, cum_irf_block_bootstrap y bootstrap_bands — y los cinco pasan por un único punto que decide entre el grupo de hilos y el bucle:

  • Cada motor extrae todo su material aleatorio por adelantado con el generador que usted le pasa y después aplica una función pura por replicación. Executor.map preserva el orden de entrada, de modo que el grupo de hilos y el bucle devuelven resultados idénticos; solo cambia el tiempo de reloj.
  • Antes de construir un ejecutor, el motor consulta runtime.capabilities().threads. Cuando la respuesta es negativa — un núcleo Pyodide/WASM, o PUREMACRO_THREADS=0 — se ejecuta el bucle y no se construye ejecutor alguno. Su n_jobs simplemente se ignora; nada lanza excepción.
  • Si se intenta crear el grupo de hilos y no puede arrancar (RuntimeError, OSError o ValueError desde ThreadPoolExecutor), un RuntimeWarning nombra el motor e indica que las replicaciones se evalúan secuencialmente.
  • n_jobs=0 lanza ValueError antes de ejecutar nada; un n_jobs negativo significa todos los núcleos de CPU; n_jobs=1 es el bucle.
  • Una excepción lanzada dentro de su función por replicación nunca se silencia: se propaga sin alterar por cualquiera de las dos rutas, de modo que un reajuste fallido se reporta en vez de reintentarse en silencio.

PUREMACRO_THREADS es un interruptor booleano, no un número de hilos. Como las demás variables de anulación de capacidades, solo es verdadera con 1, true, yes u on; cualquier otro valor — incluido PUREMACRO_THREADS=8 — se lee como sin hilos y convierte todo n_jobs en un bucle secuencial. Para elegir el número de trabajadores, use n_jobs.

from puremacro import runtime
runtime.capabilities().threads      # el veredicto que consultan los cinco motores
runtime.capabilities().overridden   # ('threads',) cuando lo fijó la variable de entorno

4. Descarga de cómputo a Google Colab (runtime.colab)

Cuando una tarea excede la memoria o capacidad de cálculo del dispositivo móvil (como cadenas largas de MCMC o bootstraps de gran escala), puremacro.runtime.colab genera automáticamente un cuaderno de Google Colab conectado con Google Drive:

from puremacro.runtime.colab import (
    generate_colab_notebook,
    show_colab_offload_dialog,
    load_colab_result,
)

# 1. Genera un cuaderno autocontenido: el código se pasa como primer argumento;
#    output_filename es el cartucho que el cuaderno guardará al terminar.
nb = generate_colab_notebook(
    """
import puremacro as pm
result = pm.dsge.estimate_sw07(n_draws=10000, n_chains=4)
""",
    mount_drive=True,
    save_path="sw07_colab.ipynb",
    output_filename="sw07_posterior.pmz",
)

# 2. Muestra las instrucciones (tarjeta HTML en Juno/Jupyter, texto en la terminal)
show_colab_offload_dialog("sw07_colab.ipynb")

# 3. Al volver el cartucho desde Google Drive:
# posterior = load_colab_result("sw07_posterior.pmz")

Modelos locales en el iPad

El Python propio de la app Juno (no el kernel Pyodide de juno.sh) incluye MLX y mlx-lm. En el iPad con Juno se selecciona MLX, donde mlx y mlx-lm importan; una completación ahí aún no está verificada en un dispositivo. Los motores se eligen por lo que se puede importar, nunca por el sistema operativo, y por eso allí se elige MLX; las funciones narrativas con LLM (score_llm, llm_prob_kernel) pasan por la misma puerta de entrada de puremacro.llm. Tres cosas que conviene saber:

  • Mantenga Juno en primer plano mientras se descarga el modelo y mientras se puntúa un corpus; una app suspendida detiene ambas cosas. La caché de completados conserva cada respuesta ya calculada.
  • Los modelos persisten en el contenedor de la app. El primer completado descarga el modelo de referencia, qwen3-4b (unos 2.3 GB); las sesiones posteriores lo encuentran allí.
  • Menos cómputo: 117 GFLOPS en un producto de matrices float32 en un iPad Pro M4, frente a 430 en una Mac M3 Pro. Una caché construida en una Mac y llevada al iPad responde en modo cache sin ningún modelo, y eso es también lo que puede hacer el navegador.

Un completado con el mlx-lm de Juno aún no se ha verificado en un dispositivo. La instalación, la escalera de modelos, los modos, la distribución de una caché y la solución de problemas están en Modelos de lenguaje locales.

El Python propio de la app Juno no tiene pip ni micropip: instale puremacro allí con el gestor de paquetes de Juno. micropip solo existe en el kernel de navegador de juno.sh (Pyodide).