diff options
| author | Aiden Woodruff <woodra@rpi.edu> | 2025-11-14 11:36:02 -0500 |
|---|---|---|
| committer | Aiden Woodruff <woodra@rpi.edu> | 2025-11-14 11:36:02 -0500 |
| commit | 81b45c03ed83658f80b32a46d9f51e298fa8e2f1 (patch) | |
| tree | 31a33cdeb8ef832c41e2d237dd321716ec583276 /src | |
| parent | c4b6d2dad64b8136b99fa7358ec141d4a46ff4e9 (diff) | |
| download | tipping-points-81b45c03ed83658f80b32a46d9f51e298fa8e2f1.tar.gz tipping-points-81b45c03ed83658f80b32a46d9f51e298fa8e2f1.tar.bz2 tipping-points-81b45c03ed83658f80b32a46d9f51e298fa8e2f1.zip | |
created encapsulated NameGame class
- src/CMakeLists.txt: add NameGame.cc.
- src/NameGame.cc: move best_move, update_memory, run round, round into this
class and file.
- move begin/end for TVec here.
- add initGraph and initMemory for changing sizes.
- src/NameGame.h: add class declaration.
- main.cc: move all functions out of here.
- use one object and re initialize memory for different committed minority
sizes.
Signed-off-by: Aiden Woodruff <woodra@rpi.edu>
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | src/NameGame.cc | 130 | ||||
| -rw-r--r-- | src/NameGame.h | 34 | ||||
| -rw-r--r-- | src/main.cc | 111 |
4 files changed, 173 insertions, 107 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c66a468..536e475 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -1,2 +1,5 @@ | |||
| 1 | add_executable(main main.cc) | 1 | add_executable(main |
| 2 | main.cc | ||
| 3 | NameGame.cc | ||
| 4 | ) | ||
| 2 | target_link_libraries(main PRIVATE SNAP::SNAP) | 5 | target_link_libraries(main PRIVATE SNAP::SNAP) |
diff --git a/src/NameGame.cc b/src/NameGame.cc new file mode 100644 index 0000000..8ea3dcf --- /dev/null +++ b/src/NameGame.cc | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | #include <iostream> | ||
| 2 | #include <map> | ||
| 3 | #include <stdexcept> | ||
| 4 | #include <vector> | ||
| 5 | |||
| 6 | #include "NameGame.h" | ||
| 7 | |||
| 8 | template<typename TVal, typename TSizeTy> | ||
| 9 | auto begin(const TVec<TVal, TSizeTy>& v) -> decltype(v.BegI()) { | ||
| 10 | return v.BegI(); | ||
| 11 | } | ||
| 12 | |||
| 13 | template<typename TVal, typename TSizeTy> | ||
| 14 | auto end(const TVec<TVal, TSizeTy>& v) -> decltype(v.EndI()) { | ||
| 15 | return v.EndI(); | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace tp { | ||
| 19 | |||
| 20 | NameGame::NameGame(int group_size, int cm_size, int memory) : | ||
| 21 | gsize(group_size), cmsize(cm_size), memlen(memory), | ||
| 22 | rng(std::random_device{}()), dist(0, group_size - 1), | ||
| 23 | graph(group_size, group_size * group_size) | ||
| 24 | { | ||
| 25 | if (group_size <= 0) | ||
| 26 | throw std::invalid_argument("NameGame invalid group_size"); | ||
| 27 | if (cm_size < 0) | ||
| 28 | throw std::invalid_argument("NameGame invalid cm_size"); | ||
| 29 | if (cm_size > group_size) | ||
| 30 | throw std::invalid_argument("NameGame cm_size > group_size"); | ||
| 31 | if (memory <= 0) | ||
| 32 | throw std::invalid_argument("NameGame memory <= 0"); | ||
| 33 | initGraph(); | ||
| 34 | } | ||
| 35 | |||
| 36 | void NameGame::setCMsize(int cm_size) { | ||
| 37 | if (cm_size <= 0 || cm_size > gsize) | ||
| 38 | throw std::invalid_argument("NameGame::setCMsize invalid cm_size"); | ||
| 39 | cmsize = cm_size; | ||
| 40 | } | ||
| 41 | |||
| 42 | void NameGame::initGraph() { | ||
| 43 | // Generate complete graph. | ||
| 44 | for (int i = 0; i < gsize; ++i) graph.AddNode(i, {false, {}}); | ||
| 45 | for (int i = 0; i < gsize; ++i) | ||
| 46 | for (int j = 0; j < gsize; ++j) | ||
| 47 | if (i != j) graph.AddEdge(i, j); | ||
| 48 | initMemory(); | ||
| 49 | } | ||
| 50 | |||
| 51 | void NameGame::initMemory() { | ||
| 52 | std::vector<int> ids(gsize); | ||
| 53 | std::iota(ids.begin(), ids.end(), 0); | ||
| 54 | // Set committed minority. | ||
| 55 | for (int i = 0; i < cmsize; ++i) { | ||
| 56 | int r = dist(rng) % ids.size(); | ||
| 57 | int rId = ids[r]; | ||
| 58 | auto& rDat = graph.GetNDat(rId); | ||
| 59 | rDat.Val1 = true; | ||
| 60 | rDat.Val2.Clr(); | ||
| 61 | rDat.Val2.Add(2); | ||
| 62 | ids.erase(ids.begin() + r); | ||
| 63 | } | ||
| 64 | // Establish norm (fully memory of strategy 1) for remaining nodes. | ||
| 65 | for (int id : ids) { | ||
| 66 | auto& d = graph.GetNDat(id); | ||
| 67 | d.Val1 = false; | ||
| 68 | d.Val2.Clr(); | ||
| 69 | while (d.Val2.Len() < memlen) d.Val2.Add(1); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | void NameGame::run(int rounds) { | ||
| 74 | if (rounds < 0) throw std::invalid_argument("NameGame::run rounds < 0"); | ||
| 75 | for (int i = 0; i < rounds; ++i) runRound(); | ||
| 76 | // Track all plays and plays by non-CM. | ||
| 77 | // Report average plays. | ||
| 78 | } | ||
| 79 | |||
| 80 | void NameGame::runRound() { | ||
| 81 | // Select random speaker and hearer. | ||
| 82 | auto speaker = graph.GetNI(dist(rng)); | ||
| 83 | auto hearer = graph.GetNI( | ||
| 84 | speaker.GetNbrNId(dist(rng) % speaker.GetDeg()) | ||
| 85 | ); | ||
| 86 | |||
| 87 | // Speaker chooses best strategy. | ||
| 88 | int s_strategy = best_move(speaker.GetId()); | ||
| 89 | std::cout << "speaker (" << speaker.GetId() << " " | ||
| 90 | << std::boolalpha << speaker().Val1 | ||
| 91 | << ") chose: " << s_strategy << std::endl; | ||
| 92 | |||
| 93 | // Listener updates memory. | ||
| 94 | update_memory(hearer.GetId(), s_strategy); | ||
| 95 | |||
| 96 | // Listener chooses best strategy. | ||
| 97 | int h_strategy = best_move(hearer.GetId()); | ||
| 98 | std::cout << "hearer (" << hearer.GetId() << " " | ||
| 99 | << std::boolalpha << hearer().Val1 | ||
| 100 | << ") chose: " << h_strategy << std::endl; | ||
| 101 | |||
| 102 | // Speaker updates memory. | ||
| 103 | update_memory(speaker.GetId(), s_strategy); | ||
| 104 | } | ||
| 105 | |||
| 106 | int NameGame::best_move(int nId) const { | ||
| 107 | const auto& mem = graph.GetNDat(nId).Val2; | ||
| 108 | std::map<int, int> votes; | ||
| 109 | for (const auto& v : mem) { | ||
| 110 | if (votes.count(v)) votes[v] += 1; | ||
| 111 | else votes[v] = 1; | ||
| 112 | } | ||
| 113 | std::map<int, float> fractions; | ||
| 114 | for (auto kv : votes) fractions[kv.first] = kv.second * 1./mem.Len(); | ||
| 115 | // Simple majority vote: | ||
| 116 | for (auto kv : fractions) | ||
| 117 | if (kv.second >= 1./mem.Len()) return kv.first; | ||
| 118 | return 0; | ||
| 119 | } | ||
| 120 | |||
| 121 | void NameGame::update_memory(int nId, int strategy) { | ||
| 122 | auto d = graph.GetNDat(nId); | ||
| 123 | if (!d.Val1) { | ||
| 124 | auto& mem = d.Val2; | ||
| 125 | mem.Ins(0, strategy); | ||
| 126 | mem.Trunc(memlen); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | } // namespace tp | ||
diff --git a/src/NameGame.h b/src/NameGame.h new file mode 100644 index 0000000..8a817ce --- /dev/null +++ b/src/NameGame.h | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #ifndef TIPPING_POINTS_NAMEGAME_H | ||
| 2 | #define TIPPING_POINTS_NAMEGAME_H | ||
| 3 | |||
| 4 | #include <random> | ||
| 5 | |||
| 6 | #include <Snap.h> | ||
| 7 | |||
| 8 | namespace tp { | ||
| 9 | |||
| 10 | class NameGame { | ||
| 11 | public: | ||
| 12 | NameGame(int group_size, int cm_size, int memory = 12); | ||
| 13 | |||
| 14 | void setCMsize(int cm_size); | ||
| 15 | |||
| 16 | void initGraph(); | ||
| 17 | void initMemory(); | ||
| 18 | void run(int rounds); | ||
| 19 | |||
| 20 | protected: | ||
| 21 | int best_move(int nId) const; | ||
| 22 | void update_memory(int nId, int strategy); | ||
| 23 | |||
| 24 | private: | ||
| 25 | void runRound(); | ||
| 26 | int gsize, cmsize, memlen; | ||
| 27 | std::mt19937 rng; | ||
| 28 | std::uniform_int_distribution<> dist; | ||
| 29 | TNodeNet<TPair<TBool, TIntV>> graph; | ||
| 30 | }; // class NameGame | ||
| 31 | |||
| 32 | } // namespace tp | ||
| 33 | |||
| 34 | #endif // TIPPING_POINTS_NAMEGAME_H | ||
diff --git a/src/main.cc b/src/main.cc index 0f391eb..e41f4ff 100644 --- a/src/main.cc +++ b/src/main.cc | |||
| @@ -1,119 +1,18 @@ | |||
| 1 | #include <cassert> | ||
| 2 | #include <iostream> | 1 | #include <iostream> |
| 3 | #include <map> | ||
| 4 | #include <random> | ||
| 5 | 2 | ||
| 6 | #include <Snap.h> | 3 | #include "NameGame.h" |
| 7 | |||
| 8 | void name_game(int group_size, int cmsize); | ||
| 9 | |||
| 10 | constexpr int memory_length = 12; | ||
| 11 | std::mt19937 rng(std::random_device{}()); | ||
| 12 | 4 | ||
| 13 | int main(int argc, char* argv[]) { | 5 | int main(int argc, char* argv[]) { |
| 14 | rng.seed(std::random_device{}()); | ||
| 15 | int group_size = 100; | 6 | int group_size = 100; |
| 7 | tp::NameGame namegame(group_size, 0); | ||
| 16 | for (int cmperc = 18; cmperc < 28; cmperc += 2) { | 8 | for (int cmperc = 18; cmperc < 28; cmperc += 2) { |
| 17 | int cmsize = group_size * cmperc / 100; | 9 | int cmsize = group_size * cmperc / 100; |
| 10 | namegame.setCMsize(cmsize); | ||
| 11 | namegame.initMemory(); | ||
| 18 | std::cout << "CM " << cmsize << " / " << group_size << std::endl; | 12 | std::cout << "CM " << cmsize << " / " << group_size << std::endl; |
| 19 | name_game(group_size, cmsize); | 13 | namegame.run(10); |
| 20 | } | ||
| 21 | return 0; | ||
| 22 | } | ||
| 23 | |||
| 24 | using ngData = TPair<TBool, TIntV>; | ||
| 25 | |||
| 26 | template<typename TVal, typename TSizeTy> | ||
| 27 | auto begin(const TVec<TVal, TSizeTy>& v) -> decltype(v.BegI()) { | ||
| 28 | return v.BegI(); | ||
| 29 | } | ||
| 30 | |||
| 31 | template<typename TVal, typename TSizeTy> | ||
| 32 | auto end(const TVec<TVal, TSizeTy>& v) -> decltype(v.EndI()) { | ||
| 33 | return v.EndI(); | ||
| 34 | } | ||
| 35 | |||
| 36 | int best_move(const ngData& d) { | ||
| 37 | const auto& mem = d.Val2; | ||
| 38 | std::map<int, int> votes; | ||
| 39 | for (const auto& v : mem) { | ||
| 40 | if (votes.count(v)) votes[v] += 1; | ||
| 41 | else votes[v] = 1; | ||
| 42 | } | 14 | } |
| 43 | std::map<int, float> fractions; | ||
| 44 | for (auto kv : votes) fractions[kv.first] = kv.second * 1./mem.Len(); | ||
| 45 | // Simple majority vote: | ||
| 46 | for (auto kv : fractions) | ||
| 47 | if (kv.second >= 1./mem.Len()) return kv.first; | ||
| 48 | return 0; | 15 | return 0; |
| 49 | } | 16 | } |
| 50 | 17 | ||
| 51 | void update_memory(ngData& d, int move) { | ||
| 52 | if (!d.Val1) { | ||
| 53 | auto& mem = d.Val2; | ||
| 54 | mem.Ins(0, move); | ||
| 55 | mem.Trunc(memory_length); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | void name_game_round(TNodeNet<ngData>& graph) { | ||
| 60 | std::uniform_int_distribution<> dist(0, graph.GetNodes()); | ||
| 61 | // Select random speaker and hearer. | ||
| 62 | auto speaker = graph.GetNI(dist(rng)); | ||
| 63 | auto hearer = graph.GetNI( | ||
| 64 | speaker.GetNbrNId(dist(rng) % speaker.GetDeg()) | ||
| 65 | ); | ||
| 66 | |||
| 67 | // Speaker chooses best strategy. | ||
| 68 | int s_strategy = best_move(speaker.GetDat()); | ||
| 69 | std::cout << "speaker (" << speaker.GetId() << " " | ||
| 70 | << std::boolalpha << speaker().Val1 | ||
| 71 | << ") chose: " << s_strategy << std::endl; | ||
| 72 | |||
| 73 | // Listener updates memory. | ||
| 74 | update_memory(hearer.GetDat(), s_strategy); | ||
| 75 | |||
| 76 | // Listener chooses best strategy. | ||
| 77 | int h_strategy = best_move(hearer.GetDat()); | ||
| 78 | std::cout << "hearer (" << hearer.GetId() << " " | ||
| 79 | << std::boolalpha << hearer().Val1 | ||
| 80 | << ") chose: " << h_strategy << std::endl; | ||
| 81 | |||
| 82 | // Speaker updates memory. | ||
| 83 | update_memory(speaker.GetDat(), s_strategy); | ||
| 84 | } | ||
| 85 | |||
| 86 | void name_game(int group_size, int cmsize) { | ||
| 87 | assert(cmsize > 0 && group_size > 0); | ||
| 88 | assert(cmsize < group_size); | ||
| 89 | TNodeNet<ngData> graph; | ||
| 90 | // Generate complete graph. | ||
| 91 | for (int i = 0; i < group_size; ++i) graph.AddNode(i, {false, {}}); | ||
| 92 | for (int i = 0; i < group_size; ++i) | ||
| 93 | for (int j = 0; j < group_size; ++j) | ||
| 94 | if (i != j) graph.AddEdge(i, j); | ||
| 95 | // Set committed minority. | ||
| 96 | std::uniform_int_distribution<> dist(0, group_size - 1); | ||
| 97 | for (int i = 0; i < cmsize;) { | ||
| 98 | int r = dist(rng); | ||
| 99 | if (!graph.GetNDat(r).Val1) { | ||
| 100 | graph.GetNDat(r).Val1 = true; | ||
| 101 | auto& mem = graph.GetNDat(r).Val2; | ||
| 102 | mem.Clr(); | ||
| 103 | mem.Add(2); | ||
| 104 | ++i; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | // Establish norm (fully memory of strategy 1) | ||
| 108 | for (int i = 0; i < group_size; ++i) { | ||
| 109 | auto& d = graph.GetNDat(i); | ||
| 110 | if (!d.Val1) while (d.Val2.Len() < memory_length) d.Val2.Add(1); | ||
| 111 | } | ||
| 112 | std::cout << "Nodes: " << graph.GetNodes() | ||
| 113 | << " Edges: " << graph.GetEdges() << std::endl; | ||
| 114 | |||
| 115 | name_game_round(graph); | ||
| 116 | 18 | ||
| 117 | // Track all plays and plays by non-CM. | ||
| 118 | // Report average plays. | ||
| 119 | } | ||
