Verbundene Agents in Rhai lesen

Nutzen Sie get_connected, sum_prop und count_prop, um Agents den Zustand voneinander über den Graphen lesen zu lassen — so koordinieren sich Metapad-Agents.

In Metapad koordinieren sich Agents über den Graphen selbst — sie lesen den Zustand der jeweils anderen über ihre Links. Es gibt kein Message-Passing, kein Ereignissystem; nur direkte Lesezugriffe. So bleibt jede Interaktion im Modell sichtbar, und Simulationen lassen sich leicht debuggen, weil alles Wichtige auf dem Diagramm steht.

get_connected — der grundlegende Durchlauf

let teammates = get_connected(agent, "belongs_to");

get_connected liefert ein Array von Agents zurück, die vom aktuellen Agent über einen bestimmten Beziehungstyp erreichbar sind. Es läuft den Link in beide Richtungen ab, sodass derselbe Aufruf funktioniert, egal ob Ihr Agent die Quelle oder das Ziel der Beziehung ist. Sobald Sie ein Array haben, können Sie alles tun, was die Array-Methoden von Rhai unterstützen:

teammates.len()                                   // zählen
teammates.filter(|a| a.get_prop("role") == "Eng") // Teilmenge

sum_prop und count_prop — gängige Aggregationen

Für die beiden häufigsten Muster — das Summieren einer numerischen Eigenschaft über das Array oder das Zählen von Agents mit einem bestimmten Wert — stellt Metapad Abkürzungen bereit:

sum_prop(get_connected(agent, "has_member"), "salary")
count_prop(get_connected(agent, "has_member"), "state", "Active")

Genau das brauchen Sie für KPI-Rollups, Personalstärke, Gesamtkosten, Gesamtumsatz — überall dort, wo ein übergeordneter Agent seine untergeordneten zusammenfassen muss.

Den Durchlauf an eine Variable zu binden und zweimal zu aggregieren liest sich besser als den Aufruf zu wiederholen — und kostet nichts zusätzlich:

let members = get_connected(agent, "has_member");
sum_prop(members, "salary") / count_prop(members, "state", "Active")

Wenn ein Knotentyp verschiedene Arten von Instanzen bedient

Formeln liegen auf dem Knotentyp, also führt jede Instanz dieses Typs dieselbe Formel aus — auch dann, wenn die Instanzen unterschiedlich verbunden sind. Ein Berichtstyp kann Agents je Einzelobjekt haben, die ihr Objekt über dokumentiert lesen, plus einen Aggregat-Agent, der alle über aggregiert liest.

Eine Beziehung abzufragen, die ein Agent nicht hat, ist kein Fehler: get_connected liefert ein leeres Array, sum_prop darauf ergibt 0, und es wird keine Diagnose erzeugt. Die einfachste Formel deckt daher beide Fälle ab und lässt die abwesende Seite nichts beitragen:

sum_prop(get_connected(agent, "dokumentiert"), "volumen")
  + sum_prop(get_connected(agent, "aggregiert"), "volumen")

(Ein Beziehungsname, der zu keinem Typ in Ihrem Metamodell passt, wird gemeldet — das ist ein Tippfehler und kein legitim fehlender Link.)

Wenn die beiden Fälle wirklich unterschiedliche Logik brauchen und keine Summe, verzweigen Sie mit has_relationship:

if has_relationship(agent, "aggregiert") {
    sum_prop(get_connected(agent, "aggregiert"), "volumen") * agent.get_prop("rollup_faktor")
} else {
    sum_prop(get_connected(agent, "dokumentiert"), "volumen")
}

Und wenn sich nur der Name unterscheidet, heben Sie ihn in eine Variable, statt den Rumpf zu duplizieren — solange jeder zugewiesene Wert eine einfache Zeichenkette ist, löst Metapad die Abhängigkeit weiterhin auf:

let rel = "dokumentiert";
if agent.get_prop("ist_aggregat") { rel = "aggregiert"; }
sum_prop(get_connected(agent, rel), "volumen")

count_connected(agent, "rel") liefert die Anzahl der Links — für Fälle wie "erst ab N Objekten aggregieren".

Wann finden verbundene Lesezugriffe statt? (T, nicht T−1)

Eine Formel ohne Offset liest die Werte ihrer Nachbarn im aktuellen Zeitschritt. Metapad ermittelt die Abhängigkeiten zwischen allen berechneten Eigenschaften und wertet sie in dieser Reihenfolge aus: Wenn die personnel_costs eines Teams laufen, sind die salary-Werte seiner Mitglieder für diesen Schritt bereits berechnet.

// Die Gehälter der Mitglieder IN DIESEM Zeitschritt
sum_prop(get_connected(agent, "has_member"), "salary")

Auf den Wert des vorherigen Schritts fällt die Engine nur dort zurück, wo diese Reihenfolge unmöglich ist — also wo die Eigenschaften tatsächlich einen Zyklus bilden. Sie brauchen -1-Offsets also nicht, um "mögliche Zyklen aufzubrechen": Setzen Sie einen Offset, wenn Ihr Modell eine echte Verzögerung hat, und lassen Sie ansonsten die Reihenfolge ihre Arbeit tun. (Vorsorglich gesetzte Offsets sind ein häufiger Weg, einen Schritt — oder ein ganzes simuliertes Jahr — Verzögerung einzubauen, die es im realen System nicht gibt.)

Act-Methoden sind die bewusste Ausnahme: Da jeder set_prop-Schreibvorgang erst angewendet wird, nachdem alle Agents gelaufen sind, sieht ein Skript, das eine von einer Act-Methode geschriebene Eigenschaft eines anderen Agents liest, den Wert des vorherigen Schritts. Siehe Act-Methoden schreiben.

Die Vergangenheit lesen

Manche Prozesse haben natürliche Zeitverzögerungen — eine Fabrik versendet heute, das Lager empfängt morgen. Die Drei-Argument-Form von get_connected lässt eine Formel sehen, wie ihre Nachbarn vor einem oder mehreren Zeitschritten aussahen:

// Was die verbundenen Fabriken vor einem Tick versendet haben
sum_prop(get_connected(agent, "supplied_by", -1), "shipped")

Das ist das berühmte Beergame-Muster: Die Lagernachfrage zum Zeitpunkt T zieht aus den Fabriklieferungen bei T−1 und modelliert die Lieferzeit als strukturelles Merkmal des Modells, statt als etwas, das Sie zu kodieren nicht vergessen dürfen.

Offset-Regeln — und welche Formen schnell sind

Ein Offset muss eine endliche Zahl ≤ 0 sein (Gleitkommazahlen werden auf den nächsten ganzen Schritt gerundet, -9.7 bedeutet also zehn Schritte zurück). Ein unendlicher, NaN- oder positiver Offset liefert nichts und erzeugt eine Diagnose, statt zu raten — ein Verzögerungsparameter, der das Vorzeichen wechselt oder durch Null teilt, soll sich melden und Ihnen nicht stillschweigend eine plausible falsche Reihe liefern.

Der Offset darf ein Ausdruck sein — so steuern Sie eine Verzögerung über einen Slider oder eine Eigenschaft je Agent. Zwei Formen bleiben auf dem schnellen Pfad, auf dem Metapad genau den einen benötigten Vergangenheitswert pro Zelle holt:

sum_prop(get_connected(agent, "supplied_by", -2), "shipped")                      // Literal
sum_prop(get_connected(agent, "supplied_by", agent.get_prop("delay")), "shipped") // Eigenschaft

(Beide funktionieren auch über eine let-Bindung.) Jeder andere Ausdruck — Arithmetik auf einer Eigenschaft, eine Bedingung, eine berechnete lokale Variable — liefert weiterhin das richtige Ergebnis, aber die Engine kann nicht mehr im Vorfeld erkennen, welche Vergangenheitswerte gebraucht werden, und baut deshalb für jede Zelle den kompletten Verlauf des Agents auf. Im Diagnose-Tab erscheint eine Warnung über "unaufgelöste dynamische Referenzen", und der Aufwand wächst mit dem Quadrat der Anzahl der Zeitschritte — bei langen Läufen deutlich spürbar:

// Langsam: Der Offset ist Arithmetik, der Verlauf lässt sich nicht vorab auflösen
get_connected(agent, "dokumentiert", -(agent.get_prop("lag_monate") / 12.0))

Die Abhilfe ist mechanisch: Berechnen Sie den Offset in einer eigenen Eigenschaft (lag_schritte, selbst eine Formel) und übergeben Sie diese Eigenschaft.

Offsets addieren sich, wenn Sie einen einzelnen Agent aus einem verschobenen Array lesen: get_connected(agent, "r", -1) liefert die Nachbarn im Zustand von T−1, ein m.get_prop("x", -1) darauf liest also T−2.

Ein ausgearbeitetes Beispiel: Limits to Growth

Stellen Sie sich eine Population vor, deren Wachstum durch eine endliche Tragfähigkeit begrenzt ist. Das Intro-Modell hat genau dieses Setup. Der Wachstumsfaktor wird durch einen verbundenen Resource Adequacy-Knoten angepasst:

// Wachstumsrate, angepasst durch die verbundene Ressourcenverfügbarkeit
let resources = get_connected(agent, "limit_growth");
let resource_factor = if resources.len() > 0 {
    resources[0].get_prop("value")
} else {
    1.0
};
agent.get_prop("growth_factor") * resource_factor

Dieselbe Formel funktioniert, egal ob es einen Ressourcenknoten oder mehrere gibt — und die Ressourcenerschöpfung verlangsamt das Wachstum automatisch, ohne dass jemand ein explizites "Ressource erschöpft"-Ereignis verdrahten muss.

Tipps

  • Halten Sie den Graphen ehrlich. Wenn sich zwei Agents koordinieren müssen, zeichnen Sie den Link. Versteckte Koordination über Namenskonventionen oder externen Zustand ist viel schwerer nachzuvollziehen.
  • Nutzen Sie Aggregationen für Aggregationen. sum_prop und count_prop sind klarer als eine manuelle for-Schleife, wenn Sie summieren oder zählen. Greifen Sie aber zur Schleife oder zum Index, wenn Sie genau das meinen — sowohl for m in get_connected(agent, "r") als auch resources[0].get_prop("value") werden genauso effizient aufgelöst, solange das Array von get_connected kommt.
  • Nutzen Sie Offsets für echte Verzögerungen, nicht um zirkuläre Abhängigkeiten zu übertünchen — und halten Sie sie in einer der beiden schnellen Formen oben.

Nächste Schritte

  • Eigenschaftsformeln schreiben — wo diese Lesezugriffe typischerweise leben
  • Act-Methoden schreiben — wenn der Lesezugriff eine Aktualisierung mehrerer Eigenschaften anstößt
  • Simulationsfehler und -warnungen diagnostizieren — wenn Lesezugriffe überraschende Werte liefern
  • Rhai-Funktionsreferenz — vollständige Signaturen für jeden Helfer

Dieser Inhalt wurde kollaborativ mit KI erstellt.