Skip to content

Commit ca6e455

Browse files
committed
add: SimulationUnitTest
1 parent e82e8cb commit ca6e455

2 files changed

Lines changed: 276 additions & 44 deletions

File tree

Library/PAX_SAPIENTICA/Simulation/Manager/SettlementSimulator.hpp

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -488,9 +488,8 @@ namespace paxs {
488488
close_settlements_list.clear();
489489
for (int i = -1; i <= 1; ++i) {
490490
for (int j = -1; j <= 1; ++j) {
491-
auto it = settlement_grids.find((grid_position + Vector2(i, j)).to(SettlementGridsType{}));
492-
if (it != settlement_grids.end()) {
493-
close_settlements_list.emplace_back(&(it->second.getSettlements()));
491+
if (auto* const ptr = settlement_grids.try_get((grid_position + Vector2(i, j)).to(SettlementGridsType{}))) {
492+
close_settlements_list.emplace_back(&(ptr->getSettlements()));
494493
}
495494
}
496495
}
@@ -633,6 +632,39 @@ namespace paxs {
633632
std::vector<std::size_t> birth_count_history_;
634633
std::size_t birth_history_index_ = 0;
635634

635+
/// @brief エージェントをランダムに生成するヘルパーメソッド
636+
/// @brief Create a random agent helper method
637+
/// @param is_farming 農耕民かどうか / Is farming
638+
/// @param district_id 地区ID / District ID
639+
/// @return 生成されたエージェント / Generated agent
640+
Agent createRandomAgent(bool is_farming, std::uint_least8_t district_id) {
641+
Genome genome = Genome::generateRandomSetMtDNA(
642+
gen,
643+
japan_provinces->getMtDNA(district_id, gen),
644+
static_cast<std::uint_least8_t>(japan_provinces->getSNP(district_id)),
645+
japan_provinces->getLanguage(district_id, gen)
646+
);
647+
const AgeType set_lifespan = kanakuma_life_span.setLifeSpan(is_farming, genome.isMale(), gen);
648+
649+
AgeType age_value = 0;
650+
if (set_lifespan > SimulationConstants::getInstance().init_lifespan_grace_period) {
651+
std::uniform_int_distribution<> lifespan_dist{
652+
0, static_cast<int>(set_lifespan - SimulationConstants::getInstance().init_lifespan_grace_period)
653+
};
654+
age_value = static_cast<AgeType>(lifespan_dist(gen));
655+
}
656+
657+
return Agent(
658+
UniqueIdentification<std::uint_least32_t>::generate(),
659+
age_value,
660+
set_lifespan,
661+
genome,
662+
static_cast<std::uint_least8_t>(japan_provinces->getFarming(district_id)),
663+
static_cast<std::uint_least8_t>(japan_provinces->getHunterGatherer(district_id)),
664+
static_cast<std::uint_least8_t>(japan_provinces->getLanguage(district_id))
665+
);
666+
}
667+
636668
/// @brief ()
637669
/// @brief 集落をランダムに配置する前の初期化処理
638670
bool initRandomizeSettlements() {
@@ -724,7 +756,7 @@ namespace paxs {
724756
// 集落配置
725757
for (std::uint_least8_t district_id = 0; district_id < district_id_max; ++district_id) {
726758

727-
Live live = (*live_list)[district_id];
759+
Live& live = (*live_list)[district_id];
728760

729761
while (live.live_probabilities.size() > 0 && // 集落を配置し切るまで
730762
district_population_map.find(district_id) != district_population_map.end() // 地区が残っている間
@@ -746,9 +778,6 @@ namespace paxs {
746778
const int live_probability_index = live_probability_dist(gen);
747779
const Vector2 live_position = Vector2::from(live.habitable_land_positions[live_probability_index]);
748780

749-
// 地区ごとに人口が決められているので、人口に空きがあるかどうかを判定
750-
// std::uint_least8_t district_id = environment->template getData<std::uint_least8_t>(SimulationConstants::getInstance().district_key, live_position);
751-
752781
auto district_population_it = district_population_map.find(district_id);
753782
if (district_population_it == district_population_map.end()) {
754783
live.live_probabilities[live_probability_index] = live.live_probabilities.back();
@@ -782,24 +811,7 @@ namespace paxs {
782811
const std::uint_least8_t immigration_and_district_id = (is_farming) ? SimulationConstants::getInstance().immigration_district_id/*toraijin*/ : district_id;
783812
settlement.resizeAgents(settlement_population);
784813
for (int i = 0; i < settlement_population; ++i) {
785-
Genome genome = Genome::generateRandomSetMtDNA(gen, japan_provinces->getMtDNA(immigration_and_district_id, gen), static_cast<std::uint_least8_t>(japan_provinces->getSNP(immigration_and_district_id)), japan_provinces->getLanguage(immigration_and_district_id, gen));
786-
const AgeType set_lifespan = kanakuma_life_span.setLifeSpan((is_farming), genome.isMale(), gen);
787-
788-
AgeType age_value = 0;
789-
if (set_lifespan > SimulationConstants::getInstance().init_lifespan_grace_period) {
790-
// 寿命の乱数分布
791-
std::uniform_int_distribution<> lifespan_dist{ 0, static_cast<int>(set_lifespan - SimulationConstants::getInstance().init_lifespan_grace_period) };
792-
age_value = static_cast<AgeType>(lifespan_dist(gen));
793-
}
794-
795-
settlement.setAgent(Agent(UniqueIdentification<std::uint_least32_t>::generate(),
796-
age_value,
797-
set_lifespan,
798-
genome,
799-
static_cast<std::uint_least8_t>(japan_provinces->getFarming(immigration_and_district_id)),
800-
static_cast<std::uint_least8_t>(japan_provinces->getHunterGatherer(immigration_and_district_id)),
801-
static_cast<std::uint_least8_t>(japan_provinces->getLanguage(immigration_and_district_id))
802-
), static_cast<std::size_t>(i));
814+
settlement.setAgent(createRandomAgent(is_farming, immigration_and_district_id), static_cast<std::size_t>(i));
803815
if (is_farming) ++migration_count; // 農耕カウント
804816
}
805817

@@ -850,25 +862,7 @@ namespace paxs {
850862
for (auto& settlement : settlements) {
851863
std::vector<Agent> agents(add_population);
852864
for (int i = 0; i < add_population; ++i) {
853-
Genome genome = Genome::generateRandomSetMtDNA(gen, japan_provinces->getMtDNA(immigration_and_district_id, gen), static_cast<std::uint_least8_t>(japan_provinces->getSNP(immigration_and_district_id)), japan_provinces->getLanguage(immigration_and_district_id, gen));
854-
const AgeType set_lifespan = kanakuma_life_span.setLifeSpan(is_farming, genome.isMale(), gen);
855-
856-
AgeType age_value = 0;
857-
if (set_lifespan > SimulationConstants::getInstance().init_lifespan_grace_period) {
858-
// 寿命の乱数分布
859-
std::uniform_int_distribution<> lifespan_dist{ 0, static_cast<int>(set_lifespan - SimulationConstants::getInstance().init_lifespan_grace_period) };
860-
age_value = static_cast<AgeType>(lifespan_dist(gen));
861-
}
862-
863-
agents[i] = Agent(
864-
UniqueIdentification<std::uint_least32_t>::generate(),
865-
age_value,
866-
set_lifespan,
867-
genome,
868-
static_cast<std::uint_least8_t>(japan_provinces->getFarming(immigration_and_district_id)),
869-
static_cast<std::uint_least8_t>(japan_provinces->getHunterGatherer(immigration_and_district_id)),
870-
static_cast<std::uint_least8_t>(japan_provinces->getLanguage(immigration_and_district_id))
871-
);
865+
agents[i] = createRandomAgent(is_farming, immigration_and_district_id);
872866
if (is_farming) {
873867
++migration_count; // 農耕カウント
874868
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*##########################################################################################
2+
3+
PAX SAPIENTICA Library 💀🌿🌏
4+
5+
[Planning] 2023-2024 As Project
6+
[Production] 2023-2024 As Project
7+
[Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA
8+
[License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/
9+
10+
##########################################################################################*/
11+
12+
#include <gtest/gtest.h>
13+
14+
#include <memory>
15+
#include <random>
16+
#include <vector>
17+
18+
#include <PAX_SAPIENTICA/Simulation/Manager/SettlementSimulator.hpp>
19+
#include <PAX_SAPIENTICA/Simulation/Config/SimulationConst.hpp>
20+
#include <PAX_SAPIENTICA/System/AppConfig.hpp>
21+
#include <PAX_SAPIENTICA/Utility/MurMur3.hpp>
22+
#include <PAX_SAPIENTICA/Utility/StringUtils.hpp>
23+
24+
// SettlementSimulator クラスのテスト
25+
26+
class SettlementSimulatorUnitTest : public ::testing::Test {
27+
protected:
28+
void SetUp() override {
29+
// モデル名を設定
30+
model_name = "Sample";
31+
32+
// SimulationConstantsの初期化
33+
paxs::SimulationConstants::getInstance(model_name).init(model_name);
34+
35+
// パスを生成(実際のシミュレーションと同じ方法)
36+
map_list_path = paxs::AppConfig::getInstance().getSettingPath(paxs::MurMur3::calcHash("SimulationXYZTiles"));
37+
japan_provinces_path = paxs::AppConfig::getInstance().getSettingPath(paxs::MurMur3::calcHash("SimulationProvincesPath"));
38+
39+
paxs::StringUtils::replace(map_list_path, "Sample", model_name);
40+
paxs::StringUtils::replace(japan_provinces_path, "Sample", model_name);
41+
42+
// テスト用のシミュレーションシードを固定
43+
seed = 12345;
44+
}
45+
46+
std::string model_name;
47+
std::string map_list_path;
48+
std::string japan_provinces_path;
49+
unsigned seed;
50+
};
51+
52+
TEST_F(SettlementSimulatorUnitTest, Construction) {
53+
// Given: パラメータを指定してシミュレータを作成
54+
paxs::SettlementSimulator simulator(map_list_path, japan_provinces_path, seed);
55+
56+
// Then: シミュレータが正常に構築される
57+
EXPECT_EQ(simulator.cgetPopulationNum(), 0);
58+
EXPECT_EQ(simulator.cgetSettlement(), 0);
59+
}
60+
61+
TEST_F(SettlementSimulatorUnitTest, SetEnvironment) {
62+
// Given: 空のシミュレータ
63+
paxs::SettlementSimulator simulator;
64+
65+
// When: 環境を設定
66+
EXPECT_NO_THROW(simulator.setEnvironment(map_list_path, japan_provinces_path, seed));
67+
68+
// Then: 正常に設定される
69+
EXPECT_EQ(simulator.cgetPopulationNum(), 0);
70+
}
71+
72+
// ========================================
73+
// RandomizeSettlements Tests
74+
// ========================================
75+
76+
/**
77+
* @brief randomizeSettlements()の動作を検証するテスト
78+
*
79+
* このテストは固定シードで2回実行し、集落配置が決定論的であることを確認します。
80+
* リファクタリング前後で同じ結果が得られることを保証します。
81+
*/
82+
TEST_F(SettlementSimulatorUnitTest, RandomizeSettlements_Deterministic) {
83+
// Given: 同じシードで2つのシミュレータを作成
84+
paxs::SettlementSimulator simulator1(map_list_path, japan_provinces_path, seed);
85+
paxs::SettlementSimulator simulator2(map_list_path, japan_provinces_path, seed);
86+
87+
// When: 両方を初期化
88+
simulator1.init();
89+
simulator2.init();
90+
91+
// Then: 同じ人口と集落数が生成される
92+
EXPECT_EQ(simulator1.cgetPopulationNum(), simulator2.cgetPopulationNum());
93+
EXPECT_EQ(simulator1.cgetSettlement(), simulator2.cgetSettlement());
94+
95+
// 集落の詳細も一致することを確認
96+
const auto& grids1 = simulator1.cgetSettlementGrids();
97+
const auto& grids2 = simulator2.cgetSettlementGrids();
98+
99+
EXPECT_EQ(grids1.size(), grids2.size());
100+
}
101+
102+
/**
103+
* @brief randomizeSettlements()で配置されたエージェントの詳細を検証
104+
*
105+
* 固定シードでエージェントの属性(年齢、ゲノム、文化)が再現可能であることを確認
106+
*/
107+
TEST_F(SettlementSimulatorUnitTest, RandomizeSettlements_AgentDetailsReproducible) {
108+
// Given: 同じシードで2つのシミュレータを作成
109+
const unsigned test_seed = 54321;
110+
paxs::SettlementSimulator simulator1(map_list_path, japan_provinces_path, test_seed);
111+
paxs::SettlementSimulator simulator2(map_list_path, japan_provinces_path, test_seed);
112+
113+
// When: 初期化
114+
simulator1.init();
115+
simulator2.init();
116+
117+
// Then: 各集落のエージェント詳細が一致
118+
const auto& grids1 = simulator1.cgetSettlementGrids();
119+
const auto& grids2 = simulator2.cgetSettlementGrids();
120+
121+
// 集落グリッド数が一致
122+
ASSERT_EQ(grids1.size(), grids2.size());
123+
124+
// 各グリッドの集落を比較
125+
for (const auto& [key, grid1] : grids1) {
126+
const auto* grid2_ptr = grids2.try_get(key);
127+
ASSERT_NE(grid2_ptr, nullptr) << "Grid key " << key << " not found in simulator2";
128+
129+
const auto& settlements1 = grid1.cgetSettlements();
130+
const auto& settlements2 = grid2_ptr->cgetSettlements();
131+
132+
EXPECT_EQ(settlements1.size(), settlements2.size())
133+
<< "Settlement count mismatch in grid " << key;
134+
135+
// 各集落のエージェント数を確認
136+
for (std::size_t i = 0; i < settlements1.size() && i < settlements2.size(); ++i) {
137+
EXPECT_EQ(settlements1[i].getPopulation(), settlements2[i].getPopulation())
138+
<< "Population mismatch in settlement " << i << " of grid " << key;
139+
}
140+
}
141+
}
142+
143+
/**
144+
* @brief 渡来人配置のテスト
145+
*
146+
* randomizeSettlements()が渡来フラグで正しく動作することを確認
147+
*/
148+
TEST_F(SettlementSimulatorUnitTest, RandomizeSettlements_ImmigrationFlag) {
149+
// Given: シミュレータを作成
150+
paxs::SettlementSimulator simulator(map_list_path, japan_provinces_path, seed);
151+
152+
// When: 初期化(在地人配置)
153+
simulator.init();
154+
const std::size_t initial_population = simulator.cgetPopulationNum();
155+
156+
// Then: 初期人口が設定される
157+
EXPECT_GT(initial_population, 0) << "Initial population should be greater than 0";
158+
}
159+
160+
/**
161+
* @brief 集落配置の空間分布を検証
162+
*
163+
* 集落が可住地にのみ配置されることを確認
164+
*/
165+
TEST_F(SettlementSimulatorUnitTest, RandomizeSettlements_OnlyOnHabitableLand) {
166+
// Given: シミュレータを作成
167+
paxs::SettlementSimulator simulator(map_list_path, japan_provinces_path, seed);
168+
169+
// When: 初期化
170+
simulator.init();
171+
172+
// Then: 全ての集落が有効な位置にある
173+
const auto& grids = simulator.cgetSettlementGrids();
174+
for (const auto& [key, grid] : grids) {
175+
for (const auto& settlement : grid.cgetSettlements()) {
176+
// 人口が0より大きい
177+
EXPECT_GT(settlement.getPopulation(), 0);
178+
179+
// 位置が有効
180+
const auto pos = settlement.getPosition();
181+
EXPECT_GE(pos.x, 0);
182+
EXPECT_GE(pos.y, 0);
183+
}
184+
}
185+
}
186+
187+
/**
188+
* @brief 異なるシードで異なる結果が生成されることを確認
189+
*/
190+
TEST_F(SettlementSimulatorUnitTest, RandomizeSettlements_DifferentSeedsDifferentResults) {
191+
// Given: 異なるシードで2つのシミュレータを作成
192+
paxs::SettlementSimulator simulator1(map_list_path, japan_provinces_path, 12345);
193+
paxs::SettlementSimulator simulator2(map_list_path, japan_provinces_path, 67890);
194+
195+
// When: 両方を初期化
196+
simulator1.init();
197+
simulator2.init();
198+
199+
// Then: 人口は同じ設定値だが、集落の詳細配置は異なる可能性が高い
200+
// (ただし、テストデータや設定によっては偶然一致する可能性もあるため、
201+
//  ここでは基本的な検証のみ行う)
202+
203+
// 両方とも集落が生成されている
204+
EXPECT_GT(simulator1.cgetSettlement(), 0);
205+
EXPECT_GT(simulator2.cgetSettlement(), 0);
206+
}
207+
208+
// ========================================
209+
// Population Calculation Tests
210+
// ========================================
211+
212+
TEST_F(SettlementSimulatorUnitTest, CalcPop_UpdatesPopulationAndSettlementCount) {
213+
// Given: 初期化されたシミュレータ
214+
paxs::SettlementSimulator simulator(map_list_path, japan_provinces_path, seed);
215+
simulator.init();
216+
217+
// When: 人口計算を実行
218+
const std::size_t pop_before = simulator.cgetPopulationNum();
219+
const std::size_t settlement_before = simulator.cgetSettlement();
220+
221+
simulator.calcPop();
222+
223+
// Then: 人口と集落数が計算される(この場合、変化なし)
224+
EXPECT_EQ(simulator.cgetPopulationNum(), pop_before);
225+
EXPECT_EQ(simulator.cgetSettlement(), settlement_before);
226+
}
227+
228+
// ========================================
229+
// Migration Count Tests
230+
// ========================================
231+
232+
TEST_F(SettlementSimulatorUnitTest, GetMigrationCount_InitiallyZero) {
233+
// Given: 新しいシミュレータ
234+
paxs::SettlementSimulator simulator(map_list_path, japan_provinces_path, seed);
235+
236+
// Then: 渡来数は0
237+
EXPECT_EQ(simulator.getMigrationCount(), 0);
238+
}

0 commit comments

Comments
 (0)