From 84e028bb4791bafd553de5a724b8aabbcf772025 Mon Sep 17 00:00:00 2001 From: Amy Liffey Date: Wed, 1 Jul 2026 16:58:16 +0200 Subject: [PATCH 01/25] chore: new test --- build.xml | 1 + src/example/README.md | 104 +++++++++++++++++++++++++++ src/example/SionMobilityOption.java | 56 +++++++++++++++ src/example/SionTIBDecision.java | 98 +++++++++++++++++++++++++ src/example/SionTask.java | 15 ++++ src/example/SionToSierreExample.java | 74 +++++++++++++++++++ test/example/SionToSierreTest.java | 75 +++++++++++++++++++ 7 files changed, 423 insertions(+) create mode 100644 src/example/README.md create mode 100644 src/example/SionMobilityOption.java create mode 100644 src/example/SionTIBDecision.java create mode 100644 src/example/SionTask.java create mode 100644 src/example/SionToSierreExample.java create mode 100644 test/example/SionToSierreTest.java diff --git a/build.xml b/build.xml index a29a380..043935d 100644 --- a/build.xml +++ b/build.xml @@ -385,6 +385,7 @@ + diff --git a/src/example/README.md b/src/example/README.md new file mode 100644 index 0000000..579f6f9 --- /dev/null +++ b/src/example/README.md @@ -0,0 +1,104 @@ +# Sion → Sierre worked example (TIB) + +A minimal, self-contained worked example of Triandis' Theory of Interpersonal +Behaviour (TIB) running on beddem_simulator's own decision engine +(`framework.agent.reasoning.*`). It reproduces the running example from the +BedDeM paper (Table 1): an **18 km trip from Sion to Sierre** with three modes — +**car, train, bike**. + +Unlike `StandardDummyAgent` (where only *time* and *cost* are implemented and +the other determinants are stubbed), every TIB determinant here has a real +value, so **all input weights influence the outcome**. + +## Files + +| File | Role | +|------|------| +| `SionMobilityOption.java` | one mobility alternative + its 8 determinant inputs | +| `SionTIBDecision.java` | TIB decision component wiring all determinants onto the engine | +| `SionToSierreExample.java` | builds the Table 1 scenario; has a `main()` and `euByService()` | +| `SionTask.java` | minimal task to satisfy the framework contract | +| `../../test/example/SionToSierreTest.java` | the JUnit 4 test | + +## Cost semantics (lower = better) + +Every determinant is expressed as a **cost**: a lower value means a more +preferred option (price in CHF, time in hours, and ranks 1 = best … 3 = worst +for norm/role/self/comfort). The engine normalises and weights these up the TIB +tree, and the agent chooses the option with the **lowest** aggregated expected +utility (EU). This matches beddem_simulator's own `pickBestOpt`, which selects +the minimum. + +Expected EU (paper Table 1): + +| Mode | EU (lower = better) | +|------|---------------------| +| **Car** | **≈ 1.13** ← chosen | +| Train | ≈ 3.07 | +| Bike | ≈ 4.81 | + +Ranking: **Car < Train < Bike**, so the agent picks the **car**. + +## How to run the test + +### Option A — standalone, no Repast (quickest) + +The example depends only on `framework.*` + `example.*` and JUnit 4, so it +compiles without Repast/Eclipse. From the repository root: + +```bash +# Point these at a working JDK and a JUnit 4 jar (+ hamcrest). +# JUnit 4.13.2 and hamcrest ship with the Repast/Eclipse plugins, e.g.: +# org.junit_4.13.2.v20230809-1000.jar, org.hamcrest.core_2.2.0*.jar +JUNIT=/path/to/junit-4.13.2.jar +HAMCREST=/path/to/hamcrest-core.jar + +mkdir -p out +# compile the engine, the example, and the test +javac -d out \ + $(find src/framework src/example -name '*.java') \ + -cp "$JUNIT:$HAMCREST" test/example/SionToSierreTest.java + +# run the JUnit 4 test +java -cp "out:$JUNIT:$HAMCREST" org.junit.runner.JUnitCore example.SionToSierreTest + +# or just print the EU table without JUnit: +java -cp out example.SionToSierreExample +``` + +Expected `main()` output: + +``` +=== Sion -> Sierre (18 km) | EU, lower = better === + Car EU = 1.1268 + Train EU = 3.0675 + Bike EU = 4.8056 +Chosen (lowest EU): Car +``` + +(EU values are shown to 4 dp; the test asserts them to ±0.05.) + +### Option B — with ant / Repast (the CI pipeline) + +The test is registered in `build.xml`'s `run-test` target, so it runs with the +existing pipeline. This path compiles the whole project, so it needs Repast +Simphony + Eclipse configured (see the top-level `README.md`): + +```bash +ant build "-DECLIPSE_HOME=/path/to/eclipse" +ant run-test "-DECLIPSE_HOME=/path/to/eclipse" +``` + +Results are written as JUnit XML under `junit/`. + +### Option C — Eclipse + +Right-click `test/example/SionToSierreTest.java` → **Run As → JUnit Test**. + +## What the test checks + +- all three modes are evaluated; +- the agent picks the option with the **lowest** EU (car); +- the ranking is **Car < Train < Bike**; +- the EU values match the paper's Table 1 (±0.05); +- all EU values are positive and finite. diff --git a/src/example/SionMobilityOption.java b/src/example/SionMobilityOption.java new file mode 100644 index 0000000..d4c857c --- /dev/null +++ b/src/example/SionMobilityOption.java @@ -0,0 +1,56 @@ +package example; + +import framework.concept.Option; + +/** + * A single mobility alternative for the Sion -> Sierre worked example + * (BedDeM paper, Table 1). It carries the raw determinant inputs for one + * transport mode. + * + *

All values follow the engine's cost convention: lower = more + * preferred. Ranks (norm/role/self/comfort) use 1 = best .. 3 = worst; + * price is in CHF, time in hours, frequency and facilitating are the raw + * Table 1 values. This keeps the whole example internally consistent with + * beddem_simulator's selection rule, which picks the option with the lowest + * aggregated expected utility.

+ */ +public class SionMobilityOption implements Option { + + private final String service; + private final double price; // CHF, lower = cheaper = better + private final double time; // hours, lower = faster = better + private final double norm; // social-norm rank, 1 = best + private final double role; // environmental rank, 1 = greenest = best + private final double selfConcept; // identity rank, 1 = best + private final double comfort; // (dis)comfort rank, 1 = best + private final double freq; // habit, lower = more habitual = better + private final double facilitating; // ease of access, lower = easier = better + + public SionMobilityOption(String service, double price, double time, double norm, double role, double selfConcept, + double comfort, double freq, double facilitating) { + this.service = service; + this.price = price; + this.time = time; + this.norm = norm; + this.role = role; + this.selfConcept = selfConcept; + this.comfort = comfort; + this.freq = freq; + this.facilitating = facilitating; + } + + public String getService() { return this.service; } + public double getPrice() { return this.price; } + public double getTime() { return this.time; } + public double getNorm() { return this.norm; } + public double getRole() { return this.role; } + public double getSelfConcept() { return this.selfConcept; } + public double getComfort() { return this.comfort; } + public double getFreq() { return this.freq; } + public double getFacilitating() { return this.facilitating; } + + @Override + public String toString() { + return this.service; + } +} diff --git a/src/example/SionTIBDecision.java b/src/example/SionTIBDecision.java new file mode 100644 index 0000000..88fbdf3 --- /dev/null +++ b/src/example/SionTIBDecision.java @@ -0,0 +1,98 @@ +package example; + +import java.util.Map; +import java.util.Set; + +import framework.agent.core.DecisionComponent; +import framework.agent.reasoning.Determinant; +import framework.agent.reasoning.LeafDeterminant; +import framework.agent.reasoning.ParentDeterminant; +import framework.agent.reasoning.TIBModel; +import framework.concept.Option; +import framework.concept.Task; + +/** + * TIB decision component for the Sion -> Sierre worked example, built on + * beddem_simulator's own reasoning engine ({@link TIBModel} / + * {@link ParentDeterminant} / {@link LeafDeterminant}). + * + *

Unlike {@code StandardDummyAgent} (where only time and cost were + * implemented and the other determinants were stubbed), every TIB determinant + * here has a real ranking function, so all input weights influence the + * outcome. Each leaf returns a cost-like value where lower = better; + * the engine normalises and weights these up the tree, and the option with the + * lowest aggregated expected utility is the preferred one.

+ * + *

Scenario data and weights come from the BedDeM paper, Table 1 (an 18 km + * trip from Sion to Sierre with three modes: car, train, bike).

+ */ +public class SionTIBDecision extends TIBModel implements DecisionComponent { + + public SionTIBDecision(Determinant belief, Determinant evaluation, Determinant norm, Determinant role, + Determinant selfConcept, Determinant emotion, Determinant facilitatingCond, Determinant freq, + double attitudeWeight, double socialWeight, double affectWeight, double intentionWeight, + double habitWeight) { + super(belief, evaluation, norm, role, selfConcept, emotion, facilitatingCond, freq, attitudeWeight, socialWeight, + affectWeight, intentionWeight, habitWeight); + } + + @Override + public Map> evaluateOptions(Set