Stride Reference Manual  1.0
SimulatorBuilder.cpp
Go to the documentation of this file.
1 /*
2  * This is free software: you can redistribute it and/or modify it
3  * under the terms of the GNU General Public License as published by
4  * the Free Software Foundation, either version 3 of the License, or
5  * any later version.
6  * The software is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9  * GNU General Public License for more details.
10  * You should have received a copy of the GNU General Public License
11  * along with the software. If not, see <http://www.gnu.org/licenses/>.
12  *
13  * Copyright 2015, Willem L, Kuylen E, Stijven S & Broeckhove J
14  */
15 
21 #include "SimulatorBuilder.h"
22 
23 #include "calendar/Calendar.h"
24 #include "core/Infector.h"
25 #include "pop/Population.h"
26 #include "pop/PopulationBuilder.h"
27 #include "util/InstallDirs.h"
28 #include "util/GeoCoordinate.h"
29 #include "util/StringUtils.h"
31 #include "core/Cluster.h"
32 #include "core/ClusterType.h"
34 
35 #include <boost/property_tree/xml_parser.hpp>
36 #include <boost/optional/optional.hpp>
37 #include <boost/algorithm/string.hpp>
38 #include <boost/filesystem.hpp>
39 #include <boost/filesystem/fstream.hpp>
40 
41 #include <map>
42 #include <string>
43 #include <utility>
44 #include <algorithm>
45 
46 namespace stride {
47 
48 using namespace std;
49 using namespace util;
50 using namespace boost::filesystem;
51 using namespace boost::property_tree;
52 using namespace stride::util;
53 
54 shared_ptr<Simulator> SimulatorBuilder::build(const ptree& pt_config) {
55  // Disease file.
56  ptree pt_disease;
57  const string file_name_d = pt_config.get<string>("run.disease.config");
58  const auto file_path_d = (InstallDirs::getDataDir() /= file_name_d);
59  if (!is_regular_file(file_path_d)) {
60  throw runtime_error(std::string(__func__) + "> No file " + file_path_d.string());
61  }
62  read_xml(file_path_d.string(), pt_disease);
63 
64  // Contact file.
65  ptree pt_contact;
66  const string file_name_c = pt_config.get("run.age_contact_matrix_file", "contact_matrix.xml");
67  const auto file_path_c = (InstallDirs::getDataDir() /= file_name_c);
68  if (!is_regular_file(file_path_c)) {
69  throw runtime_error(string(__func__) + "> No file " + file_path_c.string());
70  }
71  read_xml(file_path_c.string(), pt_contact);
72 
73  // Done.
74  return build(pt_config, pt_disease, pt_contact);
75 }
76 
77 shared_ptr<Simulator> SimulatorBuilder::build(const ptree& pt_config,
78  const ptree& pt_disease, const ptree& pt_contact) {
79  auto sim = make_shared<Simulator>();
80 
81  // initialize config ptree.
82  sim->m_config_pt = pt_config;
83 
84  // initialize track_index_case policy
85  sim->m_track_index_case = pt_config.get("run.track_index_case", false);
86 
87  // initialize number of threads.
88  sim->m_num_threads = pt_config.get("run.num_threads", -1);
89  sim->m_parallel.setNumThreads(sim->m_num_threads);
90 
91  // initialize calendar.
92  sim->m_calendar = make_shared<Calendar>(pt_config);
93 
94  // get log level.
95  const string l = pt_config.get<string>("run.outputs.log.<xmlattr>.level", "None");
96  sim->m_log_level = isLogMode(l) ? toLogMode(l) : throw runtime_error(
97  string(__func__) + "> Invalid input for LogMode.");
98 
99  // Rng's.
100  int seed = pt_config.get<int>("run.regions.region.rng_seed");
101  sim->m_rng = make_shared<util::Random>(seed);
102 
103  // Build population.
104  ptree pt_pop;
105  if (pt_config.get("run.regions.region.population", "") == "") {
106  pt_pop.put("population.people", pt_config.get<string>("run.regions.region.raw_population"));
107  } else {
108  read_xml((InstallDirs::getDataDir() / pt_config.get<string>("run.regions.region.population")).string(),
109  pt_pop, xml_parser::trim_whitespace);
110  }
111  sim->m_population = PopulationBuilder::build(pt_config, pt_disease, pt_pop, *sim->m_rng);
112  sim->m_config_pop = pt_pop;
113 
114  // Get the next id for new travellers
115  unsigned int max_id = 0;
116  const Population& population = *(sim->m_population.get());
117  for (auto& person: population) {
118  max_id = std::max(uint(max_id), uint(person.getId()));
119  }
120 
121  sim->m_next_id = max_id + 1;
122 
123  // Get the new household id for travellers
124  max_id = 0;
125  for (auto& hh: sim->m_households) {
126  max_id = std::max(uint(max_id), uint(hh.getId()));
127  }
128 
129  sim->m_next_hh_id = max_id + 1;
130 
131  // Initialize districts.
132  initializeDistricts(sim, pt_pop);
133 
134  // Initialize the facilities
135  initializeFacilities(sim, pt_pop);
136 
137  // Initialize clusters.
138  initializeClusters(sim, pt_pop);
139 
140  // initialize disease profile.
141  sim->m_disease_profile.initialize(pt_config, pt_disease);
142 
143  // Initialize contact profiles.
146  Cluster::addContactProfile(ClusterType::Work, ContactProfile(ClusterType::Work, pt_contact));
148  ContactProfile(ClusterType::PrimaryCommunity, pt_contact));
150  ContactProfile(ClusterType::SecondaryCommunity, pt_contact));
151 
152  // Done.
153  return sim;
154 }
155 
156 void SimulatorBuilder::initializeClusters(shared_ptr<Simulator> sim, const boost::property_tree::ptree& pt_config) {
157  // Determine number of clusters.
158  unsigned int max_id_households = 0U;
159  unsigned int max_id_school_clusters = 0U;
160  unsigned int max_id_work_clusters = 0U;
161  unsigned int max_id_primary_community = 0U;
162  unsigned int max_id_secondary_community = 0U;
163  Population& population = *sim->m_population;
164 
165  for (const auto& p : population) {
166  max_id_households = std::max(max_id_households, p.getClusterId(ClusterType::Household));
167  max_id_school_clusters = std::max(max_id_school_clusters, p.getClusterId(ClusterType::School));
168  max_id_work_clusters = std::max(max_id_work_clusters, p.getClusterId(ClusterType::Work));
169  max_id_primary_community = std::max(max_id_primary_community, p.getClusterId(ClusterType::PrimaryCommunity));
170  max_id_secondary_community = std::max(max_id_secondary_community,
171  p.getClusterId(ClusterType::SecondaryCommunity));
172  }
173 
174  // Keep separate id counter to provide a unique id for every cluster.
175  unsigned int cluster_id = 1;
176 
177  string cluster_filename = "";
178 
179  // Get the name of the file with the locations of the clusters
180  boost::optional<const ptree&> cluster_locations_config = pt_config.get_child_optional("population.clusters");
181  if (cluster_locations_config) {
182  cluster_filename = pt_config.get<string>("population.clusters");
183  }
184 
185  map<pair<ClusterType, uint>, GeoCoordinate> locations = initializeLocations(cluster_filename);
186 
187  for (size_t i = 0; i <= max_id_households; i++) {
188  sim->m_households.emplace_back(
189  Cluster(cluster_id, ClusterType::Household, locations[make_pair(ClusterType::Household, i)]));
190  cluster_id++;
191  }
192  for (size_t i = 0; i <= max_id_school_clusters; i++) {
193  sim->m_school_clusters.emplace_back(
194  Cluster(cluster_id, ClusterType::School, locations[make_pair(ClusterType::School, i)]));
195  cluster_id++;
196  }
197  for (size_t i = 0; i <= max_id_work_clusters; i++) {
198  sim->m_work_clusters.emplace_back(
199  Cluster(cluster_id, ClusterType::Work, locations[make_pair(ClusterType::Work, i)]));
200  cluster_id++;
201  }
202  for (size_t i = 0; i <= max_id_primary_community; i++) {
203  sim->m_primary_community.emplace_back(Cluster(cluster_id, ClusterType::PrimaryCommunity,
204  locations[make_pair(ClusterType::PrimaryCommunity, i)]));
205  cluster_id++;
206  }
207  for (size_t i = 0; i <= max_id_secondary_community; i++) {
208  sim->m_secondary_community.emplace_back(Cluster(cluster_id, ClusterType::SecondaryCommunity,
209  locations[make_pair(ClusterType::SecondaryCommunity, i)]));
210  cluster_id++;
211  }
212 
213  // TODO add cities and villages
214 
215  // Cluster id '0' means "not present in any cluster of that type".
216  for (auto& p: population) {
217  const auto hh_id = p.getClusterId(ClusterType::Household);
218  if (hh_id > 0) {
219  sim->m_households[hh_id].addPerson(&p);
220  }
221  const auto sc_id = p.getClusterId(ClusterType::School);
222  if (sc_id > 0) {
223  sim->m_school_clusters[sc_id].addPerson(&p);
224  }
225  const auto wo_id = p.getClusterId(ClusterType::Work);
226  if (wo_id > 0) {
227  sim->m_work_clusters[wo_id].addPerson(&p);
228  }
229  const auto primCom_id = p.getClusterId(ClusterType::PrimaryCommunity);
230  if (primCom_id > 0) {
231  sim->m_primary_community[primCom_id].addPerson(&p);
232  }
233  const auto secCom_id = p.getClusterId(ClusterType::SecondaryCommunity);
234  if (secCom_id > 0) {
235  sim->m_secondary_community[secCom_id].addPerson(&p);
236  }
237  }
238 }
239 
240 void SimulatorBuilder::initializeDistricts(shared_ptr<Simulator> sim, const boost::property_tree::ptree& pt_config) {
241  // Get the name of the file with the locations of the clusters
242  boost::optional<const ptree&> districts_config = pt_config.get_child_optional("population.districts");
243  if (districts_config) {
244  string district_filename = pt_config.get<string>("population.districts");
245  double influence_speed = pt_config.get<double>("population.sphere_of_influence.<xmlattr>.speed");
246  double influence_minimum = pt_config.get<double>("population.sphere_of_influence.<xmlattr>.min");
247  unsigned int influence_size = pt_config.get < unsigned
248  int > ("population.sphere_of_influence.<xmlattr>.size");
249 
250  // Check for the correctness of the file
251  const auto file_path = InstallDirs::getDataDir() /= district_filename;
252  if (!is_regular_file(file_path)) {
253  throw runtime_error(string(__func__)
254  + ">Districts file " + file_path.string() + " not present. Aborting.");
255  }
256 
257  // Open the file
258  boost::filesystem::ifstream districts_file;
259  districts_file.open(file_path.string());
260  if (!districts_file.is_open()) {
261  throw runtime_error(string(__func__)
262  + "> Error opening districts file " + file_path.string());
263  }
264 
265  // Parse the file and fill the map
266  string line;
267  getline(districts_file, line); // step over file header
268 
269  while (getline(districts_file, line)) {
270  auto values = StringUtils::split(line, ",");
271 
272  // Remove the quotes
273  values[1].erase(values[1].begin());
274  values[1].erase(values[1].end() - 1);
275 
276  // Check for duplicates
277  auto search_duplicate = [&](const District& district) { return district.getName() == values[1]; };
278  if (find_if(sim->m_districts.cbegin(), sim->m_districts.cend(), search_duplicate) ==
279  sim->m_districts.cend()) {
280  sim->m_districts.push_back(District(values[1],
281  influence_size,
282  influence_speed,
283  influence_minimum,
284  GeoCoordinate(StringUtils::fromString<double>(values[6]),
285  StringUtils::fromString<double>(values[7]))));
286  }
287  }
288  }
289 }
290 
291 
292 map<pair<ClusterType, uint>, GeoCoordinate> SimulatorBuilder::initializeLocations(string filename) {
293  map<pair<ClusterType, uint>, GeoCoordinate> cluster_locations;
294 
295  if (filename != "") {
296  // Check for the correctness of the file
297  const auto file_path = InstallDirs::getDataDir() /= filename;
298  if (!is_regular_file(file_path)) {
299  throw runtime_error(string(__func__)
300  + ">Cluster location file " + file_path.string() + " not present. Aborting.");
301  }
302 
303  // Open the file
304  boost::filesystem::ifstream locations_file;
305  locations_file.open(file_path.string());
306  if (!locations_file.is_open()) {
307  throw runtime_error(string(__func__)
308  + "> Error opening cluster location file " + file_path.string());
309  }
310 
311  // Parse the file and fill the map
312  string line;
313  getline(locations_file, line); // step over file header
314 
315  while (getline(locations_file, line)) {
316  const auto values = StringUtils::split(line, ",");
317  // NOTE: if the values are invalid, it will be zero/Null due to StringUtils/ClusterType
318  cluster_locations[make_pair(toClusterType(values[1]),
319  StringUtils::fromString<unsigned int>(values[0]))] = GeoCoordinate(
320  StringUtils::fromString<double>(values[2]),
321  StringUtils::fromString<double>(values[3]));
322  }
323  }
324  return cluster_locations;
325 }
326 
327 void SimulatorBuilder::initializeFacilities(shared_ptr<Simulator> sim, const boost::property_tree::ptree& pt_config) {
328  // Get the name of the file with the names of the facilities
329 
330  if (!pt_config.get_child_optional("population.cities"))
331  return;
332 
333  for (auto pot_city : pt_config.get_child("population.cities")) {
334  if (pot_city.first == "city") {
335  ptree city_tree = pot_city.second;
336  for (auto it : city_tree) {
337  if (it.first == "airport") {
338  // Check if the referenced district exists
339  auto find_district = [&](const District& district) { return district.getName() ==
340  pot_city.second.get<string>(
341  "<xmlattr>.name");
342  };
343  auto le_city = find_if(sim->m_districts.begin(), sim->m_districts.end(), find_district);
344 
345  // If the district exists, add the facility
346  if (le_city != sim->m_districts.end()) {
347  le_city->addFacility(it.second.get<string>("<xmlattr>.name"));
348  }
349  }
350  }
351  }
352  }
353 }
354 
355 }
static void initializeDistricts(std::shared_ptr< Simulator > sim, const boost::property_tree::ptree &pt_config)
Initialize the districts, duplicate city names are ignored (only the first occurrence is counted) ...
unsigned int uint
Definition: Influence.h:17
Interface for install directory queries.
static void addContactProfile(ClusterType cluster_type, const ContactProfile &profile)
Add contact profile.
Definition: Cluster.cpp:40
bool isLogMode(const string &s)
Check whether string is name of LogMode value.
Definition: LogMode.cpp:54
Header for the Infector class.
Header file for the Calendar class.
Time Dependent Person DataType.
Definition: NoBehaviour.h:17
Conversion from or to string.
static boost::filesystem::path getDataDir()
Utility method: get path to the directory for data files.
Utilities for the project.
Definition: Infector.h:36
A district is either a city or a village (currently, there is no difference between city and village)...
Definition: District.h:26
Header for the SimulatorBuilder class.
ClusterType toClusterType(const string &s)
Converts a string with name to ClusterType value.
Definition: ClusterType.cpp:64
Header file for the core Population class.
Container for persons in population.
Definition: Population.h:132
static std::shared_ptr< Population > build(const boost::property_tree::ptree &pt_config, const boost::property_tree::ptree &pt_disease, const boost::property_tree::ptree &pt_pop, util::Random &rng)
Initializes a Population: add persons, set immunity, seed infection.
Definition of ClusterType.
STL namespace.
static void initializeClusters(std::shared_ptr< Simulator > sim, const boost::property_tree::ptree &pt_config)
Initialize the clusters.
LogMode toLogMode(const string &s)
Converts a string with name to LogMode value.
Definition: LogMode.cpp:60
Represents a location for social contacts, an group of people.
Definition: Cluster.h:46
static std::shared_ptr< Simulator > build(const boost::property_tree::ptree &pt_config)
Build simulator.
static std::vector< std::string > split(const std::string &str, const std::string &delimiters)
Split a string (in order of occurence) by splitting it on the given delimiters.
Definition: StringUtils.h:49
static std::map< std::pair< ClusterType, uint >, util::GeoCoordinate > initializeLocations(std::string filename)
Initialize the locations (read the from the given file) and return them If the filename is ""...
Header for the core Cluster class.
static void initializeFacilities(std::shared_ptr< Simulator > sim, const boost::property_tree::ptree &pt_config)
Initialize the facilities, duplicate facility names are ignored (only the first occurrence is counted...
Initialize populations.