MATER // GUIDELINES

Ce document s'organise autour des intentions de modélisation : chaque section part d'une question physique ou économique, puis mappe vers les mécanismes Mater.
Il se lit du général vers le particulier — parcourir le chapitre I avant tout autre est recommandé.
Chapitre I Quelle est la nature de mes objets ?

Avant de déclarer quoi que ce soit, chaque objet du système doit être classifié. Ce choix conditionne tout le reste.

Les quatre catégories

Catégorie 01
Produit final scénarisé
Trajectoire de demande connue a priori. Variable pilote du modèle — câbles déployés, véhicules vendus, capacité installée.
exogenous_stock + control_flow_shares
Catégorie 02
Intermédiaire producible
Produit à la demande d'un autre. Peut avoir ou non un stock tampon explicite (just-in-time vs tampon). Si alimenté à la fois par recyclage et par production primaire commandée, reste producible — les deux sources coexistent dans le stock.
control_flow_shares (+ exogenous_stock si tampon)
Catégorie 03
Ressource finie non renouvelable
Stock initial qui se consomme sans réapprovisionnement commandé. Réserve géologique, gisement, stock historique de déchets.
uncontrolled_objects + init_non_exo_in_use_stock
Catégorie 04
Co-produit ou déchet inévitable
Généré par un procédé, jamais commandé. Le stock s'accumule quand le procédé tourne, se vide quand le co-produit est consommé ailleurs.
uncontrolled_objects (alimenté par TP+ ou recyclage)
SCHÉMA 01 — Arbre de décision : classification d'un objet
MON OBJET Trajectoire de demande connue a priori ? OUI PRODUIT FINAL SCÉNARISÉ Cat. 01 NON Produit à la demande d'un autre objet ? OUI INTERMÉDIAIRE PRODUCIBLE Cat. 02 NON Alimenté uniquement par recyclage ? OUI RECYCLÉ / SECONDAIRE uncontrolled_stock secondary_production NON Stock initial fini, non renouvelable ? OUI RESSOURCE FINIE Cat. 03 NON CO-PRODUIT / DÉCHET uncontrolled_stock — alimenté par TP+ — Cat. 04 Question Résultat Recyclé / secondaire (uncontrolled_stock)

Pourquoi déclarer uncontrolled_stock ? Le cheminement intellectuel

Mater est un système de contrôle, pas un comptable

Un logiciel de comptabilité matière se contente d'enregistrer : consommation = stock − flux_entrant + flux_sortant. Mater ne fait pas ça. Son mécanisme central est un contrôleur par rétroaction : il observe l'écart entre un stock cible et le stock réel, et commande la production pour résorber cet écart.

La conséquence : par défaut, tout stock est infini

Si un objet est consommé et qu'aucune instruction contraire n'est donnée, le contrôleur génère automatiquement une commande de production (tp_demand) pour compenser exactement cette consommation. Consommer 1 000 t de bauxite déclenche une commande de 1 000 t de bauxite. Le stock ne bouge jamais.

C'est le comportement souhaité pour de l'acier, de l'aluminium, de l'énergie. C'est le comportement absurde pour du lithium en gisement ou du charbon en réserve.

RÈGLE FONDAMENTALE
uncontrolled_stock = instruction au contrôleur : "ne compense pas".
Ce n'est pas une étiquette sur la physique de l'objet. C'est une décision de modélisation.

La question à se poser : "Si ce stock est consommé, est-ce que je veux que Mater commande automatiquement pour compenser ?"
Si la réponse est nonuncontrolled_stock.

Le cas du matériau partiellement alimenté par recyclage

Considérons de l'aluminium secondaire (alu_sec), issu du recyclage de câbles en fin de vie :

Sans uncontrolled_stockAvec uncontrolled_stock
Chaque tonne consommée génère un tp_demand → production ex nihilo d'alu_sec. La tension recyclage insuffisant est invisible. Le tp_demand est filtré. Seul le flux de recyclage alimente le stock. unmet_demand signale exactement quand et de combien le déficit apparaît.
POINT ESSENTIEL
uncontrolled_stock ne bloque pas le recyclage. Le flux de recyclage entre toujours dans le stock — il n'est pas affecté par le flag. Ce que uncontrolled_stock supprime, c'est uniquement la compensation commandée par le contrôleur.

La frontière de décision

SituationStatut
Alimenté uniquement par recyclage, jamais par production commandéeuncontrolled_stock
Alimenté uniquement par un stock initial fini (réserve)uncontrolled_stock
Alimenté par recyclage ET par production primaire commandéeProducible
Alimenté par co-production (TP+) d'un autre procédéuncontrolled_stock (Régime C)

Cas limites du chapitre I

Déchet de P1 = produit principal de P2

Un co-produit de P1 peut être l'input que P2 cherche à produire (ex : laitier de haut fourneau valorisé comme liant). L'objet a deux sources : TP+ depuis P1, et production commandée depuis P2. Il est producible, pas uncontrolled_stock. Déclarer cet objet uncontrolled_stock serait une erreur — il a une production commandée légitime.

Contradiction uncontrolled_stock + exogenous_stock

Si un objet est dans uncontrolled_objects ET possède un exogenous_stock, Mater émet un warning. Le tp_demand est bien filtré, mais le feedforward continue de générer un control_flow. Les deux instructions se contredisent.

AVERTISSEMENT
Résoudre en retirant l'exogenous_stock si l'intention est un stock fini, ou en retirant l'objet de uncontrolled_objects si la production commandée est légitime.

Statut différent selon la localisation

uncontrolled_objects accepte des tuples (location, object). Il est possible de déclarer le lithium non-producible en France et producible en Australie (mines actives), sans aucun artifice supplémentaire.

Ressource devenant producible dans le temps

Mater ne gère pas le changement de statut uncontrolled_stock → producible en cours de simulation. Le contournement : modéliser deux objets distincts — la réserve (uncontrolled_stock) et l'objet extrait (producible), reliés par un procédé TP dont les coefficients varient dans le temps.

SYNTHÈSE — CHAPITRE I
  • Classer chaque objet avant toute déclaration : produit final, intermédiaire, ressource finie, ou co-produit.
  • uncontrolled_stock est une instruction au contrôleur, pas une propriété physique. La question : "veux-je que Mater compense automatiquement la consommation ?"
  • Le recyclage continue d'alimenter un stock uncontrolled_stock — seule la commande tp_demand est supprimée.
  • Toute contradiction uncontrolled_stock + exogenous_stock génère un warning et un comportement hybride indéfini.
Chapitre II Comment mon objet est-il produit ?

Object Composition (OC) — incorporation physique

Un objet A contient B : B est incorporé à la fabrication de A et reste dans A pendant toute sa durée de vie.

object_composition    : (location, objet_parent, objet_composant) → coefficient (t/t, unité/unité…)
control_flow_shares   : (location, objet_produit, process)         → part du procédé

Le coefficient OC peut varier dans le temps — c'est une série temporelle dans la DataFrame. Un allègement progressif (câble passant de 0,5 à 0,3 t Cu/km entre 2025 et 2050) se déclare directement en renseignant la colonne temporelle correspondante.

Conséquence sur IUSEM — point critique

L'indicateur in_use_stock_embedded_material (IUSEM) estime la quantité de matière B contenue dans le parc d'objets A en service. Sa formule interne :

IUSEM(t) = OC(t) × IUS_total(t)

Mater applique le coefficient courant à l'ensemble du parc, toutes cohortes confondues. La valeur exacte serait :

IUSEM_correct(t) = Σk OC(k) × IUS(cohorte_k, t)

OC(k) est le coefficient en vigueur au moment de la production de la cohorte k.

CE QUI EST AFFECTÉ
Uniquement l'indicateur IUSEM. La simulation elle-même est correcte — la consommation de B lors de la production de A au pas k utilise bien OC(k). Les flux control_flow, tp_demand, IUS, unmet_demand ne sont pas affectés.

Direction du biais

Situation OCEffet sur IUSEM
OC décroissant (allègement)Coefficient courant < historique → IUSEM sous-estime les vieilles cohortes
OC croissant (densification)IUSEM surestime les vieilles cohortes
OC stableBiais nul
Transition rapide + vieux parc majoritaireBiais maximal

Contournement via age_cohort

L'output in_use_stock conserve la dimension age_cohort. Le calcul correct s'effectue hors modèle :

ius = mater.get("in_use_stock")   # dimension age_cohort disponible
oc  = params.object_composition   # séries temporelles

# Pour chaque cohorte, appliquer OC au moment de sa production
iusem_correct = (
    ius
    .groupby(level=["location", "object", "object_composition", "age_cohort"])
    .sum()
    # puis joindre OC[age_cohort] pour chaque ligne
)
SCHÉMA 02 — Biais IUSEM avec OC décroissant, deux cohortes
OC (t Cu/km) 2025 2030 2035 2040 2045 0.50 0.40 0.30 OC(t) réel Cohorte A produite 2025 OC(2025) = 0.50 Cohorte B produite 2035 OC(2035) = 0.40 t = 2040 OC(2040) = 0.35 Biais A -0.15 OC réel dans le temps Biais (sous-estimation) IUSEM utilise OC(2040)=0.35 pour TOUTES les cohortes

Thermodynamic Process (TP) — consommation ou co-production

Un procédé P consomme B (sans l'incorporer dans le produit) ou génère C en co-produit.

thermodynamic_process : (location, process, objet) → coefficient
  # Négatif  : B est consommé par le procédé (énergie, catalyseur, eau, réserve extraite)
  # Positif  : C est co-produit inévitable (déchet, chaleur résiduelle, émission)
  # L'objet PRODUIT par le procédé n'apparaît PAS ici — il est dans control_flow_shares

Amélioration d'efficacité progressive

Un procédé consomme 100 kWh/unité en 2025 et 65 kWh/unité en 2045. Aucune déclaration supplémentaire — le coefficient varie, la consommation suit automatiquement.

Substitution d'input dans le procédé

La part d'énergie fossile vs renouvelable dans un procédé change. Ce sont deux objets distincts (gaz naturel, électricité) avec des TP distincts dont les coefficients évoluent en sens inverse. La somme peut rester constante — c'est la façon propre de modéliser une transition de mix énergétique.

Changement de technologie (via CFS)

Si le procédé P1 (ancienne technologie) est remplacé par P2 (nouvelle technologie), modéliser via control_flow_shares : la part de P1 décroît, celle de P2 croît. Les coefficients TP de P1 et P2 peuvent être constants — c'est la composition du mix qui change.

Assembly Stock (AS) — composant à durée de vie inférieure

AS est le mécanisme approprié quand un composant B a une durée de vie plus courte que le produit A qui le contient et doit être remplacé périodiquement pendant la vie de A.

assembly_stock : (location, objet_parent, objet_composant) → coefficient (flux-based)

Le coefficient représente la fraction du parc parent renouvelée par unité de temps — c'est un coefficient de flux, pas un coefficient de stock. Un coefficient de 0,20 signifie que 20 % du parc de voitures renouvelle sa batterie à chaque pas de temps annuel.

RUPTURE CONCEPTUELLE vs ANCIENNE VERSION
Dans les versions antérieures, assembly_stock était traité comme une matrice de stock via clôture transitive, puis multiplié sur le stock de référence. Ce mécanisme créait du double-comptage avec la clôture OC et était incompatible avec les chaînes longues. AS est désormais un tableau de coefficients de flux : la demande est propagée depuis les parents vers les enfants par un mécanisme dédié.

Quand préférer AS à OC

  • Durée de vie de B < durée de vie de A, avec remplacement pendant la vie de A (batterie dans un véhicule, fenêtres dans un bâtiment)
  • Ratio d'incorporation croissant dans le temps avec impact sur les remplacements dans le parc existant — AS capture ce comportement, OC ne l'affecte que pour les nouvelles productions
  • Composant partagé entre plusieurs produits parents A1 et A2 dont la demande évolue différemment
CAS NON COUVERT NATIVEMENT
La maintenance corrective du parc existant (remplacer des B en fin de vie dans des A toujours en service) n'est pas modélisable nativement. Contournement : calculer hors modèle exogenous_stock(B) = IUS(A) × taux_remplacement_annuel et l'injecter comme scénario.

AS — propagation en ordre topologique et diagnostic

Mécanisme de propagation

La demande AS est propagée des parents vers les enfants dans l'ordre topologique du graphe d'assemblage, au sein d'un même pas de temps k. Aucune itération temporelle supplémentaire n'est nécessaire.

CF_total(B)[k] = max(feedforward(B)[k], 0) + Σ_A [ coeff_AB[k] × CF_total(A)[k] ]

Si B est lui-même parent d'un enfant C, la demande AS sur C est calculée depuis CF_total(B), qui inclut déjà la contribution de A. La propagation est donc complète en une seule passe topologique — sans délai temporel, quel que soit le nombre de niveaux d'assemblage.

# Déclaration AS : batterie dans une voiture (renouvellement 20%/an)
assembly_stock = {
  ("FR", "voiture",  "batterie")    : 0.20,
  ("FR", "batterie", "lithium_cel") : 0.05,  # 5% des cellules renouvelées/an
}
# → à chaque pas k :
#   CF(voiture)[k]     = 1000 unités (feedforward standard)
#   as_demand(batterie)[k]    = 0.20 × 1000 = 200 unités  (propagation AS niveau 1)
#   as_demand(lithium_cel)[k] = 0.05 × (CF_std(batterie) + 200)  (propagation AS niveau 2)
SCHÉMA 02b — Propagation AS en ordre topologique (intrapas k)
VOITURE exo_stock + CFS BATTERIE CFS → P_batterie LITHIUM uncontrolled_stock AS 0.20 AS 0.05 PROPAGATION TOPOLOGIQUE (intrapas k) Passe 1 (voiture) feedforward(voiture) → CF_total(voiture) = 1 000 Passe 2 (batterie) as_demand(batterie) = 0.20 × 1 000 = 200 → CF_total(batterie) = CF_std + 200 Passe 3 (lithium — uncontrolled_stock) as_demand filtré → unmet_demand(lithium) si stock insuffisant

Output de diagnostic : as_driven_demand_flow

L'output as_driven_demand_flow expose, à chaque pas de temps, la demande cumulée injectée par le mécanisme AS pour chaque objet enfant :

as_df = mater.outputs.get("as_driven_demand_flow")
# Index   : (location, object)   — objet enfant
# Colonnes : time
# Valeurs : demande AS cumulée depuis tous les parents déclarés

# Exemple : inspecter la demande AS sur les batteries
print(as_df.loc[("FR", "batterie")])

Si as_driven_demand_flow est nul pour un enfant attendu, vérifier que control_flow_shares est bien déclaré sur l'objet parent — le mécanisme AS se greffe sur le control_flow du parent et ne se déclenche que si ce dernier est actif.

Interaction AS × uncontrolled_stock

Un enfant B peut être uncontrolled_stock (ex : lithium en réserve finie, cuivre recyclé). Dans ce cas la demande AS est bien calculée, mais le filtre uncontrolled_stock l'empêche de générer un control_flow. Si la demande dépasse le stock disponible de B, unmet_demand(B) le signale avec la quantité exacte manquante.

SYNTHÈSE — CHAPITRE II
  • OC : incorporation physique, coefficient variable dans le temps nativement supporté.
  • IUSEM est approximé quand OC varie : biais systématique sur l'estimation du matériau embarqué dans les vieilles cohortes. Les flux de simulation restent corrects. Contournement via age_cohort hors modèle.
  • TP : consommation (négatif) ou co-production (positif). Coefficient variable, substitution de mix via CFS.
  • AS : réservé aux composants à durée de vie inférieure au parent avec remplacement périodique. Coefficient de flux (pas de stock) — la demande se propage en ordre topologique intrapas. Output as_driven_demand_flow pour le diagnostic.
Chapitre III Comment organiser une chaîne de production ?

La résolution simultanée — rupture conceptuelle essentielle

SCHÉMA 03 — Avant / Après le fix Jacobi
AVANT (séquentiel) A B C Pas k : A calcule → B connu à k+1 B (k) C (k) Pas k+1 : répercussion sur B et C IUS(B) < 0 ! IUS(C) < 0 ! Fantômes numériques : stocks négatifs transitoires sans signification physique APRÈS (simultané) A B C BOUCLE JACOBI (intrapas k) Passe 1 : feedforward(A) → CF(A) Passe 2 : CF(A) → tp_demand(B) → CF(B) Passe 3 : CF(B) → tp_demand(C) → CF(C) Convergence → tous cohérents Résultat : k est cohérent avant k+1 Aucun stock négatif fantôme Une passe Jacobi ≠ un pas de temps physique

Avant le fix Jacobi, Mater calculait les flux dans un ordre séquentiel : la consommation des inputs ne se répercutait qu'au pas de temps k+1. Résultat : une confusion entre "itération du solveur" et "pas de temps physique". Les stocks d'inputs apparaissaient négatifs pendant un pas puis se rétablissaient — des fantômes numériques sans signification.

Après le fix, Mater utilise une boucle Jacobi qui résout l'ensemble des dépendances algébriques à l'intérieur du pas de temps k, avant de passer à k+1. Ce n'est pas de la propagation temporelle — c'est de la résolution simultanée.

DISTINCTION ESSENTIELLE
Un "tour de boucle Jacobi" n'est pas un pas de temps physique — c'est une passe de calcul vers la cohérence interne. La profondeur d'une chaîne détermine le nombre de passes nécessaires, pas l'horizon temporel.

Chaîne OC — cascade de composition

Deux régimes selon que l'objet intermédiaire est "actionnable" (a un control_flow_shares) ou non.

Régime 1 — intermédiaire B sans CFS

La fermeture transitive est calculée : le lien A→B→C est traité comme A→B direct + A→C direct (coefficient = coeff_AB × coeff_BC). La consommation de C est générée depuis la production de A en une seule passe Jacobi.

PRÉREQUIS IMPÉRATIF
B doit être producible — avoir au minimum un exogenous_stock (même nul) ou un control_flow_shares. Sans ça, B est structurellement détecté comme uncontrolled_stock → son tp_demand est filtré → IUS(B) déplétion → la chaîne aval peut être cassée.

Régime 2 — intermédiaire B avec CFS

Seul le lien direct A→B est utilisé. La propagation vers C se fait via Jacobi : production de A → tp_demand(B) → production de B via son procédé → tp_demand(C). Chaque maillon ajoute une passe Jacobi.

Régime 1 (B sans CFS)Régime 2 (B avec CFS)
Propagation vers CFermeture transitive (1 passe)Jacobi itératif (N passes)
B doit avoir exo_stock ?Oui (sinon uncontrolled_stock)Non nécessaire
C peut être uncontrolled_stock ?
Observabilité de BStock de B dans les outputsStock de B + procédé de B actifs

Exemple — Régime 1 : câble → conducteur alu → aluminium (sans CFS sur le conducteur)

Le conducteur alu est un intermédiaire purement physique : il n'a pas de procédé de production propre déclaré dans le modèle. Il est simplement la forme que prend l'aluminium une fois mis en forme dans le câble.

# Déclaration OC
object_composition = {
  ("FR", "câble",         "conducteur_alu") : 0.82,  # t alu/km de câble
  ("FR", "conducteur_alu","aluminium")      : 1.00,  # 1:1, le conducteur est de l'alu
}
# conducteur_alu : pas de CFS, mais exogenous_stock = 0 obligatoire
# → Régime 1 : Mater applique la fermeture transitive
# → câble consomme aluminium au coefficient 0.82 × 1.00 = 0.82 t/km, en 1 passe Jacobi

Exemple — Régime 2 : câble → conducteur alu → aluminium (avec CFS sur le conducteur)

Le conducteur alu a ici son propre procédé de tréfilage (P_trefilage), ce qui permet de tracer son stock et de lui associer des consommations énergétiques via TP.

# Déclaration OC
object_composition = {
  ("FR", "câble",         "conducteur_alu") : 0.82,
  ("FR", "conducteur_alu","aluminium")      : 1.00,
}
# Déclaration CFS sur le conducteur
control_flow_shares = {
  ("FR", "conducteur_alu", "P_trefilage") : 1.0,
}
# → Régime 2 : propagation Jacobi en 3 passes
#   Passe 1 : feedforward(câble) → CF(câble)
#   Passe 2 : CF(câble) → tp_demand(conducteur_alu) → CF(conducteur_alu)
#   Passe 3 : CF(conducteur_alu) → tp_demand(aluminium) → CF(aluminium)
SCHÉMA 03b — Chaîne OC : Régime 1 (sans CFS) vs Régime 2 (avec CFS)
RÉGIME 1 — sans CFS sur B CÂBLE exo+CFS COND. ALU sans CFS / exo=0 ALU producible OC OC fermeture transitive câble → alu directement (1 passe) Jacobi : 1 passe — B invisible dans les stocks RÉGIME 2 — avec CFS sur B CÂBLE exo+CFS COND. ALU CFS → P_trefilage ALU producible OC OC BOUCLE JACOBI (3 passes) ① feedforward(câble) → CF(câble) ② CF(câble) → tp_demand(cond.alu) → CF(cond.alu) ③ CF(cond.alu) → tp_demand(alu) → CF(alu) → cond.alu observable + procédé actif Choisir Régime 2 si : observabilité du stock conducteur requise, ou si P_trefilage a des TP propres (énergie de tréfilage…) Choisir Régime 1 si : conducteur_alu est un pur intermédiaire physique sans intérêt analytique propre Lien OC déclaré Fermeture transitive (calculée)

Chaîne TP — condition bloquante

Pour une chaîne A (consomme D) → D (consomme E) :

Itération JacobiCe qui se calcule
1feedforward(A) → control_flow(A)
2P_A tourne → tp_demand(D) → control_flow(D)
3P_D tourne → tp_demand(E) → control_flow(E)
4Convergence
CONDITION BLOQUANTE
D doit avoir un control_flow_shares pointant vers P_D. Sans CFS sur D, P_D n'a aucune activité calculée — E n'est jamais consommé, la chaîne s'arrête à D même si D est correctement produit.

Exemple — filière aluminium : aluminium → alumine → bauxite

La filière Hall-Héroult / Bayer est le cas TP archétypal : chaque étape consomme une ressource en amont, qui est elle-même un objet producible avec son propre procédé.

# Chaîne TP : aluminium consomme alumine, alumine consomme bauxite
thermodynamic_process = {
  ("FR", "P_electrolysis", "alumine")  : -1.93,  # t alumine / t alu (négatif = consommé)
  ("FR", "P_bayer",        "bauxite")  : -4.80,  # t bauxite / t alumine
}
control_flow_shares = {
  ("FR", "aluminium", "P_electrolysis") : 1.0,  # OBLIGATOIRE — sinon P_electrolysis inactive
  ("FR", "alumine",   "P_bayer")        : 1.0,  # OBLIGATOIRE — sinon bauxite jamais consommée
}
# bauxite → uncontrolled_stock (stock géologique fini)
# Itérations Jacobi pour câble → alu → alumine → bauxite : 4 passes
#   ① CF(câble) ② tp_demand(alu) ③ tp_demand(alumine) ④ tp_demand(bauxite)
PIÈGE FRÉQUENT
Oublier le CFS sur alumine est une erreur silencieuse : aluminium est correctement produit, mais bauxite n'est jamais consommée. La chaîne s'arrête à alumine sans warning explicite.

Performance — profondeur vs largeur

La profondeur de la chaîne détermine le nombre d'itérations Jacobi, pas sa largeur :

  • N objets en parallèle consommés par A → 2 itérations, quelle que soit N
  • N objets en chaîne linéaire → N+1 itérations

Chaînes mixtes OC + TP — la filière câble aluminium complète

En pratique, un modèle combine toujours des liens OC (composition physique) et des liens TP (consommation de ressources par les procédés). L'exemple ci-dessous reconstitue la chaîne complète : du câble fini jusqu'à la bauxite extraite, en passant par le tréfilage et l'électrolyse.

SCHÉMA 03c — Chaîne mixte OC + TP : filière câble aluminium
Lien OC (composition) Lien TP (consommation procédé) exo_stock + CFS uncontrolled_stock CÂBLE exo_stock + CFS OC 0.82 t/km COND. ALU CFS → P_trefilage OC 1.00 t/t ALUMINIUM CFS → P_electrolysis P_câblerie P_trefilage P_electrolysis TP[-0.05 MWh/km] ÉNERGIE producible TP[-0.02 MWh/t] ÉNERGIE producible TP[-1.93 t/t] ALUMINE CFS → P_bayer P_bayer TP[-4.80 t/t] BAUXITE uncontrolled_stock PASSES JACOBI ① CF(câble) ② tp_demand(cond.alu) → CF ③ tp_demand(aluminium) → CF ④ tp_demand(alumine) → CF ⑤ tp_demand(bauxite) Profondeur 5 → 5 passes Largeur (énergie×2) = gratuite LECTURE DU SCHÉMA Traits pleins bleus = OC (qui contient quoi) Traits pointillés oranges = TP (ce que les procédés consomment)

Ce schéma illustre deux règles clés en pratique :

  • Les liens OC (bleus) forment la colonne vertébrale produit — ce que contient physiquement le câble.
  • Les liens TP (oranges pointillés) descendent des procédés vers leurs consommations — invisibles dans la composition du câble, mais bien tracés dans les flux.
  • La largeur (deux consommations d'énergie en parallèle) ne coûte rien en itérations Jacobi. La profondeur (câble → conducteur → alu → alumine → bauxite) détermine le nombre de passes.

Circularités TP

Une circularité TP existe quand A consomme B et B consomme A (ex : acier nécessaire aux infrastructures électriques, électricité nécessaire à la production d'acier). La boucle Jacobi est conçue pour résoudre ces couplages, mais l'introduction d'une non-linéarité dans la formule de control_flow (Option A, section IV) peut dans certains cas ralentir ou compromettre la convergence.

DÉTECTION RUNTIME
Mater détecte la divergence au runtime : si le résidu Jacobi dépasse 10× sa valeur initiale, une RuntimeError est levée. Une détection statique des cycles TP est prévue (développement futur).
SYNTHÈSE — CHAPITRE III
  • La résolution Jacobi est simultanée intrapas, pas séquentielle. Aucun stock négatif fantôme.
  • Chaîne OC : Régime 1 (transitif, 1 passe) vs Régime 2 (Jacobi, N passes). CFS obligatoire pour Régime 2.
  • Chaîne TP : CFS obligatoire sur chaque intermédiaire. Profondeur = nombre d'itérations.
  • En l'absence de CFS ou exo_stock sur un intermédiaire : détection heuristique → comportement uncontrolled_stock → chaîne potentiellement cassée.
Chapitre IV Fermer la boucle matière

Recyclage simple

Un objet A, en fin de vie, libère du matériau B (récupérable tel quel) ou B' (forme dégradée).

collection_rate       : fraction d'objets A collectés en fin de vie
recycling_rate        : (location, objet_collecté, composant_récupéré, forme_dégradée) → taux
recycling_flow_shares : destination du matériau recyclé
# B ou B' dans uncontrolled_objects s'ils ne sont alimentés que par recyclage

Le flux secondary_production(B) alimente IUS(B) à chaque fin de vie d'une cohorte de A. Si B est uncontrolled_stock, ce flux est la seule entrée dans son stock — quand la consommation dépasse le recyclage, unmet_demand(B) le signale.

SCHÉMA 04 — Boucle fermée A → recyclage → B → procédé → A
PROCÉDÉ control_flow_shares A EN SERVICE in_use_stock(A) FIN DE VIE collection_rate B RECYCLÉ uncontrolled_stock production A durée de vie secondary_production(B) input procédé ↑ unmet_demand(B) si stock épuisé recycling_rate

Tension recyclage / production primaire — limitation actuelle et évolution prévue

Comportement actuel

Le tp_demand généré par la consommation d'aluminium est ajouté au control_flow après le filtre de positivité :

control_flow = max(feedforward, 0) + tp_demand

Conséquence : le tp_demand est toujours honoré intégralement par la production primaire, indépendamment du stock disponible issu du recyclage. La substitution recyclé/primaire n'est pas dynamique.

Évolution planifiée — Option A

La formule cible pour les objets avec exogenous_stock (stock tampon) :

control_flow = max(feedforward + tp_demand, 0)

Avec cette formule, si le stock disponible (recyclage accumulé) couvre la demande aval, la production primaire est nulle. Si le stock ne couvre qu'une partie de la demande, le primaire complète exactement le déficit. La substitution devient dynamique.

STATUT
L'Option A nécessite un prérequis : la détection des cycles TP (pour éviter des problèmes de convergence Jacobi). Elle n'est pas encore implémentée.

Stock tampon intermédiaire — quand mettre un exogenous_stock sur un intermédiaire

CasComportementAvantage Option A
Sans exogenous_stock (just-in-time) Le tp_demand est la seule commande. Si recyclage excédentaire → accumulation infinie, aucun signal ne réduit la production primaire. Non applicable
Avec exogenous_stock non nul (stock tampon) Le feedforward pilote le stock vers une cible. Comportement identique au just-in-time dans l'état actuel. Le surplus de stock sera valorisé pour réduire la production primaire

La valeur de stock cible représente un niveau de sécurité — typiquement quelques semaines à quelques mois de consommation. Ce calibrage reste à la discrétion du modélisateur.

SYNTHÈSE — CHAPITRE IV
  • Recyclage simple : collection_rate + recycling_rate + uncontrolled_stock pour le recyclé. Le flux de recyclage est physiquement tracé.
  • Substitution dynamique primaire/secondaire : non disponible dans la version actuelle (Option A planifiée). Actuellement, la production primaire couvre toujours l'intégralité du tp_demand.
  • Stock tampon intermédiaire : exogenous_stock non nul sur un objet intermédiaire. Prérequis de l'Option A.
Chapitre V Signaux de pénurie

Un IUS négatif est le signal universel de pénurie dans Mater. Mais deux causes très différentes produisent ce symptôme identique, et les remèdes sont opposés.

SCHÉMA 05 — Deux chemins vers IUS < 0
IUS < 0 symptôme commun Cause A INSUFFICIENT INPUT Objet uncontrolled_stock épuisé Ressource / recyclé insuffisant DIAGNOSTIC outputs["unmet_demand"] non vide INTERPRÉTATION Signal physique réel — pas une erreur de modèle, c'est un résultat Cause B STOCK CIBLE INSUFFISANT Objet producible, exo_stock inférieur à la demande effective DIAGNOSTIC IUS négatif sur objet producible REMÈDE Réviser l'exogenous_stock ou accepter le plafonnement explicite

Lire les outputs de diagnostic

Trois outputs sont spécifiquement conçus pour le diagnostic de pénurie et de propagation AS. Ils sont absents si aucun événement ne les déclenche.

unmet_demand — déficits sur objets uncontrolled_stock

Présent dans outputs uniquement si au moins un objet uncontrolled_stock a un IUS < 0 à un pas de temps donné. Absent ou vide si tous les stocks uncontrolled_stock sont suffisants.

ud = mater.outputs.get("unmet_demand")
if ud is not None and not ud.empty:
    # Index   : (location, object)   — objets uncontrolled_stock en déficit
    # Colonnes : time
    # Valeurs : quantité manquante (positif = déficit)

    # Résumé : déficit cumulé sur toute la simulation, par objet
    print(ud.sum(axis=1).sort_values(ascending=False))

    # Profil temporel d'un objet spécifique
    print(ud.loc[("FR", "bauxite")])

    # Premier pas de temps en déficit
    first_shortage = (ud > 0).idxmax(axis=1)
    print(first_shortage)
INTERPRÉTATION
unmet_demand est un résultat physique, pas une erreur de modèle. Il mesure exactement de combien et quand le système ne peut plus fournir ce qui lui est demandé. Si ce résultat est inattendu, vérifier : (1) la valeur de init_non_exo_in_use_stock pour l'objet concerné, (2) que les flux de recyclage sont bien déclarés, (3) que les coefficients TP de consommation ne sont pas surestimés.

as_driven_demand_flow — demande propagée par AS

Présent uniquement si assembly_stock est déclaré et non vide. Expose la demande injectée aux objets enfants via le mécanisme AS, séparément du control_flow standard.

as_df = mater.outputs.get("as_driven_demand_flow")
if as_df is not None and not as_df.empty:
    # Index   : (location, object)   — objets enfants recevant une demande AS
    # Colonnes : time
    # Valeurs : demande AS cumulée depuis tous les parents

    # Part AS dans la demande totale d'un objet
    cf = mater.outputs["control_flow"]
    share = as_df.div(cf, fill_value=0).fillna(0)
    print(share.loc[("FR", "batterie")])

mater.log — trace d'exécution

Écrit dans <run_name>/mater.log à chaque appel à run(). Contient le nombre d'itérations Jacobi par pas de temps, les warnings uncontrolled_stock (pénuries détectées), et les éventuelles RuntimeError de divergence. Utile pour calibrer la complexité du modèle et diagnostiquer les convergences lentes.

# Extraire les lignes WARNING depuis le log
with open(f"{run_name}/mater.log") as f:
    warnings = [l for l in f if "WARNING" in l]
print("\n".join(warnings))

Insufficient input — stock non renouvelable épuisé

PropriétéDétail
CauseUn objet uncontrolled_stock consommé au-delà de son stock disponible
Signaloutputs["unmet_demand"] non vide, avec objets concernés et amplitudes par période
InterprétationSignal physique réel — ce n'est pas une erreur de modèle, c'est un résultat
Remède si non vouluAugmenter init_non_exo_in_use_stock, réviser les coefficients, ou décaler la temporalité

Stock cible insuffisant — trajectoire scénario en dessous de la demande effective

PropriétéDétail
CauseUn objet producible avec exogenous_stock inférieur à ce que la demande aval requiert (demande remontant des chaînes OC, TP ou AS)
SignalIUS négatif sur un objet producible. Pas de unmet_demand automatique dans ce cas — le signal doit être détecté manuellement
InterprétationC'est une décision de scénario (ou une erreur de calibrage) : la trajectoire exogenous_stock a été fixée en dessous de ce que les chaînes aval demandent réellement
RemèdeRéviser l'exogenous_stock pour l'aligner sur la demande effective, ou accepter explicitement le dépassement comme contrainte de scénario

Détection manuelle

# IUS négatif sur un objet producible = stock cible insuffisant
ius = mater.get("in_use_stock").groupby(level=["location","object"]).sum()
shortfall = ius[(ius < 0).any(axis=1)]
if not shortfall.empty:
    print("Stock cible insuffisant sur :", shortfall.index.tolist())

À ne pas confondre avec une contrainte de capacité de procédé

Dans Mater, la capacité de production désigne la capacité des procédés (process_max_capacity, process_reference_capacity) — par exemple la puissance installée d'une turbine ou le rendement d'un four. Cette contrainte n'est pas active dans la version actuelle : la capacité de production s'ajuste immédiatement à la demande, sans plafonnement. Un procédé peut donc toujours produire exactement ce qui lui est demandé, quelle que soit l'ampleur du signal.

LIMITATION DOCUMENTÉE — capacité de procédé
Le plafonnement de la production par process_max_capacity n'est pas encore implémenté. La capacité s'ajuste de façon illimitée à la demande. Un IUS négatif sur un objet producible traduit donc toujours un stock cible mal calibré, jamais un plafond de procédé atteint. Cette fonctionnalité est prévue dans une version future.

Plusieurs inputs, un seul qui manque

Quand un procédé consomme plusieurs inputs (B et C) et que C s'épuise, le procédé continue de tourner dans le modèle. B est toujours consommé normalement. Seul C tire son IUS en négatif et génère unmet_demand(C).

LIMITATION DOCUMENTÉE
Il n'y a pas de rétropropagation : le manque de C ne ralentit pas le procédé, ne réduit pas la consommation de B, ne réduit pas la production de A. La backpropagation des contraintes d'input est hors scope.

unmet_demand(C) indique la quantité de C qui aurait dû exister pour que le procédé tourne comme simulé — c'est un écart entre la trajectoire modélisée et ce que le système physique peut fournir.
SYNTHÈSE — CHAPITRE V
  • IUS < 0 sur uncontrolled_stock → insufficient input → signal physique via unmet_demand.
  • IUS < 0 sur producible → insufficient capacity → plafond exo_stock trop bas.
  • Input manquant : le procédé continue, les autres inputs sont consommés. Pas de backpropagation.
Chapitre VI Cas limites et hors scope

Cas documentés mais contre-nature

Co-produit commandé par sa propre demande

Si la demande en C (co-produit de P) explose mais que celle en A (produit principal) s'effondre, le modèle ne tire pas la production de A pour satisfaire C. C est non commandable — aucune rétroaction vers P. unmet_demand(C) signale le déficit, mais sans action corrective automatique.

Maintenance de parc existant

Remplacer des composants B en fin de vie dans des A encore en service (hors nouvelle production) n'est pas natif. Contournement : calculer hors modèle exogenous_stock(B) = IUS(A) × taux_remplacement et l'injecter.

Cascade OC profonde avec IUSEM

La fermeture transitive corrige la consommation, mais in_use_stock_embedded_material n'utilise que les liens OC directs. Les matériaux terminaux d'une cascade longue (A→B→C→D) n'apparaissent pas directement dans IUSEM(A) — recalcul hors modèle nécessaire.


Hors scope — évolutions planifiées

FonctionnalitéDescriptionStatut
Option A — substitution dynamique Formule max(feedforward + tp_demand, 0) pour objets avec stock tampon Planifiée — conditionnée à détection cycles TP
Détection statique des cycles TP Graphe de dépendances à l'initialisation, warning si cycle implique exogenous_stock Planifiée
Backpropagation de contrainte Si un input manque, propager la réduction de production vers le procédé et ses autres inputs Non prévu à court terme
Chapitre VII Usage intensif — Monte Carlo et analyse de sensibilité

Impact de la profondeur des chaînes

Le nombre d'itérations Jacobi par pas de temps est déterminé par la profondeur des chaînes, pas leur largeur :

TopologieItérations JacobiImpact
N objets en parallèle consommés par A2 — quelle que soit NNul
N objets en chaîne linéaireN + 1Linéaire avec N
Profondeur 15, T=100 pas annuels1 600 passes totalesvs 100 sans chaîne
RECOMMANDATION
Pour les modèles destinés à tourner en masse, aplatir les chaînes quand c'est possible : fusionner des intermédiaires, utiliser OC Régime 1 plutôt que Régime 2 quand l'observabilité des intermédiaires n'est pas requise.

Désactivation des diagnostics

Le calcul de unmet_demand est post-boucle (une seule passe pandas en fin de simulation) : son coût unitaire est faible. Un flag compute_diagnostics=False est prévu pour désactiver ce calcul en mode production intensive. En attendant, le coût reste négligeable devant celui de la boucle temporelle.

Recommandations générales pour les runs en masse

Annexe A Trade — échanges entre localisations

Mécanique

_control_flow_trade_inverse_computation modifie les control_flow pour allouer la production entre localisations selon une matrice d'échange. Les flux échangés apparaissent dans traded_control_flow. Le calcul du trade est effectué à chaque pas de temps, avant la boucle Jacobi.

Interaction avec les chaînes

La demande issue d'une localisation A peut être satisfaite par la production d'une localisation B. Les tp_demand remontés dans les chaînes amont sont générés dans la localisation productrice (B), pas dans la localisation consommatrice (A). Il faut s'assurer que les chaînes amont (CFS, TP) sont correctement déclarées pour la localisation productrice.

Interaction avec uncontrolled_stock

Le statut uncontrolled_stock est déclaré par tuple (location, object). Un objet peut être uncontrolled_stock dans une localisation et producible (importé via trade) dans une autre. Le trade ne contourne pas le flag uncontrolled_stock — si l'objet est uncontrolled_stock dans la localisation productrice, il n'y aura pas de production commandée, même pour l'export.

LIMITATION DOCUMENTÉE
Le mécanisme de trade actuel ne modélise pas les contraintes de capacité de transport, les coûts d'échange, ou les délais. C'est une allocation linéaire selon des parts définies a priori.