walopy 0.2.3__tar.gz → 0.2.6__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.3/src/walopy.egg-info → walopy-0.2.6}/PKG-INFO +476 -22
- {walopy-0.2.3 → walopy-0.2.6}/README.md +475 -21
- {walopy-0.2.3 → walopy-0.2.6}/pyproject.toml +1 -1
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/__init__.py +91 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/advanced.py +207 -0
- walopy-0.2.6/src/walopy/inventory.py +1620 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/queuing.py +146 -0
- walopy-0.2.6/src/walopy/reliability.py +298 -0
- walopy-0.2.6/src/walopy/scheduling.py +315 -0
- {walopy-0.2.3 → walopy-0.2.6/src/walopy.egg-info}/PKG-INFO +476 -22
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/SOURCES.txt +6 -1
- walopy-0.2.6/tests/test_inventory_new.py +304 -0
- walopy-0.2.6/tests/test_mg1.py +141 -0
- walopy-0.2.6/tests/test_v026.py +276 -0
- walopy-0.2.3/src/walopy/inventory.py +0 -407
- {walopy-0.2.3 → walopy-0.2.6}/LICENSE +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/setup.cfg +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/__main__.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/_utils.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/bottleneck.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/fitting.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/kpi.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/network.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/operations.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/plotting.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy/solver.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/dependency_links.txt +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/requires.txt +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/top_level.txt +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_advanced.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_bottleneck.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_fitting.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_inventory.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_kpi.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_network.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_new_models.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_operations.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_queuing.py +0 -0
- {walopy-0.2.3 → walopy-0.2.6}/tests/test_solver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: walopy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Queuing theory, operations analysis, OEE, bottleneck analysis and KPI trees for Python
|
|
5
5
|
Author-email: LeoSanta15 <angelsanta1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -63,15 +63,17 @@ pip install walopy
|
|
|
63
63
|
9. [Análisis de cuellos de botella](#9-análisis-de-cuellos-de-botella)
|
|
64
64
|
10. [Balance de línea y tiempo takt](#10-balance-de-línea-y-tiempo-takt)
|
|
65
65
|
11. [Análisis de punto de equilibrio](#11-análisis-de-punto-de-equilibrio)
|
|
66
|
-
12. [
|
|
67
|
-
13. [
|
|
68
|
-
14. [
|
|
69
|
-
15. [
|
|
70
|
-
16. [
|
|
71
|
-
17. [
|
|
72
|
-
18. [
|
|
73
|
-
19. [
|
|
74
|
-
20. [
|
|
66
|
+
12. [Programación de producción (scheduling)](#12-programación-de-producción-scheduling)
|
|
67
|
+
13. [Confiabilidad](#13-confiabilidad--reliability)
|
|
68
|
+
14. [Árboles de KPI](#14-árboles-de-kpi)
|
|
69
|
+
15. [Solvers y optimización](#15-solvers-y-optimización)
|
|
70
|
+
16. [Análisis de sensibilidad](#16-análisis-de-sensibilidad)
|
|
71
|
+
17. [Escenarios en lote (batch_model)](#17-escenarios-en-lote-batch_model)
|
|
72
|
+
18. [Comparar múltiples resultados](#18-comparar-múltiples-resultados)
|
|
73
|
+
19. [Gráficas](#19-gráficas)
|
|
74
|
+
20. [CLI](#20-cli)
|
|
75
|
+
21. [Referencia de módulos](#21-referencia-de-módulos)
|
|
76
|
+
22. [Requisitos](#22-requisitos)
|
|
75
77
|
|
|
76
78
|
---
|
|
77
79
|
|
|
@@ -200,6 +202,49 @@ r = wl.kingman(**fit.to_model_kwargs())
|
|
|
200
202
|
|
|
201
203
|
---
|
|
202
204
|
|
|
205
|
+
### `mg1(lam, mu, cs2)` — M/G/1 (Pollaczek-Khinchine exacto)
|
|
206
|
+
|
|
207
|
+
Llegadas Poisson, servicio de **distribución arbitraria** parametrizado por su media (1/μ) y su variabilidad (cs²). La fórmula P-K es **exacta**, no una aproximación.
|
|
208
|
+
|
|
209
|
+
**Fórmula P-K:**
|
|
210
|
+
Wq = λ · E[S²] / (2 · (1 − ρ)), donde E[S²] = (1 + cs²) / μ²
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# Servicio con distribución triangular: calcular cs² primero
|
|
214
|
+
cs2 = wl.cv2_triangular(a=2, m=5, b=10) # a=mín, m=moda, b=máx
|
|
215
|
+
r = wl.mg1(lam=0.15, mu=1/5, cs2=cs2)
|
|
216
|
+
|
|
217
|
+
# Equivalencia con M/M/1 (cs²=1) y M/D/1 (cs²=0)
|
|
218
|
+
r_mm1 = wl.mg1(lam=3.0, mu=5.0, cs2=1.0) # idéntico a mm1(3, 5)
|
|
219
|
+
r_md1 = wl.mg1(lam=3.0, mu=5.0, cs2=0.0) # idéntico a md1(3, 5)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Helpers `cv2_*` — CV² por distribución
|
|
223
|
+
|
|
224
|
+
El CV² (varianza / media²) es el único parámetro de distribución que M/G/1 necesita:
|
|
225
|
+
|
|
226
|
+
| Función | Distribución | CV² |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `cv2_normal(mean, std)` | Normal | (σ/μ)² |
|
|
229
|
+
| `cv2_triangular(a, m, b)` | Triangular | fórmula exacta |
|
|
230
|
+
| `cv2_uniform(a, b)` | Uniforme | (b−a)² / (12·μ²) |
|
|
231
|
+
| `cv2_erlang(k)` | Erlang-k | 1/k |
|
|
232
|
+
| `cv2_gamma(shape)` | Gamma | 1/shape |
|
|
233
|
+
| `cv2_lognormal(mean, std)` | Lognormal | (σ/μ)² |
|
|
234
|
+
| `cv2_weibull(shape)` | Weibull | Γ(1+2/k)/Γ(1+1/k)²−1 |
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# Servicio con distribución Weibull de forma 2 (rayleigh)
|
|
238
|
+
cs2 = wl.cv2_weibull(shape=2.0) # ≈ 0.273
|
|
239
|
+
r = wl.mg1(lam=1.0, mu=2.0, cs2=cs2)
|
|
240
|
+
print(r.Wq) # tiempo medio de espera en cola
|
|
241
|
+
|
|
242
|
+
# Servicio Erlang-3 (menos variable que exponencial)
|
|
243
|
+
r = wl.mg1(lam=3.0, mu=5.0, cs2=wl.cv2_erlang(3))
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
203
248
|
### `littles_law(*, L, lam, W)` — Ley de Little
|
|
204
249
|
|
|
205
250
|
Resuelve L = λ · W para la variable que falte. Exactamente uno de los tres parámetros debe ser `None`.
|
|
@@ -654,6 +699,204 @@ print(r_bajo.optimal_qty < 100) # True
|
|
|
654
699
|
|
|
655
700
|
---
|
|
656
701
|
|
|
702
|
+
### `ebq(demand_rate, setup_cost, holding_cost, production_rate)` — Lote Económico de Producción (EBQ/EPQ)
|
|
703
|
+
|
|
704
|
+
Extiende el EOQ al caso en que la producción y el consumo ocurren **simultáneamente** (tasa de producción finita P > D).
|
|
705
|
+
|
|
706
|
+
**Fórmula:**
|
|
707
|
+
```
|
|
708
|
+
Q* = √(2 · D · S / (h · (1 − D/P)))
|
|
709
|
+
Inventario máximo = Q* · (1 − D/P)
|
|
710
|
+
Inventario medio = Inventario máximo / 2
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
```python
|
|
714
|
+
r = wl.ebq(
|
|
715
|
+
demand_rate=1000, # D: unidades/período
|
|
716
|
+
setup_cost=50, # S: costo fijo de setup por lote
|
|
717
|
+
holding_cost=2, # h: costo/unidad/período
|
|
718
|
+
production_rate=4000, # P: tasa de producción (debe ser > D)
|
|
719
|
+
)
|
|
720
|
+
print(r)
|
|
721
|
+
# EBQ (batch size) : 258.2 units
|
|
722
|
+
# Max inventory : 193.6
|
|
723
|
+
# Avg inventory : 96.82
|
|
724
|
+
# Order frequency : 3.873 runs/period
|
|
725
|
+
# Cycle time : 0.2582 periods
|
|
726
|
+
# Production time/cyc : 0.06455 periods
|
|
727
|
+
# Total cost : 193.6
|
|
728
|
+
# Holding cost : 96.82
|
|
729
|
+
# Setup cost : 96.82
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
**`EBQResult` — atributos:**
|
|
733
|
+
|
|
734
|
+
| Atributo | Descripción |
|
|
735
|
+
|---|---|
|
|
736
|
+
| `ebq` | Lote óptimo Q* |
|
|
737
|
+
| `max_inventory` | Nivel máximo de inventario Q*(1−D/P) |
|
|
738
|
+
| `avg_inventory` | Inventario promedio = max/2 |
|
|
739
|
+
| `production_time` | Tiempo de producción por ciclo = Q*/P |
|
|
740
|
+
| `cycle_time` | Duración del ciclo = Q*/D |
|
|
741
|
+
| `total_cost` | Costo total mínimo |
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
### `eoq_multi(demand_rates, ordering_costs, holding_costs, *, names)` — EOQ multi-ítem independiente
|
|
746
|
+
|
|
747
|
+
Resuelve el EOQ para cada ítem por separado sin restricciones compartidas.
|
|
748
|
+
|
|
749
|
+
```python
|
|
750
|
+
r = wl.eoq_multi(
|
|
751
|
+
demand_rates = [1000, 500, 800],
|
|
752
|
+
ordering_costs = [50, 30, 40 ],
|
|
753
|
+
holding_costs = [2, 1, 1.5],
|
|
754
|
+
names = ["A", "B", "C"],
|
|
755
|
+
)
|
|
756
|
+
print(r.total_cost) # suma de costos óptimos individuales
|
|
757
|
+
df = r.to_frame() # DataFrame con EOQ, costos y frecuencias por ítem
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
### `ebq_multi(demand_rates, setup_costs, holding_costs, production_rates, *, names)` — EBQ multi-ítem independiente
|
|
763
|
+
|
|
764
|
+
```python
|
|
765
|
+
r = wl.ebq_multi(
|
|
766
|
+
demand_rates = [1000, 500],
|
|
767
|
+
setup_costs = [50, 30 ],
|
|
768
|
+
holding_costs = [2, 1 ],
|
|
769
|
+
production_rates= [4000, 2000],
|
|
770
|
+
)
|
|
771
|
+
df = r.to_frame() # EBQ, inventario máximo/medio, costo por ítem
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
### `eoq_multi_constrained(demand_rates, ordering_costs, holding_costs, ...)` — EOQ multi-ítem con restricciones
|
|
777
|
+
|
|
778
|
+
Minimiza el costo total de inventario sujeto a **restricciones lineales** sobre las cantidades de pedido mediante **relajación Lagrangiana** (búsqueda binaria sobre el multiplicador).
|
|
779
|
+
|
|
780
|
+
**Restricciones soportadas:**
|
|
781
|
+
|
|
782
|
+
| Parámetro | Restricción |
|
|
783
|
+
|---|---|
|
|
784
|
+
| `budget`, `budget_unit_costs` | Σ(c_i · Q_i / 2) ≤ budget — valor promedio en inventario |
|
|
785
|
+
| `space`, `space_per_unit` | Σ(s_i · Q_i / 2) ≤ space — espacio promedio ocupado |
|
|
786
|
+
| `constraints` | Lista de `{"name", "weights", "bound"}` — Σ(a_i · Q_i) ≤ B |
|
|
787
|
+
|
|
788
|
+
```python
|
|
789
|
+
# Restricción de presupuesto
|
|
790
|
+
r = wl.eoq_multi_constrained(
|
|
791
|
+
[1000, 500, 800], [50, 30, 40], [2, 1, 1.5],
|
|
792
|
+
budget=5000,
|
|
793
|
+
budget_unit_costs=[10, 8, 12], # costo unitario de compra
|
|
794
|
+
)
|
|
795
|
+
print(r.binding_constraints) # ["budget"] si la restricción está activa
|
|
796
|
+
print(r.lagrange_multipliers) # {"budget": λ}
|
|
797
|
+
df = r.to_frame() # Q*, holding cost, ordering cost por ítem
|
|
798
|
+
|
|
799
|
+
# Restricción de espacio
|
|
800
|
+
r2 = wl.eoq_multi_constrained(
|
|
801
|
+
[1000, 500, 800], [50, 30, 40], [2, 1, 1.5],
|
|
802
|
+
space=200,
|
|
803
|
+
space_per_unit=[0.5, 0.3, 0.4],
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# Restricción genérica: suma total de unidades pedidas ≤ 400
|
|
807
|
+
r3 = wl.eoq_multi_constrained(
|
|
808
|
+
[1000, 500, 800], [50, 30, 40], [2, 1, 1.5],
|
|
809
|
+
constraints=[{"name": "total_qty", "weights": [1, 1, 1], "bound": 400}],
|
|
810
|
+
)
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
**`ConstrainedMultiEOQResult` — atributos:**
|
|
814
|
+
|
|
815
|
+
| Atributo | Descripción |
|
|
816
|
+
|---|---|
|
|
817
|
+
| `items` | Lista de dicts por ítem: nombre, Q*, costos |
|
|
818
|
+
| `total_cost` | Costo total bajo las restricciones |
|
|
819
|
+
| `unconstrained_total_cost` | Costo sin restricciones (cota inferior) |
|
|
820
|
+
| `lagrange_multipliers` | Diccionario {nombre_restricción: λ} |
|
|
821
|
+
| `binding_constraints` | Nombres de restricciones activas (λ > 0) |
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
### `lot_for_lot(demands, setup_cost, holding_cost)` — Lote por Lote
|
|
826
|
+
|
|
827
|
+
Heurística de dimensionado dinámico: ordena **exactamente la demanda de cada período**. Minimiza el costo de almacenamiento (cero inventario en tránsito) a cambio de un setup por período.
|
|
828
|
+
|
|
829
|
+
```python
|
|
830
|
+
demands = [100, 80, 0, 120, 60] # demandas por período
|
|
831
|
+
r = wl.lot_for_lot(demands, setup_cost=200, holding_cost=1)
|
|
832
|
+
print(r.total_holding_cost) # 0.0 — sin inventario residual
|
|
833
|
+
print(r.n_orders) # 4 — un pedido por período con demanda > 0
|
|
834
|
+
print(r)
|
|
835
|
+
# Method : Lot-for-Lot
|
|
836
|
+
# Number of orders : 4
|
|
837
|
+
# Total cost : 800.0
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
### `silver_meal(demands, setup_cost, holding_cost)` — Silver-Meal
|
|
843
|
+
|
|
844
|
+
Heurística de dimensionado dinámico que **agrupa períodos** mientras el costo promedio por período decrece, reduciendo el número de setups.
|
|
845
|
+
|
|
846
|
+
```python
|
|
847
|
+
r = wl.silver_meal(demands, setup_cost=200, holding_cost=1)
|
|
848
|
+
print(r.total_cost <= wl.lot_for_lot(demands, 200, 1).total_cost) # True
|
|
849
|
+
df = r.to_frame() # pedidos: período, cantidad, períodos cubiertos
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**`LotSizingResult` — atributos (Lot-for-Lot y Silver-Meal):**
|
|
853
|
+
|
|
854
|
+
| Atributo | Descripción |
|
|
855
|
+
|---|---|
|
|
856
|
+
| `orders` | Lista de pedidos: período, cantidad, períodos cubiertos |
|
|
857
|
+
| `total_cost` | Costo total (setup + almacenamiento) |
|
|
858
|
+
| `total_setup_cost` | Costo total de setups |
|
|
859
|
+
| `total_holding_cost` | Costo total de almacenamiento |
|
|
860
|
+
| `n_orders` | Número de pedidos emitidos |
|
|
861
|
+
| `method` | Nombre de la heurística usada |
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
### `eoq_quantity_discount(demand_rate, ordering_cost, holding_cost_rate, price_breaks)` — EOQ con descuentos por cantidad
|
|
866
|
+
|
|
867
|
+
Evalúa todos los tramos de precio (all-units) y selecciona la cantidad que **minimiza el costo total anual** (compra + pedido + almacenamiento), ajustando el EOQ al tramo válido cuando cae fuera de rango.
|
|
868
|
+
|
|
869
|
+
```python
|
|
870
|
+
r = wl.eoq_quantity_discount(
|
|
871
|
+
demand_rate=1000,
|
|
872
|
+
ordering_cost=50,
|
|
873
|
+
holding_cost_rate=0.20, # 20% del precio unitario por año
|
|
874
|
+
price_breaks=[
|
|
875
|
+
(0, 10.00), # precio base
|
|
876
|
+
(500, 9.50), # ≥ 500 unidades → $9.50
|
|
877
|
+
(1000, 9.00), # ≥ 1000 unidades → $9.00
|
|
878
|
+
],
|
|
879
|
+
)
|
|
880
|
+
print(r.optimal_qty) # cantidad óptima seleccionada
|
|
881
|
+
print(r.unit_price) # precio en ese tramo
|
|
882
|
+
print(r.total_cost) # costo anual mínimo
|
|
883
|
+
df = r.to_frame() # todos los candidatos evaluados
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
**`QuantityDiscountResult` — atributos:**
|
|
887
|
+
|
|
888
|
+
| Atributo | Descripción |
|
|
889
|
+
|---|---|
|
|
890
|
+
| `optimal_qty` | Cantidad óptima ajustada al tramo |
|
|
891
|
+
| `unit_price` | Precio unitario en el tramo seleccionado |
|
|
892
|
+
| `total_cost` | Costo total anual (compra + pedido + almacenamiento) |
|
|
893
|
+
| `purchase_cost` | Costo de compra anual D · precio |
|
|
894
|
+
| `ordering_cost_total` | Costo de pedido anual (D/Q) · K |
|
|
895
|
+
| `holding_cost_total` | Costo de almacenamiento anual (Q/2) · h · precio |
|
|
896
|
+
| `candidates` | Lista de todos los tramos evaluados |
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
657
900
|
## 8. Análisis de operaciones
|
|
658
901
|
|
|
659
902
|
### `oee(availability, performance, quality)` — OEE
|
|
@@ -762,9 +1005,59 @@ print(be.profit) # 5000.0 — beneficio con actual_units
|
|
|
762
1005
|
be.plot() # líneas de ingresos/costos con BEP y zona de beneficio
|
|
763
1006
|
```
|
|
764
1007
|
|
|
1008
|
+
### `break_even_multi(fixed_cost, prices, variable_costs, sales_mix)` — Multi-producto
|
|
1009
|
+
|
|
1010
|
+
Calcula el punto de equilibrio para una mezcla de varios productos usando la **contribución marginal ponderada (WACM)**.
|
|
1011
|
+
|
|
1012
|
+
**Fórmula:** WACM = Σ(CMᵢ × mixᵢ) / Σmixᵢ, luego BEP_total = FC / WACM
|
|
1013
|
+
|
|
1014
|
+
```python
|
|
1015
|
+
r = wl.break_even_multi(
|
|
1016
|
+
fixed_cost=120_000,
|
|
1017
|
+
prices=[50, 80, 120],
|
|
1018
|
+
variable_costs=[30, 50, 70],
|
|
1019
|
+
sales_mix=[3, 2, 1], # proporción de ventas
|
|
1020
|
+
)
|
|
1021
|
+
print(r.weighted_avg_cm) # WACM ≈ 36.67
|
|
1022
|
+
print(r.bep_units_total) # unidades totales en el punto de equilibrio
|
|
1023
|
+
print(r.bep_revenue_total) # ingresos totales en el punto de equilibrio
|
|
1024
|
+
|
|
1025
|
+
for item in r.items:
|
|
1026
|
+
print(item["Name"], item["BEP units"])
|
|
1027
|
+
|
|
1028
|
+
r.to_frame() # DataFrame con una fila por producto
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
| Atributo | Descripción |
|
|
1032
|
+
|---|---|
|
|
1033
|
+
| `weighted_avg_cm` | Contribución marginal ponderada |
|
|
1034
|
+
| `bep_units_total` | Unidades totales en el BEP |
|
|
1035
|
+
| `bep_revenue_total` | Ingresos totales en el BEP |
|
|
1036
|
+
| `items` | Lista de dicts por producto con `BEP units`, `BEP revenue` |
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
1040
|
+
### `break_even_sales(fixed_cost, variable_cost_ratio, *, actual_revenue)` — Punto de equilibrio en ingresos
|
|
1041
|
+
|
|
1042
|
+
Calcula el BEP en términos de ingresos cuando los costos variables se expresan como porcentaje de las ventas.
|
|
1043
|
+
|
|
1044
|
+
**Fórmula:** BEP = FC / (1 − RCV)
|
|
1045
|
+
|
|
1046
|
+
```python
|
|
1047
|
+
r = wl.break_even_sales(
|
|
1048
|
+
fixed_cost=50_000,
|
|
1049
|
+
variable_cost_ratio=0.60, # 60% de los ingresos son costos variables
|
|
1050
|
+
actual_revenue=200_000,
|
|
1051
|
+
)
|
|
1052
|
+
print(r.bep_revenue) # 125 000 — ingresos en el BEP
|
|
1053
|
+
print(r.contribution_margin_ratio) # 0.40 — margen de contribución
|
|
1054
|
+
print(r.margin_of_safety_units) # 75 000 — margen de seguridad en ingresos
|
|
1055
|
+
print(r.margin_of_safety_pct) # 0.375 — porcentaje del margen de seguridad
|
|
1056
|
+
```
|
|
1057
|
+
|
|
765
1058
|
---
|
|
766
1059
|
|
|
767
|
-
##
|
|
1060
|
+
## 14. Árboles de KPI
|
|
768
1061
|
|
|
769
1062
|
Los árboles de KPI crean jerarquías interactivas visualizadas como **treemap** o **sunburst** con Plotly.
|
|
770
1063
|
|
|
@@ -817,7 +1110,7 @@ fig.show()
|
|
|
817
1110
|
|
|
818
1111
|
---
|
|
819
1112
|
|
|
820
|
-
##
|
|
1113
|
+
## 15. Solvers y optimización
|
|
821
1114
|
|
|
822
1115
|
### `solve_lam(metric, target, *, mu, model)` — Máxima tasa de llegada
|
|
823
1116
|
|
|
@@ -864,7 +1157,7 @@ r.plot() # gráfica de costo vs número de servidores
|
|
|
864
1157
|
|
|
865
1158
|
---
|
|
866
1159
|
|
|
867
|
-
##
|
|
1160
|
+
## 16. Análisis de sensibilidad
|
|
868
1161
|
|
|
869
1162
|
### `sensitivity(model_fn, param, values, **fixed_kwargs)` — Barrido paramétrico
|
|
870
1163
|
|
|
@@ -895,7 +1188,7 @@ fig.show()
|
|
|
895
1188
|
|
|
896
1189
|
---
|
|
897
1190
|
|
|
898
|
-
##
|
|
1191
|
+
## 17. Escenarios en lote (batch_model)
|
|
899
1192
|
|
|
900
1193
|
### `batch_model(model_fn, df, **fixed_kwargs)` — Aplicar modelo a un DataFrame
|
|
901
1194
|
|
|
@@ -930,7 +1223,7 @@ print(resultado[resultado["_error"].notna()]) # filas fallidas
|
|
|
930
1223
|
|
|
931
1224
|
---
|
|
932
1225
|
|
|
933
|
-
##
|
|
1226
|
+
## 18. Comparar múltiples resultados
|
|
934
1227
|
|
|
935
1228
|
### `compare(*results, labels)` — DataFrame comparativo
|
|
936
1229
|
|
|
@@ -955,7 +1248,7 @@ df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
|
|
|
955
1248
|
|
|
956
1249
|
---
|
|
957
1250
|
|
|
958
|
-
##
|
|
1251
|
+
## 19. Gráficas
|
|
959
1252
|
|
|
960
1253
|
Todos los resultados exponen `.plot()` que devuelve una figura de Matplotlib o Plotly. También se pueden llamar directamente desde `walopy.plotting`.
|
|
961
1254
|
|
|
@@ -991,7 +1284,7 @@ fig.show()
|
|
|
991
1284
|
|
|
992
1285
|
---
|
|
993
1286
|
|
|
994
|
-
##
|
|
1287
|
+
## 20. CLI
|
|
995
1288
|
|
|
996
1289
|
walopy incluye una interfaz de línea de comandos para uso rápido sin escribir código.
|
|
997
1290
|
|
|
@@ -1033,14 +1326,175 @@ Wq : 0.3 (avg wait time in queue)
|
|
|
1033
1326
|
|
|
1034
1327
|
---
|
|
1035
1328
|
|
|
1036
|
-
##
|
|
1329
|
+
## 12. Programación de producción (scheduling)
|
|
1330
|
+
|
|
1331
|
+
### Máquina única — `schedule_single`
|
|
1332
|
+
|
|
1333
|
+
Asigna *n* trabajos a una sola máquina según una regla de prioridad.
|
|
1334
|
+
|
|
1335
|
+
```python
|
|
1336
|
+
import walopy as wl
|
|
1337
|
+
|
|
1338
|
+
p = [3, 1, 4, 1, 5] # tiempos de proceso
|
|
1339
|
+
d = [8, 3, 10, 4, 12] # fechas de entrega
|
|
1340
|
+
w = [2, 5, 1, 4, 1] # pesos de importancia
|
|
1341
|
+
|
|
1342
|
+
r = wl.schedule_single(p, rule="SPT", due_dates=d, weights=w,
|
|
1343
|
+
names=["A","B","C","D","E"])
|
|
1344
|
+
print(r)
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
```
|
|
1348
|
+
Rule : SPT
|
|
1349
|
+
Sequence : B → D → A → C → E
|
|
1350
|
+
Makespan (Cmax) : 14
|
|
1351
|
+
Total completion ΣCj : 37
|
|
1352
|
+
Weighted completion ΣwCj: 89
|
|
1353
|
+
Max lateness : 2
|
|
1354
|
+
Total tardiness ΣTj : 2
|
|
1355
|
+
Tardy jobs : 1
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
**Reglas disponibles**
|
|
1359
|
+
|
|
1360
|
+
| `rule` | Criterio minimizado | Datos requeridos |
|
|
1361
|
+
|--------|---------------------|-----------------|
|
|
1362
|
+
| `'SPT'` | ΣCj (tiempo total de completación) | — |
|
|
1363
|
+
| `'EDD'` | Lmax (máxima tardanza) | `due_dates` |
|
|
1364
|
+
| `'WSPT'` | ΣwjCj (completación ponderada) | `weights` |
|
|
1365
|
+
| `'CR'` | Ratio crítico dj/pj | `due_dates` |
|
|
1366
|
+
| `'FIFO'` | Orden de llegada (línea base) | — |
|
|
1367
|
+
|
|
1368
|
+
**Atributos de `ScheduleResult`**
|
|
1369
|
+
|
|
1370
|
+
| Atributo | Descripción |
|
|
1371
|
+
|----------|-------------|
|
|
1372
|
+
| `rule` | Regla utilizada |
|
|
1373
|
+
| `sequence` | Lista de nombres en orden de procesamiento |
|
|
1374
|
+
| `jobs` | Lista de `JobSchedule` (por trabajo) |
|
|
1375
|
+
| `makespan` | Cmax = suma de tiempos de proceso |
|
|
1376
|
+
| `total_completion_time` | ΣCj |
|
|
1377
|
+
| `total_weighted_completion_time` | ΣwjCj |
|
|
1378
|
+
| `max_lateness` | max(Lj) |
|
|
1379
|
+
| `total_tardiness` | ΣTj |
|
|
1380
|
+
| `n_tardy` | Número de trabajos tarde |
|
|
1381
|
+
|
|
1382
|
+
```python
|
|
1383
|
+
df = r.to_frame() # columnas: Name, p, d, w, Start, C, L, T, Tardy
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
---
|
|
1387
|
+
|
|
1388
|
+
### Flow-shop de 2 máquinas — `johnson_flowshop`
|
|
1389
|
+
|
|
1390
|
+
El algoritmo de Johnson encuentra la secuencia óptima que minimiza el makespan cuando todos los trabajos pasan primero por M1 y luego por M2.
|
|
1391
|
+
|
|
1392
|
+
```python
|
|
1393
|
+
m1 = [3, 8, 5, 7, 2] # tiempos en máquina 1
|
|
1394
|
+
m2 = [5, 2, 8, 4, 6] # tiempos en máquina 2
|
|
1395
|
+
|
|
1396
|
+
r = wl.johnson_flowshop(m1, m2)
|
|
1397
|
+
print(r)
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
```
|
|
1401
|
+
Sequence : J1 → J5 → J4 → J3 → J2
|
|
1402
|
+
Makespan : 34
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
```python
|
|
1406
|
+
df = r.to_frame()
|
|
1407
|
+
# columnas: Job | M1 start | M1 end | M2 start | M2 end
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
---
|
|
1411
|
+
|
|
1412
|
+
## 13. Confiabilidad — `reliability`
|
|
1413
|
+
|
|
1414
|
+
Modelos de confiabilidad con distribución exponencial (tasa de falla constante).
|
|
1415
|
+
|
|
1416
|
+
### Componente individual — `mtbf_analysis`
|
|
1417
|
+
|
|
1418
|
+
```python
|
|
1419
|
+
r = wl.mtbf_analysis(failure_rate=0.01, mttr=5.0, t=100)
|
|
1420
|
+
print(r)
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
```
|
|
1424
|
+
Topology : component
|
|
1425
|
+
Components : 1
|
|
1426
|
+
Failure rates λ : ['0.01']
|
|
1427
|
+
MTBF : 100
|
|
1428
|
+
R(t=100) : 0.367879
|
|
1429
|
+
Availability : 95.2381%
|
|
1430
|
+
```
|
|
1431
|
+
|
|
1432
|
+
### Sistema en serie — `series_system`
|
|
1433
|
+
|
|
1434
|
+
El sistema falla si **cualquier** componente falla. λ_sys = Σλi.
|
|
1435
|
+
|
|
1436
|
+
```python
|
|
1437
|
+
r = wl.series_system([0.01, 0.02, 0.03], t=10)
|
|
1438
|
+
print(r.mtbf) # 1/0.06 ≈ 16.67
|
|
1439
|
+
print(r.R_t) # e^{-0.06·10} ≈ 0.549
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
### Sistema en paralelo — `parallel_system`
|
|
1443
|
+
|
|
1444
|
+
El sistema opera si **al menos uno** de los componentes opera.
|
|
1445
|
+
R_sys(t) = 1 − ∏(1 − e^{−λi·t}). MTBF calculado numéricamente.
|
|
1446
|
+
|
|
1447
|
+
```python
|
|
1448
|
+
r = wl.parallel_system([0.01, 0.02], t=50)
|
|
1449
|
+
print(r.R_t) # mayor que el sistema en serie
|
|
1450
|
+
print(r.mtbf) # mayor que cualquier componente individual
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
### Sistema k-de-n — `koon_system`
|
|
1454
|
+
|
|
1455
|
+
Opera si al menos **k** de **n** componentes idénticos están en servicio.
|
|
1456
|
+
MTBF exacto = (1/λ) · Σ_{j=k}^{n} (1/j).
|
|
1457
|
+
|
|
1458
|
+
```python
|
|
1459
|
+
# 2-de-3: requiere al menos 2 de 3 componentes idénticos
|
|
1460
|
+
r = wl.koon_system(n=3, k=2, failure_rate=0.01, t=50)
|
|
1461
|
+
print(r.mtbf) # (1/0.01) * (1/2 + 1/3) ≈ 83.33
|
|
1462
|
+
print(r.R_t)
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
**Casos especiales**
|
|
1466
|
+
- `k=1` → equivale a `parallel_system` con n componentes idénticos.
|
|
1467
|
+
- `k=n` → equivale a `series_system` con n componentes idénticos.
|
|
1468
|
+
|
|
1469
|
+
**Atributos de `ReliabilityResult`**
|
|
1470
|
+
|
|
1471
|
+
| Atributo | Descripción |
|
|
1472
|
+
|----------|-------------|
|
|
1473
|
+
| `topology` | `'component'`, `'series'`, `'parallel'`, `'k-of-n'` |
|
|
1474
|
+
| `n_components` | Número de componentes |
|
|
1475
|
+
| `failure_rates` | Lista de λi |
|
|
1476
|
+
| `mtbf` | MTBF del sistema |
|
|
1477
|
+
| `R_t` | Confiabilidad R(t) si se proporcionó `t` |
|
|
1478
|
+
| `availability` | Disponibilidad estacionaria A si se proporcionó `mttr` |
|
|
1479
|
+
| `mttr` | Tiempo medio de reparación (si se proporcionó) |
|
|
1480
|
+
|
|
1481
|
+
```python
|
|
1482
|
+
df = r.to_frame() # columnas: Topology, Components, MTBF[, R(t), Availability]
|
|
1483
|
+
r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
---
|
|
1487
|
+
|
|
1488
|
+
## 21. Referencia de módulos
|
|
1037
1489
|
|
|
1038
1490
|
| Módulo | Funciones y clases principales |
|
|
1039
1491
|
|---|---|
|
|
1040
|
-
| `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `littles_law`, `QueueResult` |
|
|
1041
|
-
| `advanced` | `mm1k`, `mmck`, `erlang_b`, `mm1_priority`, `monte_carlo_gg1`, `takt_time`, `line_balance`, `break_even`, `queue_length_pmf`, `sojourn_cdf`, `PriorityQueueResult`, `SimulationResult`, `LineBalanceResult`, `BreakEvenResult` |
|
|
1492
|
+
| `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `mg1`, `littles_law`, `QueueResult`, `cv2_normal`, `cv2_triangular`, `cv2_uniform`, `cv2_erlang`, `cv2_gamma`, `cv2_lognormal`, `cv2_weibull` |
|
|
1493
|
+
| `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` |
|
|
1042
1494
|
| `fitting` | `fit_from_data`, `FitResult` |
|
|
1043
|
-
| `inventory` | `eoq`, `reorder_point`, `newsvendor`, `EOQResult`, `ReorderResult`, `NewsvendorResult` |
|
|
1495
|
+
| `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult` |
|
|
1496
|
+
| `scheduling` | `schedule_single`, `johnson_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult` |
|
|
1497
|
+
| `reliability` | `mtbf_analysis`, `series_system`, `parallel_system`, `koon_system`, `ReliabilityResult` |
|
|
1044
1498
|
| `network` | `jackson_network`, `JacksonResult`, `StationMetrics` |
|
|
1045
1499
|
| `operations` | `oee`, `utilization_efficiency`, `unit_cost`, `OEEResult`, `UtilizationResult`, `UnitCostResult` |
|
|
1046
1500
|
| `bottleneck` | `bottleneck_analysis`, `BottleneckResult`, `StationResult` |
|
|
@@ -1050,7 +1504,7 @@ Wq : 0.3 (avg wait time in queue)
|
|
|
1050
1504
|
|
|
1051
1505
|
---
|
|
1052
1506
|
|
|
1053
|
-
##
|
|
1507
|
+
## 22. Requisitos
|
|
1054
1508
|
|
|
1055
1509
|
```
|
|
1056
1510
|
Python ≥ 3.9
|