walopy 0.2.8__tar.gz → 0.4.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.
- {walopy-0.2.8 → walopy-0.4.0}/PKG-INFO +680 -463
- walopy-0.2.8/src/walopy.egg-info/PKG-INFO → walopy-0.4.0/README.md +664 -495
- walopy-0.4.0/pyproject.toml +85 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/__init__.py +116 -116
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/__main__.py +20 -19
- walopy-0.4.0/src/walopy/_catalogo_en.py +708 -0
- walopy-0.4.0/src/walopy/_catalogo_es.py +604 -0
- walopy-0.4.0/src/walopy/_i18n.py +161 -0
- walopy-0.4.0/src/walopy/_utils.py +99 -0
- walopy-0.4.0/src/walopy/advanced.py +1175 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/bottleneck.py +51 -32
- walopy-0.4.0/src/walopy/fitting.py +203 -0
- walopy-0.4.0/src/walopy/inventory.py +2816 -0
- walopy-0.4.0/src/walopy/kpi.py +325 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/network.py +48 -46
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/operations.py +109 -71
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/plotting.py +143 -135
- walopy-0.4.0/src/walopy/project.py +394 -0
- walopy-0.4.0/src/walopy/py.typed +0 -0
- walopy-0.4.0/src/walopy/queuing.py +631 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/reliability.py +180 -95
- walopy-0.4.0/src/walopy/scheduling.py +555 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy/solver.py +189 -99
- walopy-0.2.8/README.md → walopy-0.4.0/src/walopy.egg-info/PKG-INFO +712 -458
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy.egg-info/SOURCES.txt +28 -1
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy.egg-info/requires.txt +9 -2
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_abc_xyz.py +5 -4
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_advanced.py +10 -13
- walopy-0.4.0/tests/test_cli.py +67 -0
- walopy-0.4.0/tests/test_complejidad.py +107 -0
- walopy-0.4.0/tests/test_contrato_entradas.py +225 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_cpm_pert.py +22 -2
- walopy-0.4.0/tests/test_documentacion.py +31 -0
- walopy-0.4.0/tests/test_documentacion_ingles.py +146 -0
- walopy-0.4.0/tests/test_ejemplos.py +26 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_exchange_curve.py +17 -4
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_fitting.py +2 -2
- walopy-0.4.0/tests/test_i18n.py +190 -0
- walopy-0.4.0/tests/test_i18n_paridad.py +107 -0
- walopy-0.4.0/tests/test_idioma.py +172 -0
- walopy-0.4.0/tests/test_inventario_i18n.py +177 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_inventory.py +2 -4
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_inventory_new.py +9 -12
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_kpi.py +9 -11
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_mg1.py +2 -4
- walopy-0.4.0/tests/test_mmc_estable.py +58 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_mrp.py +7 -2
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_neh_flowshop.py +5 -3
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_network.py +2 -2
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_new_models.py +9 -9
- walopy-0.4.0/tests/test_notas_release.py +31 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_operations.py +1 -1
- walopy-0.4.0/tests/test_plotting.py +106 -0
- walopy-0.4.0/tests/test_politicas_inventario.py +104 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_queuing.py +2 -4
- walopy-0.4.0/tests/test_readme.py +42 -0
- walopy-0.4.0/tests/test_referencias_externas.py +57 -0
- walopy-0.4.0/tests/test_regresion_validacion.py +305 -0
- walopy-0.4.0/tests/test_reliability.py +91 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_safety_stock_curve.py +7 -5
- walopy-0.4.0/tests/test_salida_identica.py +32 -0
- walopy-0.4.0/tests/test_salida_por_idioma.py +112 -0
- walopy-0.4.0/tests/test_scheduling.py +91 -0
- walopy-0.4.0/tests/test_scorecard.py +28 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_solver.py +28 -4
- walopy-0.4.0/tests/test_trazabilidad.py +72 -0
- walopy-0.4.0/tests/test_version.py +48 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_weibull.py +4 -1
- walopy-0.2.8/pyproject.toml +0 -52
- walopy-0.2.8/src/walopy/_utils.py +0 -67
- walopy-0.2.8/src/walopy/advanced.py +0 -1010
- walopy-0.2.8/src/walopy/fitting.py +0 -188
- walopy-0.2.8/src/walopy/inventory.py +0 -2523
- walopy-0.2.8/src/walopy/kpi.py +0 -274
- walopy-0.2.8/src/walopy/project.py +0 -348
- walopy-0.2.8/src/walopy/queuing.py +0 -448
- walopy-0.2.8/src/walopy/scheduling.py +0 -469
- walopy-0.2.8/tests/test_v026.py +0 -276
- {walopy-0.2.8 → walopy-0.4.0}/LICENSE +0 -0
- {walopy-0.2.8 → walopy-0.4.0}/setup.cfg +0 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy.egg-info/dependency_links.txt +0 -0
- {walopy-0.2.8 → walopy-0.4.0}/src/walopy.egg-info/top_level.txt +0 -0
- {walopy-0.2.8 → walopy-0.4.0}/tests/test_bottleneck.py +0 -0
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: walopy
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Herramientas de investigación de operaciones: colas, inventarios, scheduling, CPM/PERT, confiabilidad, OEE, cuellos de botella y árboles de KPI
|
|
5
5
|
Author-email: LeoSanta15 <angelsanta1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/LeoSanta15/walopy
|
|
8
8
|
Project-URL: Issues, https://github.com/LeoSanta15/walopy/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/LeoSanta15/walopy/blob/main/CHANGELOG.md
|
|
10
|
-
|
|
10
|
+
Project-URL: README (English), https://github.com/LeoSanta15/walopy/blob/main/README.en.md
|
|
11
|
+
Keywords: operations research,queuing theory,little's law,kingman,inventory,EOQ,MRP,ABC analysis,scheduling,flow-shop,CPM,PERT,reliability,weibull,bottleneck,OEE,KPI
|
|
11
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
19
|
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Natural Language :: Spanish
|
|
21
|
+
Classifier: Natural Language :: English
|
|
18
22
|
Classifier: Intended Audience :: Science/Research
|
|
19
23
|
Classifier: Topic :: Scientific/Engineering
|
|
24
|
+
Classifier: Typing :: Typed
|
|
20
25
|
Requires-Python: >=3.9
|
|
21
26
|
Description-Content-Type: text/markdown
|
|
22
27
|
License-File: LICENSE
|
|
@@ -27,18 +32,26 @@ Requires-Dist: plotly>=5.0
|
|
|
27
32
|
Provides-Extra: dev
|
|
28
33
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
34
|
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
35
|
+
Requires-Dist: pytest-timeout>=2; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff~=0.16.10; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy<3,>=1.10; extra == "dev"
|
|
38
|
+
Provides-Extra: release
|
|
39
|
+
Requires-Dist: build; extra == "release"
|
|
40
|
+
Requires-Dist: twine; extra == "release"
|
|
41
|
+
Requires-Dist: pip-audit; extra == "release"
|
|
32
42
|
Provides-Extra: docs
|
|
33
43
|
Requires-Dist: sphinx>=7; extra == "docs"
|
|
34
44
|
Requires-Dist: sphinx-rtd-theme>=2; extra == "docs"
|
|
35
45
|
Requires-Dist: myst-parser>=2; extra == "docs"
|
|
46
|
+
Requires-Dist: sphinx-intl>=2.1; extra == "docs"
|
|
36
47
|
Dynamic: license-file
|
|
37
48
|
|
|
38
49
|
# walopy
|
|
39
50
|
|
|
40
51
|
**Teoría de colas, análisis de operaciones, modelos de inventario, árboles de KPI y más para Python.**
|
|
41
52
|
|
|
53
|
+
🇬🇧 [English version of this document](https://github.com/LeoSanta15/walopy/blob/main/README.en.md)
|
|
54
|
+
|
|
42
55
|
[](https://pypi.org/project/walopy/)
|
|
43
56
|
[](https://pypi.org/project/walopy/)
|
|
44
57
|
[](LICENSE)
|
|
@@ -66,15 +79,17 @@ pip install walopy
|
|
|
66
79
|
12. [Curvas de intercambio](#12-curvas-de-intercambio)
|
|
67
80
|
13. [Programación de producción (scheduling)](#13-programación-de-producción-scheduling)
|
|
68
81
|
14. [Confiabilidad](#14-confiabilidad--reliability)
|
|
69
|
-
15. [
|
|
70
|
-
16. [
|
|
71
|
-
17. [
|
|
72
|
-
18. [
|
|
73
|
-
19. [
|
|
74
|
-
20. [
|
|
75
|
-
21. [
|
|
76
|
-
22. [
|
|
77
|
-
23. [
|
|
82
|
+
15. [Proyectos: CPM y PERT](#15-proyectos-cpm-y-pert--project)
|
|
83
|
+
16. [Árboles de KPI](#16-árboles-de-kpi)
|
|
84
|
+
17. [Solvers y optimización](#17-solvers-y-optimización)
|
|
85
|
+
18. [Análisis de sensibilidad](#18-análisis-de-sensibilidad)
|
|
86
|
+
19. [Escenarios en lote (batch_model)](#19-escenarios-en-lote-batch_model)
|
|
87
|
+
20. [Comparar múltiples resultados](#20-comparar-múltiples-resultados)
|
|
88
|
+
21. [Gráficas](#21-gráficas)
|
|
89
|
+
22. [CLI](#22-cli)
|
|
90
|
+
23. [Referencia de módulos](#23-referencia-de-módulos)
|
|
91
|
+
24. [Idioma de los textos](#24-idioma-de-los-textos)
|
|
92
|
+
25. [Requisitos](#25-requisitos)
|
|
78
93
|
|
|
79
94
|
---
|
|
80
95
|
|
|
@@ -119,14 +134,14 @@ import walopy as wl
|
|
|
119
134
|
|
|
120
135
|
r = wl.mm1(lam=3.0, mu=5.0)
|
|
121
136
|
print(r)
|
|
122
|
-
#
|
|
123
|
-
# λ
|
|
124
|
-
# μ
|
|
125
|
-
# ρ
|
|
126
|
-
# L
|
|
127
|
-
# Lq
|
|
128
|
-
# W
|
|
129
|
-
# Wq
|
|
137
|
+
# Modelo : M/M/1
|
|
138
|
+
# λ : 3.0 (tasa de llegada)
|
|
139
|
+
# μ : 5.0 (tasa de servicio por servidor)
|
|
140
|
+
# ρ : 0.6 (utilización por servidor)
|
|
141
|
+
# L : 1.5 (unidades promedio en el sistema)
|
|
142
|
+
# Lq : 0.9 (unidades promedio en cola)
|
|
143
|
+
# W : 0.5 (tiempo promedio en el sistema)
|
|
144
|
+
# Wq : 0.3 (tiempo promedio de espera en cola)
|
|
130
145
|
|
|
131
146
|
df = r.to_frame() # exportar a DataFrame
|
|
132
147
|
r.plot() # gráfica Wq y Lq vs ρ
|
|
@@ -193,7 +208,9 @@ r = wl.kingman(lam=3.0, mu=5.0, ca2=0.5, cs2=1.0)
|
|
|
193
208
|
r = wl.kingman(lam=3.0, mu=5.0, ca2=1.0, cs2=3.0)
|
|
194
209
|
|
|
195
210
|
# Recuperar parámetros ajustados desde datos reales y usarlos directamente
|
|
196
|
-
|
|
211
|
+
import numpy as np
|
|
212
|
+
rng = np.random.default_rng(42)
|
|
213
|
+
fit = wl.fit_from_data(inter_arrivals=rng.exponential(0.2, 500), service_times=rng.exponential(0.1, 500))
|
|
197
214
|
r = wl.kingman(**fit.to_model_kwargs())
|
|
198
215
|
```
|
|
199
216
|
|
|
@@ -266,12 +283,12 @@ Cola con **capacidad finita** K (servidor + sala de espera). Los clientes que ll
|
|
|
266
283
|
|
|
267
284
|
**Parámetros adicionales en `result.params`:**
|
|
268
285
|
- `PK (blocking prob)` — probabilidad de rechazo (sistema lleno)
|
|
269
|
-
- `
|
|
286
|
+
- `λ_eff (effective rate)` — tasa de llegada efectiva = λ · (1 − P_K)
|
|
270
287
|
|
|
271
288
|
```python
|
|
272
289
|
r = wl.mm1k(lam=5.0, mu=3.0, K=10)
|
|
273
|
-
print(r.params["PK (
|
|
274
|
-
print(r.params["
|
|
290
|
+
print(r.params["PK (prob. de bloqueo)"]) # fracción de clientes rechazados
|
|
291
|
+
print(r.params["λ_eff (tasa efectiva)"]) # tasa real de entrada al sistema
|
|
275
292
|
```
|
|
276
293
|
|
|
277
294
|
---
|
|
@@ -282,7 +299,7 @@ Multi-servidor con capacidad finita. Generaliza tanto M/M/c (K→∞) como M/M/1
|
|
|
282
299
|
|
|
283
300
|
```python
|
|
284
301
|
r = wl.mmck(lam=8.0, mu=3.0, c=2, K=20)
|
|
285
|
-
print(r.params["PK (
|
|
302
|
+
print(r.params["PK (prob. de bloqueo)"])
|
|
286
303
|
|
|
287
304
|
# Verificación: con K muy grande se acerca a M/M/c
|
|
288
305
|
r_inf = wl.mmc(lam=3.0, mu=5.0, c=2)
|
|
@@ -455,8 +472,9 @@ print(fit_ia.mu) # None
|
|
|
455
472
|
ts = np.cumsum(rng.exponential(0.2, 1000)) # timestamps acumulados
|
|
456
473
|
fit = wl.fit_from_data(arrival_timestamps=ts)
|
|
457
474
|
|
|
458
|
-
# 4. Conectar directamente con un modelo
|
|
459
|
-
|
|
475
|
+
# 4. Conectar directamente con un modelo (necesita λ, μ, ca² y cs²: ajuste con ambas muestras)
|
|
476
|
+
fit_completo = wl.fit_from_data(ia, svc)
|
|
477
|
+
r = wl.kingman(**fit_completo.to_model_kwargs()) # usa lam, mu, ca2, cs2
|
|
460
478
|
```
|
|
461
479
|
|
|
462
480
|
**`FitResult` — atributos:**
|
|
@@ -570,12 +588,12 @@ r = wl.eoq(
|
|
|
570
588
|
holding_cost=2, # h: costo por unidad por período
|
|
571
589
|
)
|
|
572
590
|
print(r)
|
|
573
|
-
# EOQ (
|
|
574
|
-
#
|
|
575
|
-
#
|
|
576
|
-
#
|
|
577
|
-
#
|
|
578
|
-
#
|
|
591
|
+
# EOQ (cantidad de pedido): 223.6 unidades
|
|
592
|
+
# Frecuencia de pedidos : 4.472 pedidos/periodo
|
|
593
|
+
# Tiempo de ciclo : 0.2236 periodos
|
|
594
|
+
# Costo total : 447.2
|
|
595
|
+
# Costo de mantener : 223.6
|
|
596
|
+
# Costo de ordenar : 223.6 ← iguales en el óptimo
|
|
579
597
|
|
|
580
598
|
r.plot() # gráfica de curvas de costo
|
|
581
599
|
df = r.to_frame()
|
|
@@ -613,11 +631,11 @@ r = wl.reorder_point(
|
|
|
613
631
|
service_level=0.95, # nivel de servicio deseado
|
|
614
632
|
)
|
|
615
633
|
print(r)
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
#
|
|
619
|
-
# z
|
|
620
|
-
#
|
|
634
|
+
# Punto de reorden : 132.4 unidades
|
|
635
|
+
# Stock de seguridad : 32.4 unidades
|
|
636
|
+
# Nivel de servicio : 95.00%
|
|
637
|
+
# z : 1.645
|
|
638
|
+
# Demanda media en LT : 100.0
|
|
621
639
|
# Std demand LT : 19.72
|
|
622
640
|
|
|
623
641
|
# Sin variabilidad → solo demanda esperada durante LT
|
|
@@ -665,14 +683,14 @@ r = wl.newsvendor(
|
|
|
665
683
|
salvage=2, # valor residual de unidades no vendidas
|
|
666
684
|
)
|
|
667
685
|
print(r)
|
|
668
|
-
#
|
|
669
|
-
#
|
|
670
|
-
#
|
|
671
|
-
#
|
|
672
|
-
#
|
|
673
|
-
#
|
|
674
|
-
#
|
|
675
|
-
#
|
|
686
|
+
# Cantidad óptima : 100.0 unidades
|
|
687
|
+
# Razón crítica : 0.5
|
|
688
|
+
# Utilidad esperada : 320.0
|
|
689
|
+
# Ventas esperadas : 100.0
|
|
690
|
+
# Sobrante esperado : 0.0
|
|
691
|
+
# Faltante esperado : 0.0
|
|
692
|
+
# Costo de subestimar Cu: 4.0
|
|
693
|
+
# Costo de sobrestimar Co: 4.0
|
|
676
694
|
|
|
677
695
|
# Producto con alto margen → pedir por encima de la media
|
|
678
696
|
r_alto = wl.newsvendor(demand_mean=100, demand_std=20,
|
|
@@ -719,15 +737,15 @@ r = wl.ebq(
|
|
|
719
737
|
production_rate=4000, # P: tasa de producción (debe ser > D)
|
|
720
738
|
)
|
|
721
739
|
print(r)
|
|
722
|
-
# EBQ (
|
|
723
|
-
#
|
|
724
|
-
#
|
|
725
|
-
#
|
|
726
|
-
#
|
|
727
|
-
#
|
|
728
|
-
#
|
|
729
|
-
#
|
|
730
|
-
#
|
|
740
|
+
# EBQ (tamaño de lote): 258.2 unidades
|
|
741
|
+
# Inventario máximo : 193.6
|
|
742
|
+
# Inventario promedio : 96.82
|
|
743
|
+
# Frecuencia de lotes : 3.873 lotes/periodo
|
|
744
|
+
# Tiempo de ciclo : 0.2582 periodos
|
|
745
|
+
# Tiempo de producción: 0.06455 periodos
|
|
746
|
+
# Costo total : 193.6
|
|
747
|
+
# Costo de mantener : 96.82
|
|
748
|
+
# Costo de preparación: 96.82
|
|
731
749
|
```
|
|
732
750
|
|
|
733
751
|
**`EBQResult` — atributos:**
|
|
@@ -833,9 +851,9 @@ r = wl.lot_for_lot(demands, setup_cost=200, holding_cost=1)
|
|
|
833
851
|
print(r.total_holding_cost) # 0.0 — sin inventario residual
|
|
834
852
|
print(r.n_orders) # 4 — un pedido por período con demanda > 0
|
|
835
853
|
print(r)
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
#
|
|
854
|
+
# Método : Lote por lote
|
|
855
|
+
# Número de pedidos : 4
|
|
856
|
+
# Costo total : 800.0
|
|
839
857
|
```
|
|
840
858
|
|
|
841
859
|
---
|
|
@@ -898,11 +916,83 @@ df = r.to_frame() # todos los candidatos evaluados
|
|
|
898
916
|
|
|
899
917
|
---
|
|
900
918
|
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
### ABC, XYZ y ABC-XYZ — clasificación de artículos
|
|
922
|
+
|
|
923
|
+
`abc_analysis` clasifica por **valor anual** (Pareto): un artículo es A mientras el acumulado *previo* a él sea
|
|
924
|
+
menor que `a_threshold` (80 %), B mientras sea menor que `b_threshold` (95 %) y C en el resto. Los artículos
|
|
925
|
+
sin demanda quedan en C. `xyz_analysis` clasifica por **variabilidad** (coeficiente de variación): X si
|
|
926
|
+
CV ≤ `x_threshold` (0.5), Y hasta `y_threshold` (1.0) y Z por encima. `abc_xyz` combina ambas y devuelve la
|
|
927
|
+
matriz 3×3.
|
|
928
|
+
|
|
929
|
+
```python
|
|
930
|
+
items = [
|
|
931
|
+
{"name": "A", "demand": 500, "unit_value": 10.0, "demand_std": 120},
|
|
932
|
+
{"name": "B", "demand": 300, "unit_value": 10.0, "demand_std": 60},
|
|
933
|
+
{"name": "C", "demand": 100, "unit_value": 10.0, "demand_std": 150},
|
|
934
|
+
{"name": "D", "demand": 700, "unit_value": 1.0, "demand_std": 300},
|
|
935
|
+
{"name": "E", "demand": 300, "unit_value": 1.0, "demand_std": 10},
|
|
936
|
+
]
|
|
937
|
+
|
|
938
|
+
abc = wl.abc_analysis(items)
|
|
939
|
+
print(abc) # resumen por clase: A = 2 artículos (80 % del valor)
|
|
940
|
+
print(abc.to_frame()[["artículo", "valor_anual", "pct_acumulado", "clase"]])
|
|
941
|
+
|
|
942
|
+
# XYZ: cada artículo aporta 'cv' directamente o 'demand_std' + 'demand_mean' (o 'demand_rate')
|
|
943
|
+
xyz = wl.xyz_analysis([
|
|
944
|
+
{"name": i["name"], "demand_std": i["demand_std"], "demand_mean": i["demand"]} for i in items
|
|
945
|
+
])
|
|
946
|
+
print(xyz.to_frame()) # columnas: artículo, cv, clase
|
|
947
|
+
|
|
948
|
+
# ABC-XYZ: matriz de conteos (filas ABC, columnas XYZ)
|
|
949
|
+
combo = wl.abc_xyz([{**i, "cv": i["demand_std"] / i["demand"]} for i in items])
|
|
950
|
+
print(combo.matrix_frame())
|
|
951
|
+
print(combo.items[0]["combined_class"]) # 'AX'
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
| Parámetro | Descripción |
|
|
955
|
+
|---|---|
|
|
956
|
+
| `items` | Lista de diccionarios. ABC: `demand` (≥ 0) y `unit_value` (> 0). XYZ: `cv` o `demand_std` + `demand_mean`/`demand_rate` |
|
|
957
|
+
| `a_threshold`, `b_threshold` | Límites acumulados de las clases A y B (0 < a < b < 1) |
|
|
958
|
+
| `x_threshold`, `y_threshold` | Límites de CV de las clases X e Y |
|
|
959
|
+
|
|
960
|
+
Errores: lista vacía, elementos que no son diccionarios, claves faltantes y valores no finitos lanzan `ValueError`/`TypeError`
|
|
961
|
+
con el elemento y la clave afectados; si todas las demandas son 0 no hay nada que clasificar y se lanza `ValueError`.
|
|
962
|
+
|
|
963
|
+
---
|
|
964
|
+
|
|
965
|
+
### MRP de un nivel — `mrp`
|
|
966
|
+
|
|
967
|
+
Plan de requerimientos de materiales para un artículo: necesidades netas, órdenes planificadas (recepción y
|
|
968
|
+
liberación) y existencias proyectadas. Admite lote por lote (`"LFL"`) o lote fijo, tiempo de entrega,
|
|
969
|
+
stock de seguridad y recepciones programadas.
|
|
970
|
+
|
|
971
|
+
```python
|
|
972
|
+
r = wl.mrp(
|
|
973
|
+
gross_requirements=[0, 50, 0, 30, 60, 20],
|
|
974
|
+
initial_on_hand=40,
|
|
975
|
+
scheduled_receipts=[0, 0, 25, 0, 0, 0],
|
|
976
|
+
lead_time=1,
|
|
977
|
+
lot_size=40, # "LFL" para lote por lote
|
|
978
|
+
safety_stock=10,
|
|
979
|
+
item_name="Eje",
|
|
980
|
+
)
|
|
981
|
+
print(r) # tabla por periodo: GR, SR, OH, NR, PR, PO
|
|
982
|
+
print(r.to_frame()) # el mismo plan como DataFrame
|
|
983
|
+
print(r.past_due_releases) # 0.0
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
Si el `lead_time` es mayor que el periodo en que se necesita la orden, la liberación cae antes del periodo 1:
|
|
987
|
+
`mrp` emite un `UserWarning` y acumula esa cantidad en `past_due_releases` (no aparece en `planned_releases`).
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
901
991
|
## 8. Análisis de operaciones
|
|
902
992
|
|
|
903
993
|
### `oee(availability, performance, quality)` — OEE
|
|
904
994
|
|
|
905
|
-
Calcula la **Eficiencia Global de los Equipos** (OEE =
|
|
995
|
+
Calcula la **Eficiencia Global de los Equipos** (OEE = Disponibilidad × Rendimiento × Calidad).
|
|
906
996
|
|
|
907
997
|
```python
|
|
908
998
|
r = wl.oee(
|
|
@@ -917,21 +1007,21 @@ print(r)
|
|
|
917
1007
|
r.plot() # gráfica horizontal con referencia world-class (85%)
|
|
918
1008
|
```
|
|
919
1009
|
|
|
920
|
-
### `utilization_efficiency(actual_output,
|
|
1010
|
+
### `utilization_efficiency(actual_output, capacity)` — Utilización
|
|
921
1011
|
|
|
922
1012
|
```python
|
|
923
|
-
r = wl.utilization_efficiency(actual_output=750,
|
|
1013
|
+
r = wl.utilization_efficiency(actual_output=750, capacity=1000)
|
|
924
1014
|
print(r.utilization) # 0.75 — 75% de utilización
|
|
925
|
-
print(r.
|
|
1015
|
+
print(1 - r.utilization) # 0.25 — 25% de capacidad ociosa
|
|
926
1016
|
```
|
|
927
1017
|
|
|
928
|
-
### `unit_cost(fixed_cost,
|
|
1018
|
+
### `unit_cost(fixed_cost, variable_cost_per_unit, units_produced)` — Costo unitario
|
|
929
1019
|
|
|
930
1020
|
```python
|
|
931
|
-
r = wl.unit_cost(fixed_cost=10_000,
|
|
932
|
-
print(r.unit_cost)
|
|
933
|
-
print(r.fixed_cost_per_unit)
|
|
934
|
-
print(r.
|
|
1021
|
+
r = wl.unit_cost(fixed_cost=10_000, variable_cost_per_unit=10, units_produced=500)
|
|
1022
|
+
print(r.unit_cost) # 30.0 $/unidad
|
|
1023
|
+
print(r.fixed_cost_per_unit) # 20.0
|
|
1024
|
+
print(r.variable_cost_per_unit) # 10.0
|
|
935
1025
|
```
|
|
936
1026
|
|
|
937
1027
|
---
|
|
@@ -949,7 +1039,7 @@ r = wl.bottleneck_analysis(
|
|
|
949
1039
|
demand_rate=70.0, # demanda requerida
|
|
950
1040
|
)
|
|
951
1041
|
print(r)
|
|
952
|
-
#
|
|
1042
|
+
# Cuello de botella: Soldadura (capacidad 80 < demanda 70 con menor margen)
|
|
953
1043
|
# Utilization: Corte=58.3%, Soldadura=87.5%, Pintura=70.0%
|
|
954
1044
|
|
|
955
1045
|
r.plot() # barras de utilización con cuello de botella en rojo
|
|
@@ -968,19 +1058,19 @@ takt = wl.takt_time(available_time=480, demand=60)
|
|
|
968
1058
|
print(takt) # 8.0 min/unidad
|
|
969
1059
|
```
|
|
970
1060
|
|
|
971
|
-
### `line_balance(
|
|
1061
|
+
### `line_balance(station_names, cycle_times, takt)` — Balance de línea
|
|
972
1062
|
|
|
973
1063
|
Analiza si cada estación puede cumplir con el tiempo takt y calcula la eficiencia de balance.
|
|
974
1064
|
|
|
975
1065
|
```python
|
|
976
1066
|
lb = wl.line_balance(
|
|
977
|
-
|
|
1067
|
+
station_names=["A", "B", "C"],
|
|
978
1068
|
cycle_times=[5.0, 9.0, 4.0],
|
|
979
1069
|
takt=10.0,
|
|
980
1070
|
)
|
|
981
1071
|
print(lb.bottleneck) # "B" — estación más lenta
|
|
982
|
-
print(lb.balance_efficiency) #
|
|
983
|
-
print(lb.
|
|
1072
|
+
print(lb.balance_efficiency) # eficiencia de balance de la línea
|
|
1073
|
+
print(lb.theoretical_min_stations) # número mínimo teórico de estaciones
|
|
984
1074
|
|
|
985
1075
|
lb.plot() # barras de cycle time vs takt
|
|
986
1076
|
```
|
|
@@ -1000,8 +1090,8 @@ be = wl.break_even(
|
|
|
1000
1090
|
)
|
|
1001
1091
|
print(be.bep_units) # 1000.0 — punto de equilibrio en unidades
|
|
1002
1092
|
print(be.bep_revenue) # 25000.0 — ingresos en el punto de equilibrio
|
|
1003
|
-
print(be.margin_of_safety_pct) #
|
|
1004
|
-
print(be.
|
|
1093
|
+
print(be.margin_of_safety_pct) # 0.333 — margen de seguridad (fracción) sobre ventas actuales
|
|
1094
|
+
print(be.margin_of_safety_units) # 500.0 — unidades por encima del punto de equilibrio
|
|
1005
1095
|
|
|
1006
1096
|
be.plot() # líneas de ingresos/costos con BEP y zona de beneficio
|
|
1007
1097
|
```
|
|
@@ -1024,7 +1114,7 @@ print(r.bep_units_total) # unidades totales en el punto de equilibrio
|
|
|
1024
1114
|
print(r.bep_revenue_total) # ingresos totales en el punto de equilibrio
|
|
1025
1115
|
|
|
1026
1116
|
for item in r.items:
|
|
1027
|
-
print(item["
|
|
1117
|
+
print(item["Producto"], item["PE unidades"])
|
|
1028
1118
|
|
|
1029
1119
|
r.to_frame() # DataFrame con una fila por producto
|
|
1030
1120
|
```
|
|
@@ -1058,577 +1148,668 @@ print(r.margin_of_safety_pct) # 0.375 — porcentaje del margen de seguridad
|
|
|
1058
1148
|
|
|
1059
1149
|
---
|
|
1060
1150
|
|
|
1061
|
-
##
|
|
1151
|
+
## 12. Curvas de intercambio
|
|
1062
1152
|
|
|
1063
|
-
|
|
1153
|
+
Herramientas para tomar decisiones de política agregada en familias de artículos.
|
|
1064
1154
|
|
|
1065
|
-
###
|
|
1155
|
+
### Tipo 1 — ciclo: `exchange_curve`
|
|
1066
1156
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1157
|
+
Traza la hipérbola entre número de pedidos por año (N) e inversión promedio en inventario de ciclo (I):
|
|
1158
|
+
|
|
1159
|
+
```
|
|
1160
|
+
N(k) = N* / k I(k) = k · I* → N · I = N* · I* (constante)
|
|
1071
1161
|
```
|
|
1072
1162
|
|
|
1073
|
-
|
|
1163
|
+
Dado un target de N o de I, resuelve el multiplicador k óptimo y recalcula Q para cada artículo.
|
|
1074
1164
|
|
|
1075
1165
|
```python
|
|
1076
|
-
|
|
1077
|
-
actual_throughput=750,
|
|
1078
|
-
capacity=1000,
|
|
1079
|
-
defect_rate=0.05,
|
|
1080
|
-
)
|
|
1081
|
-
tree.plot()
|
|
1082
|
-
```
|
|
1166
|
+
import walopy as wl
|
|
1083
1167
|
|
|
1084
|
-
|
|
1168
|
+
items = [
|
|
1169
|
+
{"name": "A", "demand": 1000, "ordering_cost": 50, "holding_cost": 2, "unit_value": 10},
|
|
1170
|
+
{"name": "B", "demand": 500, "ordering_cost": 30, "holding_cost": 1, "unit_value": 5},
|
|
1171
|
+
{"name": "C", "demand": 2000, "ordering_cost": 100, "holding_cost": 4, "unit_value": 8},
|
|
1172
|
+
]
|
|
1085
1173
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
fixed_cost=10_000,
|
|
1090
|
-
variable_cost_per_unit=8,
|
|
1091
|
-
units_sold=2_000,
|
|
1092
|
-
investment=20_000,
|
|
1093
|
-
)
|
|
1094
|
-
print(tree.value) # ROI = 0.2 (20%)
|
|
1095
|
-
tree.plot()
|
|
1174
|
+
# Sin target → punto EOQ (k = 1)
|
|
1175
|
+
r = wl.exchange_curve(items)
|
|
1176
|
+
print(r)
|
|
1096
1177
|
```
|
|
1097
1178
|
|
|
1098
|
-
|
|
1179
|
+
```
|
|
1180
|
+
Objetivo : eoq
|
|
1181
|
+
Multiplicador k : 1.0000
|
|
1182
|
+
N pedidos/año (EOQ) : 87.27
|
|
1183
|
+
N pedidos/año (óptimo) : 87.27
|
|
1184
|
+
Inversión (EOQ) : 5430
|
|
1185
|
+
Inversión (óptimo) : 5430
|
|
1186
|
+
```
|
|
1099
1187
|
|
|
1100
1188
|
```python
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
cost = root.add_child("Operating costs", value=100_000, unit="€")
|
|
1104
|
-
rev.add_child("Product A", value=120_000, unit="€")
|
|
1105
|
-
rev.add_child("Product B", value=80_000, unit="€")
|
|
1189
|
+
# Reducir a 20 pedidos/año (k > 1 → Q más grandes → más inversión)
|
|
1190
|
+
r = wl.exchange_curve(items, target_orders=20)
|
|
1106
1191
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1192
|
+
# Limitar inversión a 3 000 (k < 1 → Q más pequeños → más pedidos)
|
|
1193
|
+
r = wl.exchange_curve(items, target_investment=3000)
|
|
1194
|
+
|
|
1195
|
+
# Tabla por artículo
|
|
1196
|
+
df = r.to_frame()
|
|
1197
|
+
# columnas: artículo | Q_eoq | Q_óptima | n_pedidos | inversión
|
|
1198
|
+
|
|
1199
|
+
# Hipérbola para graficar
|
|
1200
|
+
curve = r.curve_to_frame()
|
|
1201
|
+
# columnas: N | I
|
|
1110
1202
|
```
|
|
1111
1203
|
|
|
1204
|
+
**Parámetros de cada artículo**
|
|
1205
|
+
|
|
1206
|
+
| Clave | Requerido | Descripción |
|
|
1207
|
+
|-------|-----------|-------------|
|
|
1208
|
+
| `demand` | ✓ | Demanda anual Di |
|
|
1209
|
+
| `ordering_cost` | ✓ | Costo de pedido Ki |
|
|
1210
|
+
| `holding_cost` | ✓ | Costo de mantenimiento hi |
|
|
1211
|
+
| `unit_value` | — | Valor unitario vi (default 1) |
|
|
1212
|
+
| `name` | — | Etiqueta (default I1, I2, …) |
|
|
1213
|
+
|
|
1112
1214
|
---
|
|
1113
1215
|
|
|
1114
|
-
|
|
1216
|
+
### Tipo 2 — seguridad: `safety_stock_curve`
|
|
1115
1217
|
|
|
1116
|
-
|
|
1218
|
+
Traza la curva entre inversión en stock de seguridad e nivel de servicio por ciclo usando una **política z común** para toda la familia:
|
|
1117
1219
|
|
|
1118
|
-
|
|
1220
|
+
```
|
|
1221
|
+
SS_i(z) = z · σᵢ_DLT · vᵢ σᵢ_DLT = √(Lᵢ·σ²_Di + D²ᵢ·σ²_Li)
|
|
1222
|
+
SS_total(z) = z · Σ σᵢ_DLT · vᵢ SL(z) = Φ(z)
|
|
1223
|
+
```
|
|
1119
1224
|
|
|
1120
1225
|
```python
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1226
|
+
items = [
|
|
1227
|
+
{"name": "A", "demand_rate": 100, "demand_std": 10, "lead_time": 2, "unit_value": 10},
|
|
1228
|
+
{"name": "B", "demand_rate": 50, "demand_std": 5, "lead_time": 1, "unit_value": 5},
|
|
1229
|
+
{"name": "C", "demand_rate": 200, "demand_std": 20, "lead_time": 3, "unit_value": 8},
|
|
1230
|
+
]
|
|
1231
|
+
|
|
1232
|
+
# Target: nivel de servicio 95%
|
|
1233
|
+
r = wl.safety_stock_curve(items, target_service_level=0.95)
|
|
1234
|
+
print(r)
|
|
1125
1235
|
```
|
|
1126
1236
|
|
|
1127
|
-
|
|
1237
|
+
```
|
|
1238
|
+
Objetivo : nivel_de_servicio=95.0000%
|
|
1239
|
+
z : 1.6449
|
|
1240
|
+
Nivel de servicio : 95.0000%
|
|
1241
|
+
Inversión en SS : 1 847
|
|
1242
|
+
```
|
|
1128
1243
|
|
|
1129
1244
|
```python
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
```
|
|
1245
|
+
# Target: presupuesto máximo de SS
|
|
1246
|
+
r = wl.safety_stock_curve(items, target_ss_investment=1000)
|
|
1133
1247
|
|
|
1134
|
-
|
|
1248
|
+
# Tabla por artículo (incluye punto de reorden cuando se da demand_rate)
|
|
1249
|
+
df = r.to_frame()
|
|
1250
|
+
# columnas: artículo | sigma_dlt | stock_de_seguridad | inversión | punto_de_reorden
|
|
1135
1251
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
print(r.model_result) # QueueResult para M/M/3
|
|
1252
|
+
# Curva completa (z de −2 a 4)
|
|
1253
|
+
curve = r.curve_to_frame()
|
|
1254
|
+
# columnas: z | service_level | ss_investment
|
|
1140
1255
|
```
|
|
1141
1256
|
|
|
1142
|
-
|
|
1257
|
+
**Parámetros de cada artículo**
|
|
1143
1258
|
|
|
1144
|
-
|
|
1259
|
+
| Clave | Requerido | Descripción |
|
|
1260
|
+
|-------|-----------|-------------|
|
|
1261
|
+
| `demand_std` | ✓ | Desviación estándar de la demanda |
|
|
1262
|
+
| `lead_time` | ✓ | Lead time medio |
|
|
1263
|
+
| `demand_rate` | — | Demanda media; activa columna `reorder_point` |
|
|
1264
|
+
| `lead_time_std` | — | Desviación estándar del lead time (default 0) |
|
|
1265
|
+
| `unit_value` | — | Valor unitario (default 1) |
|
|
1266
|
+
| `name` | — | Etiqueta (default I1, I2, …) |
|
|
1145
1267
|
|
|
1146
|
-
|
|
1147
|
-
r = wl.optimize_servers(
|
|
1148
|
-
lam=6.0,
|
|
1149
|
-
mu=5.0,
|
|
1150
|
-
cost_per_server=10.0, # costo fijo por servidor por unidad de tiempo
|
|
1151
|
-
cost_per_wait=5.0, # costo por unidad de tiempo de espera de cada cliente
|
|
1152
|
-
)
|
|
1153
|
-
print(r.optimal_servers) # c óptimo
|
|
1154
|
-
print(r.optimal_cost) # costo mínimo
|
|
1268
|
+
---
|
|
1155
1269
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1270
|
+
### Relación entre ambas curvas
|
|
1271
|
+
|
|
1272
|
+
| | Tipo 1 (ciclo) | Tipo 2 (seguridad) |
|
|
1273
|
+
|---|---|---|
|
|
1274
|
+
| **Función** | `exchange_curve` | `safety_stock_curve` |
|
|
1275
|
+
| **Decide** | Tamaño de lote Q | Punto de reorden r |
|
|
1276
|
+
| **Tradeoff** | N pedidos/año ↔ Inversión ciclo | Nivel de servicio ↔ SS |
|
|
1277
|
+
| **Resultado** | `ExchangeCurveResult` | `SafetyStockCurveResult` |
|
|
1278
|
+
| **Parámetro** | Multiplicador k | Factor z |
|
|
1279
|
+
|
|
1280
|
+
Usadas juntas cubren la política completa `(Q, r)` para toda la familia.
|
|
1158
1281
|
|
|
1159
1282
|
---
|
|
1160
1283
|
|
|
1161
|
-
##
|
|
1284
|
+
## 13. Programación de producción (scheduling)
|
|
1162
1285
|
|
|
1163
|
-
###
|
|
1286
|
+
### Máquina única — `schedule_single`
|
|
1164
1287
|
|
|
1165
|
-
|
|
1288
|
+
Asigna *n* trabajos a una sola máquina según una regla de prioridad.
|
|
1166
1289
|
|
|
1167
1290
|
```python
|
|
1168
|
-
import
|
|
1169
|
-
|
|
1170
|
-
df = wl.sensitivity(
|
|
1171
|
-
wl.mm1,
|
|
1172
|
-
param="lam",
|
|
1173
|
-
values=np.linspace(0.5, 4.5, 20),
|
|
1174
|
-
mu=5.0, # parámetros fijos
|
|
1175
|
-
)
|
|
1176
|
-
print(df.columns.tolist()) # ['lam', 'rho', 'L', 'Lq', 'W', 'Wq', ...]
|
|
1291
|
+
import walopy as wl
|
|
1177
1292
|
|
|
1178
|
-
#
|
|
1179
|
-
|
|
1293
|
+
p = [3, 1, 4, 1, 5] # tiempos de proceso
|
|
1294
|
+
d = [8, 3, 10, 4, 12] # fechas de entrega
|
|
1295
|
+
w = [2, 5, 1, 4, 1] # pesos de importancia
|
|
1180
1296
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1297
|
+
r = wl.schedule_single(p, rule="SPT", due_dates=d, weights=w,
|
|
1298
|
+
names=["A","B","C","D","E"])
|
|
1299
|
+
print(r)
|
|
1300
|
+
```
|
|
1183
1301
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1302
|
+
```
|
|
1303
|
+
Regla : SPT
|
|
1304
|
+
Secuencia : B → D → A → C → E
|
|
1305
|
+
Makespan (Cmax) : 14
|
|
1306
|
+
Finalización total ΣCj : 37
|
|
1307
|
+
Finalización pond. ΣwCj : 89
|
|
1308
|
+
Retraso máximo : 2
|
|
1309
|
+
Tardanza total ΣTj : 2
|
|
1310
|
+
Trabajos con retraso : 1
|
|
1188
1311
|
```
|
|
1189
1312
|
|
|
1190
|
-
|
|
1313
|
+
**Reglas disponibles**
|
|
1191
1314
|
|
|
1192
|
-
|
|
1315
|
+
| `rule` | Criterio minimizado | Datos requeridos |
|
|
1316
|
+
|--------|---------------------|-----------------|
|
|
1317
|
+
| `'SPT'` | ΣCj (tiempo total de completación) | — |
|
|
1318
|
+
| `'EDD'` | Lmax (máxima tardanza) | `due_dates` |
|
|
1319
|
+
| `'WSPT'` | ΣwjCj (completación ponderada) | `weights` |
|
|
1320
|
+
| `'CR'` | Ratio crítico dj/pj | `due_dates` |
|
|
1321
|
+
| `'FIFO'` | Orden de llegada (línea base) | — |
|
|
1193
1322
|
|
|
1194
|
-
|
|
1323
|
+
**Atributos de `ScheduleResult`**
|
|
1195
1324
|
|
|
1196
|
-
|
|
1325
|
+
| Atributo | Descripción |
|
|
1326
|
+
|----------|-------------|
|
|
1327
|
+
| `rule` | Regla utilizada |
|
|
1328
|
+
| `sequence` | Lista de nombres en orden de procesamiento |
|
|
1329
|
+
| `jobs` | Lista de `JobSchedule` (por trabajo) |
|
|
1330
|
+
| `makespan` | Cmax = suma de tiempos de proceso |
|
|
1331
|
+
| `total_completion_time` | ΣCj |
|
|
1332
|
+
| `total_weighted_completion_time` | ΣwjCj |
|
|
1333
|
+
| `max_lateness` | max(Lj) |
|
|
1334
|
+
| `total_tardiness` | ΣTj |
|
|
1335
|
+
| `n_tardy` | Número de trabajos tarde |
|
|
1197
1336
|
|
|
1198
1337
|
```python
|
|
1199
|
-
|
|
1338
|
+
df = r.to_frame() # columnas: Trabajo, p, d, w, Inicio, C, L, T, Con retraso
|
|
1339
|
+
```
|
|
1200
1340
|
|
|
1201
|
-
|
|
1202
|
-
escenarios = pd.DataFrame({
|
|
1203
|
-
"lam": [1.0, 2.0, 3.0, 4.0]
|
|
1204
|
-
})
|
|
1205
|
-
resultado = wl.batch_model(wl.mm1, escenarios, mu=5.0)
|
|
1206
|
-
print(resultado[["lam", "rho", "Wq", "L"]])
|
|
1341
|
+
---
|
|
1207
1342
|
|
|
1208
|
-
|
|
1209
|
-
escenarios2 = pd.DataFrame({
|
|
1210
|
-
"lam": [2.0, 3.0, 4.0],
|
|
1211
|
-
"mu": [5.0, 6.0, 7.0],
|
|
1212
|
-
})
|
|
1213
|
-
resultado2 = wl.batch_model(wl.mm1, escenarios2)
|
|
1343
|
+
### Flow-shop de 2 máquinas — `johnson_flowshop`
|
|
1214
1344
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1345
|
+
El algoritmo de Johnson encuentra la secuencia óptima que minimiza el makespan cuando todos los trabajos pasan primero por M1 y luego por M2.
|
|
1346
|
+
|
|
1347
|
+
```python
|
|
1348
|
+
m1 = [3, 8, 5, 7, 2] # tiempos en máquina 1
|
|
1349
|
+
m2 = [5, 2, 8, 4, 6] # tiempos en máquina 2
|
|
1350
|
+
|
|
1351
|
+
r = wl.johnson_flowshop(m1, m2)
|
|
1352
|
+
print(r)
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
```
|
|
1356
|
+
Secuencia : J1 → J5 → J4 → J3 → J2
|
|
1357
|
+
Makespan : 34
|
|
1358
|
+
```
|
|
1220
1359
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1360
|
+
```python
|
|
1361
|
+
df = r.to_frame()
|
|
1362
|
+
# columnas: Job | M1 start | M1 end | M2 start | M2 end
|
|
1223
1363
|
```
|
|
1224
1364
|
|
|
1225
1365
|
---
|
|
1226
1366
|
|
|
1227
|
-
|
|
1367
|
+
### Flow-shop de m máquinas — `neh_flowshop`
|
|
1228
1368
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1369
|
+
Heurística de **Nawaz-Enscore-Ham** para el flow-shop de permutación con cualquier número de máquinas:
|
|
1370
|
+
ordena los trabajos por tiempo total decreciente e inserta cada uno en la mejor posición (aceleración de
|
|
1371
|
+
Taillard: O(n²·m)). Es una heurística: en casos pequeños el makespan queda típicamente a pocos puntos
|
|
1372
|
+
porcentuales del óptimo.
|
|
1232
1373
|
|
|
1233
1374
|
```python
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
)
|
|
1240
|
-
print(df[["label", "ρ (utilization)", "Wq (wait time)", "L (system)"]])
|
|
1241
|
-
# label ρ (utilization) Wq (wait time) L (system)
|
|
1242
|
-
# 0 M/M/1 0.6 0.3000 1.50
|
|
1243
|
-
# 1 M/M/2 0.3 0.0302 1.02
|
|
1244
|
-
# 2 M/D/1 0.6 0.1500 1.05
|
|
1245
|
-
|
|
1246
|
-
# Sin etiquetas → scenario_1, scenario_2, ...
|
|
1247
|
-
df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
|
|
1375
|
+
# filas = trabajos, columnas = máquinas
|
|
1376
|
+
tiempos = [[5, 9, 8], [9, 3, 10], [9, 4, 5], [4, 8, 8]]
|
|
1377
|
+
r = wl.neh_flowshop(tiempos, names=["P1", "P2", "P3", "P4"])
|
|
1378
|
+
print(r) # secuencia y makespan
|
|
1379
|
+
print(r.to_frame()) # Gantt: columnas M1_inicio, M1_fin, M2_inicio, ...
|
|
1380
|
+
print(r.sequence, r.makespan)
|
|
1248
1381
|
```
|
|
1249
1382
|
|
|
1250
1383
|
---
|
|
1251
1384
|
|
|
1252
|
-
##
|
|
1385
|
+
## 14. Confiabilidad — `reliability`
|
|
1253
1386
|
|
|
1254
|
-
|
|
1387
|
+
Modelos de confiabilidad con distribución exponencial (tasa de falla constante).
|
|
1255
1388
|
|
|
1256
|
-
|
|
1257
|
-
|---|---|---|
|
|
1258
|
-
| `QueueResult.plot()` | Matplotlib | Curvas Wq y Lq vs ρ con punto operativo |
|
|
1259
|
-
| `OEEResult.plot()` | Matplotlib | Barras horizontales OEE con referencia 85% |
|
|
1260
|
-
| `BottleneckResult.plot()` | Matplotlib | Utilización por estación, cuello de botella en rojo |
|
|
1261
|
-
| `EOQResult.plot()` | Matplotlib | Curvas de costo de mantener, ordenar y total |
|
|
1262
|
-
| `LineBalanceResult.plot()` | Plotly | Cycle time vs takt por estación |
|
|
1263
|
-
| `BreakEvenResult.plot()` | Plotly | Líneas ingreso/costo con BEP y zona de beneficio |
|
|
1264
|
-
| `SimulationResult.plot()` | Plotly | Histograma de Wq + CDF empírica con percentiles |
|
|
1265
|
-
| `OptimizeResult.plot()` | Plotly | Costo por servidor, costo de espera y total vs c |
|
|
1266
|
-
| `KPINode.plot(kind=)` | Plotly | Treemap o sunburst interactivo |
|
|
1389
|
+
### Componente individual — `mtbf_analysis`
|
|
1267
1390
|
|
|
1268
1391
|
```python
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1392
|
+
r = wl.mtbf_analysis(failure_rate=0.01, mttr=5.0, t=100)
|
|
1393
|
+
print(r)
|
|
1394
|
+
```
|
|
1272
1395
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1396
|
+
```
|
|
1397
|
+
Topología : component
|
|
1398
|
+
Componentes : 1
|
|
1399
|
+
Tasas de falla λ: ['0.01']
|
|
1400
|
+
MTBF : 100
|
|
1401
|
+
R(t=100) : 0.367879
|
|
1402
|
+
Disponibilidad : 95.2381%
|
|
1276
1403
|
```
|
|
1277
1404
|
|
|
1278
|
-
|
|
1405
|
+
### Sistema en serie — `series_system`
|
|
1406
|
+
|
|
1407
|
+
El sistema falla si **cualquier** componente falla. λ_sys = Σλi.
|
|
1408
|
+
|
|
1279
1409
|
```python
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
fig.show()
|
|
1410
|
+
r = wl.series_system([0.01, 0.02, 0.03], t=10)
|
|
1411
|
+
print(r.mtbf) # 1/0.06 ≈ 16.67
|
|
1412
|
+
print(r.R_t) # e^{-0.06·10} ≈ 0.549
|
|
1284
1413
|
```
|
|
1285
1414
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
## 21. CLI
|
|
1415
|
+
### Sistema en paralelo — `parallel_system`
|
|
1289
1416
|
|
|
1290
|
-
|
|
1417
|
+
El sistema opera si **al menos uno** de los componentes opera.
|
|
1418
|
+
R_sys(t) = 1 − ∏(1 − e^{−λi·t}). MTBF calculado numéricamente.
|
|
1291
1419
|
|
|
1292
|
-
```
|
|
1293
|
-
|
|
1294
|
-
|
|
1420
|
+
```python
|
|
1421
|
+
r = wl.parallel_system([0.01, 0.02], t=50)
|
|
1422
|
+
print(r.R_t) # mayor que el sistema en serie
|
|
1423
|
+
print(r.mtbf) # mayor que cualquier componente individual
|
|
1424
|
+
```
|
|
1295
1425
|
|
|
1296
|
-
|
|
1297
|
-
python -m walopy mmc --lam 8 --mu 5 --c 2
|
|
1426
|
+
### Sistema k-de-n — `koon_system`
|
|
1298
1427
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1428
|
+
Opera si al menos **k** de **n** componentes idénticos están en servicio.
|
|
1429
|
+
MTBF exacto = (1/λ) · Σ_{j=k}^{n} (1/j).
|
|
1301
1430
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1431
|
+
```python
|
|
1432
|
+
# 2-de-3: requiere al menos 2 de 3 componentes idénticos
|
|
1433
|
+
r = wl.koon_system(n=3, k=2, failure_rate=0.01, t=50)
|
|
1434
|
+
print(r.mtbf) # (1/0.01) * (1/2 + 1/3) ≈ 83.33
|
|
1435
|
+
print(r.R_t)
|
|
1436
|
+
```
|
|
1304
1437
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1438
|
+
**Casos especiales**
|
|
1439
|
+
- `k=1` → equivale a `parallel_system` con n componentes idénticos.
|
|
1440
|
+
- `k=n` → equivale a `series_system` con n componentes idénticos.
|
|
1307
1441
|
|
|
1308
|
-
|
|
1309
|
-
python -m walopy eoq --demand 1000 --ordering 50 --holding 2
|
|
1442
|
+
**Atributos de `ReliabilityResult`**
|
|
1310
1443
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1444
|
+
| Atributo | Descripción |
|
|
1445
|
+
|----------|-------------|
|
|
1446
|
+
| `topology` | `'component'`, `'series'`, `'parallel'`, `'k-of-n'` |
|
|
1447
|
+
| `n_components` | Número de componentes |
|
|
1448
|
+
| `failure_rates` | Lista de λi |
|
|
1449
|
+
| `mtbf` | MTBF del sistema |
|
|
1450
|
+
| `R_t` | Confiabilidad R(t) si se proporcionó `t` |
|
|
1451
|
+
| `availability` | Disponibilidad estacionaria A si se proporcionó `mttr` |
|
|
1452
|
+
| `mttr` | Tiempo medio de reparación (si se proporcionó) |
|
|
1314
1453
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
λ : 3.0 (arrival rate)
|
|
1319
|
-
μ : 5.0 (service rate per server)
|
|
1320
|
-
ρ : 0.6 (utilization)
|
|
1321
|
-
L : 1.5 (avg units in system)
|
|
1322
|
-
Lq : 0.9 (avg units in queue)
|
|
1323
|
-
W : 0.5 (avg time in system)
|
|
1324
|
-
Wq : 0.3 (avg wait time in queue)
|
|
1325
|
-
P0 : 0.4
|
|
1454
|
+
```python
|
|
1455
|
+
df = r.to_frame() # columnas: Topología, Componentes, MTBF[, R(t), Disponibilidad]
|
|
1456
|
+
r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
|
|
1326
1457
|
```
|
|
1327
1458
|
|
|
1328
1459
|
---
|
|
1329
1460
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
Herramientas para tomar decisiones de política agregada en familias de artículos.
|
|
1461
|
+
### Distribución de Weibull — `weibull_analysis`
|
|
1333
1462
|
|
|
1334
|
-
|
|
1463
|
+
Ajusta una Weibull de dos parámetros (forma β, escala η) a tiempos de falla completos por **máxima
|
|
1464
|
+
verosimilitud** (`method="MLE"`, bisección sobre la ecuación de verosimilitud) o por **regresión de rangos**
|
|
1465
|
+
(`method="RRY"`, rangos medianos de Benard). β < 1 indica mortalidad infantil, β ≈ 1 fallas aleatorias y
|
|
1466
|
+
β > 1 desgaste.
|
|
1335
1467
|
|
|
1336
|
-
|
|
1468
|
+
```python
|
|
1469
|
+
tiempos = [12.5, 18.3, 24.1, 31.7, 38.2, 45.9, 52.4, 61.0, 70.8, 85.3]
|
|
1470
|
+
w = wl.weibull_analysis(tiempos)
|
|
1471
|
+
print(w) # β ≈ 2.10, η ≈ 49.86, MTTF ≈ 44.16
|
|
1472
|
+
print(w.R(30), w.F(30)) # confiabilidad y probabilidad de falla a t = 30
|
|
1473
|
+
print(w.h(30)) # tasa de falla instantánea
|
|
1474
|
+
print(w.b10, w.b50, w.b_life(5)) # vidas B10, B50 y B5
|
|
1475
|
+
print(w.to_frame())
|
|
1337
1476
|
|
|
1338
|
-
|
|
1339
|
-
N(k) = N* / k I(k) = k · I* → N · I = N* · I* (constante)
|
|
1477
|
+
w_rry = wl.weibull_analysis(tiempos, method="RRY")
|
|
1340
1478
|
```
|
|
1341
1479
|
|
|
1342
|
-
|
|
1480
|
+
Se necesitan al menos 2 tiempos finitos y positivos; si todos son iguales la forma no es estimable
|
|
1481
|
+
(`ValueError`) y, si casi no hay dispersión, se avisa (`UserWarning`) de que β alcanzó la cota superior de 100.
|
|
1343
1482
|
|
|
1344
|
-
|
|
1345
|
-
import walopy as wl
|
|
1483
|
+
---
|
|
1346
1484
|
|
|
1347
|
-
|
|
1348
|
-
{"name": "A", "demand": 1000, "ordering_cost": 50, "holding_cost": 2, "unit_value": 10},
|
|
1349
|
-
{"name": "B", "demand": 500, "ordering_cost": 30, "holding_cost": 1, "unit_value": 5},
|
|
1350
|
-
{"name": "C", "demand": 2000, "ordering_cost": 100, "holding_cost": 4, "unit_value": 8},
|
|
1351
|
-
]
|
|
1485
|
+
## 15. Proyectos: CPM y PERT — `project`
|
|
1352
1486
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
```
|
|
1487
|
+
Programación de proyectos sobre una red de actividades. Cada actividad es un diccionario con `name`,
|
|
1488
|
+
`predecessors` (lista de nombres, puede omitirse) y su duración. Los nombres deben ser únicos; los
|
|
1489
|
+
ciclos y los predecesores desconocidos lanzan `ValueError`.
|
|
1357
1490
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
N orders/yr (EOQ) : 87.27
|
|
1362
|
-
N orders/yr (optimal) : 87.27
|
|
1363
|
-
Investment (EOQ) : 5430
|
|
1364
|
-
Investment (optimal) : 5430
|
|
1365
|
-
```
|
|
1491
|
+
### CPM — duraciones determinísticas
|
|
1492
|
+
|
|
1493
|
+
Paso hacia adelante (ES, EF) y hacia atrás (LS, LF); holgura total HT = LS − ES, holgura libre HL y ruta crítica (HT = 0).
|
|
1366
1494
|
|
|
1367
1495
|
```python
|
|
1368
|
-
|
|
1369
|
-
|
|
1496
|
+
acts = [
|
|
1497
|
+
{"name": "A", "duration": 3, "predecessors": []},
|
|
1498
|
+
{"name": "B", "duration": 4, "predecessors": ["A"]},
|
|
1499
|
+
{"name": "C", "duration": 2, "predecessors": ["A"]},
|
|
1500
|
+
{"name": "D", "duration": 1, "predecessors": ["B", "C"]},
|
|
1501
|
+
]
|
|
1502
|
+
r = wl.cpm(acts)
|
|
1503
|
+
print(r) # duración 8, ruta crítica A → B → D
|
|
1504
|
+
print(r.to_frame()) # ES, EF, LS, LF, HT, HL y si es crítica
|
|
1505
|
+
print(r.critical_path) # ['A', 'B', 'D']
|
|
1506
|
+
```
|
|
1370
1507
|
|
|
1371
|
-
|
|
1372
|
-
r = wl.exchange_curve(items, target_investment=3000)
|
|
1508
|
+
### PERT — tres estimaciones por actividad
|
|
1373
1509
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
# columnas: name | Q_eoq | Q_optimal | n_orders | investment
|
|
1510
|
+
Duración esperada tₑ = (a + 4m + b)/6 y varianza ((b − a)/6)²; la varianza del proyecto es la suma de las
|
|
1511
|
+
de la ruta crítica. `probability(T)` da P(duración ≤ T) con aproximación normal.
|
|
1377
1512
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1513
|
+
```python
|
|
1514
|
+
acts = [
|
|
1515
|
+
{"name": "A", "optimistic": 1, "most_likely": 3, "pessimistic": 5, "predecessors": []},
|
|
1516
|
+
{"name": "B", "optimistic": 2, "most_likely": 4, "pessimistic": 9, "predecessors": ["A"]},
|
|
1517
|
+
]
|
|
1518
|
+
p = wl.pert(acts)
|
|
1519
|
+
print(p) # duración esperada 7.5, σ ≈ 1.34
|
|
1520
|
+
print(p.probability(9.0)) # ≈ 0.868: probabilidad de terminar en 9 unidades o menos
|
|
1381
1521
|
```
|
|
1382
1522
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
| Clave | Requerido | Descripción |
|
|
1386
|
-
|-------|-----------|-------------|
|
|
1387
|
-
| `demand` | ✓ | Demanda anual Di |
|
|
1388
|
-
| `ordering_cost` | ✓ | Costo de pedido Ki |
|
|
1389
|
-
| `holding_cost` | ✓ | Costo de mantenimiento hi |
|
|
1390
|
-
| `unit_value` | — | Valor unitario vi (default 1) |
|
|
1391
|
-
| `name` | — | Etiqueta (default I1, I2, …) |
|
|
1523
|
+
`probability` solo está disponible en resultados PERT con varianza distinta de cero.
|
|
1392
1524
|
|
|
1393
1525
|
---
|
|
1394
1526
|
|
|
1395
|
-
|
|
1527
|
+
## 16. Árboles de KPI
|
|
1396
1528
|
|
|
1397
|
-
|
|
1529
|
+
Los árboles de KPI crean jerarquías interactivas visualizadas como **treemap** o **sunburst** con Plotly.
|
|
1398
1530
|
|
|
1399
|
-
|
|
1400
|
-
SS_i(z) = z · σᵢ_DLT · vᵢ σᵢ_DLT = √(Lᵢ·σ²_Di + D²ᵢ·σ²_Li)
|
|
1401
|
-
SS_total(z) = z · Σ σᵢ_DLT · vᵢ SL(z) = Φ(z)
|
|
1402
|
-
```
|
|
1531
|
+
### `oee_kpi_tree(availability, performance, quality)`
|
|
1403
1532
|
|
|
1404
1533
|
```python
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
{"name": "C", "demand_rate": 200, "demand_std": 20, "lead_time": 3, "unit_value": 8},
|
|
1409
|
-
]
|
|
1410
|
-
|
|
1411
|
-
# Target: nivel de servicio 95%
|
|
1412
|
-
r = wl.safety_stock_curve(items, target_service_level=0.95)
|
|
1413
|
-
print(r)
|
|
1534
|
+
tree = wl.oee_kpi_tree(0.90, 0.80, 0.95)
|
|
1535
|
+
tree.plot() # treemap (por defecto)
|
|
1536
|
+
tree.plot(kind="sunburst") # diagrama radial
|
|
1414
1537
|
```
|
|
1415
1538
|
|
|
1416
|
-
|
|
1417
|
-
Target : service_level=95.0000%
|
|
1418
|
-
z : 1.6449
|
|
1419
|
-
Service level : 95.0000%
|
|
1420
|
-
SS investment : 1 847
|
|
1421
|
-
```
|
|
1539
|
+
### `throughput_kpi_tree(actual_throughput, capacity, defect_rate)`
|
|
1422
1540
|
|
|
1423
1541
|
```python
|
|
1424
|
-
|
|
1425
|
-
|
|
1542
|
+
tree = wl.throughput_kpi_tree(
|
|
1543
|
+
actual_throughput=750,
|
|
1544
|
+
capacity=1000,
|
|
1545
|
+
defect_rate=0.05,
|
|
1546
|
+
)
|
|
1547
|
+
tree.plot()
|
|
1548
|
+
```
|
|
1426
1549
|
|
|
1427
|
-
|
|
1428
|
-
df = r.to_frame()
|
|
1429
|
-
# columnas: name | sigma_dlt | safety_stock | investment | reorder_point
|
|
1550
|
+
### `roi_kpi_tree(revenue, fixed_cost, variable_cost_per_unit, units_sold, investment)`
|
|
1430
1551
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1552
|
+
```python
|
|
1553
|
+
tree = wl.roi_kpi_tree(
|
|
1554
|
+
revenue=50_000,
|
|
1555
|
+
fixed_cost=10_000,
|
|
1556
|
+
variable_cost_per_unit=8,
|
|
1557
|
+
units_sold=2_000,
|
|
1558
|
+
investment=20_000,
|
|
1559
|
+
)
|
|
1560
|
+
print(tree.value) # ROI = 0.2 (20%)
|
|
1561
|
+
tree.plot()
|
|
1434
1562
|
```
|
|
1435
1563
|
|
|
1436
|
-
|
|
1564
|
+
### `KPINode` — árbol personalizado
|
|
1437
1565
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1566
|
+
```python
|
|
1567
|
+
rev = wl.KPINode("Revenue", value=200_000, unit="€", children=[
|
|
1568
|
+
wl.KPINode("Product A", value=120_000, unit="€"),
|
|
1569
|
+
wl.KPINode("Product B", value=80_000, unit="€"),
|
|
1570
|
+
])
|
|
1571
|
+
cost = wl.KPINode("Operating costs", value=100_000, unit="€")
|
|
1572
|
+
root = wl.KPINode("EBITDA", value=100_000, unit="€", formula="Revenue − Operating costs",
|
|
1573
|
+
children=[rev, cost])
|
|
1574
|
+
|
|
1575
|
+
from walopy.plotting import plot_kpi_tree
|
|
1576
|
+
fig = plot_kpi_tree(root, kind="treemap")
|
|
1577
|
+
fig.show()
|
|
1578
|
+
```
|
|
1446
1579
|
|
|
1447
1580
|
---
|
|
1448
1581
|
|
|
1449
|
-
|
|
1582
|
+
## 17. Solvers y optimización
|
|
1450
1583
|
|
|
1451
|
-
|
|
1452
|
-
|---|---|---|
|
|
1453
|
-
| **Función** | `exchange_curve` | `safety_stock_curve` |
|
|
1454
|
-
| **Decide** | Tamaño de lote Q | Punto de reorden r |
|
|
1455
|
-
| **Tradeoff** | N pedidos/año ↔ Inversión ciclo | Nivel de servicio ↔ SS |
|
|
1456
|
-
| **Resultado** | `ExchangeCurveResult` | `SafetyStockCurveResult` |
|
|
1457
|
-
| **Parámetro** | Multiplicador k | Factor z |
|
|
1584
|
+
### `solve_lam(target_metric, target_value, *, mu, model)` — Máxima tasa de llegada
|
|
1458
1585
|
|
|
1459
|
-
|
|
1586
|
+
Encuentra la mayor λ tal que una métrica no supere el objetivo.
|
|
1460
1587
|
|
|
1461
|
-
|
|
1588
|
+
```python
|
|
1589
|
+
r = wl.solve_lam("Wq", 0.5, mu=5.0, model="mm1")
|
|
1590
|
+
print(r.value) # λ máxima ≈ 3.33
|
|
1591
|
+
print(r.achieved_value) # Wq ≈ 0.5
|
|
1592
|
+
print(r.model_result) # QueueResult completo en la solución
|
|
1593
|
+
```
|
|
1462
1594
|
|
|
1463
|
-
|
|
1595
|
+
### `solve_mu(target_metric, target_value, *, lam, model)` — Mínima tasa de servicio
|
|
1464
1596
|
|
|
1465
|
-
|
|
1597
|
+
```python
|
|
1598
|
+
r = wl.solve_mu("Wq", 0.3, lam=3.0, model="mm1")
|
|
1599
|
+
print(r.value) # μ mínima necesaria ≈ 5.0
|
|
1600
|
+
```
|
|
1466
1601
|
|
|
1467
|
-
|
|
1602
|
+
### `solve_servers(target_metric, target_value, *, lam, mu)` — Mínimo número de servidores
|
|
1468
1603
|
|
|
1469
1604
|
```python
|
|
1470
|
-
|
|
1605
|
+
r = wl.solve_servers("Wq", 0.1, lam=8.0, mu=5.0)
|
|
1606
|
+
print(r.value) # c mínimo = 3
|
|
1607
|
+
print(r.model_result) # QueueResult para M/M/3
|
|
1608
|
+
```
|
|
1471
1609
|
|
|
1472
|
-
|
|
1473
|
-
d = [8, 3, 10, 4, 12] # fechas de entrega
|
|
1474
|
-
w = [2, 5, 1, 4, 1] # pesos de importancia
|
|
1610
|
+
### `optimize_servers(lam, mu, cost_per_server, cost_per_wait)` — Optimización de costo total
|
|
1475
1611
|
|
|
1476
|
-
|
|
1477
|
-
names=["A","B","C","D","E"])
|
|
1478
|
-
print(r)
|
|
1479
|
-
```
|
|
1612
|
+
Minimiza cost_per_server × c + cost_per_wait × λ × Wq evaluando todos los c viables.
|
|
1480
1613
|
|
|
1481
|
-
```
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1614
|
+
```python
|
|
1615
|
+
r = wl.optimize_servers(
|
|
1616
|
+
lam=6.0,
|
|
1617
|
+
mu=5.0,
|
|
1618
|
+
cost_per_server=10.0, # costo fijo por servidor por unidad de tiempo
|
|
1619
|
+
cost_per_wait=5.0, # costo por unidad de tiempo de espera de cada cliente
|
|
1620
|
+
)
|
|
1621
|
+
print(r.optimal_servers) # c óptimo
|
|
1622
|
+
print(r.min_cost) # costo mínimo
|
|
1623
|
+
|
|
1624
|
+
r.plot() # gráfica de costo vs número de servidores
|
|
1490
1625
|
```
|
|
1491
1626
|
|
|
1492
|
-
|
|
1627
|
+
---
|
|
1493
1628
|
|
|
1494
|
-
|
|
1495
|
-
|--------|---------------------|-----------------|
|
|
1496
|
-
| `'SPT'` | ΣCj (tiempo total de completación) | — |
|
|
1497
|
-
| `'EDD'` | Lmax (máxima tardanza) | `due_dates` |
|
|
1498
|
-
| `'WSPT'` | ΣwjCj (completación ponderada) | `weights` |
|
|
1499
|
-
| `'CR'` | Ratio crítico dj/pj | `due_dates` |
|
|
1500
|
-
| `'FIFO'` | Orden de llegada (línea base) | — |
|
|
1629
|
+
## 18. Análisis de sensibilidad
|
|
1501
1630
|
|
|
1502
|
-
**
|
|
1631
|
+
### `sensitivity(model_fn, param, values, **fixed_kwargs)` — Barrido paramétrico
|
|
1503
1632
|
|
|
1504
|
-
|
|
1505
|
-
|----------|-------------|
|
|
1506
|
-
| `rule` | Regla utilizada |
|
|
1507
|
-
| `sequence` | Lista de nombres en orden de procesamiento |
|
|
1508
|
-
| `jobs` | Lista de `JobSchedule` (por trabajo) |
|
|
1509
|
-
| `makespan` | Cmax = suma de tiempos de proceso |
|
|
1510
|
-
| `total_completion_time` | ΣCj |
|
|
1511
|
-
| `total_weighted_completion_time` | ΣwjCj |
|
|
1512
|
-
| `max_lateness` | max(Lj) |
|
|
1513
|
-
| `total_tardiness` | ΣTj |
|
|
1514
|
-
| `n_tardy` | Número de trabajos tarde |
|
|
1633
|
+
Evalúa un modelo sobre un rango de valores de un parámetro.
|
|
1515
1634
|
|
|
1516
1635
|
```python
|
|
1517
|
-
|
|
1636
|
+
import numpy as np
|
|
1637
|
+
|
|
1638
|
+
df = wl.sensitivity(
|
|
1639
|
+
wl.mm1,
|
|
1640
|
+
param="lam",
|
|
1641
|
+
values=np.linspace(0.5, 4.5, 20),
|
|
1642
|
+
mu=5.0, # parámetros fijos
|
|
1643
|
+
)
|
|
1644
|
+
print(df.columns.tolist()) # ['lam', 'rho', 'L', 'Lq', 'W', 'Wq', ...]
|
|
1645
|
+
|
|
1646
|
+
# Barrido sobre μ
|
|
1647
|
+
df2 = wl.sensitivity(wl.mm1, "mu", np.linspace(4.0, 10.0, 15), lam=3.0)
|
|
1648
|
+
|
|
1649
|
+
# Barrido sobre número de servidores en M/M/c
|
|
1650
|
+
df3 = wl.sensitivity(wl.mmc, "c", range(1, 6), lam=8.0, mu=5.0)
|
|
1651
|
+
|
|
1652
|
+
# Visualizar
|
|
1653
|
+
from walopy.plotting import plot_sensitivity
|
|
1654
|
+
fig = plot_sensitivity(df, "lam", metrics=["Wq", "Lq"])
|
|
1655
|
+
fig.show()
|
|
1518
1656
|
```
|
|
1519
1657
|
|
|
1520
1658
|
---
|
|
1521
1659
|
|
|
1522
|
-
|
|
1660
|
+
## 19. Escenarios en lote (batch_model)
|
|
1523
1661
|
|
|
1524
|
-
|
|
1662
|
+
### `batch_model(model_fn, df, **fixed_kwargs)` — Aplicar modelo a un DataFrame
|
|
1663
|
+
|
|
1664
|
+
Aplica cualquier función de walopy a cada fila de un DataFrame de escenarios.
|
|
1525
1665
|
|
|
1526
1666
|
```python
|
|
1527
|
-
|
|
1528
|
-
m2 = [5, 2, 8, 4, 6] # tiempos en máquina 2
|
|
1667
|
+
import pandas as pd
|
|
1529
1668
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1669
|
+
# Varios escenarios con distintas tasas de llegada
|
|
1670
|
+
escenarios = pd.DataFrame({
|
|
1671
|
+
"lam": [1.0, 2.0, 3.0, 4.0]
|
|
1672
|
+
})
|
|
1673
|
+
resultado = wl.batch_model(wl.mm1, escenarios, mu=5.0)
|
|
1674
|
+
print(resultado[["lam", "rho", "Wq", "L"]])
|
|
1533
1675
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1676
|
+
# Escenarios mixtos (lambda y mu variables)
|
|
1677
|
+
escenarios2 = pd.DataFrame({
|
|
1678
|
+
"lam": [2.0, 3.0, 4.0],
|
|
1679
|
+
"mu": [5.0, 6.0, 7.0],
|
|
1680
|
+
})
|
|
1681
|
+
resultado2 = wl.batch_model(wl.mm1, escenarios2)
|
|
1538
1682
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1683
|
+
# Con número de servidores variable (columna int)
|
|
1684
|
+
escenarios3 = pd.DataFrame({
|
|
1685
|
+
"c": [1, 2, 3, 4],
|
|
1686
|
+
})
|
|
1687
|
+
resultado3 = wl.batch_model(wl.mmc, escenarios3, lam=8.0, mu=5.0)
|
|
1688
|
+
|
|
1689
|
+
# Las filas con error quedan marcadas en "_error" (la columna solo existe si alguna fila falla;
|
|
1690
|
+
# aquí c=1 es inestable porque λ=8 > μ=5)
|
|
1691
|
+
print(resultado3[resultado3["_error"].notna()]) # filas fallidas
|
|
1692
|
+
|
|
1693
|
+
# Para que la primera excepción se propague en lugar de recogerse:
|
|
1694
|
+
# wl.batch_model(wl.mmc, escenarios3, errors="raise", lam=8.0, mu=5.0)
|
|
1542
1695
|
```
|
|
1543
1696
|
|
|
1544
1697
|
---
|
|
1545
1698
|
|
|
1546
|
-
##
|
|
1699
|
+
## 20. Comparar múltiples resultados
|
|
1547
1700
|
|
|
1548
|
-
|
|
1701
|
+
### `compare(*results, labels)` — DataFrame comparativo
|
|
1549
1702
|
|
|
1550
|
-
|
|
1703
|
+
Construye un DataFrame con una fila por resultado para facilitar la comparación.
|
|
1551
1704
|
|
|
1552
1705
|
```python
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1706
|
+
df = wl.compare(
|
|
1707
|
+
wl.mm1(3.0, 5.0),
|
|
1708
|
+
wl.mmc(3.0, 5.0, 2),
|
|
1709
|
+
wl.md1(3.0, 5.0),
|
|
1710
|
+
labels=["M/M/1", "M/M/2", "M/D/1"],
|
|
1711
|
+
)
|
|
1712
|
+
print(df[["label", "ρ (utilización)", "Wq (tiempo de espera)", "L (en el sistema)"]])
|
|
1713
|
+
# label ρ (utilización) Wq (tiempo de espera) L (en el sistema)
|
|
1714
|
+
# 0 M/M/1 0.6 0.3000 1.50
|
|
1715
|
+
# 1 M/M/2 0.3 0.0302 1.02
|
|
1716
|
+
# 2 M/D/1 0.6 0.1500 1.05
|
|
1556
1717
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
Components : 1
|
|
1560
|
-
Failure rates λ : ['0.01']
|
|
1561
|
-
MTBF : 100
|
|
1562
|
-
R(t=100) : 0.367879
|
|
1563
|
-
Availability : 95.2381%
|
|
1718
|
+
# Sin etiquetas → scenario_1, scenario_2, ...
|
|
1719
|
+
df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
|
|
1564
1720
|
```
|
|
1565
1721
|
|
|
1566
|
-
|
|
1722
|
+
---
|
|
1567
1723
|
|
|
1568
|
-
|
|
1724
|
+
## 21. Gráficas
|
|
1569
1725
|
|
|
1570
|
-
|
|
1571
|
-
r = wl.series_system([0.01, 0.02, 0.03], t=10)
|
|
1572
|
-
print(r.mtbf) # 1/0.06 ≈ 16.67
|
|
1573
|
-
print(r.R_t) # e^{-0.06·10} ≈ 0.549
|
|
1574
|
-
```
|
|
1726
|
+
Todos los resultados exponen `.plot()` que devuelve una figura de Matplotlib o Plotly. También se pueden llamar directamente desde `walopy.plotting`.
|
|
1575
1727
|
|
|
1576
|
-
|
|
1728
|
+
| Función `.plot()` | Tipo | Descripción |
|
|
1729
|
+
|---|---|---|
|
|
1730
|
+
| `QueueResult.plot()` | Matplotlib | Curvas Wq y Lq vs ρ con punto operativo |
|
|
1731
|
+
| `OEEResult.plot()` | Matplotlib | Barras horizontales OEE con referencia 85% |
|
|
1732
|
+
| `BottleneckResult.plot()` | Matplotlib | Utilización por estación, cuello de botella en rojo |
|
|
1733
|
+
| `EOQResult.plot()` | Matplotlib | Curvas de costo de mantener, ordenar y total |
|
|
1734
|
+
| `LineBalanceResult.plot()` | Plotly | Tiempo de ciclo vs takt por estación |
|
|
1735
|
+
| `BreakEvenResult.plot()` | Plotly | Líneas ingreso/costo con BEP y zona de beneficio |
|
|
1736
|
+
| `SimulationResult.plot()` | Plotly | Histograma de Wq + CDF empírica con percentiles |
|
|
1737
|
+
| `OptimizeResult.plot()` | Plotly | Costo por servidor, costo de espera y total vs c |
|
|
1738
|
+
| `KPINode.plot(kind=)` | Plotly | Treemap o sunburst interactivo |
|
|
1577
1739
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1740
|
+
```python
|
|
1741
|
+
# Guardar como HTML interactivo (Plotly)
|
|
1742
|
+
fig = r.plot()
|
|
1743
|
+
fig.write_html("resultado.html")
|
|
1580
1744
|
|
|
1745
|
+
# Guardar como imagen (Matplotlib)
|
|
1746
|
+
fig = wl.oee(0.9, 0.8, 0.95).plot()
|
|
1747
|
+
fig.savefig("oee.png", dpi=150)
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
Para la curva de sensibilidad:
|
|
1581
1751
|
```python
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1752
|
+
from walopy.plotting import plot_sensitivity
|
|
1753
|
+
df = wl.sensitivity(wl.mm1, "lam", np.linspace(0.5, 4.5, 20), mu=5.0)
|
|
1754
|
+
fig = plot_sensitivity(df, "lam", metrics=["Wq", "Lq", "L"])
|
|
1755
|
+
fig.show()
|
|
1585
1756
|
```
|
|
1586
1757
|
|
|
1587
|
-
|
|
1758
|
+
---
|
|
1588
1759
|
|
|
1589
|
-
|
|
1590
|
-
MTBF exacto = (1/λ) · Σ_{j=k}^{n} (1/j).
|
|
1760
|
+
## 22. CLI
|
|
1591
1761
|
|
|
1592
|
-
|
|
1593
|
-
# 2-de-3: requiere al menos 2 de 3 componentes idénticos
|
|
1594
|
-
r = wl.koon_system(n=3, k=2, failure_rate=0.01, t=50)
|
|
1595
|
-
print(r.mtbf) # (1/0.01) * (1/2 + 1/3) ≈ 83.33
|
|
1596
|
-
print(r.R_t)
|
|
1597
|
-
```
|
|
1762
|
+
walopy incluye una interfaz de línea de comandos para uso rápido sin escribir código.
|
|
1598
1763
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
-
|
|
1764
|
+
```bash
|
|
1765
|
+
# M/M/1
|
|
1766
|
+
python -m walopy mm1 --lam 3 --mu 5
|
|
1602
1767
|
|
|
1603
|
-
|
|
1768
|
+
# M/M/c
|
|
1769
|
+
python -m walopy mmc --lam 8 --mu 5 --c 2
|
|
1604
1770
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
| `topology` | `'component'`, `'series'`, `'parallel'`, `'k-of-n'` |
|
|
1608
|
-
| `n_components` | Número de componentes |
|
|
1609
|
-
| `failure_rates` | Lista de λi |
|
|
1610
|
-
| `mtbf` | MTBF del sistema |
|
|
1611
|
-
| `R_t` | Confiabilidad R(t) si se proporcionó `t` |
|
|
1612
|
-
| `availability` | Disponibilidad estacionaria A si se proporcionó `mttr` |
|
|
1613
|
-
| `mttr` | Tiempo medio de reparación (si se proporcionó) |
|
|
1771
|
+
# M/D/1
|
|
1772
|
+
python -m walopy md1 --lam 3 --mu 5
|
|
1614
1773
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1774
|
+
# G/G/1 (Kingman)
|
|
1775
|
+
python -m walopy gg1 --lam 3 --mu 5 --ca2 1.2 --cs2 0.8
|
|
1776
|
+
|
|
1777
|
+
# Ley de Little (resolver W)
|
|
1778
|
+
python -m walopy littles --L 2.5 --lam 5.0
|
|
1779
|
+
|
|
1780
|
+
# EOQ
|
|
1781
|
+
python -m walopy eoq --demand 1000 --ordering 50 --holding 2
|
|
1782
|
+
|
|
1783
|
+
# Versión
|
|
1784
|
+
python -m walopy --version
|
|
1785
|
+
```
|
|
1786
|
+
|
|
1787
|
+
Ejemplo de salida:
|
|
1788
|
+
```
|
|
1789
|
+
Modelo : M/M/1
|
|
1790
|
+
λ : 3.0 (tasa de llegada)
|
|
1791
|
+
μ : 5.0 (tasa de servicio por servidor)
|
|
1792
|
+
ρ : 0.6 (utilización por servidor)
|
|
1793
|
+
L : 1.5 (unidades promedio en el sistema)
|
|
1794
|
+
Lq : 0.9 (unidades promedio en cola)
|
|
1795
|
+
W : 0.5 (tiempo promedio en el sistema)
|
|
1796
|
+
Wq : 0.3 (tiempo promedio de espera en cola)
|
|
1797
|
+
P0 : 0.4
|
|
1618
1798
|
```
|
|
1619
1799
|
|
|
1620
1800
|
---
|
|
1621
1801
|
|
|
1622
|
-
##
|
|
1802
|
+
## 23. Referencia de módulos
|
|
1623
1803
|
|
|
1624
1804
|
| Módulo | Funciones y clases principales |
|
|
1625
1805
|
|---|---|
|
|
1626
1806
|
| `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `mg1`, `littles_law`, `QueueResult`, `cv2_normal`, `cv2_triangular`, `cv2_uniform`, `cv2_erlang`, `cv2_gamma`, `cv2_lognormal`, `cv2_weibull` |
|
|
1627
1807
|
| `advanced` | `mm1k`, `mmck`, `erlang_b`, `mm1_priority`, `monte_carlo_gg1`, `takt_time`, `line_balance`, `break_even`, `break_even_multi`, `break_even_sales`, `queue_length_pmf`, `sojourn_cdf`, `PriorityQueueResult`, `SimulationResult`, `LineBalanceResult`, `BreakEvenResult`, `BreakEvenMultiResult` |
|
|
1628
1808
|
| `fitting` | `fit_from_data`, `FitResult` |
|
|
1629
|
-
| `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `exchange_curve`, `safety_stock_curve`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult`, `ExchangeCurveResult`, `SafetyStockCurveResult` |
|
|
1630
|
-
| `scheduling` | `schedule_single`, `johnson_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult` |
|
|
1631
|
-
| `
|
|
1809
|
+
| `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `exchange_curve`, `safety_stock_curve`, `abc_analysis`, `xyz_analysis`, `abc_xyz`, `mrp`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult`, `ExchangeCurveResult`, `SafetyStockCurveResult`, `ABCResult`, `XYZResult`, `ABCXYZResult`, `MRPResult` |
|
|
1810
|
+
| `scheduling` | `schedule_single`, `johnson_flowshop`, `neh_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult`, `NEHResult` |
|
|
1811
|
+
| `project` | `cpm`, `pert`, `ProjectResult`, `ActivityResult` |
|
|
1812
|
+
| `reliability` | `mtbf_analysis`, `series_system`, `parallel_system`, `koon_system`, `weibull_analysis`, `ReliabilityResult`, `WeibullResult` |
|
|
1632
1813
|
| `network` | `jackson_network`, `JacksonResult`, `StationMetrics` |
|
|
1633
1814
|
| `operations` | `oee`, `utilization_efficiency`, `unit_cost`, `OEEResult`, `UtilizationResult`, `UnitCostResult` |
|
|
1634
1815
|
| `bottleneck` | `bottleneck_analysis`, `BottleneckResult`, `StationResult` |
|
|
@@ -1638,7 +1819,43 @@ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
|
|
|
1638
1819
|
|
|
1639
1820
|
---
|
|
1640
1821
|
|
|
1641
|
-
##
|
|
1822
|
+
## 24. Idioma de los textos
|
|
1823
|
+
|
|
1824
|
+
Los mensajes de error, avisos, etiquetas de `summary()`, cabeceras de `to_frame()`, títulos de gráficas y la ayuda del CLI se muestran en el
|
|
1825
|
+
idioma activo: **español (`"es"`, por defecto) o inglés (`"en"`)**. Si algún texto no estuviera traducido, se muestra en español: nunca falla por eso.
|
|
1826
|
+
|
|
1827
|
+
El idioma se elige, de mayor a menor prioridad, con:
|
|
1828
|
+
|
|
1829
|
+
1. `with walopy.language("en"):` — solo dentro del bloque (seguro con hilos y `asyncio`).
|
|
1830
|
+
2. `walopy.set_language("en")` — para todo el proceso.
|
|
1831
|
+
3. La variable de entorno `WALOPY_LANG=en` (también para el CLI). Un valor desconocido se ignora y se usa el español.
|
|
1832
|
+
|
|
1833
|
+
```python
|
|
1834
|
+
import walopy as wl
|
|
1835
|
+
|
|
1836
|
+
wl.get_language() # 'es' (por defecto)
|
|
1837
|
+
with wl.language("en"): # cambia el idioma solo dentro del bloque
|
|
1838
|
+
print(wl.mm1(2, 3).summary().splitlines()[1]) # λ : 2 (arrival rate)
|
|
1839
|
+
print(list(wl.mm1(2, 3).to_frame().columns)[:3])
|
|
1840
|
+
wl.set_language("es") # fija el idioma para todo el proceso
|
|
1841
|
+
try:
|
|
1842
|
+
wl.set_language("fr") # un idioma no admitido lanza ValueError
|
|
1843
|
+
except ValueError as e:
|
|
1844
|
+
print(e)
|
|
1845
|
+
```
|
|
1846
|
+
|
|
1847
|
+
Qué **no** cambia con el idioma: los nombres de funciones y argumentos, las claves de `result.params` (por ejemplo `"P0 (prob. de sistema vacío)"`;
|
|
1848
|
+
`summary()` sí muestra su etiqueta traducida) y los nombres de las claves de datos (`"Q*"`, `"name"`, las clases `"A"`/`"B"`/`"C"`). Los números
|
|
1849
|
+
tampoco cambian. Sí cambian, porque se crean en el idioma activo en ese momento: los nombres de las **columnas** de los `DataFrame`
|
|
1850
|
+
(`"Artículo"` → `"Item"`), las claves de texto de las tablas internas (`result.items[0]["Producto"]`), el texto de `result.model` o `result.method`
|
|
1851
|
+
cuando es una frase (`"Lote por lote"` → `"Lot-for-lot"`) y los nombres por defecto (`Artículo-1` → `Item-1`). Si cambias de idioma entre crear
|
|
1852
|
+
un resultado y mostrarlo o graficarlo, `walopy` sigue encontrando sus columnas; tu propio código debe usar el nombre que la tabla tenga.
|
|
1853
|
+
|
|
1854
|
+
Desde la línea de comandos: `WALOPY_LANG=en python -m walopy mm1 --lam 2 --mu 3`.
|
|
1855
|
+
|
|
1856
|
+
---
|
|
1857
|
+
|
|
1858
|
+
## 25. Requisitos
|
|
1642
1859
|
|
|
1643
1860
|
```
|
|
1644
1861
|
Python ≥ 3.9
|