File size: 10,273 Bytes
da4b3db | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | import math
import pandas as pd
import gradio as gr
import plotly.graph_objects as go
MATERIALS = {
"aluminum": {"name":"Aluminio", "family":"structural"},
"pmc_cf_resin": {"name":"Compuesto (CF+Resina)", "family":"structural"},
"eva_waste": {"name":"EVA/Tejidos sintéticos", "family":"textiles"},
"foam_pack": {"name":"Espuma de empaque (Zotek/Plastazote)", "family":"foam"},
"bubble_wrap": {"name":"Burbuja / Air Cushions", "family":"packaging"},
"textiles": {"name":"Textiles / Wipes", "family":"textiles"},
"food_pouches": {"name":"Pouches comida/bebida/termales", "family":"packaging"},
"filters_mesh": {"name":"Filtros/Mallas lab", "family":"lab"},
"carbon": {"name":"Carbono excedente (experimentos O₂)", "family":"lab"},
"resealable_bags": {"name":"Bolsas resellables/antiestáticas", "family":"packaging"},
"nitrile_gloves": {"name":"Guantes nitrilo", "family":"lab"},
}
PROCESS_EFF = {
"shred": 0.95, "sort": 0.97, "compact": 0.98, "regolith_mix": 0.96, "form": 0.95
}
WATER = {"wash": {"use_L_per_kg": 0.6, "recovery": 0.93},
"mix": {"use_L_per_kg": 0.4, "recovery": 0.95}}
PRODUCT_YIELDS = {
"Utensilios": {"kg_per_unit": 0.05},
"Cajas de Almacen": {"kg_per_unit": 0.8},
"Paneles Interiores": {"kg_per_unit": 2.0},
"Herramientas": {"kg_per_unit": 0.2},
"Pads Aislantes": {"kg_per_unit": 0.5},
}
REGOLITH_FRACTION = 0.20
SCENARIOS = {
"Residence Renovations": {
"desc": "Reuso del cubo 3D y empaques del hábitat.",
"inventory_kg": {"aluminum": 120, "pmc_cf_resin": 60, "foam_pack": 80, "bubble_wrap": 20, "resealable_bags": 10}
},
"Cosmic Celebrations": {
"desc": "Fiesta con materiales reutilizados dentro del hábitat.",
"inventory_kg": {"textiles": 25, "eva_waste": 15, "bubble_wrap": 10, "food_pouches": 12, "resealable_bags": 5}
},
"Daring Discoveries": {
"desc": "Post-experimentos de O₂: reutilizar equipos y carbono excedente.",
"inventory_kg": {"filters_mesh": 18, "carbon": 35, "eva_waste": 12, "nitrile_gloves": 6, "resealable_bags": 4}
}
}
def simulate_flow(inventory_kg: dict, eff=PROCESS_EFF, rego_frac=REGOLITH_FRACTION):
inv = pd.Series(inventory_kg, dtype=float).reindex(MATERIALS.keys()).fillna(0.0)
total_in = inv.sum()
wash_kg = inv[["eva_waste","textiles","foam_pack","bubble_wrap","food_pouches","resealable_bags","nitrile_gloves","pmc_cf_resin"]].sum()
water_wash_use = wash_kg * WATER["wash"]["use_L_per_kg"]
water_wash_recovered = water_wash_use * WATER["wash"]["recovery"]
water_wash_loss = water_wash_use - water_wash_recovered
after_shred = total_in * eff["shred"]
after_sort = after_shred * eff["sort"]
after_compact = after_sort * eff["compact"]
polymer_mass = after_compact
regolith_added = polymer_mass * rego_frac
mix_total = polymer_mass + regolith_added
after_mix = mix_total * eff["regolith_mix"]
final_useful = after_mix * eff["form"]
water_mix_use = polymer_mass * WATER["mix"]["use_L_per_kg"]
water_mix_recovered = water_mix_use * WATER["mix"]["recovery"]
water_mix_loss = water_mix_use - water_mix_recovered
kpi = {
"Input total (kg)": round(total_in, 2),
"After Shred (kg)": round(after_shred, 2),
"After Sort (kg)": round(after_sort, 2),
"After Compact (kg)": round(after_compact, 2),
"Regolith added (kg)": round(regolith_added, 2),
"After Mix (kg)": round(after_mix, 2),
"Final Useful Output (kg)": round(final_useful, 2),
"Water use wash (L)": round(water_wash_use, 2),
"Water recovered wash (L)": round(water_wash_recovered, 2),
"Water loss wash (L)": round(water_wash_loss, 2),
"Water use mix (L)": round(water_mix_use, 2),
"Water recovered mix (L)": round(water_mix_recovered, 2),
"Water loss mix (L)": round(water_mix_loss, 2),
"Water total loss (L)": round(water_wash_loss + water_mix_loss, 2),
"Crew time (min/day)": 12
}
family_weights = {"structural": 0.35, "textiles": 0.15, "foam": 0.15, "packaging": 0.20, "lab": 0.15}
family_mass = {f: final_useful*wt for f, wt in family_weights.items()}
def units(m, kgpu): return int(max(0, m // kgpu))
product_plan = {
"Utensilios": units(family_mass["packaging"]*0.5 + family_mass["textiles"]*0.2, PRODUCT_YIELDS["Utensilios"]["kg_per_unit"]),
"Cajas de Almacen": units(family_mass["structural"]*0.35 + family_mass["packaging"]*0.15, PRODUCT_YIELDS["Cajas de Almacen"]["kg_per_unit"]),
"Paneles Interiores":units(family_mass["structural"]*0.4 + family_mass["foam"]*0.3, PRODUCT_YIELDS["Paneles Interiores"]["kg_per_unit"]),
"Herramientas": units(family_mass["lab"]*0.6 + family_mass["structural"]*0.1, PRODUCT_YIELDS["Herramientas"]["kg_per_unit"]),
"Pads Aislantes": units(family_mass["foam"]*0.7 + family_mass["textiles"]*0.1, PRODUCT_YIELDS["Pads Aislantes"]["kg_per_unit"]),
}
return kpi, product_plan, inv
def sankey_from_kpis(kpi):
labels = ["Input","Shred","Sort","Compact","Regolith","Mix","Form (Final)"]
idx = {lab:i for i,lab in enumerate(labels)}
inp,a1,a2,a3,reg,mix,final = kpi["Input total (kg)"],kpi["After Shred (kg)"],kpi["After Sort (kg)"],kpi["After Compact (kg)"],kpi["Regolith added (kg)"],kpi["After Mix (kg)"],kpi["Final Useful Output (kg)"]
loss_shred=max(0,inp-a1); loss_sort=max(0,a1-a2); loss_compact=max(0,a2-a3); loss_mix=max(0,(a3+reg)-mix); loss_form=max(0,mix-final)
def l(s,t,v): return (s,t,round(v,2)) if v>0.0001 else None
links=[l(idx["Input"],idx["Shred"],a1), l(idx["Input"],idx["Input"],loss_shred),
l(idx["Shred"],idx["Sort"],a2), l(idx["Shred"],idx["Shred"],loss_sort),
l(idx["Sort"],idx["Compact"],a3), l(idx["Sort"],idx["Sort"],loss_compact),
l(idx["Compact"],idx["Regolith"],reg), l(idx["Regolith"],idx["Mix"],a3+reg),
l(idx["Mix"],idx["Form (Final)"],final), l(idx["Mix"],idx["Mix"],loss_mix),
l(idx["Form (Final)"],idx["Form (Final)"],loss_form)]
links=[x for x in links if x]
fig=go.Figure(data=[go.Sankey(
node=dict(pad=20, thickness=18, line=dict(color="white", width=0.5), label=labels),
link=dict(source=[s for s,_,__ in links], target=[t for _,t,__ in links], value=[v for __,__,v in links])
)])
fig.update_layout(margin=dict(l=10,r=10,t=10,b=10), height=430)
return fig
def run_sim(scenario, aluminum, pmc, eva, foam, bubble, textiles, pouches, filters, carbon, bags, gloves, rego_frac):
base = SCENARIOS[scenario]["inventory_kg"].copy()
base.update({
"aluminum": aluminum, "pmc_cf_resin": pmc, "eva_waste": eva, "foam_pack": foam, "bubble_wrap": bubble,
"textiles": textiles, "food_pouches": pouches, "filters_mesh": filters, "carbon": carbon,
"resealable_bags": bags, "nitrile_gloves": gloves
})
kpi, products, inv = simulate_flow(base, PROCESS_EFF, rego_frac)
kpi_df = pd.DataFrame(list(kpi.items()), columns=["Métrica","Valor"])
prod_df = pd.DataFrame(list(products.items()), columns=["Producto","Unidades (aprox)"])
inv_df = pd.DataFrame({"Material":[MATERIALS[k]["name"] for k in inv.index], "kg":inv.values})
fig = sankey_from_kpis(kpi)
return kpi_df, prod_df, inv_df, fig
with gr.Blocks(title="MARS-LOOP — Jezero Crater") as demo:
gr.Markdown("# 🪐 MARS-LOOP — Reciclaje circular en Marte\n**Convierte residuos inorgánicos + regolito MGS-1 en piezas útiles del hábitat.** \n_Sin incinerar, minimizando agua, y con operación ligera (<15 min/día)._")
with gr.Row():
scenario = gr.Dropdown(choices=list(SCENARIOS.keys()), value="Residence Renovations", label="Escenario NASA")
rego_frac = gr.Slider(0.0, 0.5, value=REGOLITH_FRACTION, step=0.05, label="Fracción de regolito (0–50%)")
gr.Markdown("### Inventario (kg) — Ajusta por material")
with gr.Row():
with gr.Column():
aluminum = gr.Slider(0, 300, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("aluminum",0), step=5, label="Aluminio (kg)")
pmc = gr.Slider(0, 300, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("pmc_cf_resin",0), step=5, label="Compuesto CF+Resina (kg)")
eva = gr.Slider(0, 200, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("eva_waste",0), step=2, label="EVA/Textiles sintéticos (kg)")
foam = gr.Slider(0, 200, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("foam_pack",0), step=2, label="Espuma empaque (kg)")
bubble = gr.Slider(0, 100, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("bubble_wrap",0), step=2, label="Burbuja/Air Cushions (kg)")
textiles = gr.Slider(0, 100, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("textiles",0), step=2, label="Textiles/Wipes (kg)")
with gr.Column():
pouches = gr.Slider(0, 100, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("food_pouches",0), step=2, label="Pouches comida/bebida/termal (kg)")
filters = gr.Slider(0, 100, value=SCENARIOS["Daring Discoveries"]["inventory_kg"].get("filters_mesh",0), step=2, label="Filtros/Mallas (kg)")
carbon = gr.Slider(0, 100, value=SCENARIOS["Daring Discoveries"]["inventory_kg"].get("carbon",0), step=2, label="Carbono excedente (kg)")
bags = gr.Slider(0, 60, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("resealable_bags",0), step=1, label="Bolsas resellables (kg)")
gloves = gr.Slider(0, 40, value=SCENARIOS["Daring Discoveries"]["inventory_kg"].get("nitrile_gloves",0), step=1, label="Guantes nitrilo (kg)")
run = gr.Button("▶️ Ejecutar simulación")
kpi_df = gr.Dataframe(label="KPIs")
prod_df = gr.Dataframe(label="Productos estimados")
inv_df = gr.Dataframe(label="Inventario usado (kg)")
sankey = gr.Plot(label="Flujo de materiales — Sankey")
run.click(run_sim,
inputs=[scenario, aluminum, pmc, eva, foam, bubble, textiles, pouches, filters, carbon, bags, gloves, rego_frac],
outputs=[kpi_df, prod_df, inv_df, sankey])
demo.launch()
|