WaSH Docs
io.hpp
1 
16 #pragma once
17 
18 #include <iostream>
19 #include <filesystem>
20 #include <fstream>
21 #include <stdint.h>
22 #include <limits.h>
23 #include <unordered_map>
24 
25 // Only need to include MPI headers with the MPI supporting implementations (> CSTONE)
26 #if defined WASH_CSTONE || defined WASH_WONE
27 #include <mpi.h>
28 #endif
29 
30 #include "wash.hpp"
31 #include "vector.hpp"
32 #include "particle_data.hpp"
33 
34 #if SIZE_MAX == UCHAR_MAX
35  #define MPI_SIZE_T MPI_UNSIGNED_CHAR
36 #elif SIZE_MAX == USHRT_MAX
37  #define MPI_SIZE_T MPI_UNSIGNED_SHORT
38 #elif SIZE_MAX == UINT_MAX
39  #define MPI_SIZE_T MPI_UNSIGNED
40 #elif SIZE_MAX == ULONG_MAX
41  #define MPI_SIZE_T MPI_UNSIGNED_LONG
42 #elif SIZE_MAX == ULLONG_MAX
43  #define MPI_SIZE_T MPI_UNSIGNED_LONG_LONG
44 #else
45  #error "Unknown size_t size"
46 #endif
47 
48 namespace fs = std::filesystem;
49 
50 namespace wash {
51 
52 namespace io {
53 
54  const std::string get_simulation_name();
55 
56  const std::string get_output_name();
57 
58  struct SimulationData {
59  // Number of particles (rows) in the data
60  size_t particle_count;
61  // `particle_count * sum(dim[labels[i]])` doubles representing particle data
62  // each row is a particle, columns are the property/force data
63  std::vector<double> data;
64  // labels for each particle property/force. Should contain global IDs and all predeined properties
65  std::vector<std::string> labels;
66  // dimension for each label
67  std::vector<unsigned short> dim;
68 
69  friend std::ostream& operator<<(std::ostream& os, const SimulationData& data) {
70  os << "SimulationData[ count=" << data.particle_count << ", data=" << data.data.size() << ", ";
71  os << " labels=";
72  for (int i = 0; i < data.labels.size(); i++) {
73  os << "(" << data.labels.at(i) << "," << data.dim.at(i) << "),";
74  }
75  os << "]";
76 
77  return os;
78  }
79  };
80 
81  // Return a SimulationData struct holding a copy of all data at that point.
82  SimulationData copy_simulation_data();
83 
87  class IOManager {
88  public:
89  using WriterFuncT = std::function<int(const io::IOManager&, const SimulationData&, const size_t)>;
90  static const std::unordered_map<std::string, std::string> label_map;
91  private:
92  std::string path;
93  WriterFuncT writer;
94  size_t output_nth;
95 
96  size_t rank;
97  size_t size;
98  bool gather;
99 
100  bool do_timings;
101 
102  SimulationData data;
103  public:
104 
105  IOManager(const std::string format, WriterFuncT writer, const size_t nth, const size_t rank = 0, const size_t size = 1, const bool timings = true) : writer(writer), output_nth(nth), rank(rank), size(size), do_timings(timings) {
106  path = "./out/" + get_simulation_name() + std::string("/");
107  std::cout << "IO Manager: Output Path: " << path << "; Type: " << format << "; Rank " << rank << "; of " << size << ";" << std::endl;
108 
109  if (!fs::exists(path)) {
110  try {
111  fs::create_directories(path);
112  } catch (const std::exception& e) {
113  std::cerr << "Error creating directory: " << e.what() << std::endl;
114  throw std::runtime_error("Error initialising IO manager: Creating Output directory");
115  }
116  }
117 
118  if (do_timings) {
119  new_timings();
120  }
121  }
122 
123  IOManager(const std::string format, WriterFuncT writer) : IOManager(format, writer, 1) {}
124 
125  IOManager(const std::string format, WriterFuncT writer, const size_t rank, const size_t size) : IOManager(format, writer, 1, rank, size) {}
126 
127  IOManager();
128 
129  const std::string get_path() const {
130  return this->path;
131  }
132 
133  size_t get_rank() const {
134  return this->rank;
135  }
136 
137  size_t get_size() const {
138  return this->size;
139  }
140 
141  void set_gather(bool value = true) {
142  this->gather = value;
143  }
144 
148  const std::string& expand_label(const std::string& label) const {
149  auto find = label_map.find(label);
150  if (find != label_map.end()) {
151  return find->second;
152  }
153 
154  return label;
155  }
156 
165  data = copy_simulation_data();
166 #if defined WASH_WSER || defined WASH_WISB || defined WASH_WEST
167  // Just return the copied data in non-MPI capable implementations
168  return data;
169 #elif defined WASH_CSTONE || defined WASH_WONE
170  // If we're not running in more than one rank, or not using gather then just return
171  if (size == 1 || !gather) {
172  return data;
173  // Else we need to gather from all ranks to write the data out from the 1st rank
174  } else {
175  std::vector<int> recv_counts(size, 1);
176  std::vector<int> displs (size);
177  for (size_t i = 0 ; i < size; i++) { // Displace each element `i` from start
178  displs[i] = i;
179  }
180 
181  size_t particle_counts[size];
182  MPI_Gatherv(&data.particle_count, 1, MPI_SIZE_T, particle_counts, recv_counts.data(), displs.data(), MPI_SIZE_T, 0, MPI_COMM_WORLD);
183 
184  // Have to downcast here for MPI - only accepts an int.
185  int int_particle_counts[size];
186  unsigned sim_particle_count = 0;
187  for (size_t idx = 0; idx < size; idx++) {
188  int_particle_counts[idx] = particle_counts[idx];
189  sim_particle_count += particle_counts[idx];
190  }
191 
192  int total_width = 0;
193  for (size_t idx = 0; idx < data.labels.size(); idx++) {
194  total_width += data.dim[idx];
195  }
196 
197  // Row = particle_data, cols = force/property in labels order
198  std::vector<double> sim_data(sim_particle_count * total_width);
199 
200  std::vector<int> send_sizes(size);
201  for (size_t i = 0; i < size; i++) {
202  send_sizes[i] = total_width * int_particle_counts[i];
203  }
204 
205  std::vector<int> displs_2(size);
206  displs_2[0] = 0;
207  for (size_t i = 1; i < size; i++) {
208  displs_2[i] = displs_2[i-1] + send_sizes[i];
209  }
210 
211  MPI_Gatherv(data.data.data(), data.data.size(), MPI_DOUBLE, sim_data.data(), send_sizes.data(), displs_2.data(), MPI_DOUBLE, 0, MPI_COMM_WORLD );
212 
213  if (rank == 0) {
214  return SimulationData { .particle_count = sim_particle_count, .data = sim_data, .labels = data.labels, .dim = data.dim };
215  } else {
216  return data;
217  }
218  }
219 #endif
220  }
221 
227  void write_iteration(const size_t iteration) {
228  if (writer && iteration % this->output_nth == 0 && this->path != "") {
229  auto sim_data = get_simulation_data();
230  if (gather && rank != 0) { // Don't write if we're using the gather and not rnk 0
231  return;
232  }
233 
234  writer(*this, sim_data, iteration);
235  }
236  }
237 
244  void write_timings(const std::string& event_name, const int tag, const int64_t time_taken) const {
245  if (do_timings) {
246  std::string fpath = (new std::string(this->path))->append(get_output_name() + ".timings.csv");
247 
248  std::ios_base::openmode mode = std::ofstream::app;
249  std::ofstream outputFile(fpath, mode);
250 
251  outputFile << event_name << "," << tag << "," << time_taken << "," << rank << "," << size << std::endl;
252 
253  outputFile.close();
254  }
255  }
256 
260  void new_timings() {
261  try {
262 #if defined WASH_CSTONE || defined WASH_WONE
263  if (rank != 0) { // Only perform this operation on the primary rank.
264  return;
265  }
266 #endif
267  std::string fpath = (new std::string(this->path))->append(get_output_name() + ".timings.csv");
268 
269  if (fs::exists(fpath)) {
270  fs::path old_fpath = this->path + get_output_name() + ".old.timings.csv";
271  fs::rename(fpath, old_fpath);
272  }
273  } catch (const std::exception& ex) {
274  std::cerr << "Error trying to create timings output " << ex.what() << std::endl;
275  throw ex;
276  }
277  }
278  };
279 
280  IOManager::WriterFuncT return_writer(const std::string format);
281 
282 }
283 
294  io::IOManager create_io(const std::string format, const size_t output_nth, const bool use_gather = false, const size_t rank = 0, const size_t size = 1, const bool timings = true);
295 
301  std::vector<double> copy_variables();
302 
303  // std::vector<std::string> get_force_scalars_names();
304 
305  // std::vector<std::string> get_force_vectors_names();
306 
307  std::vector<std::string> get_variables_names();
308 }
void new_timings()
Clear the timings output file.
Definition: io.hpp:260
Manages the IO options for the simulation.
Definition: io.hpp:87
std::vector< double > copy_variables()
Copy the variables of the simulation.
Definition: wash.cpp:399
const SimulationData get_simulation_data()
Copies the simulation data at the current point of the simulation.
Definition: io.hpp:164
void write_iteration(const size_t iteration)
Dispatches an iteration call to the writer based on the iteration number.
Definition: io.hpp:227
TODO: Consider having this as a private header in WISB/WS2ST/etc implementations. ...
Definition: ascii.hpp:5
const std::string & expand_label(const std::string &label) const
Helper function to expand a label if an expansion exists.
Definition: io.hpp:148
The public facing API for all Wash programs to be written with.
Definition: io.hpp:58
io::IOManager create_io(const std::string format, const size_t output_nth, const bool use_gather=false, const size_t rank=0, const size_t size=1, const bool timings=true)
Set-up the IO options for the simulation.
Definition: io.cpp:59
void write_timings(const std::string &event_name, const int tag, const int64_t time_taken) const
Write a timing even out to a file.
Definition: io.hpp:244