modularq 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
modularq-1.1.0/AUTHORS ADDED
@@ -0,0 +1,12 @@
1
+ # Authors — modularq
2
+
3
+ ## Theory & Research
4
+ Christian H. Balfagón
5
+ - Paper: "A Modular Software Stack for Quantum Computing" (2025)
6
+ - Data: https://doi.org/10.5281/zenodo.18066279
7
+ - Contact: cb@balfagonresearch.org
8
+ - ORCID: 0009-0003-0835-5519
9
+
10
+ ## Implementation & Product
11
+ Mariano M. Castro
12
+ - Repository: https://github.com/nanocastro79/modularq
modularq-1.1.0/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ MIT License (Non-Commercial)
2
+
3
+ Copyright (c) 2025 Christian H. Balfagón and Mariano M. Castro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software for academic, research, and non-commercial purposes, subject
7
+ to the following conditions:
8
+
9
+ 1. The above copyright notice and this permission notice shall be included in
10
+ all copies or substantial portions of the Software.
11
+
12
+ 2. Commercial use — defined as use in a product or service that generates
13
+ revenue, or use by a for-profit organization — requires a separate
14
+ commercial license. Contact: cb@balfagonresearch.org
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: modularq
3
+ Version: 1.1.0
4
+ Summary: Modular Software Stack for Quantum Computing — measurement diagnostics and correction
5
+ Author: Mariano M. Castro
6
+ Author-email: "Christian H. Balfagon" <cb@balfagonresearch.org>
7
+ License: MIT License (Non-Commercial)
8
+
9
+ Copyright (c) 2025 Christian H. Balfagón and Mariano M. Castro
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software for academic, research, and non-commercial purposes, subject
13
+ to the following conditions:
14
+
15
+ 1. The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ 2. Commercial use — defined as use in a product or service that generates
19
+ revenue, or use by a for-profit organization — requires a separate
20
+ commercial license. Contact: cb@balfagonresearch.org
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
23
+
24
+ Project-URL: Homepage, https://nanocastro79.github.io/modularq/
25
+ Project-URL: Repository, https://github.com/nanocastro79/modularq
26
+ Keywords: quantum computing,qiskit,measurement,NISQ,Born rule
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Topic :: Scientific/Engineering :: Physics
29
+ Classifier: Intended Audience :: Science/Research
30
+ Requires-Python: >=3.9
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ License-File: AUTHORS
34
+ Requires-Dist: numpy>=1.24
35
+ Requires-Dist: scipy>=1.10
36
+ Provides-Extra: qiskit
37
+ Requires-Dist: qiskit>=1.0; extra == "qiskit"
38
+ Requires-Dist: qiskit-ibm-runtime>=0.20; extra == "qiskit"
39
+ Dynamic: license-file
40
+
41
+ # modularq 🔬
42
+
43
+ **Modular Software Stack for Quantum Computing**
44
+
45
+ Una librería Python que implementa diagnóstico y corrección de no-equilibrio modular en mediciones cuánticas, basada en:
46
+
47
+ > Balfagón, C. (2025). *A Modular Software Stack for Quantum Computing: From Born-Rule Equilibrium to Nonequilibrium-Aware Quantum Software*. Universidad de Buenos Aires.
48
+
49
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/nanocastro79/modularq/blob/main/demo_colab.ipynb)
50
+
51
+ ---
52
+
53
+ ## ¿Qué problema resuelve?
54
+
55
+ Las computadoras cuánticas NISQ operan como sistemas abiertos y ruidosos. Las estadísticas de medición pueden desviarse sistemáticamente de las predicciones ideales (Born rule) cuando el proceso de medición opera fuera del equilibrio termodinámico (KMS).
56
+
57
+ **modularq** detecta y corrige estas desviaciones automáticamente, sin modificar el hardware ni los circuitos.
58
+
59
+ ---
60
+
61
+ ## Instalación
62
+
63
+ ```bash
64
+ pip install numpy scipy matplotlib
65
+ # Luego descargá modularq.py desde este repo
66
+ ```
67
+
68
+ O en Google Colab:
69
+
70
+ ```python
71
+ !wget https://raw.githubusercontent.com/nanocastro79/modularq/main/modularq.py
72
+ from modularq import ModularAnalyzer
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Uso básico
78
+
79
+ ```python
80
+ import numpy as np
81
+ from modularq import ModularAnalyzer
82
+
83
+ # Matriz de densidad reconstruida (del subsistema medido)
84
+ rho = np.array([[0.52, 0.01], [0.01, 0.48]])
85
+
86
+ # Conteos experimentales
87
+ counts = {"0": 5200, "1": 4800}
88
+
89
+ # Born baseline
90
+ p0 = {"0": 0.5, "1": 0.5}
91
+
92
+ # Analizar
93
+ analyzer = ModularAnalyzer(eps=1e-6, mode="auto")
94
+ result = analyzer.analyze(rho, counts, p0)
95
+
96
+ print(result.summary())
97
+ # → MIS, régimen, probabilidades corregidas, comparación AIC
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Qué hace el stack (capas)
103
+
104
+ | Capa | Función |
105
+ |------|---------|
106
+ | 0 | Hardware cuántico (sin modificar) |
107
+ | 1 | Reconstrucción de estado (tomografía / shadows) |
108
+ | **2** | **Diagnóstico modular: K = -log(ρ), δK_i** ← núcleo |
109
+ | 3 | Clasificación: Born-válido / perturbativo / no-KMS |
110
+ | 4 | Regla de medición efectiva: reweighting exponencial |
111
+ | 5 | Control modular (minimizar imbalance) |
112
+ | 6 | API de software |
113
+ | 7 | Inferencia estadística (AIC/BIC) |
114
+
115
+ ---
116
+
117
+ ## Modular Imbalance Score (MIS)
118
+
119
+ Métrica escalar que indica la validez del Born rule:
120
+
121
+ | MIS | Régimen | Acción |
122
+ |-----|---------|--------|
123
+ | < 0.02 | ✅ KMS-balanced | Ninguna (Born válido) |
124
+ | 0.02 – 0.10 | ⚠️ Perturbativo | Corrección leve |
125
+ | > 0.10 | 🔴 No-KMS | Corrección necesaria |
126
+
127
+ ---
128
+
129
+ ## Reproducibilidad
130
+
131
+ Los experimentos originales están disponibles en Zenodo:
132
+ - DOI: [10.5281/zenodo.18066279](https://doi.org/10.5281/zenodo.18066279)
133
+ - Hardware: IBM Quantum (ibm_marrakesh, ibm_fez, ibm_torino)
134
+ - Circuitos: Bell (2q) y GHZ (3q)
135
+
136
+ ---
137
+
138
+ ## Licencia
139
+
140
+ **Uso académico y no comercial:** libre y gratuito bajo los términos de esta licencia.
141
+
142
+ **Uso comercial:** requiere acuerdo de licencia separado.
143
+ Contacto: cb@balfagonresearch.org
144
+
145
+ © 2025 Christian H. Balfagón and Mariano M. Castro.
@@ -0,0 +1,105 @@
1
+ # modularq 🔬
2
+
3
+ **Modular Software Stack for Quantum Computing**
4
+
5
+ Una librería Python que implementa diagnóstico y corrección de no-equilibrio modular en mediciones cuánticas, basada en:
6
+
7
+ > Balfagón, C. (2025). *A Modular Software Stack for Quantum Computing: From Born-Rule Equilibrium to Nonequilibrium-Aware Quantum Software*. Universidad de Buenos Aires.
8
+
9
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/nanocastro79/modularq/blob/main/demo_colab.ipynb)
10
+
11
+ ---
12
+
13
+ ## ¿Qué problema resuelve?
14
+
15
+ Las computadoras cuánticas NISQ operan como sistemas abiertos y ruidosos. Las estadísticas de medición pueden desviarse sistemáticamente de las predicciones ideales (Born rule) cuando el proceso de medición opera fuera del equilibrio termodinámico (KMS).
16
+
17
+ **modularq** detecta y corrige estas desviaciones automáticamente, sin modificar el hardware ni los circuitos.
18
+
19
+ ---
20
+
21
+ ## Instalación
22
+
23
+ ```bash
24
+ pip install numpy scipy matplotlib
25
+ # Luego descargá modularq.py desde este repo
26
+ ```
27
+
28
+ O en Google Colab:
29
+
30
+ ```python
31
+ !wget https://raw.githubusercontent.com/nanocastro79/modularq/main/modularq.py
32
+ from modularq import ModularAnalyzer
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Uso básico
38
+
39
+ ```python
40
+ import numpy as np
41
+ from modularq import ModularAnalyzer
42
+
43
+ # Matriz de densidad reconstruida (del subsistema medido)
44
+ rho = np.array([[0.52, 0.01], [0.01, 0.48]])
45
+
46
+ # Conteos experimentales
47
+ counts = {"0": 5200, "1": 4800}
48
+
49
+ # Born baseline
50
+ p0 = {"0": 0.5, "1": 0.5}
51
+
52
+ # Analizar
53
+ analyzer = ModularAnalyzer(eps=1e-6, mode="auto")
54
+ result = analyzer.analyze(rho, counts, p0)
55
+
56
+ print(result.summary())
57
+ # → MIS, régimen, probabilidades corregidas, comparación AIC
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Qué hace el stack (capas)
63
+
64
+ | Capa | Función |
65
+ |------|---------|
66
+ | 0 | Hardware cuántico (sin modificar) |
67
+ | 1 | Reconstrucción de estado (tomografía / shadows) |
68
+ | **2** | **Diagnóstico modular: K = -log(ρ), δK_i** ← núcleo |
69
+ | 3 | Clasificación: Born-válido / perturbativo / no-KMS |
70
+ | 4 | Regla de medición efectiva: reweighting exponencial |
71
+ | 5 | Control modular (minimizar imbalance) |
72
+ | 6 | API de software |
73
+ | 7 | Inferencia estadística (AIC/BIC) |
74
+
75
+ ---
76
+
77
+ ## Modular Imbalance Score (MIS)
78
+
79
+ Métrica escalar que indica la validez del Born rule:
80
+
81
+ | MIS | Régimen | Acción |
82
+ |-----|---------|--------|
83
+ | < 0.02 | ✅ KMS-balanced | Ninguna (Born válido) |
84
+ | 0.02 – 0.10 | ⚠️ Perturbativo | Corrección leve |
85
+ | > 0.10 | 🔴 No-KMS | Corrección necesaria |
86
+
87
+ ---
88
+
89
+ ## Reproducibilidad
90
+
91
+ Los experimentos originales están disponibles en Zenodo:
92
+ - DOI: [10.5281/zenodo.18066279](https://doi.org/10.5281/zenodo.18066279)
93
+ - Hardware: IBM Quantum (ibm_marrakesh, ibm_fez, ibm_torino)
94
+ - Circuitos: Bell (2q) y GHZ (3q)
95
+
96
+ ---
97
+
98
+ ## Licencia
99
+
100
+ **Uso académico y no comercial:** libre y gratuito bajo los términos de esta licencia.
101
+
102
+ **Uso comercial:** requiere acuerdo de licencia separado.
103
+ Contacto: cb@balfagonresearch.org
104
+
105
+ © 2025 Christian H. Balfagón and Mariano M. Castro.
@@ -0,0 +1 @@
1
+ from .core import ModularAnalyzer, ModularResult
@@ -0,0 +1,533 @@
1
+ """
2
+ modularq.py — Modular Software Stack for Quantum Systems
3
+ =========================================================
4
+ Implementación basada en:
5
+ Balfagón, C. (2025). "A Modular Software Stack for Quantum Computing"
6
+ Universidad de Buenos Aires.
7
+
8
+ Uso en Google Colab:
9
+ # Clonar repo y hacer:
10
+ from modularq import ModularAnalyzer
11
+
12
+ Autores: C. Balfagón (framework teórico) — Implementación open-source
13
+
14
+ Changelog:
15
+ v1.1 — Fix umbrales MIS contradictorios entre docstring y classify_regime
16
+ — Fix recomendación final unificada (MIS + AIC juntos)
17
+ """
18
+
19
+ import numpy as np
20
+ from scipy.linalg import logm
21
+ from typing import Dict, Tuple, Optional
22
+
23
+
24
+ # ─────────────────────────────────────────────
25
+ # CAPA 2: DIAGNÓSTICO MODULAR (núcleo)
26
+ # ─────────────────────────────────────────────
27
+
28
+ def regularize_state(rho: np.ndarray, eps: float = 1e-6) -> np.ndarray:
29
+ """
30
+ Regulariza la matriz de densidad para evitar valores singulares.
31
+ Ecuación (5) del paper: rho_eps = (1-eps)*rho + eps*I/d
32
+
33
+ Parámetros
34
+ ----------
35
+ rho : np.ndarray
36
+ Matriz de densidad (debe ser cuadrada, hermítica, traza=1)
37
+ eps : float
38
+ Parámetro de regularización (default: 1e-6)
39
+
40
+ Retorna
41
+ -------
42
+ np.ndarray : Matriz de densidad regularizada
43
+ """
44
+ d = rho.shape[0]
45
+ rho_eps = (1 - eps) * rho + eps * np.eye(d) / d
46
+ return rho_eps
47
+
48
+
49
+ def modular_generator(rho: np.ndarray, eps: float = 1e-6) -> np.ndarray:
50
+ """
51
+ Calcula el generador modular K = -log(rho_eps).
52
+ Ecuación (4) del paper.
53
+
54
+ Este es el objeto central del framework: captura cuánto se desvía
55
+ el estado medido del equilibrio (KMS).
56
+
57
+ Parámetros
58
+ ----------
59
+ rho : np.ndarray
60
+ Matriz de densidad del subsistema medido
61
+ eps : float
62
+ Parámetro de regularización
63
+
64
+ Retorna
65
+ -------
66
+ np.ndarray : Generador modular K
67
+ """
68
+ rho_eps = regularize_state(rho, eps)
69
+ K = -logm(rho_eps)
70
+ # Forzar que sea real si la parte imaginaria es despreciable
71
+ if np.max(np.abs(K.imag)) < 1e-10:
72
+ K = K.real
73
+ return K
74
+
75
+
76
+ def modular_imbalance(
77
+ K: np.ndarray,
78
+ p0: Dict[str, float]
79
+ ) -> Dict[str, float]:
80
+ """
81
+ Calcula el desbalance modular por resultado: deltaK_i.
82
+ Ecuación (6) del paper.
83
+
84
+ deltaK_i = <i|K|i> - sum_j(p0_j * <j|K|j>)
85
+
86
+ Mide cuánto se desvía cada resultado del equilibrio KMS.
87
+ Si todos los deltaK_i son ~0, el Born rule es válido.
88
+
89
+ Parámetros
90
+ ----------
91
+ K : np.ndarray
92
+ Generador modular (matriz)
93
+ p0 : dict
94
+ Probabilidades Born baseline {'00': 0.5, '11': 0.5, ...}
95
+
96
+ Retorna
97
+ -------
98
+ dict : Desbalance modular por resultado {'00': deltaK_00, ...}
99
+ """
100
+ outcomes = list(p0.keys())
101
+ n = len(outcomes)
102
+ d = K.shape[0]
103
+
104
+ if n != d:
105
+ raise ValueError(
106
+ f"Dimensión incompatible: p0 tiene {n} outcomes pero K es {d}x{d}. "
107
+ f"Asegurate de que el subsistema medido tenga {int(np.log2(d))} qubits."
108
+ )
109
+
110
+ # Elementos diagonales de K en la base de medición: <i|K|i>
111
+ Ki = {outcomes[i]: K[i, i].real for i in range(n)}
112
+
113
+ # Promedio ponderado por Born: sum_j p0_j * K_jj
114
+ K_bar = sum(p0[outcomes[j]] * Ki[outcomes[j]] for j in range(n))
115
+
116
+ # Desbalance: deltaK_i = K_ii - K_bar
117
+ deltaK = {outcome: Ki[outcome] - K_bar for outcome in outcomes}
118
+
119
+ return deltaK
120
+
121
+
122
+ def modular_imbalance_score(deltaK: Dict[str, float]) -> float:
123
+ """
124
+ Calcula el Modular Imbalance Score (MIS).
125
+ Ecuación (10) del paper.
126
+
127
+ MIS = (1/|O|) * sum_i |deltaK_i|
128
+
129
+ Interpretación:
130
+ - MIS < 0.05 → régimen KMS/Born válido (medición confiable)
131
+ - 0.05–0.15 → no-equilibrio perturbativo (posible corrección)
132
+ - MIS > 0.15 → no-KMS fuerte (corrección necesaria)
133
+
134
+ NOTA: estos umbrales son indicativos. La recomendación final
135
+ siempre se determina combinando MIS + comparación AIC.
136
+
137
+ Parámetros
138
+ ----------
139
+ deltaK : dict
140
+ Desbalance modular por resultado
141
+
142
+ Retorna
143
+ -------
144
+ float : MIS (adimensional, comparable entre dispositivos)
145
+ """
146
+ values = list(deltaK.values())
147
+ mis = np.mean(np.abs(values))
148
+ return float(mis)
149
+
150
+
151
+ # ─────────────────────────────────────────────
152
+ # CAPA 3: CLASIFICACIÓN DE RÉGIMEN
153
+ # ─────────────────────────────────────────────
154
+
155
+ def classify_regime(mis: float) -> Tuple[str, str]:
156
+ """
157
+ Clasifica el régimen de medición según el MIS.
158
+ Capa 3 del stack (Sección 3.4 del paper).
159
+
160
+ IMPORTANTE: esta función solo clasifica el régimen físico.
161
+ La recomendación final (si aplicar corrección o no) se determina
162
+ combinando este resultado con la comparación AIC en summary().
163
+
164
+ Umbrales (consistentes con modular_imbalance_score):
165
+ - MIS < 0.05 → KMS_BALANCED
166
+ - 0.05–0.15 → PERTURBATIVE_NEQ
167
+ - MIS > 0.15 → NON_KMS
168
+
169
+ Parámetros
170
+ ----------
171
+ mis : float
172
+ Modular Imbalance Score
173
+
174
+ Retorna
175
+ -------
176
+ tuple : (régimen, descripción)
177
+ """
178
+ if mis < 0.05:
179
+ return ("KMS_BALANCED",
180
+ "No-equilibrio no detectado (Born posiblemente válido).")
181
+ elif mis < 0.15:
182
+ return ("PERTURBATIVE_NEQ",
183
+ "No-equilibrio perturbativo detectado.")
184
+ else:
185
+ return ("NON_KMS",
186
+ "No-KMS fuerte detectado.")
187
+
188
+
189
+ # ─────────────────────────────────────────────
190
+ # CAPA 4: REGLA DE MEDICIÓN EFECTIVA
191
+ # ─────────────────────────────────────────────
192
+
193
+ def modular_probabilities(
194
+ p0: Dict[str, float],
195
+ deltaK: Dict[str, float]
196
+ ) -> Dict[str, float]:
197
+ """
198
+ Calcula las probabilidades corregidas por no-equilibrio modular.
199
+ Ecuación (7) del paper.
200
+
201
+ p_i = p0_i * exp(-deltaK_i) / Z
202
+ Z = sum_j p0_j * exp(-deltaK_j)
203
+
204
+ Propiedades garantizadas:
205
+ - Preserva normalización (suma = 1)
206
+ - Preserva positividad
207
+ - Reduce exactamente a Born cuando deltaK_i = 0
208
+
209
+ Parámetros
210
+ ----------
211
+ p0 : dict
212
+ Probabilidades Born baseline
213
+ deltaK : dict
214
+ Desbalance modular por resultado
215
+
216
+ Retorna
217
+ -------
218
+ dict : Probabilidades corregidas
219
+ """
220
+ outcomes = list(p0.keys())
221
+
222
+ # Pesos exponenciales
223
+ weights = {o: p0[o] * np.exp(-deltaK[o]) for o in outcomes}
224
+
225
+ # Factor de normalización
226
+ Z = sum(weights.values())
227
+
228
+ # Probabilidades normalizadas
229
+ p_mod = {o: weights[o] / Z for o in outcomes}
230
+
231
+ return p_mod
232
+
233
+
234
+ # ─────────────────────────────────────────────
235
+ # CAPA 7: INFERENCIA Y COMPARACIÓN DE MODELOS
236
+ # ─────────────────────────────────────────────
237
+
238
+ def log_likelihood(
239
+ counts: Dict[str, int],
240
+ probs: Dict[str, float],
241
+ eps_clip: float = 1e-12
242
+ ) -> float:
243
+ """
244
+ Calcula la log-verosimilitud multinomial.
245
+ Ecuación (22) del paper.
246
+
247
+ log L = sum_i n_i * log(p_i)
248
+
249
+ Parámetros
250
+ ----------
251
+ counts : dict
252
+ Conteos observados por resultado {'00': 5200, '11': 4700, ...}
253
+ probs : dict
254
+ Probabilidades del modelo
255
+ eps_clip : float
256
+ Evita log(0)
257
+
258
+ Retorna
259
+ -------
260
+ float : Log-verosimilitud
261
+ """
262
+ ll = 0.0
263
+ for outcome, count in counts.items():
264
+ p = max(probs.get(outcome, eps_clip), eps_clip)
265
+ ll += count * np.log(p)
266
+ return float(ll)
267
+
268
+
269
+ def compute_aic(log_l: float, k_params: int) -> float:
270
+ """
271
+ Criterio de Información de Akaike (AIC).
272
+ Penaliza modelos con más parámetros.
273
+
274
+ AIC = 2k - 2*log(L)
275
+
276
+ Parámetros
277
+ ----------
278
+ log_l : float
279
+ Log-verosimilitud del modelo
280
+ k_params : int
281
+ Número de parámetros libres del modelo
282
+
283
+ Retorna
284
+ -------
285
+ float : AIC (menor = mejor modelo)
286
+ """
287
+ return 2 * k_params - 2 * log_l
288
+
289
+
290
+ def compute_bic(log_l: float, k_params: int, n_shots: int) -> float:
291
+ """
292
+ Criterio de Información Bayesiano (BIC).
293
+
294
+ BIC = k*log(N) - 2*log(L)
295
+ """
296
+ return k_params * np.log(n_shots) - 2 * log_l
297
+
298
+
299
+ def model_comparison(
300
+ counts: Dict[str, int],
301
+ p_born: Dict[str, float],
302
+ p_modular: Dict[str, float]
303
+ ) -> Dict:
304
+ """
305
+ Compara el modelo Born vs modelo Modular usando AIC y BIC.
306
+ Sección 5.3 del paper.
307
+
308
+ El modelo Born tiene 0 parámetros libres (es la baseline).
309
+ El modelo Modular tiene 1 parámetro libre (epsilon de regularización).
310
+
311
+ Parámetros
312
+ ----------
313
+ counts : dict
314
+ Conteos experimentales
315
+ p_born : dict
316
+ Probabilidades Born (sin corrección)
317
+ p_modular : dict
318
+ Probabilidades con corrección modular
319
+
320
+ Retorna
321
+ -------
322
+ dict : Resultados completos de la comparación
323
+ """
324
+ N = sum(counts.values())
325
+
326
+ ll_born = log_likelihood(counts, p_born)
327
+ ll_mod = log_likelihood(counts, p_modular)
328
+
329
+ aic_born = compute_aic(ll_born, k_params=0)
330
+ aic_mod = compute_aic(ll_mod, k_params=1)
331
+
332
+ bic_born = compute_bic(ll_born, k_params=0, n_shots=N)
333
+ bic_mod = compute_bic(ll_mod, k_params=1, n_shots=N)
334
+
335
+ delta_aic = aic_born - aic_mod # positivo = prefiere modular
336
+ delta_bic = bic_born - bic_mod
337
+
338
+ if delta_aic > 2:
339
+ preference = "MODULAR"
340
+ strength = "fuerte" if delta_aic > 10 else "moderada"
341
+ elif delta_aic < -2:
342
+ preference = "BORN"
343
+ strength = "fuerte" if delta_aic < -10 else "moderada"
344
+ else:
345
+ preference = "EMPATE"
346
+ strength = "evidencia insuficiente"
347
+
348
+ return {
349
+ "N_shots": N,
350
+ "logL_born": round(ll_born, 2),
351
+ "logL_modular": round(ll_mod, 2),
352
+ "AIC_born": round(aic_born, 2),
353
+ "AIC_modular": round(aic_mod, 2),
354
+ "BIC_born": round(bic_born, 2),
355
+ "BIC_modular": round(bic_mod, 2),
356
+ "delta_AIC": round(delta_aic, 2),
357
+ "delta_BIC": round(delta_bic, 2),
358
+ "preferred_model": preference,
359
+ "evidence_strength": strength,
360
+ }
361
+
362
+
363
+ # ─────────────────────────────────────────────
364
+ # CLASE PRINCIPAL: ModularAnalyzer
365
+ # ─────────────────────────────────────────────
366
+
367
+ class ModularAnalyzer:
368
+ """
369
+ Interfaz principal del Modular Software Stack.
370
+
371
+ Uso básico
372
+ ----------
373
+ >>> analyzer = ModularAnalyzer(eps=1e-6)
374
+ >>> result = analyzer.analyze(rho, counts, p0)
375
+ >>> print(result.summary())
376
+ """
377
+
378
+ def __init__(self, eps: float = 1e-6, mode: str = "auto"):
379
+ """
380
+ Parámetros
381
+ ----------
382
+ eps : float
383
+ Parámetro de regularización (default: 1e-6)
384
+ mode : str
385
+ 'auto' → aplica corrección solo si AIC lo favorece
386
+ 'born' → fuerza Born (sin corrección)
387
+ 'modular' → fuerza corrección modular siempre
388
+ """
389
+ self.eps = eps
390
+ self.mode = mode
391
+ self._results = {}
392
+
393
+ def analyze(
394
+ self,
395
+ rho: np.ndarray,
396
+ counts: Dict[str, int],
397
+ p0: Optional[Dict[str, float]] = None
398
+ ) -> "ModularResult":
399
+ """
400
+ Ejecuta el análisis completo del stack modular.
401
+
402
+ Parámetros
403
+ ----------
404
+ rho : np.ndarray
405
+ Matriz de densidad reconstruida del subsistema medido
406
+ counts : dict
407
+ Conteos experimentales {'00': 5200, '11': 4700, ...}
408
+ p0 : dict, opcional
409
+ Born baseline. Si None, se calcula desde rho.
410
+
411
+ Retorna
412
+ -------
413
+ ModularResult : Objeto con todos los resultados
414
+ """
415
+ outcomes = list(counts.keys())
416
+ N = sum(counts.values())
417
+
418
+ # Si no hay p0, calcularlo desde la diagonal de rho
419
+ if p0 is None:
420
+ diag = np.diag(rho).real
421
+ diag = np.abs(diag) / np.sum(np.abs(diag))
422
+ p0 = {outcomes[i]: diag[i] for i in range(len(outcomes))}
423
+
424
+ # Paso 1: Generador modular
425
+ K = modular_generator(rho, self.eps)
426
+
427
+ # Paso 2: Desbalance modular
428
+ deltaK = modular_imbalance(K, p0)
429
+
430
+ # Paso 3: MIS y clasificación de régimen
431
+ mis = modular_imbalance_score(deltaK)
432
+ regime, regime_desc = classify_regime(mis)
433
+
434
+ # Paso 4: Probabilidades corregidas
435
+ p_mod = modular_probabilities(p0, deltaK)
436
+
437
+ # Paso 5: Comparación de modelos
438
+ comparison = model_comparison(counts, p0, p_mod)
439
+
440
+ # Frecuencias empíricas
441
+ freq_empirical = {o: counts[o] / N for o in outcomes}
442
+
443
+ return ModularResult(
444
+ outcomes=outcomes,
445
+ N_shots=N,
446
+ p0=p0,
447
+ p_modular=p_mod,
448
+ freq_empirical=freq_empirical,
449
+ K=K,
450
+ deltaK=deltaK,
451
+ mis=mis,
452
+ regime=regime,
453
+ regime_description=regime_desc,
454
+ model_comparison=comparison,
455
+ eps=self.eps,
456
+ mode=self.mode,
457
+ )
458
+
459
+
460
+ class ModularResult:
461
+ """
462
+ Contenedor de resultados del análisis modular.
463
+ """
464
+
465
+ def __init__(self, **kwargs):
466
+ for k, v in kwargs.items():
467
+ setattr(self, k, v)
468
+
469
+ def summary(self) -> str:
470
+ """Resumen legible de los resultados."""
471
+
472
+ # Recomendación final unificada (MIS + AIC)
473
+ preferred = self.model_comparison['preferred_model']
474
+ strength = self.model_comparison['evidence_strength']
475
+
476
+ if self.mode == "born":
477
+ recomendacion = "→ RECOMENDACIÓN: Born forzado por el usuario."
478
+ elif self.mode == "modular":
479
+ recomendacion = "→ RECOMENDACIÓN: Corrección modular forzada por el usuario."
480
+ elif preferred == "MODULAR":
481
+ recomendacion = f"→ RECOMENDACIÓN: usar probabilidades corregidas (Modular) — evidencia {strength}."
482
+ elif preferred == "BORN":
483
+ recomendacion = f"→ RECOMENDACIÓN: usar probabilidades Born (sin corrección) — evidencia {strength}."
484
+ else:
485
+ recomendacion = "→ RECOMENDACIÓN: evidencia insuficiente para preferir un modelo. Usar Born por defecto."
486
+
487
+ lines = [
488
+ "=" * 55,
489
+ " MODULAR SOFTWARE STACK — Resultados",
490
+ "=" * 55,
491
+ f" Shots totales : {self.N_shots:,}",
492
+ f" Epsilon (reg.) : {self.eps}",
493
+ f" MIS : {self.mis:.4f}",
494
+ f" Régimen físico : {self.regime}",
495
+ f" {self.regime_description}",
496
+ "-" * 55,
497
+ " Probabilidades por resultado:",
498
+ f" {'Outcome':<8} {'Born':>10} {'Modular':>10} {'Empírico':>10}",
499
+ ]
500
+ for o in self.outcomes:
501
+ lines.append(
502
+ f" {o:<8} "
503
+ f"{self.p0[o]:>10.4f} "
504
+ f"{self.p_modular[o]:>10.4f} "
505
+ f"{self.freq_empirical[o]:>10.4f}"
506
+ )
507
+ lines += [
508
+ "-" * 55,
509
+ " Comparación de modelos (AIC):",
510
+ f" AIC Born : {self.model_comparison['AIC_born']}",
511
+ f" AIC Modular : {self.model_comparison['AIC_modular']}",
512
+ f" ΔAIC (Born-Mod) : {self.model_comparison['delta_AIC']}",
513
+ f" Modelo preferido : {self.model_comparison['preferred_model']}",
514
+ f" Evidencia : {self.model_comparison['evidence_strength']}",
515
+ "-" * 55,
516
+ f" {recomendacion}",
517
+ "=" * 55,
518
+ ]
519
+ return "\n".join(lines)
520
+
521
+ def best_probabilities(self) -> Dict[str, float]:
522
+ """
523
+ Retorna las mejores probabilidades según el criterio AIC.
524
+ En modo 'auto', elige entre Born y Modular automáticamente.
525
+ """
526
+ if self.mode == "born":
527
+ return self.p0
528
+ elif self.mode == "modular":
529
+ return self.p_modular
530
+ else: # auto
531
+ if self.model_comparison["preferred_model"] == "MODULAR":
532
+ return self.p_modular
533
+ return self.p0
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: modularq
3
+ Version: 1.1.0
4
+ Summary: Modular Software Stack for Quantum Computing — measurement diagnostics and correction
5
+ Author: Mariano M. Castro
6
+ Author-email: "Christian H. Balfagon" <cb@balfagonresearch.org>
7
+ License: MIT License (Non-Commercial)
8
+
9
+ Copyright (c) 2025 Christian H. Balfagón and Mariano M. Castro
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software for academic, research, and non-commercial purposes, subject
13
+ to the following conditions:
14
+
15
+ 1. The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ 2. Commercial use — defined as use in a product or service that generates
19
+ revenue, or use by a for-profit organization — requires a separate
20
+ commercial license. Contact: cb@balfagonresearch.org
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
23
+
24
+ Project-URL: Homepage, https://nanocastro79.github.io/modularq/
25
+ Project-URL: Repository, https://github.com/nanocastro79/modularq
26
+ Keywords: quantum computing,qiskit,measurement,NISQ,Born rule
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Topic :: Scientific/Engineering :: Physics
29
+ Classifier: Intended Audience :: Science/Research
30
+ Requires-Python: >=3.9
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ License-File: AUTHORS
34
+ Requires-Dist: numpy>=1.24
35
+ Requires-Dist: scipy>=1.10
36
+ Provides-Extra: qiskit
37
+ Requires-Dist: qiskit>=1.0; extra == "qiskit"
38
+ Requires-Dist: qiskit-ibm-runtime>=0.20; extra == "qiskit"
39
+ Dynamic: license-file
40
+
41
+ # modularq 🔬
42
+
43
+ **Modular Software Stack for Quantum Computing**
44
+
45
+ Una librería Python que implementa diagnóstico y corrección de no-equilibrio modular en mediciones cuánticas, basada en:
46
+
47
+ > Balfagón, C. (2025). *A Modular Software Stack for Quantum Computing: From Born-Rule Equilibrium to Nonequilibrium-Aware Quantum Software*. Universidad de Buenos Aires.
48
+
49
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/nanocastro79/modularq/blob/main/demo_colab.ipynb)
50
+
51
+ ---
52
+
53
+ ## ¿Qué problema resuelve?
54
+
55
+ Las computadoras cuánticas NISQ operan como sistemas abiertos y ruidosos. Las estadísticas de medición pueden desviarse sistemáticamente de las predicciones ideales (Born rule) cuando el proceso de medición opera fuera del equilibrio termodinámico (KMS).
56
+
57
+ **modularq** detecta y corrige estas desviaciones automáticamente, sin modificar el hardware ni los circuitos.
58
+
59
+ ---
60
+
61
+ ## Instalación
62
+
63
+ ```bash
64
+ pip install numpy scipy matplotlib
65
+ # Luego descargá modularq.py desde este repo
66
+ ```
67
+
68
+ O en Google Colab:
69
+
70
+ ```python
71
+ !wget https://raw.githubusercontent.com/nanocastro79/modularq/main/modularq.py
72
+ from modularq import ModularAnalyzer
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Uso básico
78
+
79
+ ```python
80
+ import numpy as np
81
+ from modularq import ModularAnalyzer
82
+
83
+ # Matriz de densidad reconstruida (del subsistema medido)
84
+ rho = np.array([[0.52, 0.01], [0.01, 0.48]])
85
+
86
+ # Conteos experimentales
87
+ counts = {"0": 5200, "1": 4800}
88
+
89
+ # Born baseline
90
+ p0 = {"0": 0.5, "1": 0.5}
91
+
92
+ # Analizar
93
+ analyzer = ModularAnalyzer(eps=1e-6, mode="auto")
94
+ result = analyzer.analyze(rho, counts, p0)
95
+
96
+ print(result.summary())
97
+ # → MIS, régimen, probabilidades corregidas, comparación AIC
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Qué hace el stack (capas)
103
+
104
+ | Capa | Función |
105
+ |------|---------|
106
+ | 0 | Hardware cuántico (sin modificar) |
107
+ | 1 | Reconstrucción de estado (tomografía / shadows) |
108
+ | **2** | **Diagnóstico modular: K = -log(ρ), δK_i** ← núcleo |
109
+ | 3 | Clasificación: Born-válido / perturbativo / no-KMS |
110
+ | 4 | Regla de medición efectiva: reweighting exponencial |
111
+ | 5 | Control modular (minimizar imbalance) |
112
+ | 6 | API de software |
113
+ | 7 | Inferencia estadística (AIC/BIC) |
114
+
115
+ ---
116
+
117
+ ## Modular Imbalance Score (MIS)
118
+
119
+ Métrica escalar que indica la validez del Born rule:
120
+
121
+ | MIS | Régimen | Acción |
122
+ |-----|---------|--------|
123
+ | < 0.02 | ✅ KMS-balanced | Ninguna (Born válido) |
124
+ | 0.02 – 0.10 | ⚠️ Perturbativo | Corrección leve |
125
+ | > 0.10 | 🔴 No-KMS | Corrección necesaria |
126
+
127
+ ---
128
+
129
+ ## Reproducibilidad
130
+
131
+ Los experimentos originales están disponibles en Zenodo:
132
+ - DOI: [10.5281/zenodo.18066279](https://doi.org/10.5281/zenodo.18066279)
133
+ - Hardware: IBM Quantum (ibm_marrakesh, ibm_fez, ibm_torino)
134
+ - Circuitos: Bell (2q) y GHZ (3q)
135
+
136
+ ---
137
+
138
+ ## Licencia
139
+
140
+ **Uso académico y no comercial:** libre y gratuito bajo los términos de esta licencia.
141
+
142
+ **Uso comercial:** requiere acuerdo de licencia separado.
143
+ Contacto: cb@balfagonresearch.org
144
+
145
+ © 2025 Christian H. Balfagón and Mariano M. Castro.
@@ -0,0 +1,12 @@
1
+ AUTHORS
2
+ LICENSE
3
+ README.md
4
+ pyproject.toml
5
+ modularq/__init__.py
6
+ modularq/core.py
7
+ modularq.egg-info/PKG-INFO
8
+ modularq.egg-info/SOURCES.txt
9
+ modularq.egg-info/dependency_links.txt
10
+ modularq.egg-info/requires.txt
11
+ modularq.egg-info/top_level.txt
12
+ tests/test_core.py
@@ -0,0 +1,6 @@
1
+ numpy>=1.24
2
+ scipy>=1.10
3
+
4
+ [qiskit]
5
+ qiskit>=1.0
6
+ qiskit-ibm-runtime>=0.20
@@ -0,0 +1 @@
1
+ modularq
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "modularq"
7
+ version = "1.1.0"
8
+ description = "Modular Software Stack for Quantum Computing — measurement diagnostics and correction"
9
+ readme = "README.md"
10
+ license = {file = "LICENSE"}
11
+ authors = [
12
+ {name = "Christian H. Balfagon", email = "cb@balfagonresearch.org"},
13
+ {name = "Mariano M. Castro"}
14
+ ]
15
+ keywords = ["quantum computing", "qiskit", "measurement", "NISQ", "Born rule"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Scientific/Engineering :: Physics",
19
+ "Intended Audience :: Science/Research"
20
+ ]
21
+ requires-python = ">=3.9"
22
+ dependencies = [
23
+ "numpy>=1.24",
24
+ "scipy>=1.10"
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ qiskit = ["qiskit>=1.0", "qiskit-ibm-runtime>=0.20"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://nanocastro79.github.io/modularq/"
32
+ Repository = "https://github.com/nanocastro79/modularq"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,54 @@
1
+ import numpy as np
2
+ import pytest
3
+ from modularq import ModularAnalyzer
4
+
5
+ # Estado |+> ideal
6
+ RHO_IDEAL = np.array([[0.5, 0.5], [0.5, 0.5]])
7
+
8
+ # Estado con ruido (similar al hardware real)
9
+ RHO_NOISY = np.array([[0.505+0.j, 0.494-0.028j],
10
+ [0.494+0.028j, 0.495+0.j ]])
11
+
12
+ def test_mis_ideal_es_cero():
13
+ """Con rho ideal, el MIS debe ser 0"""
14
+ counts = {'0': 1000, '1': 1000}
15
+ p0 = {'0': 0.5, '1': 0.5}
16
+ result = ModularAnalyzer().analyze(RHO_IDEAL, counts, p0)
17
+ assert result.mis == pytest.approx(0.0, abs=1e-6)
18
+
19
+ def test_regimen_kms_con_rho_ideal():
20
+ """Con rho ideal el regimen debe ser KMS_BALANCED"""
21
+ counts = {'0': 1000, '1': 1000}
22
+ p0 = {'0': 0.5, '1': 0.5}
23
+ result = ModularAnalyzer().analyze(RHO_IDEAL, counts, p0)
24
+ assert result.regime == 'KMS_BALANCED'
25
+
26
+ def test_mis_positivo_con_ruido():
27
+ """Con rho ruidosa el MIS debe ser > 0"""
28
+ counts = {'0': 987, '1': 1013}
29
+ p0 = {'0': 0.5, '1': 0.5}
30
+ result = ModularAnalyzer().analyze(RHO_NOISY, counts, p0)
31
+ assert result.mis > 0.0
32
+
33
+ def test_probabilidades_normalizadas():
34
+ """Las probabilidades corregidas deben sumar 1"""
35
+ counts = {'0': 987, '1': 1013}
36
+ p0 = {'0': 0.5, '1': 0.5}
37
+ result = ModularAnalyzer().analyze(RHO_NOISY, counts, p0)
38
+ total = sum(result.p_modular.values())
39
+ assert total == pytest.approx(1.0, abs=1e-9)
40
+
41
+ def test_summary_contiene_mis():
42
+ """El summary debe incluir el MIS"""
43
+ counts = {'0': 987, '1': 1013}
44
+ p0 = {'0': 0.5, '1': 0.5}
45
+ result = ModularAnalyzer().analyze(RHO_NOISY, counts, p0)
46
+ assert 'MIS' in result.summary()
47
+
48
+ def test_dimension_incompatible():
49
+ """Debe lanzar error si p0 y rho tienen dimensiones distintas"""
50
+ rho_3x3 = np.eye(3) / 3
51
+ counts = {'0': 500, '1': 500}
52
+ p0 = {'0': 0.5, '1': 0.5}
53
+ with pytest.raises(ValueError):
54
+ ModularAnalyzer().analyze(rho_3x3, counts, p0)