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_propundcount_propsind klarer als eine manuellefor-Schleife, wenn Sie summieren oder zählen. Greifen Sie aber zur Schleife oder zum Index, wenn Sie genau das meinen — sowohlfor m in get_connected(agent, "r")als auchresources[0].get_prop("value")werden genauso effizient aufgelöst, solange das Array vonget_connectedkommt. - 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
Live ausprobieren
Öffnen Sie diese funktionierenden Beispiele in der App und erkunden Sie sie selbst.
Dieser Inhalt wurde kollaborativ mit KI erstellt.