walopy 0.4.3__tar.gz → 0.4.4__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.
Files changed (85) hide show
  1. {walopy-0.4.3/src/walopy.egg-info → walopy-0.4.4}/PKG-INFO +1 -1
  2. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/__init__.py +1 -1
  3. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/_catalogo_en.py +3 -0
  4. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/_catalogo_es.py +3 -0
  5. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/advanced.py +19 -2
  6. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/inventory.py +27 -8
  7. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/project.py +36 -1
  8. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/reliability.py +22 -5
  9. {walopy-0.4.3 → walopy-0.4.4/src/walopy.egg-info}/PKG-INFO +1 -1
  10. {walopy-0.4.3 → walopy-0.4.4}/src/walopy.egg-info/SOURCES.txt +3 -0
  11. {walopy-0.4.3 → walopy-0.4.4}/tests/test_advanced.py +1 -0
  12. walopy-0.4.4/tests/test_avisos_validez.py +46 -0
  13. {walopy-0.4.3 → walopy-0.4.4}/tests/test_cpm_pert.py +14 -8
  14. walopy-0.4.4/tests/test_disponibilidad_redundante.py +63 -0
  15. {walopy-0.4.3 → walopy-0.4.4}/tests/test_inventario_i18n.py +3 -0
  16. walopy-0.4.4/tests/test_pert_varianza.py +74 -0
  17. {walopy-0.4.3 → walopy-0.4.4}/tests/test_plotting.py +1 -1
  18. {walopy-0.4.3 → walopy-0.4.4}/LICENSE +0 -0
  19. {walopy-0.4.3 → walopy-0.4.4}/README.md +0 -0
  20. {walopy-0.4.3 → walopy-0.4.4}/pyproject.toml +0 -0
  21. {walopy-0.4.3 → walopy-0.4.4}/setup.cfg +0 -0
  22. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/__main__.py +0 -0
  23. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/_i18n.py +0 -0
  24. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/_utils.py +0 -0
  25. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/bottleneck.py +0 -0
  26. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/fitting.py +0 -0
  27. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/kpi.py +0 -0
  28. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/network.py +0 -0
  29. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/operations.py +0 -0
  30. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/plotting.py +0 -0
  31. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/py.typed +0 -0
  32. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/queuing.py +0 -0
  33. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/scheduling.py +0 -0
  34. {walopy-0.4.3 → walopy-0.4.4}/src/walopy/solver.py +0 -0
  35. {walopy-0.4.3 → walopy-0.4.4}/src/walopy.egg-info/dependency_links.txt +0 -0
  36. {walopy-0.4.3 → walopy-0.4.4}/src/walopy.egg-info/requires.txt +0 -0
  37. {walopy-0.4.3 → walopy-0.4.4}/src/walopy.egg-info/top_level.txt +0 -0
  38. {walopy-0.4.3 → walopy-0.4.4}/tests/test_abc_xyz.py +0 -0
  39. {walopy-0.4.3 → walopy-0.4.4}/tests/test_bottleneck.py +0 -0
  40. {walopy-0.4.3 → walopy-0.4.4}/tests/test_cli.py +0 -0
  41. {walopy-0.4.3 → walopy-0.4.4}/tests/test_complejidad.py +0 -0
  42. {walopy-0.4.3 → walopy-0.4.4}/tests/test_contrato_entradas.py +0 -0
  43. {walopy-0.4.3 → walopy-0.4.4}/tests/test_dibujo_plotly.py +0 -0
  44. {walopy-0.4.3 → walopy-0.4.4}/tests/test_documentacion.py +0 -0
  45. {walopy-0.4.3 → walopy-0.4.4}/tests/test_documentacion_ingles.py +0 -0
  46. {walopy-0.4.3 → walopy-0.4.4}/tests/test_ejemplos.py +0 -0
  47. {walopy-0.4.3 → walopy-0.4.4}/tests/test_exchange_curve.py +0 -0
  48. {walopy-0.4.3 → walopy-0.4.4}/tests/test_fitting.py +0 -0
  49. {walopy-0.4.3 → walopy-0.4.4}/tests/test_i18n.py +0 -0
  50. {walopy-0.4.3 → walopy-0.4.4}/tests/test_i18n_paridad.py +0 -0
  51. {walopy-0.4.3 → walopy-0.4.4}/tests/test_idioma.py +0 -0
  52. {walopy-0.4.3 → walopy-0.4.4}/tests/test_inventory.py +0 -0
  53. {walopy-0.4.3 → walopy-0.4.4}/tests/test_inventory_new.py +0 -0
  54. {walopy-0.4.3 → walopy-0.4.4}/tests/test_kpi.py +0 -0
  55. {walopy-0.4.3 → walopy-0.4.4}/tests/test_kpi_grafica.py +0 -0
  56. {walopy-0.4.3 → walopy-0.4.4}/tests/test_mg1.py +0 -0
  57. {walopy-0.4.3 → walopy-0.4.4}/tests/test_mmc_estable.py +0 -0
  58. {walopy-0.4.3 → walopy-0.4.4}/tests/test_mrp.py +0 -0
  59. {walopy-0.4.3 → walopy-0.4.4}/tests/test_neh_flowshop.py +0 -0
  60. {walopy-0.4.3 → walopy-0.4.4}/tests/test_network.py +0 -0
  61. {walopy-0.4.3 → walopy-0.4.4}/tests/test_new_models.py +0 -0
  62. {walopy-0.4.3 → walopy-0.4.4}/tests/test_notas_release.py +0 -0
  63. {walopy-0.4.3 → walopy-0.4.4}/tests/test_operations.py +0 -0
  64. {walopy-0.4.3 → walopy-0.4.4}/tests/test_politicas_inventario.py +0 -0
  65. {walopy-0.4.3 → walopy-0.4.4}/tests/test_propiedades_algoritmos.py +0 -0
  66. {walopy-0.4.3 → walopy-0.4.4}/tests/test_propiedades_diferenciales.py +0 -0
  67. {walopy-0.4.3 → walopy-0.4.4}/tests/test_propiedades_inventario_fiabilidad.py +0 -0
  68. {walopy-0.4.3 → walopy-0.4.4}/tests/test_queuing.py +0 -0
  69. {walopy-0.4.3 → walopy-0.4.4}/tests/test_readme.py +0 -0
  70. {walopy-0.4.3 → walopy-0.4.4}/tests/test_referencias_externas.py +0 -0
  71. {walopy-0.4.3 → walopy-0.4.4}/tests/test_regresion_validacion.py +0 -0
  72. {walopy-0.4.3 → walopy-0.4.4}/tests/test_reliability.py +0 -0
  73. {walopy-0.4.3 → walopy-0.4.4}/tests/test_safety_stock_curve.py +0 -0
  74. {walopy-0.4.3 → walopy-0.4.4}/tests/test_salida_identica.py +0 -0
  75. {walopy-0.4.3 → walopy-0.4.4}/tests/test_salida_por_idioma.py +0 -0
  76. {walopy-0.4.3 → walopy-0.4.4}/tests/test_scheduling.py +0 -0
  77. {walopy-0.4.3 → walopy-0.4.4}/tests/test_scorecard.py +0 -0
  78. {walopy-0.4.3 → walopy-0.4.4}/tests/test_servidores_cmax.py +0 -0
  79. {walopy-0.4.3 → walopy-0.4.4}/tests/test_servidores_minimos.py +0 -0
  80. {walopy-0.4.3 → walopy-0.4.4}/tests/test_solver.py +0 -0
  81. {walopy-0.4.3 → walopy-0.4.4}/tests/test_solver_contratos.py +0 -0
  82. {walopy-0.4.3 → walopy-0.4.4}/tests/test_trazabilidad.py +0 -0
  83. {walopy-0.4.3 → walopy-0.4.4}/tests/test_version.py +0 -0
  84. {walopy-0.4.3 → walopy-0.4.4}/tests/test_wagner_whitin_ceros.py +0 -0
  85. {walopy-0.4.3 → walopy-0.4.4}/tests/test_weibull.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: walopy
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
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
@@ -164,7 +164,7 @@ from .solver import (
164
164
  solve_servers,
165
165
  )
166
166
 
167
- __version__ = "0.4.3" # fuente única de la versión; pyproject.toml la lee con [tool.setuptools.dynamic]
167
+ __version__ = "0.4.4" # fuente única de la versión; pyproject.toml la lee con [tool.setuptools.dynamic]
168
168
 
169
169
  __all__ = [
170
170
  # idioma de los textos
@@ -39,6 +39,7 @@ EN: dict[str, str] = {
39
39
  "advanced.error.mm1_priority.sistema_inestable_total": "Unstable system: ρ_total = {rho_total:.4g} ≥ 1.",
40
40
  "advanced.error.mmck.capacidad_sistema_debe_ser_servidores": "'K' (system capacity) must be ≥ c (servers); got K={K}, c={c}.",
41
41
  "advanced.error.monte_carlo_gg1.sistema_inestable": "Unstable system: ρ = {rho:.4g} ≥ 1.",
42
+ "advanced.aviso.monte_carlo_gg1.estimacion_imprecisa": "The estimate Wq = {wq:.4g} has a 95 % half-width of ±{semiancho:.3g} ({relativo:.0%}) (means of 20 batches) with {n} customers: increase n_customers or average several replications (different seeds).",
42
43
  "advanced.error.queue_length_pmf.max_debe_estar_entre_recibio": "'n_max' must be between 0 and {MAX_ESTADOS}, got {n_max!r}.",
43
44
  "advanced.error.queue_length_pmf.max_debe_ser_entero_recibio": "'n_max' must be an integer >= 0, got {__name__!r}.",
44
45
  "advanced.etiqueta.summary.margen_contribucion": "Contribution margin : {contribution_margin:.4g}",
@@ -231,6 +232,7 @@ EN: dict[str, str] = {
231
232
  "inventory.error.mrp.periods_debe_tener_misma_longitud": "'periods' must have the same length as 'gross_requirements'.",
232
233
  "inventory.error.mrp.scheduled_receipts_debe_tener_misma": "'scheduled_receipts' must have the same length as 'gross_requirements'.",
233
234
  "inventory.error.newsvendor.cost_debe_ser_menor_price": "'cost' must be less than 'price' (otherwise Cu ≤ 0).",
235
+ "inventory.aviso.newsvendor.demanda_normal_cv_alto": "The normal demand has CV = {cv:.2f} > 0.5: the model assigns P(D < 0) = {p_negativa:.1%} and Q* may be biased. With such variable demand a non-negative distribution (lognormal, gamma or empirical) is preferable.",
234
236
  "inventory.error.newsvendor.salvage_debe_ser_menor_cost": "'salvage' must be less than 'cost' (otherwise Co ≤ 0).",
235
237
  "inventory.error.reorder_point.service_level_debe_estar_estrictamente": "'service_level' must be strictly between 0 and 1.",
236
238
  "inventory.error.safety_stock_curve.indique_como_maximo_uno_target": "Provide at most one of 'target_service_level' or 'target_ss_investment'.",
@@ -483,6 +485,7 @@ EN: dict[str, str] = {
483
485
  "project.error.leer_actividades.activities_predecessors_debe_ser_lista": "activities[{i}]['predecessors'] must be a list of names.",
484
486
  "project.error.leer_actividades.nombre_actividad_duplicado_cada_actividad": "Duplicate activity name: '{nm}'. Each activity must have a unique name.",
485
487
  "project.error.pert.actividad_falta_clave": "Activity '{nm}': missing key '{clave}'.",
488
+ "project.aviso.pert.rutas_casi_criticas": "{n} activities outside the critical path used have total float ≤ the project σ ({sigma:.3g}): {actividades}. The PERT probability considers a single path and is optimistic when other near-critical paths merge; consider a Monte Carlo simulation.",
486
489
  "project.error.pert.actividad_requiere_optimistic_most_likely": "Activity '{nm}': optimistic ≤ most_likely ≤ pessimistic is required.",
487
490
  "project.error.probability.probability_requiere_resultado_pert_varianza": "probability() requires a PERT result with nonzero variance.",
488
491
  "project.error.toposort_and_succ.actividad_referencia_predecesor_desconocido": "Activity '{name}' references an unknown predecessor '{pred}'.",
@@ -39,6 +39,7 @@ ES: dict[str, str] = {
39
39
  "advanced.error.mm1_priority.sistema_inestable_total": "Sistema inestable: ρ_total = {rho_total:.4g} ≥ 1.",
40
40
  "advanced.error.mmck.capacidad_sistema_debe_ser_servidores": "'K' (capacidad del sistema) debe ser ≥ c (servidores); se recibió K={K}, c={c}.",
41
41
  "advanced.error.monte_carlo_gg1.sistema_inestable": "Sistema inestable: ρ = {rho:.4g} ≥ 1.",
42
+ "advanced.aviso.monte_carlo_gg1.estimacion_imprecisa": "La estimación Wq = {wq:.4g} tiene un semiancho de ±{semiancho:.3g} ({relativo:.0%}) al 95 % (medias de 20 lotes) con {n} clientes: aumente n_customers o promedie varias réplicas (seed distintas).",
42
43
  "advanced.error.queue_length_pmf.max_debe_estar_entre_recibio": "'n_max' debe estar entre 0 y {MAX_ESTADOS}, se recibió {n_max!r}.",
43
44
  "advanced.error.queue_length_pmf.max_debe_ser_entero_recibio": "'n_max' debe ser un entero >= 0, se recibió {__name__!r}.",
44
45
  "advanced.etiqueta.summary.margen_contribucion": "Margen de contribución : {contribution_margin:.4g}",
@@ -231,6 +232,7 @@ ES: dict[str, str] = {
231
232
  "inventory.error.mrp.periods_debe_tener_misma_longitud": "'periods' debe tener la misma longitud que 'gross_requirements'.",
232
233
  "inventory.error.mrp.scheduled_receipts_debe_tener_misma": "'scheduled_receipts' debe tener la misma longitud que 'gross_requirements'.",
233
234
  "inventory.error.newsvendor.cost_debe_ser_menor_price": "'cost' debe ser menor que 'price' (de lo contrario Cu ≤ 0).",
235
+ "inventory.aviso.newsvendor.demanda_normal_cv_alto": "La demanda normal tiene CV = {cv:.2f} > 0,5: el modelo asigna P(D < 0) = {p_negativa:.1%} y Q* puede estar sesgada. Con demanda tan variable conviene una distribución no negativa (lognormal, gamma o empírica).",
234
236
  "inventory.error.newsvendor.salvage_debe_ser_menor_cost": "'salvage' debe ser menor que 'cost' (de lo contrario Co ≤ 0).",
235
237
  "inventory.error.reorder_point.service_level_debe_estar_estrictamente": "'service_level' debe estar estrictamente entre 0 y 1.",
236
238
  "inventory.error.safety_stock_curve.indique_como_maximo_uno_target": "Indique como máximo uno de 'target_service_level' o 'target_ss_investment'.",
@@ -483,6 +485,7 @@ ES: dict[str, str] = {
483
485
  "project.error.leer_actividades.activities_predecessors_debe_ser_lista": "activities[{i}]['predecessors'] debe ser una lista de nombres.",
484
486
  "project.error.leer_actividades.nombre_actividad_duplicado_cada_actividad": "Nombre de actividad duplicado: '{nm}'. Cada actividad debe tener un nombre único.",
485
487
  "project.error.pert.actividad_falta_clave": "Actividad '{nm}': falta la clave '{clave}'.",
488
+ "project.aviso.pert.rutas_casi_criticas": "Hay {n} actividades fuera de la ruta crítica usada con holgura ≤ σ del proyecto ({sigma:.3g}): {actividades}. La probabilidad PERT solo considera una ruta y es optimista cuando otras rutas casi críticas convergen; considere una simulación Monte Carlo.",
486
489
  "project.error.pert.actividad_requiere_optimistic_most_likely": "Actividad '{nm}': se requiere optimistic ≤ most_likely ≤ pessimistic.",
487
490
  "project.error.probability.probability_requiere_resultado_pert_varianza": "probability() requiere un resultado PERT con varianza distinta de cero.",
488
491
  "project.error.toposort_and_succ.actividad_referencia_predecesor_desconocido": "La actividad '{name}' referencia un predecesor desconocido '{pred}'.",
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import math
5
+ import warnings
5
6
  from collections.abc import Sequence
6
7
  from dataclasses import dataclass, field
7
8
  from typing import TYPE_CHECKING
@@ -226,6 +227,11 @@ class SimulationResult:
226
227
  return plot_simulation(self, **kwargs)
227
228
 
228
229
 
230
+ LOTES_MC = 20 # lotes para el intervalo de confianza de Wq por medias de lotes
231
+ T_975_19 = 2.093 # cuantil 0,975 de la t de Student con 19 grados de libertad
232
+ PRECISION_RELATIVA_MC = 0.10 # avisar si el semiancho al 95 % supera el 10 % de Wq
233
+
234
+
229
235
  def monte_carlo_gg1(
230
236
  lam: float,
231
237
  mu: float,
@@ -271,9 +277,9 @@ def monte_carlo_gg1(
271
277
 
272
278
  Examples
273
279
  --------
274
- >>> r = monte_carlo_gg1(lam=2.0, mu=3.0, ca2=1.0, cs2=1.0, n_customers=500, seed=1)
280
+ >>> r = monte_carlo_gg1(lam=2.0, mu=3.0, ca2=1.0, cs2=1.0, n_customers=50_000, seed=1)
275
281
  >>> round(r.L, 4)
276
- 2.0427
282
+ 2.0169
277
283
  """
278
284
  lam = as_positive(lam, "lam")
279
285
  mu = as_positive(mu, "mu")
@@ -311,6 +317,17 @@ def monte_carlo_gg1(
311
317
  sojourn = depart - arrival
312
318
  pct = np.percentile(wait, [50, 90, 95, 99])
313
319
 
320
+ # Precisión de Wq por medias de lotes (20 lotes, t de Student con 19 g.l. al 95 %); una sola réplica puede engañar
321
+ lotes = wait[: n_customers - n_customers % LOTES_MC].reshape(LOTES_MC, -1).mean(axis=1) if n_customers >= LOTES_MC else None
322
+ if lotes is not None and wait.mean() > 0:
323
+ semiancho = T_975_19 * float(lotes.std(ddof=1)) / math.sqrt(LOTES_MC)
324
+ if semiancho > PRECISION_RELATIVA_MC * wait.mean():
325
+ warnings.warn(
326
+ _t("advanced.aviso.monte_carlo_gg1.estimacion_imprecisa", wq=float(wait.mean()), semiancho=semiancho,
327
+ relativo=semiancho / float(wait.mean()), n=n_customers),
328
+ UserWarning, stacklevel=2,
329
+ )
330
+
314
331
  return SimulationResult(
315
332
  model=_t("advanced.modelo.monte_carlo_gg1.simulacion_ca2_cs2", ca2=ca2, cs2=cs2),
316
333
  lam=lam,
@@ -253,12 +253,14 @@ def reorder_point(
253
253
  lead_time : float
254
254
  Tiempo de entrega medio L̄ (en las mismas unidades de tiempo que demand_rate).
255
255
  demand_std : float
256
- Desviación estándar de la demanda por periodo σ_D (por defecto 0).
256
+ Desviación estándar de la demanda por periodo σ_D (por defecto 0). Si la demanda se pronostica, use la
257
+ desviación del error de pronóstico por periodo (p. ej. el RMSE), que es la incertidumbre que cubre el stock
258
+ de seguridad.
257
259
  lead_time_std : float
258
260
  Desviación estándar del tiempo de entrega σ_L (por defecto 0).
259
261
  service_level : float
260
- Probabilidad de no tener faltante por ciclo de reposición ∈ (0, 1).
261
- Por defecto 0.95.
262
+ Nivel de servicio por ciclo (tipo 1): probabilidad de no tener faltante en un ciclo de reposición ∈ (0, 1).
263
+ No es la tasa de llenado (fracción de la demanda servida desde inventario), que suele ser mayor. Por defecto 0.95.
262
264
 
263
265
  Returns
264
266
  -------
@@ -368,6 +370,9 @@ class NewsvendorResult:
368
370
  }])
369
371
 
370
372
 
373
+ CV_MAX_NEWSVENDOR_NORMAL = 0.5 # con CV = σ/μ > 0,5 la normal da P(D < 0) > 2,3 %
374
+
375
+
371
376
  def newsvendor(
372
377
  demand_mean: float,
373
378
  demand_std: float,
@@ -425,6 +430,13 @@ def newsvendor(
425
430
  if s >= c:
426
431
  raise ValueError(_t("inventory.error.newsvendor.salvage_debe_ser_menor_cost"))
427
432
 
433
+ if sig > CV_MAX_NEWSVENDOR_NORMAL * mu:
434
+ # La normal asigna probabilidad apreciable a demanda negativa: Q* y las ventas esperadas pierden sentido
435
+ warnings.warn(
436
+ _t("inventory.aviso.newsvendor.demanda_normal_cv_alto", cv=sig / mu, p_negativa=NormalDist().cdf(-mu / sig)),
437
+ UserWarning, stacklevel=2,
438
+ )
439
+
428
440
  Cu = p - c # underage cost (opportunity loss)
429
441
  Co = c - s # overage cost (holding/disposal loss)
430
442
  CR = Cu / (Cu + Co) # critical ratio
@@ -1587,11 +1599,14 @@ def rq_policy(
1587
1599
  lead_time : float
1588
1600
  Tiempo de entrega medio de reposición L̄.
1589
1601
  demand_std : float
1590
- Desviación estándar de la demanda por periodo σ_D (por defecto 0).
1602
+ Desviación estándar de la demanda por periodo σ_D (por defecto 0). Si la demanda se pronostica, use la
1603
+ desviación del error de pronóstico por periodo (p. ej. el RMSE), que es la incertidumbre que cubre el stock
1604
+ de seguridad.
1591
1605
  lead_time_std : float
1592
1606
  Desviación estándar del tiempo de entrega σ_L (por defecto 0).
1593
1607
  service_level : float
1594
- Nivel de servicio por ciclo ∈ (0, 1). Por defecto 0.95.
1608
+ Nivel de servicio por ciclo (tipo 1): probabilidad de no tener faltante en un ciclo de reposición ∈ (0, 1).
1609
+ No es la tasa de llenado (fracción de la demanda servida desde inventario), que suele ser mayor. Por defecto 0.95.
1595
1610
 
1596
1611
  Returns
1597
1612
  -------
@@ -1730,11 +1745,14 @@ def rs_policy(
1730
1745
  review_period : float
1731
1746
  Intervalo de revisión R (periodos entre revisiones de inventario).
1732
1747
  demand_std : float
1733
- Desviación estándar de la demanda por periodo σ_D (por defecto 0).
1748
+ Desviación estándar de la demanda por periodo σ_D (por defecto 0). Si la demanda se pronostica, use la
1749
+ desviación del error de pronóstico por periodo (p. ej. el RMSE), que es la incertidumbre que cubre el stock
1750
+ de seguridad.
1734
1751
  lead_time_std : float
1735
1752
  Desviación estándar del tiempo de entrega σ_L (por defecto 0).
1736
1753
  service_level : float
1737
- Nivel de servicio por ciclo ∈ (0, 1). Por defecto 0.95.
1754
+ Nivel de servicio por ciclo (tipo 1): probabilidad de no tener faltante en un ciclo de reposición ∈ (0, 1).
1755
+ No es la tasa de llenado (fracción de la demanda servida desde inventario), que suele ser mayor. Por defecto 0.95.
1738
1756
 
1739
1757
  Returns
1740
1758
  -------
@@ -2078,7 +2096,8 @@ def safety_stock_curve(
2078
2096
  items : list of dict
2079
2097
  Cada diccionario debe contener:
2080
2098
 
2081
- - ``demand_std`` (float) — desviación estándar de la demanda por unidad de tiempo.
2099
+ - ``demand_std`` (float) — desviación estándar de la demanda por unidad de tiempo (o del error de
2100
+ pronóstico, si la demanda se pronostica).
2082
2101
  - ``lead_time`` (float) — tiempo de entrega de reposición (misma unidad de tiempo).
2083
2102
 
2084
2103
  Claves opcionales:
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import math
5
+ import warnings
5
6
  from collections import deque
6
7
  from dataclasses import dataclass
7
8
  from statistics import NormalDist
@@ -211,6 +212,33 @@ def _forward_backward(acts: dict, durations: dict) -> tuple:
211
212
  return ES, EF, LS, LF, T, succ
212
213
 
213
214
 
215
+ def _ruta_critica_de_mayor_varianza(acts: dict, crit: list, variances: dict, ES: dict, EF: dict, T: float) -> tuple:
216
+ """Ruta crítica (de inicio a fin) con la mayor varianza y esa varianza.
217
+
218
+ Con varias rutas críticas empatadas, la varianza del proyecto es la de **una** ruta (la convención es la de mayor
219
+ varianza), no la suma de todas las actividades críticas.
220
+ """
221
+ criticas = set(crit)
222
+ mejor: dict[str, float] = {}
223
+ previa: dict[str, str | None] = {}
224
+ for nm in [n for n in _toposort_and_succ(acts)[0] if n in criticas]:
225
+ candidatas = [p for p in acts[nm]["predecessors"] if p in criticas and abs(EF[p] - ES[nm]) < 1e-9]
226
+ p_max = max(candidatas, key=lambda p: mejor[p], default=None)
227
+ mejor[nm] = variances[nm] + (mejor[p_max] if p_max is not None else 0.0)
228
+ previa[nm] = p_max
229
+ finales = [nm for nm in mejor if abs(EF[nm] - T) < 1e-9]
230
+ if not finales:
231
+ return [], 0.0
232
+ final = max(finales, key=lambda n: mejor[n])
233
+ varianza = mejor[final]
234
+ nodo: str | None = final
235
+ ruta: list[str] = []
236
+ while nodo is not None:
237
+ ruta.append(nodo)
238
+ nodo = previa[nodo]
239
+ return ruta[::-1], varianza
240
+
241
+
214
242
  def _build_results(acts: dict, durations: dict, ES, EF, LS, LF, T, succ,
215
243
  opt=None, ml=None, pess=None, variances=None) -> list:
216
244
  results = []
@@ -382,7 +410,14 @@ def pert(activities: list) -> ProjectResult:
382
410
  results = _build_results(acts, durations, ES, EF, LS, LF, T, succ,
383
411
  opt=opt_d, ml=ml_d, pess=pess_d, variances=variances)
384
412
  crit = [a.name for a in results if a.is_critical]
385
- proj_var = sum(variances[nm] for nm in crit)
413
+ ruta, proj_var = _ruta_critica_de_mayor_varianza(acts, crit, variances, ES, EF, T)
414
+ sigma = math.sqrt(proj_var)
415
+ casi_criticas = [a.name for a in results if a.name not in ruta and a.total_float <= sigma + 1e-9]
416
+ if sigma > 0 and casi_criticas:
417
+ warnings.warn(
418
+ _t("project.aviso.pert.rutas_casi_criticas", n=len(casi_criticas), actividades=", ".join(casi_criticas[:5]), sigma=sigma),
419
+ UserWarning, stacklevel=2,
420
+ )
386
421
 
387
422
  return ProjectResult(
388
423
  activities=results,
@@ -199,7 +199,8 @@ def series_system(
199
199
  t : float, optional
200
200
  Tiempo para evaluar R(t).
201
201
  mttr : float, optional
202
- Tiempo medio de reparación para calcular la disponibilidad.
202
+ Tiempo medio de reparación. Disponibilidad A = MTBF / (MTBF + MTTR) del sistema: mientras se repara el
203
+ componente que falló el sistema está detenido y los demás no fallan.
203
204
 
204
205
  Returns
205
206
  -------
@@ -237,6 +238,11 @@ def series_system(
237
238
  )
238
239
 
239
240
 
241
+ def _disponibilidad_componente(lam: float, mttr: float) -> float:
242
+ """Disponibilidad en régimen de un componente reparable: MTBF / (MTBF + MTTR) = 1 / (1 + λ·MTTR)."""
243
+ return 1.0 / (1.0 + lam * mttr)
244
+
245
+
240
246
  def parallel_system(
241
247
  failure_rates: Sequence[float],
242
248
  *,
@@ -256,7 +262,9 @@ def parallel_system(
256
262
  t : float, optional
257
263
  Tiempo para evaluar R(t).
258
264
  mttr : float, optional
259
- Tiempo medio de reparación para calcular la disponibilidad.
265
+ Tiempo medio de reparación de cada componente. La disponibilidad supone que cada componente se repara
266
+ en cuanto falla, de forma independiente: A = 1 − ∏(1 − Aᵢ), con Aᵢ = MTBFᵢ / (MTBFᵢ + MTTR). El MTBF
267
+ del resultado es el tiempo medio hasta la falla del sistema sin reparación.
260
268
 
261
269
  Returns
262
270
  -------
@@ -283,7 +291,8 @@ def parallel_system(
283
291
  return _r_system("parallel", lams, None, tt)
284
292
 
285
293
  mtbf_val = _mtbf_numeric(R_func, min(lams))
286
- avail = mtbf_val / (mtbf_val + mttr) if mttr is not None else None
294
+ # Disponibilidad en régimen con reparación independiente de cada componente: A = 1 − ∏(1 − Aᵢ)
295
+ avail = 1.0 - math.prod(1.0 - _disponibilidad_componente(lam, mttr) for lam in lams) if mttr is not None else None
287
296
  R_t = R_func(t) if t is not None else None
288
297
  return ReliabilityResult(
289
298
  topology="parallel",
@@ -326,7 +335,10 @@ def koon_system(
326
335
  t : float, optional
327
336
  Tiempo para evaluar R(t).
328
337
  mttr : float, optional
329
- Tiempo medio de reparación para calcular la disponibilidad.
338
+ Tiempo medio de reparación de cada componente. La disponibilidad supone reparación independiente de cada
339
+ componente: A = Σ_{j=k}^{n} C(n,j)·aʲ·(1 − a)ⁿ⁻ʲ, con a = MTBF / (MTBF + MTTR) de un componente. El MTBF
340
+ del resultado es el tiempo medio hasta la falla del sistema sin reparación. Con k = n, ``series_system``
341
+ supone en cambio que los demás componentes no fallan mientras el sistema está detenido.
330
342
 
331
343
  Returns
332
344
  -------
@@ -357,7 +369,12 @@ def koon_system(
357
369
  # Exact MTBF for k-of-n identical exponential: (1/λ) * Σ_{j=k}^{n} 1/j
358
370
  mtbf_val = (1.0 / lam) * sum(1.0 / j for j in range(k, n + 1))
359
371
 
360
- avail = mtbf_val / (mtbf_val + mttr) if mttr is not None else None
372
+ if mttr is not None:
373
+ # Reparación independiente: cada componente está disponible con probabilidad a, y el sistema si funcionan ≥ k
374
+ a = _disponibilidad_componente(lam, mttr)
375
+ avail: float | None = sum(math.comb(n, j) * a**j * (1.0 - a) ** (n - j) for j in range(k, n + 1))
376
+ else:
377
+ avail = None
361
378
  R_t = _r_system("k-of-n", lams, k, t) if t is not None else None
362
379
  return ReliabilityResult(
363
380
  topology="k-of-n",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: walopy
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
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
@@ -28,12 +28,14 @@ src/walopy.egg-info/requires.txt
28
28
  src/walopy.egg-info/top_level.txt
29
29
  tests/test_abc_xyz.py
30
30
  tests/test_advanced.py
31
+ tests/test_avisos_validez.py
31
32
  tests/test_bottleneck.py
32
33
  tests/test_cli.py
33
34
  tests/test_complejidad.py
34
35
  tests/test_contrato_entradas.py
35
36
  tests/test_cpm_pert.py
36
37
  tests/test_dibujo_plotly.py
38
+ tests/test_disponibilidad_redundante.py
37
39
  tests/test_documentacion.py
38
40
  tests/test_documentacion_ingles.py
39
41
  tests/test_ejemplos.py
@@ -55,6 +57,7 @@ tests/test_network.py
55
57
  tests/test_new_models.py
56
58
  tests/test_notas_release.py
57
59
  tests/test_operations.py
60
+ tests/test_pert_varianza.py
58
61
  tests/test_plotting.py
59
62
  tests/test_politicas_inventario.py
60
63
  tests/test_propiedades_algoritmos.py
@@ -76,6 +76,7 @@ def test_monte_carlo_gg1_deterministic_service():
76
76
  assert r_sim.Wq_mean == pytest.approx(r_analytic.Wq, rel=0.05)
77
77
 
78
78
 
79
+ @pytest.mark.filterwarnings("ignore::UserWarning") # solo el orden de los percentiles; la precisión avisa aparte
79
80
  def test_monte_carlo_percentiles_ordered():
80
81
  r = monte_carlo_gg1(lam=3.0, mu=5.0, ca2=1.0, cs2=1.0,
81
82
  n_customers=10_000, seed=7)
@@ -0,0 +1,46 @@
1
+ """Avisos de validez (0.4.4): el modelo se usa fuera del rango en que su resultado es fiable.
2
+
3
+ * ``newsvendor`` con demanda normal y CV > 0,5: la normal asigna probabilidad apreciable a demanda negativa.
4
+ * ``monte_carlo_gg1``: el semiancho al 95 % de Wq (medias de 20 lotes) supera el 10 %; una réplica puede engañar.
5
+ * ``pert``: ver ``test_pert_varianza.py``.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import warnings
10
+
11
+ import pytest
12
+
13
+ import walopy as wl
14
+
15
+
16
+ def test_newsvendor_avisa_con_cv_alto():
17
+ with pytest.warns(UserWarning) as avisos:
18
+ wl.newsvendor(20.0, 20.0, 10.0, 4.0)
19
+ assert "15.9%" in str(avisos[0].message) or "15,9" in str(avisos[0].message) # P(D < 0) del modelo
20
+
21
+
22
+ def test_newsvendor_no_avisa_con_cv_moderado():
23
+ with warnings.catch_warnings():
24
+ warnings.simplefilter("error")
25
+ wl.newsvendor(100.0, 30.0, 10.0, 4.0)
26
+ wl.newsvendor(100.0, 50.0, 10.0, 4.0) # CV = 0,5 justo en el límite
27
+
28
+
29
+ def test_simulacion_avisa_si_la_estimacion_es_imprecisa():
30
+ with pytest.warns(UserWarning) as avisos:
31
+ wl.monte_carlo_gg1(0.95, 1.0, 1.0, 1.0, n_customers=20_000, seed=1)
32
+ assert "n_customers" in str(avisos[0].message)
33
+
34
+
35
+ def test_simulacion_no_avisa_si_la_estimacion_es_precisa():
36
+ with warnings.catch_warnings():
37
+ warnings.simplefilter("error")
38
+ wl.monte_carlo_gg1(2.0, 3.0, 1.0, 1.0, n_customers=50_000, seed=1)
39
+
40
+
41
+ def test_el_aviso_no_cambia_el_resultado():
42
+ with warnings.catch_warnings():
43
+ warnings.simplefilter("ignore", UserWarning)
44
+ a = wl.monte_carlo_gg1(0.95, 1.0, 1.0, 1.0, n_customers=5_000, seed=3)
45
+ b = wl.newsvendor(20.0, 20.0, 10.0, 4.0)
46
+ assert a.Wq_mean > 0 and b.optimal_qty > 0
@@ -119,34 +119,40 @@ def test_cpm_empty_raises():
119
119
 
120
120
  # ─── PERT ─────────────────────────────────────────────────────────────────────
121
121
 
122
+ def _pert_red_de_prueba():
123
+ # A→C y B→D duran lo mismo (5): dos rutas críticas empatadas, así que pert avisa de rutas casi críticas
124
+ with pytest.warns(UserWarning):
125
+ return wl.pert(PERT_ACTS)
126
+
127
+
122
128
  def test_pert_returns_result():
123
- r = wl.pert(PERT_ACTS)
129
+ r = _pert_red_de_prueba()
124
130
  assert isinstance(r, wl.ProjectResult)
125
131
 
126
132
 
127
133
  def test_pert_method():
128
- r = wl.pert(PERT_ACTS)
134
+ r = _pert_red_de_prueba()
129
135
  assert r.method == "PERT"
130
136
 
131
137
 
132
138
  def test_pert_expected_duration():
133
- r = wl.pert(PERT_ACTS)
139
+ r = _pert_red_de_prueba()
134
140
  assert r.project_duration == pytest.approx(5.0, abs=1e-9)
135
141
 
136
142
 
137
143
  def test_pert_has_variance():
138
- r = wl.pert(PERT_ACTS)
144
+ r = _pert_red_de_prueba()
139
145
  assert r.project_variance is not None
140
146
  assert r.project_variance > 0
141
147
 
142
148
 
143
149
  def test_pert_std_is_sqrt_variance():
144
- r = wl.pert(PERT_ACTS)
150
+ r = _pert_red_de_prueba()
145
151
  assert r.project_std == pytest.approx(math.sqrt(r.project_variance), rel=1e-9)
146
152
 
147
153
 
148
154
  def test_pert_activity_variance_formula():
149
- r = wl.pert(PERT_ACTS)
155
+ r = _pert_red_de_prueba()
150
156
  for a in r.activities:
151
157
  if a.variance is not None:
152
158
  expected = ((a.pessimistic - a.optimistic) / 6.0) ** 2
@@ -154,12 +160,12 @@ def test_pert_activity_variance_formula():
154
160
 
155
161
 
156
162
  def test_pert_probability_below_mean_is_50pct():
157
- r = wl.pert(PERT_ACTS)
163
+ r = _pert_red_de_prueba()
158
164
  assert r.probability(r.project_duration) == pytest.approx(0.5, abs=1e-6)
159
165
 
160
166
 
161
167
  def test_pert_probability_increases_with_target():
162
- r = wl.pert(PERT_ACTS)
168
+ r = _pert_red_de_prueba()
163
169
  assert r.probability(r.project_duration + 1) > r.probability(r.project_duration)
164
170
 
165
171
 
@@ -0,0 +1,63 @@
1
+ """W-33: la disponibilidad de los sistemas redundantes suponía que nada se repara hasta que cae todo el sistema.
2
+
3
+ ``parallel_system`` y ``koon_system`` calculaban ``A = MTTF_sistema / (MTTF_sistema + MTTR)``: con dos componentes en paralelo
4
+ (λ = 0,01, MTTR = 10) daban 0,9375, cuando con reparación independiente de cada componente es 1 − (1 − Aᵢ)² = 0,9917
5
+ (8 veces menos indisponibilidad). Ahora se usa la disponibilidad en régimen con reparación independiente.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ import random
11
+
12
+ import pytest
13
+
14
+ import walopy as wl
15
+
16
+
17
+ def _a(lam: float, mttr: float) -> float:
18
+ return (1 / lam) / (1 / lam + mttr)
19
+
20
+
21
+ def test_paralelo_de_dos_con_reparacion_independiente():
22
+ r = wl.parallel_system([0.01, 0.01], mttr=10.0)
23
+ assert r.availability == pytest.approx(1 - (1 - 100 / 110) ** 2) # 0,9917; antes 0,9375
24
+
25
+
26
+ def test_k_de_n_es_la_binomial_de_la_disponibilidad_de_cada_componente():
27
+ a = _a(0.01, 10.0)
28
+ r = wl.koon_system(n=3, k=2, failure_rate=0.01, mttr=10.0)
29
+ assert r.availability == pytest.approx(3 * a**2 * (1 - a) + a**3)
30
+
31
+
32
+ @pytest.mark.parametrize("semilla", range(40))
33
+ def test_paralelo_y_k_de_n_contra_formulas_independientes(semilla):
34
+ r = random.Random(3300 + semilla)
35
+ lams = [r.uniform(1e-4, 0.1) for _ in range(r.randint(2, 5))]
36
+ mttr = r.uniform(0.1, 50)
37
+ esperado = 1 - math.prod(1 - _a(x, mttr) for x in lams)
38
+ assert wl.parallel_system(lams, mttr=mttr).availability == pytest.approx(esperado, rel=1e-12)
39
+ n = r.randint(2, 6)
40
+ k = r.randint(1, n - 1)
41
+ a = _a(lams[0], mttr)
42
+ binomial = sum(math.comb(n, j) * a**j * (1 - a) ** (n - j) for j in range(k, n + 1))
43
+ assert wl.koon_system(n=n, k=k, failure_rate=lams[0], mttr=mttr).availability == pytest.approx(binomial, rel=1e-12)
44
+
45
+
46
+ def test_redundancia_nunca_es_menos_disponible_que_un_componente():
47
+ for lam, mttr in ((0.01, 10.0), (0.2, 3.0), (1e-3, 100.0)):
48
+ a = _a(lam, mttr)
49
+ assert wl.parallel_system([lam, lam], mttr=mttr).availability > a
50
+ assert wl.koon_system(n=3, k=1, failure_rate=lam, mttr=mttr).availability > a
51
+
52
+
53
+ def test_un_solo_componente_y_la_serie_no_cambian():
54
+ # Guarda: un componente (paralelo de 1 o 1-de-1) y la serie conservan A = MTBF / (MTBF + MTTR)
55
+ assert wl.parallel_system([0.01], mttr=10.0).availability == pytest.approx(_a(0.01, 10.0))
56
+ assert wl.koon_system(n=1, k=1, failure_rate=0.01, mttr=10.0).availability == pytest.approx(_a(0.01, 10.0))
57
+ assert wl.series_system([0.01, 0.02], mttr=10.0).availability == pytest.approx(1 / (1 + 0.03 * 10.0))
58
+
59
+
60
+ def test_sin_mttr_no_hay_disponibilidad():
61
+ # Guarda
62
+ assert wl.parallel_system([0.01, 0.02]).availability is None
63
+ assert wl.koon_system(n=3, k=2, failure_rate=0.01).availability is None
@@ -135,6 +135,9 @@ AÑADIDAS_TRAS_V030 = {
135
135
  "solver.error.sensitivity.values_no_puede_estar_vacio", # W-32
136
136
  "solver.error.sensitivity.param_no_es_parametro_modelo",
137
137
  "solver.error.compare.sin_resultados", # W-31
138
+ "project.aviso.pert.rutas_casi_criticas", # 0.4.4: avisos de validez de los modelos
139
+ "inventory.aviso.newsvendor.demanda_normal_cv_alto",
140
+ "advanced.aviso.monte_carlo_gg1.estimacion_imprecisa",
138
141
  "project.cabecera.to_frame.ht", # «HT»/«HL» (holgura total/libre): acrónimos españoles que el extractor no veía; ya existían en v0.3.0
139
142
  "project.cabecera.to_frame.hl",
140
143
  }
@@ -0,0 +1,74 @@
1
+ """W-34: con varias rutas críticas empatadas, ``pert`` sumaba las varianzas de **todas** las actividades críticas.
2
+
3
+ La varianza del proyecto en PERT es la de una ruta (la convención, con empates, es la de mayor varianza). Con 5 rutas
4
+ idénticas de 3 actividades, σ salía 3,23 en lugar de 1,44 (√5 veces mayor). Además ``pert`` avisa cuando hay rutas casi
5
+ críticas, porque entonces la probabilidad PERT (una sola ruta) es optimista.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import warnings
10
+
11
+ import pytest
12
+
13
+ import walopy as wl
14
+
15
+
16
+ def _rutas_paralelas(n_rutas: int = 5) -> list[dict]:
17
+ acts = []
18
+ for p in range(n_rutas):
19
+ previa: list[str] = []
20
+ for j in range(3):
21
+ nombre = f"P{p}A{j}"
22
+ acts.append({"name": nombre, "optimistic": 4, "most_likely": 5, "pessimistic": 9, "predecessors": previa})
23
+ previa = [nombre]
24
+ acts.append({"name": "FIN", "optimistic": 1, "most_likely": 1, "pessimistic": 1, "predecessors": [f"P{p}A2" for p in range(n_rutas)]})
25
+ return acts
26
+
27
+
28
+ def _pert(acts):
29
+ with warnings.catch_warnings():
30
+ warnings.simplefilter("ignore", UserWarning)
31
+ return wl.pert(acts)
32
+
33
+
34
+ def test_rutas_empatadas_usan_la_varianza_de_una_ruta():
35
+ r = _pert(_rutas_paralelas())
36
+ assert r.project_variance == pytest.approx(3 * (5 / 6) ** 2) # antes 15 · (5/6)² = 10,42
37
+ assert len(r.critical_path) == 16 # el conjunto de actividades críticas no cambia
38
+
39
+
40
+ def test_con_empate_se_elige_la_ruta_de_mayor_varianza():
41
+ acts = [
42
+ {"name": "A", "optimistic": 2, "most_likely": 5, "pessimistic": 8, "predecessors": []}, # var 1
43
+ {"name": "B", "optimistic": 4, "most_likely": 5, "pessimistic": 6, "predecessors": []}, # var 1/9
44
+ {"name": "C", "optimistic": 1, "most_likely": 1, "pessimistic": 1, "predecessors": ["A", "B"]},
45
+ ]
46
+ assert _pert(acts).project_variance == pytest.approx(1.0) # antes 1 + 1/9
47
+
48
+
49
+ def test_una_sola_ruta_critica_no_cambia():
50
+ # Guarda: sin empates la varianza es la de la ruta crítica, como siempre
51
+ acts = [
52
+ {"name": "A", "optimistic": 2, "most_likely": 4, "pessimistic": 6, "predecessors": []},
53
+ {"name": "B", "optimistic": 1, "most_likely": 1, "pessimistic": 1, "predecessors": []},
54
+ {"name": "C", "optimistic": 1, "most_likely": 2, "pessimistic": 3, "predecessors": ["A", "B"]},
55
+ ]
56
+ r = wl.pert(acts)
57
+ assert r.project_variance == pytest.approx((4 / 6) ** 2 + (2 / 6) ** 2)
58
+
59
+
60
+ def test_avisa_si_hay_rutas_casi_criticas():
61
+ with pytest.warns(UserWarning) as avisos:
62
+ wl.pert(_rutas_paralelas())
63
+ assert "P1A0" in str(avisos[0].message)
64
+
65
+
66
+ def test_no_avisa_si_las_demas_rutas_tienen_holgura_amplia():
67
+ acts = [
68
+ {"name": "Larga", "optimistic": 20, "most_likely": 22, "pessimistic": 24, "predecessors": []},
69
+ {"name": "Corta", "optimistic": 1, "most_likely": 2, "pessimistic": 3, "predecessors": []},
70
+ {"name": "Fin", "optimistic": 1, "most_likely": 1, "pessimistic": 1, "predecessors": ["Larga", "Corta"]},
71
+ ]
72
+ with warnings.catch_warnings():
73
+ warnings.simplefilter("error")
74
+ wl.pert(acts)
@@ -37,7 +37,7 @@ RESULTADOS = {
37
37
  "break_even_sales_real": lambda: wl.break_even_sales(1000.0, 0.4, actual_revenue=3000.0),
38
38
  "eoq": lambda: wl.eoq(1000.0, 50.0, 2.0),
39
39
  "optimize_servers": lambda: wl.optimize_servers(4.0, 3.0, cost_per_server=10.0, cost_per_wait=5.0),
40
- "monte_carlo": lambda: wl.monte_carlo_gg1(2.0, 3.0, 1.0, 1.0, n_customers=2000, seed=1),
40
+ "monte_carlo": lambda: wl.monte_carlo_gg1(2.0, 3.0, 1.0, 1.0, n_customers=50_000, seed=1), # sin aviso de precisión
41
41
  "oee_arbol": lambda: wl.oee_kpi_tree(0.9, 0.9, 0.9),
42
42
  "throughput_arbol": lambda: wl.throughput_kpi_tree(80.0, 100.0, 0.05),
43
43
  "roi_arbol": lambda: wl.roi_kpi_tree(1000.0, 200.0, 3.0, 100.0, 500.0),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes