From 381d48ebfab198c8bd9ac3382b6136cc8f134811 Mon Sep 17 00:00:00 2001 From: Toru Seo <34780089+toruseo@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:44:13 +0900 Subject: [PATCH] Add test_verification_route_choice.py etc. --- .../workflows/run-verifications_random.yml | 24 + tests/test_verification_route_choice.py | 451 ++++++++++++++++++ tests/test_verification_straight_road.py | 82 +++- 3 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/run-verifications_random.yml create mode 100644 tests/test_verification_route_choice.py diff --git a/.github/workflows/run-verifications_random.yml b/.github/workflows/run-verifications_random.yml new file mode 100644 index 0000000..11d96b9 --- /dev/null +++ b/.github/workflows/run-verifications_random.yml @@ -0,0 +1,24 @@ +name: Run Python verifications (flaky due to random numbers) + +on: + push: + pull_request: + workflow_dispatch: + schedule: + - cron: '0 6 * * 1' + +jobs: + run-verifications-random: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install uxsim and dependencies + run: pip install . + - name: Install pytest other dependencies + run: pip install pytest pytest-rerunfailures setuptools + - name: Run verifications with pytest + run: pytest tests\test_verification_route_choice.py --durations=0 -v diff --git a/tests/test_verification_route_choice.py b/tests/test_verification_route_choice.py new file mode 100644 index 0000000..604935a --- /dev/null +++ b/tests/test_verification_route_choice.py @@ -0,0 +1,451 @@ +""" +This script verifies whether UXsim outputs reasonable solutions for two route networks in various configurations. +Note that it uses random numbers for rouce choice behavior, so the results may vary slightly between runs. +""" + +import pytest +from uxsim import * +import pandas as pd + +def equal_tolerance(val, check, rel_tol=0.1, abs_tol=0.0): + if check == 0 and abs_tol == 0: + abs_tol = 0.1 + return abs(val - check) <= abs(check*rel_tol) + abs_tol + +@pytest.mark.flaky(reruns=5) +def test_2route_equal(): + tt1s = [] + tt2s = [] + vol1s = [] + vol2s = [] + ttas = [] + + for i in range(10): + W = World( + name="", + deltan=5, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid2", -1, 1) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=1000, free_flow_speed=20, jam_density=0.2) + link12 = W.addLink("link12", "mid1", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid2", length=1000, free_flow_speed=20, jam_density=0.2) + link22 = W.addLink("link22", "mid2", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 1500, 0.8) + + W.exec_simulation() + + W.analyzer.print_simple_stats() + + #W.analyzer.time_space_diagram_traj_links([link11, link12]) + #W.analyzer.time_space_diagram_traj_links([link21, link22]) + W.analyzer.basic_analysis() + + df = W.analyzer.link_to_pandas() + + tt1 = df[df["link"].isin(("link11", "link12"))]["average_travel_time"].sum() + tt2 = df[df["link"].isin(("link21", "link22"))]["average_travel_time"].sum() + vol1 = df[df["link"].isin(("link11", "link12"))]["traffic_volume"].sum()/2 + vol2 = df[df["link"].isin(("link21", "link22"))]["traffic_volume"].sum()/2 + + df2 = W.analyzer.od_to_pandas() + tta = df2["average_travel_time"] + + tt1s.append(tt1) + tt2s.append(tt2) + vol1s.append(vol1) + vol2s.append(vol2) + ttas.append(tta) + + print(f"{np.average(tt1s) = }, {np.average(tt2s) = }, {np.average(vol1s) = }, {np.average(vol2s) = }, {np.average(tt1s) = }, {np.average(ttas) = }") + + assert equal_tolerance(np.average(tt1s), np.average(tt2s)) + assert equal_tolerance(np.average(vol1s), np.average(vol2s)) + assert equal_tolerance(np.average(tt1s), 100) + assert equal_tolerance(np.average(ttas), 100) + assert equal_tolerance(np.average(vol1s)+np.average(vol2s), 1200) + +@pytest.mark.flaky(reruns=5) +def test_2route_equal_deltan1(): + tt1s = [] + tt2s = [] + vol1s = [] + vol2s = [] + ttas = [] + + for i in range(5): + W = World( + name="", + deltan=1, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid2", -1, 1) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=1000, free_flow_speed=20, jam_density=0.2) + link12 = W.addLink("link12", "mid1", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid2", length=1000, free_flow_speed=20, jam_density=0.2) + link22 = W.addLink("link22", "mid2", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 1500, 0.8) + + W.exec_simulation() + + W.analyzer.print_simple_stats() + + #W.analyzer.time_space_diagram_traj_links([link11, link12]) + #W.analyzer.time_space_diagram_traj_links([link21, link22]) + W.analyzer.basic_analysis() + + df = W.analyzer.link_to_pandas() + + tt1 = df[df["link"].isin(("link11", "link12"))]["average_travel_time"].sum() + tt2 = df[df["link"].isin(("link21", "link22"))]["average_travel_time"].sum() + vol1 = df[df["link"].isin(("link11", "link12"))]["traffic_volume"].sum()/2 + vol2 = df[df["link"].isin(("link21", "link22"))]["traffic_volume"].sum()/2 + + df2 = W.analyzer.od_to_pandas() + tta = df2["average_travel_time"] + + tt1s.append(tt1) + tt2s.append(tt2) + vol1s.append(vol1) + vol2s.append(vol2) + ttas.append(tta) + + print(f"{np.average(tt1s) = }, {np.average(tt2s) = }, {np.average(vol1s) = }, {np.average(vol2s) = }, {np.average(tt1s) = }, {np.average(ttas) = }") + + assert equal_tolerance(np.average(tt1s), np.average(tt2s)) + assert equal_tolerance(np.average(vol1s), np.average(vol2s)) + assert equal_tolerance(np.average(tt1s), 100) + assert equal_tolerance(np.average(ttas), 100) + assert equal_tolerance(np.average(vol1s)+np.average(vol2s), 1200) + +@pytest.mark.flaky(reruns=5) +def test_2route_equal_iterative_exec(): + tt1s = [] + tt2s = [] + vol1s = [] + vol2s = [] + ttas = [] + + for i in range(10): + W = World( + name="", + deltan=5, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid2", -1, 1) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=1000, free_flow_speed=20, jam_density=0.2) + link12 = W.addLink("link12", "mid1", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid2", length=1000, free_flow_speed=20, jam_density=0.2) + link22 = W.addLink("link22", "mid2", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 1500, 0.8) + + while W.check_simulation_ongoing(): + W.exec_simulation(duration_t=random.randint(50, 200)) + + W.analyzer.print_simple_stats() + + #W.analyzer.time_space_diagram_traj_links([link11, link12]) + #W.analyzer.time_space_diagram_traj_links([link21, link22]) + W.analyzer.basic_analysis() + + df = W.analyzer.link_to_pandas() + + tt1 = df[df["link"].isin(("link11", "link12"))]["average_travel_time"].sum() + tt2 = df[df["link"].isin(("link21", "link22"))]["average_travel_time"].sum() + vol1 = df[df["link"].isin(("link11", "link12"))]["traffic_volume"].sum()/2 + vol2 = df[df["link"].isin(("link21", "link22"))]["traffic_volume"].sum()/2 + + df2 = W.analyzer.od_to_pandas() + tta = df2["average_travel_time"] + + tt1s.append(tt1) + tt2s.append(tt2) + vol1s.append(vol1) + vol2s.append(vol2) + ttas.append(tta) + + print(f"{np.average(tt1s) = }\n{np.average(tt2s) = }\n{np.average(vol1s) = }\n{np.average(vol2s) = }\n{np.average(tt1s) = }\n{np.average(ttas) = }") + + assert equal_tolerance(np.average(tt1s), np.average(tt2s)) + assert equal_tolerance(np.average(vol1s), np.average(vol2s)) + assert equal_tolerance(np.average(tt1s), 100) + assert equal_tolerance(np.average(ttas), 100) + assert equal_tolerance(np.average(vol1s)+np.average(vol2s), 1200) + +@pytest.mark.flaky(reruns=5) +def test_2route_equal_but_different_structure(): + tt1s = [] + tt2s = [] + vol1s = [] + vol2s = [] + ttas = [] + + for i in range(10): + W = World( + name="", + deltan=5, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid21", -1, 1.5) + W.addNode("mid22", -1, 0.5) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=2000, free_flow_speed=20, jam_density=0.2) + link12 = W.addLink("link12", "mid1", "dest", length=2000, free_flow_speed=10, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid21", length=1000, free_flow_speed=10, jam_density=0.2) + link22 = W.addLink("link22", "mid21", "mid22", length=1000, free_flow_speed=10, jam_density=0.2) + link23 = W.addLink("link22", "mid22", "dest", length=2000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 1500, 0.8) + + W.exec_simulation() + + W.analyzer.print_simple_stats() + + W.analyzer.basic_analysis() + + df = W.analyzer.link_to_pandas() + tt1 = df[df["link"].isin(("link11", "link12"))]["average_travel_time"].sum() + tt2 = df[df["link"].isin(("link21", "link22", "link23"))]["average_travel_time"].sum() + vol1 = df[df["link"].isin(("link11", "link12"))]["traffic_volume"].sum()/2 + vol2 = df[df["link"].isin(("link21", "link22", "link23"))]["traffic_volume"].sum()/3 + + df2 = W.analyzer.od_to_pandas() + tta = df2["average_travel_time"] + + tt1s.append(tt1) + tt2s.append(tt2) + vol1s.append(vol1) + vol2s.append(vol2) + ttas.append(tta) + + print(f"{np.average(tt1s) = }, {np.average(tt2s) = }, {np.average(vol1s) = }, {np.average(vol2s) = }, {np.average(tt1s) = }, {np.average(ttas) = }") + + assert equal_tolerance(np.average(tt1s), np.average(tt2s)) + assert equal_tolerance(np.average(vol1s), np.average(vol2s)) + assert equal_tolerance(np.average(tt1s), 300) + assert equal_tolerance(np.average(ttas), 300) + assert equal_tolerance(np.average(vol1s)+np.average(vol2s), 1200) + +@pytest.mark.flaky(reruns=5) +def test_2route_one_is_too_short(): + tt1s = [] + tt2s = [] + vol1s = [] + vol2s = [] + ttas = [] + + for i in range(3): + W = World( + name="", + deltan=5, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid2", -1, 1) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=1000, free_flow_speed=20, jam_density=0.2) + link12 = W.addLink("link12", "mid1", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid2", length=2000, free_flow_speed=20, jam_density=0.2) + link22 = W.addLink("link22", "mid2", "dest", length=2000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 1500, 0.8) + + W.exec_simulation() + + W.analyzer.print_simple_stats() + + #W.analyzer.time_space_diagram_traj_links([link11, link12]) + #W.analyzer.time_space_diagram_traj_links([link21, link22]) + W.analyzer.basic_analysis() + + df = W.analyzer.link_to_pandas() + + tt1 = df[df["link"].isin(("link11", "link12"))]["average_travel_time"].sum() + tt2 = df[df["link"].isin(("link21", "link22"))]["average_travel_time"].sum() + vol1 = df[df["link"].isin(("link11", "link12"))]["traffic_volume"].sum()/2 + vol2 = df[df["link"].isin(("link21", "link22"))]["traffic_volume"].sum()/2 + + df2 = W.analyzer.od_to_pandas() + tta = df2["average_travel_time"] + + tt1s.append(tt1) + tt2s.append(tt2) + vol1s.append(vol1) + vol2s.append(vol2) + ttas.append(tta) + + print(f"{np.average(tt1s) = }\n{np.average(tt2s) = }\n{np.average(vol1s) = }\n{np.average(vol2s) = }\n{np.average(tt1s) = }\n{np.average(ttas) = }") + + assert equal_tolerance(np.average(tt1s), 100) + assert equal_tolerance(np.average(vol1s), 1200) + assert equal_tolerance(np.average(vol2s), 0, abs_tol=100) + assert equal_tolerance(np.average(ttas), 100) + assert equal_tolerance(np.average(vol1s)+np.average(vol2s), 1200) + +@pytest.mark.flaky(reruns=5) +def test_2route_one_is_too_short_situation_changes_during_simulation(): + vol1_es = [] + vol2_es = [] + vol1_ls = [] + vol2_ls = [] + + for i in range(10): + W = World( + name="", + deltan=5, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid2", -1, 1) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=1000, free_flow_speed=20, jam_density=0.2) + link12 = W.addLink("link12", "mid1", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid2", length=2000, free_flow_speed=20, jam_density=0.2) + link22 = W.addLink("link22", "mid2", "dest", length=2000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 1500, 0.8) + + W.exec_simulation(duration_t=500) + link11.free_flow_speed = 5 + link12.free_flow_speed = 5 + W.exec_simulation() + + + W.analyzer.print_simple_stats() + + W.analyzer.basic_analysis() + + W.analyzer.compute_edie_state() + + vol1_es.append(link11.q_mat[:8,5].sum()*link11.edie_dt) + vol1_ls.append(link11.q_mat[8:,5].sum()*link11.edie_dt) + vol2_es.append(link21.q_mat[:8,5].sum()*link21.edie_dt) + vol2_ls.append(link21.q_mat[8:,5].sum()*link21.edie_dt) + + print(f"{np.average(vol1_es) = }\n{np.average(vol1_ls) = }\n{np.average(vol2_es) = }\n{np.average(vol2_ls) = }") + assert equal_tolerance(np.average(vol1_es), 500) + assert equal_tolerance(np.average(vol1_ls), 20, abs_tol=100) + assert equal_tolerance(np.average(vol2_es), 200) + assert equal_tolerance(np.average(vol2_ls), 470) + +@pytest.mark.flaky(reruns=5) +def test_4route_congestion_avoidance(): + tt1s = [] + tt2s = [] + tt3s = [] + tt4s = [] + vol1s = [] + vol2s = [] + vol3s = [] + vol4s = [] + ttas = [] + + for i in range(10): + W = World( + name="", + deltan=5, + tmax=3000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=None, + duo_update_time=100, + duo_update_weight=0.3 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid1", 1, 1) + W.addNode("mid2", 2, 1) + W.addNode("mid3", 3, 1) + W.addNode("mid4", 4, 1) + W.addNode("dest", 0, 2) + link11 = W.addLink("link11", "orig", "mid1", length=1000, free_flow_speed=20, jam_density=0.2, capacity_out=0.3) + link12 = W.addLink("link12", "mid1", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link21 = W.addLink("link21", "orig", "mid2", length=1000, free_flow_speed=20, jam_density=0.2, capacity_out=0.3) + link22 = W.addLink("link22", "mid2", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link31 = W.addLink("link31", "orig", "mid3", length=1000, free_flow_speed=20, jam_density=0.2, capacity_out=0.3) + link32 = W.addLink("link32", "mid3", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + link41 = W.addLink("link41", "orig", "mid4", length=1000, free_flow_speed=20, jam_density=0.2, capacity_out=0.3) + link42 = W.addLink("link42", "mid4", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 2000, 0.8) + + W.exec_simulation() + + W.analyzer.print_simple_stats() + + # W.analyzer.time_space_diagram_traj_links([link11, link12]) + # W.analyzer.time_space_diagram_traj_links([link21, link22]) + # W.analyzer.time_space_diagram_traj_links([link31, link32]) + # W.analyzer.time_space_diagram_traj_links([link41, link42]) + W.analyzer.basic_analysis() + + df = W.analyzer.link_to_pandas() + + tt1 = df[df["link"].isin(("link11", "link12"))]["average_travel_time"].sum() + tt2 = df[df["link"].isin(("link21", "link22"))]["average_travel_time"].sum() + tt3 = df[df["link"].isin(("link31", "link32"))]["average_travel_time"].sum() + tt4 = df[df["link"].isin(("link41", "link42"))]["average_travel_time"].sum() + vol1 = df[df["link"].isin(["link11"])]["traffic_volume"].values[0] + vol2 = df[df["link"].isin(["link21"])]["traffic_volume"].values[0] + vol3 = df[df["link"].isin(["link31"])]["traffic_volume"].values[0] + vol4 = df[df["link"].isin(["link41"])]["traffic_volume"].values[0] + + df2 = W.analyzer.od_to_pandas() + tta = df2["average_travel_time"].values[0] + + tt1s.append(tt1) + tt2s.append(tt2) + tt3s.append(tt3) + tt4s.append(tt4) + vol1s.append(vol1) + vol2s.append(vol2) + vol3s.append(vol3) + vol4s.append(vol4) + + ttas.append(tta) + + print(f"{np.average(tt1s) = }\n{np.average(tt2s) = }\n{np.average(tt3s) = }\n{np.average(tt4s) = }\n{np.average(vol1s) = }\n{np.average(vol2s) = }\n{np.average(vol3s) = }\n{np.average(vol4s) = }\n{np.average(np.concatenate((tt1s, tt2s, tt3s, tt4s))) = }\n{np.average(np.concatenate((vol1s, vol2s, vol3s, vol4s))) = }") + ttave = np.average(np.concatenate((tt1s, tt2s, tt3s, tt4s))) + volave = np.average(np.concatenate((vol1s, vol2s, vol3s, vol4s))) + + assert equal_tolerance(np.average(tt1s), ttave) + assert equal_tolerance(np.average(tt2s), ttave) + assert equal_tolerance(np.average(tt3s), ttave) + assert equal_tolerance(np.average(tt4s), ttave) + assert equal_tolerance(np.average(vol1s), volave) + assert equal_tolerance(np.average(vol2s), volave) + assert equal_tolerance(np.average(vol3s), volave) + assert equal_tolerance(np.average(vol4s), volave) + assert equal_tolerance(volave*4, 2000*0.8) \ No newline at end of file diff --git a/tests/test_verification_straight_road.py b/tests/test_verification_straight_road.py index 996564c..e1ea79a 100644 --- a/tests/test_verification_straight_road.py +++ b/tests/test_verification_straight_road.py @@ -6,7 +6,7 @@ from uxsim import * def equal_tolerance(val, check, rel_tol=0.1, abs_tol=0.0): - if check == 0: + if check == 0 and abs_tol == 0: abs_tol = 0.1 return abs(val - check) <= abs(check*rel_tol) + abs_tol @@ -53,6 +53,40 @@ def test_1link(): assert equal_tolerance(link.v_mat[2, 5], 20) assert equal_tolerance(link.v_mat[7, 5], 20) +def test_1link_iterative_exec(): + W = World( + name="", + deltan=5, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=0 + ) + + W.addNode("orig", 0, 0) + W.addNode("dest", 1, 1) + link = W.addLink("link", "orig", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 0, 500, 0.5) + + while W.check_simulation_ongoing(): + W.exec_simulation(duration_t=random.randint(50, 200)) + + W.analyzer.print_simple_stats() + + W.analyzer.basic_analysis() + assert equal_tolerance(W.analyzer.trip_all, 250) + assert equal_tolerance(W.analyzer.trip_completed, 250) + assert equal_tolerance(W.analyzer.total_travel_time, 12500) + assert equal_tolerance(W.analyzer.average_travel_time, 50) + assert equal_tolerance(W.analyzer.average_delay, 0) + + W.analyzer.compute_edie_state() + assert equal_tolerance(link.q_mat[2, 5], 0.5) + assert equal_tolerance(link.q_mat[7, 5], 0) + assert equal_tolerance(link.k_mat[2, 5], 0.025) + assert equal_tolerance(link.k_mat[7, 5], 0) + assert equal_tolerance(link.v_mat[2, 5], 20) + assert equal_tolerance(link.v_mat[7, 5], 20) + def test_1link_deltan1(): W = World( name="", @@ -730,3 +764,49 @@ def test_2link_signal(): assert equal_tolerance(link2.v_mat[:,8].mean() , 20.0) +def test_2link_signal_deltan1(): + W = World( + name="", + deltan=1, + tmax=2000, + print_mode=1, save_mode=1, show_mode=1, + random_seed=0 + ) + + W.addNode("orig", 0, 0) + W.addNode("mid", 1, 1, signal=[60,60]) + W.addNode("dest", 2, 2) + link1 = W.addLink("link", "orig", "mid", length=1000, free_flow_speed=20, jam_density=0.2) + link2 = W.addLink("link", "mid", "dest", length=1000, free_flow_speed=20, jam_density=0.2) + W.adddemand("orig", "dest", 300, 800, 0.4) + W.adddemand("orig", "dest", 0, 2000, 0.2) + + W.exec_simulation() + + W.analyzer.print_simple_stats() + + W.analyzer.basic_analysis() + assert equal_tolerance(W.analyzer.trip_all, 600) + assert equal_tolerance(W.analyzer.trip_completed, 568) + assert equal_tolerance(W.analyzer.total_travel_time, 112348) + assert equal_tolerance(W.analyzer.average_travel_time, 197) + assert equal_tolerance(W.analyzer.average_delay, 97) + + W.analyzer.compute_edie_state() + assert equal_tolerance(link1.q_mat[:,8].mean() , 0.2994791666666667) + assert equal_tolerance(link1.k_mat[:,8].mean() , 0.06033072916666667) + assert equal_tolerance(link1.v_mat[:,8].mean() , 11.848820125791734) + assert equal_tolerance(link1.q_mat[:,2].mean() , 0.3026041666666667) + assert equal_tolerance(link1.k_mat[:,2].mean() , 0.02812717013888889) + assert equal_tolerance(link1.v_mat[:,2].mean() , 16.587738607378817) + assert equal_tolerance(link1.q_mat[8,:].mean() , 0.3302083333333333) + assert equal_tolerance(link1.k_mat[8,:].mean() , 0.07739166666666668) + assert equal_tolerance(link1.v_mat[8,:].mean() , 7.4833973250262265) + assert equal_tolerance(link1.q_mat[12,:].mean(), 0.2) + assert equal_tolerance(link1.k_mat[12,:].mean(), 0.013500000000000002) + assert equal_tolerance(link1.v_mat[12,:].mean(), 18.444444444444446) + assert equal_tolerance(link2.q_mat[:,8].mean() , 0.29427083333333337) + assert equal_tolerance(link2.k_mat[:,8].mean() , 0.014713541666666666) + assert equal_tolerance(link2.v_mat[:,8].mean() , 20.0) + +