Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : #ifndef IOFiles_hh_INCLUDED
49 : #define IOFiles_hh_INCLUDED
50 :
51 : // C++ Headers
52 : #include <array>
53 : #include <cassert>
54 : #include <fstream>
55 : #include <iostream>
56 : #include <limits>
57 : #include <ostream>
58 : #include <vector>
59 :
60 : // EnergyPlus Headers
61 : #include <EnergyPlus/EnergyPlus.hh>
62 : #include <EnergyPlus/FileSystem.hh>
63 :
64 : // Third Party Headers
65 : #include <fmt/compile.h>
66 : #include <fmt/format.h>
67 : #include <fmt/os.h>
68 : #include <fmt/ostream.h>
69 : #include <fmt/printf.h>
70 : #include <fmt/ranges.h>
71 : #include <nlohmann/json.hpp>
72 :
73 : namespace {
74 : struct DoubleWrapper
75 : {
76 : // this cannot be marked explicit
77 : // we need the implicit conversion for it to work
78 16801513 : DoubleWrapper(double val) : value(val){};
79 16834942 : operator double() const
80 : {
81 16834942 : return value;
82 : };
83 : DoubleWrapper &operator=(const double &other)
84 : {
85 : value = other;
86 : return *this;
87 : }
88 :
89 : private:
90 : double value;
91 : };
92 : } // namespace
93 :
94 : namespace fmt {
95 : template <> struct formatter<DoubleWrapper>
96 : {
97 : private:
98 : fmt::detail::dynamic_format_specs<char> specs_;
99 : const char *format_str_;
100 : fmt::memory_buffer buffer = fmt::memory_buffer();
101 :
102 : struct null_handler : detail::error_handler
103 : {
104 16834942 : void on_align(align_t)
105 : {
106 16834942 : }
107 0 : void on_sign(sign_t)
108 : {
109 0 : }
110 2537667 : void on_hash()
111 : {
112 2537667 : }
113 : };
114 :
115 3350234 : static constexpr bool should_be_fixed_output(const double value)
116 : {
117 3350234 : return (value >= 0.099999999999999995 || value <= -0.099999999999999995) || (value == 0.0) || (value == -0.0);
118 : }
119 :
120 36666 : static constexpr bool fixed_will_fit(const double value, const int places)
121 : {
122 36666 : if (value < 1.0 && value > -1.0) {
123 8821 : return true;
124 : } else {
125 27845 : return static_cast<int>(std::log10(std::abs(value))) < places;
126 : }
127 : }
128 :
129 200645 : static std::string &zero_pad_exponent(std::string &str)
130 : {
131 : // if necessary, pad the exponent with a 0 to match the old formatting from Objexx
132 200645 : if (str.size() > 3) {
133 200645 : if (!std::isdigit(str[str.size() - 3])) {
134 : // wants a 0 inserted
135 200645 : str.insert(str.size() - 2, "0");
136 : }
137 : }
138 200645 : return str;
139 : }
140 :
141 16834942 : std::string_view spec_builder()
142 : {
143 16834942 : buffer.clear();
144 16834942 : buffer.push_back('{');
145 16834942 : buffer.push_back(':');
146 : // [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
147 :
148 : // [[fill]align]
149 16834942 : switch (specs_.align) {
150 0 : case align_t::left:
151 0 : if (specs_.fill.size()) buffer.append(specs_.fill);
152 0 : buffer.push_back('<');
153 0 : break;
154 0 : case align_t::right:
155 0 : if (specs_.fill.size()) buffer.append(specs_.fill);
156 0 : buffer.push_back('>');
157 0 : break;
158 0 : case align_t::center:
159 0 : if (specs_.fill.size()) buffer.append(specs_.fill);
160 0 : buffer.push_back('^');
161 0 : break;
162 16834942 : case align_t::none:
163 : case align_t::numeric:
164 16834942 : break;
165 0 : default:
166 0 : throw fmt::format_error("Bad alignment");
167 : }
168 :
169 : // [sign]
170 16834942 : switch (specs_.sign) {
171 0 : case sign_t::plus:
172 0 : buffer.push_back('+');
173 0 : break;
174 0 : case sign_t::minus:
175 0 : buffer.push_back('-');
176 0 : break;
177 0 : case sign_t::space:
178 0 : buffer.push_back(' ');
179 0 : break;
180 16834942 : case sign_t::none:
181 16834942 : break;
182 0 : default:
183 0 : throw fmt::format_error("Bad sign");
184 : }
185 :
186 : // [alt]
187 16834942 : if (specs_.alt) {
188 2571234 : buffer.push_back('#');
189 : }
190 :
191 : // [width]
192 16834942 : if (specs_.width >= 0) {
193 16834942 : if (specs_.fill[0] == '0') {
194 0 : buffer.push_back('0');
195 : }
196 16834942 : auto fmt_int = fmt::format_int(specs_.width);
197 16834942 : buffer.append(fmt_int.data(), fmt_int.data() + fmt_int.size());
198 : }
199 :
200 : // [precision]
201 16834942 : if (specs_.precision >= 0) {
202 16834002 : buffer.push_back('.');
203 :
204 16834002 : auto fmt_int = fmt::format_int(specs_.precision);
205 16834002 : buffer.append(fmt_int.data(), fmt_int.data() + fmt_int.size());
206 : }
207 :
208 : // [locale]
209 16834942 : if (specs_.localized) {
210 0 : buffer.push_back('L');
211 : }
212 :
213 : // [type]
214 16834942 : buffer.push_back(specs_.type);
215 :
216 16834942 : buffer.push_back('}');
217 :
218 16834942 : return {buffer.data(), buffer.size()};
219 : }
220 :
221 16834942 : template <typename Context> void handle_specs(Context &ctx)
222 : {
223 16834942 : detail::handle_dynamic_spec<detail::width_checker>(specs_.width, specs_.width_ref, ctx);
224 16834942 : detail::handle_dynamic_spec<detail::precision_checker>(specs_.precision, specs_.precision_ref, ctx);
225 16834942 : }
226 :
227 : public:
228 16834942 : template <typename ParseContext> constexpr auto parse(ParseContext &ctx)
229 : {
230 16834942 : auto begin = ctx.begin(), end = ctx.end();
231 16834942 : format_str_ = begin;
232 16834942 : if (begin == end) return begin;
233 : using handler_type = fmt::detail::dynamic_specs_handler<ParseContext>;
234 16834334 : auto it = fmt::detail::parse_format_specs(begin, end, handler_type(specs_, ctx));
235 16834334 : return it;
236 : }
237 :
238 16834942 : template <typename FormatContext> auto format(const DoubleWrapper &doubleWrapper, FormatContext &ctx)
239 : {
240 4818188 : const auto next_float = [](const double value) {
241 4818188 : if (std::signbit(value)) {
242 1081341 : if (value == -0.0) {
243 0 : return value;
244 : } else {
245 1081341 : return std::nextafter(value, std::numeric_limits<decltype(value)>::lowest());
246 : }
247 : } else {
248 3736847 : if (value == 0.0) {
249 0 : return value;
250 : } else {
251 3736847 : return std::nextafter(value, std::numeric_limits<decltype(value)>::max());
252 : }
253 : }
254 : };
255 :
256 16834942 : double val = doubleWrapper;
257 :
258 16834942 : handle_specs(ctx);
259 16834942 : detail::specs_checker<null_handler> checker(null_handler(), detail::mapped_type_constant<double, FormatContext>::value);
260 16834942 : checker.on_align(specs_.align);
261 16834942 : if (specs_.sign != sign::none) checker.on_sign(specs_.sign);
262 16834942 : if (specs_.alt) checker.on_hash();
263 16834942 : if (specs_.precision >= 0) checker.end_precision();
264 :
265 : // matches Fortran's 'E' format
266 16834942 : if (specs_.type == 'Z') {
267 : // The Fortran 'G' format insists on a leading 0, even though
268 : // that actually means losing data
269 33567 : specs_.type = 'E';
270 :
271 : // 0 pad the end
272 33567 : specs_.alt = true;
273 :
274 33567 : bool initialPrecisionWas1 = false;
275 33567 : if (specs_.precision > 1) {
276 : // reduce the precision to get rounding behavior
277 33567 : --specs_.precision;
278 : } else {
279 : // We need AT LEAST one in precision so we capture a '.' below
280 0 : initialPrecisionWas1 = true;
281 0 : specs_.precision = 1;
282 0 : ++specs_.width;
283 : }
284 :
285 : // multiply by 10 to get the exponent we want
286 33567 : auto str = fmt::format(spec_builder(), val * 10);
287 : // auto str = write_to_string(value * 10);
288 :
289 : // we need "space" to insert our leading 0
290 33567 : if (str.front() != ' ') {
291 0 : str.insert(str.begin(), ' ');
292 : }
293 :
294 33567 : auto begin = std::find(std::begin(str), std::end(str), '.');
295 33567 : if (initialPrecisionWas1) {
296 : // 123.45 => 1.2E+03, except we asked for precision = 1. So we delete the thing after the dot
297 : // and this is why we manually increased the specs_.width by one above
298 0 : str.erase(std::next(begin));
299 : }
300 : // if (begin != std::end(str)) {
301 : // ' -1.2345E15'
302 : // ^
303 33567 : std::swap(*begin, *std::prev(begin));
304 : // ' -.12345E15'
305 : // ^
306 33567 : std::advance(begin, -2);
307 : // ' -.12345E15'
308 : // ^
309 33567 : if (*begin != ' ') {
310 : // found a sign
311 10045 : std::swap(*begin, *std::prev(begin));
312 : // '- .12345E15'
313 : // ^
314 : }
315 : // '-0.12345E15'
316 : // ^
317 33567 : *begin = '0';
318 67134 : return fmt::format_to(ctx.out(), "{}", str);
319 16834942 : } else if (specs_.type == 'S') {
320 : // matches Fortran's 'G', but stripped of whitespace
321 0 : specs_.type = 'N';
322 : // Need to rerun with double wrapper since 'N' is one of our custom ones
323 0 : auto str = fmt::format(spec_builder(), doubleWrapper);
324 :
325 0 : auto strip_whitespace = [](std::string_view const s) -> std::string {
326 0 : if (s.empty()) {
327 0 : return std::string{};
328 : }
329 0 : auto const first = s.find_first_not_of(' ');
330 0 : auto const last = s.find_last_not_of(' ');
331 0 : if ((first == std::string::npos) || (last == std::string::npos)) {
332 0 : return std::string{};
333 : } else {
334 0 : return std::string{s.substr(first, last - first + 1)};
335 : }
336 : };
337 :
338 0 : return fmt::format_to(ctx.out(), "{}", strip_whitespace(str));
339 16801375 : } else if (specs_.type == 'N') {
340 : // matches Fortran's 'G' format
341 :
342 70088 : if (specs_.width == 0 && specs_.precision == -1) {
343 : // Need to rerun with double wrapper since 'N' is one of our custom ones
344 0 : return fmt::format_to(ctx.out(), "{:20N}", doubleWrapper);
345 70088 : } else if (should_be_fixed_output(val) && fixed_will_fit(val, specs_.width - 5)) {
346 36659 : specs_.type = 'F';
347 :
348 : // account for alignment with E formatted
349 36659 : specs_.width -= 4;
350 36659 : if (val == 0.0) {
351 563 : --specs_.precision;
352 36096 : } else if (val < 1.0 && val > -1.0) {
353 : // No adjustment necessary
354 27838 : } else if (specs_.precision == -1) {
355 0 : const auto order_of_magnitude = static_cast<int>(std::log10(std::abs(val)));
356 0 : specs_.precision = specs_.width - (order_of_magnitude + 2);
357 : } else {
358 27838 : const auto order_of_magnitude = static_cast<int>(std::log10(std::abs(val)));
359 27838 : specs_.precision -= (order_of_magnitude + 1);
360 : }
361 :
362 : // if precision adjustment would result in negative, make it 0 to get rounding
363 : // and adjust spacing
364 36659 : if (specs_.precision <= 0) {
365 2 : specs_.width -= 1;
366 2 : specs_.precision = 0;
367 : }
368 :
369 36659 : auto str = fmt::format(spec_builder(), val);
370 :
371 : // When precision hit 0, add . to match Fortran formatting
372 36659 : if (specs_.precision == 0) {
373 : // write the last 4 chars
374 4 : return fmt::format_to(ctx.out(), "{}. ", str);
375 : } else {
376 : // write the last 4 chars
377 73314 : return fmt::format_to(ctx.out(), "{} ", str);
378 : }
379 36659 : } else {
380 : // The Fortran 'G' format insists on a leading 0, even though
381 : // that actually means losing data
382 33429 : specs_.type = 'Z';
383 : // Need to rerun with double wrapper since 'Z' is one of our custom ones
384 100287 : return fmt::format_to(ctx.out(), spec_builder(), doubleWrapper);
385 : }
386 16731287 : } else if (specs_.type == 'R') { // matches RoundSigDigits() behavior
387 : // push the value up a tad to get the same rounding behavior as Objexx
388 2005989 : const auto fixed_output = should_be_fixed_output(val);
389 :
390 2005989 : if (fixed_output) {
391 1894729 : specs_.type = 'F';
392 :
393 1894729 : if (val > 100000.0) {
394 4052 : const auto digits10 = static_cast<int>(std::log10(val));
395 : // we cannot represent this val to the required precision, truncate the floating
396 : // point portion
397 4052 : if (digits10 + specs_.precision >= std::numeric_limits<decltype(val)>::max_digits10) {
398 3 : specs_.precision = 0;
399 3 : spec_builder();
400 : // add '.' to match old RoundSigDigits
401 3 : buffer.push_back('.');
402 3 : std::string_view fmt_buffer(buffer.data(), buffer.size());
403 6 : return fmt::format_to(ctx.out(), fmt_buffer, val);
404 : } else {
405 12147 : return fmt::format_to(ctx.out(), spec_builder(), val);
406 : }
407 : } else {
408 1890677 : if (val == 0.0 || val == -0.0) {
409 965103 : return fmt::format_to(ctx.out(), spec_builder(), 0.0);
410 : } else {
411 : // nudge up to next rounded val
412 4706928 : return fmt::format_to(ctx.out(), spec_builder(), next_float(next_float(next_float(val))));
413 : }
414 : }
415 : } else {
416 111260 : specs_.type = 'E';
417 111260 : auto str = fmt::format(spec_builder(), next_float(val));
418 222520 : return fmt::format_to(ctx.out(), "{}", zero_pad_exponent(str));
419 111260 : }
420 14725298 : } else if (specs_.type == 'T') { // matches TrimSigDigits behavior
421 1274157 : const auto fixed_output = should_be_fixed_output(val);
422 :
423 1274157 : if (fixed_output) {
424 1184772 : const auto magnitude = std::pow(10, specs_.precision);
425 1184772 : const auto adjusted = (val * magnitude) + 0.0001;
426 1184772 : const auto truncated = std::trunc(adjusted) / magnitude;
427 1184772 : specs_.type = 'F';
428 3554316 : return fmt::format_to(ctx.out(), spec_builder(), truncated);
429 : } else {
430 89385 : specs_.type = 'E';
431 89385 : specs_.precision += 2;
432 :
433 : // write the `E` formatted float to a std::string
434 89385 : auto str = fmt::format(spec_builder(), val);
435 89385 : str = zero_pad_exponent(str);
436 :
437 : // Erase last 2 numbers to truncate the value
438 89385 : const auto E_itr = std::find(begin(str), end(str), 'E');
439 89385 : if (E_itr != str.end()) {
440 89385 : str.erase(std::prev(E_itr, 2), E_itr);
441 : }
442 :
443 178770 : return fmt::format_to(ctx.out(), "{}", str);
444 89385 : }
445 : }
446 40353423 : return fmt::format_to(ctx.out(), spec_builder(), val);
447 : }
448 : };
449 : } // namespace fmt
450 :
451 : namespace EnergyPlus {
452 :
453 : // Forward declarations
454 : struct EnergyPlusData;
455 :
456 : enum class FormatSyntax
457 : {
458 : Invalid = -1,
459 : Fortran,
460 : FMT,
461 : Printf,
462 : Num
463 : };
464 :
465 : inline constexpr bool is_fortran_syntax(const std::string_view format_str)
466 : {
467 : bool within_fmt_str = false;
468 : for (auto const c : format_str) {
469 : switch (c) {
470 : case '{':
471 : within_fmt_str = true;
472 : break;
473 : case '}':
474 : within_fmt_str = false;
475 : break;
476 : case 'R':
477 : case 'S':
478 : case 'N':
479 : case 'Z':
480 : case 'T':
481 : if (within_fmt_str) {
482 : return true;
483 : } else {
484 : break;
485 : }
486 : default:
487 : break;
488 : }
489 : }
490 : return false;
491 : }
492 :
493 : class InputOutputFile;
494 : template <FormatSyntax formatSyntax = FormatSyntax::Fortran, typename... Args>
495 : void print(InputOutputFile &outputFile, std::string_view format_str, Args &&... args);
496 :
497 : inline constexpr FormatSyntax check_syntax(const std::string_view format_str)
498 : {
499 : if (is_fortran_syntax(format_str)) {
500 : return FormatSyntax::Fortran;
501 : } else {
502 : return FormatSyntax::FMT;
503 : }
504 : }
505 :
506 : class InputFile
507 : {
508 : public:
509 : template <typename Type> struct ReadResult
510 : {
511 2916799 : ReadResult(Type data_, bool eof_, bool good_) : data{std::move(data_)}, eof{eof_}, good{good_}
512 : {
513 2916799 : }
514 :
515 : // Update the current eof/good state from the incoming value
516 : // but only update the `data` member if the state is good
517 : // The idea is to keep consistency with the operator>> that was used
518 : // from gio
519 2246114 : void update(ReadResult &&other)
520 : {
521 2246114 : eof = other.eof;
522 2246114 : good = other.good;
523 2246114 : if (good) {
524 2246114 : data = std::move(other.data);
525 : }
526 2246114 : }
527 :
528 : Type data;
529 : bool eof;
530 : bool good;
531 : };
532 :
533 : void close();
534 :
535 : // This is different from istream::good(), which is false if EOF is true while there were no errors (happens when no EOL at end of file)
536 : // this operate like `operator bool(istream& is)` <=> `!is.bad() && !is.fail()`
537 : bool good() const noexcept;
538 :
539 : bool is_open() const noexcept;
540 :
541 : void backspace() noexcept;
542 :
543 : std::string error_state_to_string() const;
544 :
545 : // opens the file if it is not currently open and returns
546 : // a reference back to itself
547 : InputFile &ensure_open(EnergyPlusData &state, const std::string &caller, bool output_to_file = true);
548 : std::istream::iostate rdstate() const noexcept;
549 :
550 : fs::path filePath;
551 : void open(bool = false, bool = true);
552 : std::fstream::pos_type position() const noexcept;
553 :
554 1231 : void rewind() noexcept
555 : {
556 1231 : if (is) {
557 1231 : is->clear(); // clear potentially failbit and badbit (seekg would only clear eofbit)
558 1231 : is->seekg(0, std::ios::beg);
559 : }
560 1231 : }
561 :
562 : ReadResult<std::string> readLine() noexcept;
563 :
564 6111 : template <typename T> ReadResult<T> read() noexcept
565 : {
566 6111 : if (is) {
567 : T result;
568 6111 : *is >> result;
569 : // Use operator bool, see ReadResult::good() docstring
570 6111 : return ReadResult<T>{result, is->eof(), bool(is)};
571 : } else {
572 0 : return ReadResult<T>{T{}, true, false};
573 : }
574 : }
575 :
576 : std::string readFile();
577 :
578 : nlohmann::json readJSON();
579 :
580 : explicit InputFile(fs::path FilePath);
581 :
582 : private:
583 : std::uintmax_t file_size{};
584 : std::unique_ptr<std::istream> is;
585 : friend class IOFiles;
586 : };
587 :
588 : class InputOutputFile
589 : {
590 : public:
591 : fs::path filePath;
592 : bool defaultToStdOut = false;
593 :
594 : void close();
595 : void del();
596 : bool good() const;
597 :
598 : // opens the file if it is not currently open and returns
599 : // a reference back to itself
600 : InputOutputFile &ensure_open(EnergyPlusData &state, const std::string &caller, bool output_to_file = true);
601 :
602 : void open(const bool forAppend = false, bool output_to_file = true);
603 : std::fstream::pos_type position() const noexcept;
604 : std::vector<std::string> getLines();
605 : void open_as_stringstream();
606 : std::string get_output();
607 : void flush();
608 : explicit InputOutputFile(fs::path FilePath, const bool DefaultToStdOut = false);
609 :
610 : private:
611 : std::unique_ptr<std::iostream> os;
612 : bool print_to_dev_null = false;
613 : template <FormatSyntax, typename... Args> friend void print(InputOutputFile &outputFile, std::string_view format_str, Args &&... args);
614 : friend class IOFiles;
615 : };
616 :
617 : template <typename FileType> struct IOFilePath
618 : {
619 : fs::path filePath;
620 1415 : FileType open(EnergyPlusData &state, const std::string &caller, bool output_to_file = true)
621 : {
622 1415 : FileType file{filePath};
623 1415 : file.ensure_open(state, caller, output_to_file);
624 1415 : return file;
625 0 : }
626 2833 : FileType try_open(bool output_to_file = true)
627 : {
628 2833 : FileType file{filePath};
629 2833 : file.open(false, output_to_file);
630 2833 : return file;
631 0 : }
632 : };
633 :
634 : using InputOutputFilePath = IOFilePath<InputOutputFile>;
635 : using InputFilePath = IOFilePath<InputFile>;
636 :
637 : struct JsonOutputFilePaths
638 : {
639 : fs::path outputJsonFilePath;
640 : fs::path outputTSHvacJsonFilePath;
641 : fs::path outputTSZoneJsonFilePath;
642 : fs::path outputTSJsonFilePath;
643 : fs::path outputYRJsonFilePath;
644 : fs::path outputMNJsonFilePath;
645 : fs::path outputDYJsonFilePath;
646 : fs::path outputHRJsonFilePath;
647 : fs::path outputSMJsonFilePath;
648 : fs::path outputCborFilePath;
649 : fs::path outputTSHvacCborFilePath;
650 : fs::path outputTSZoneCborFilePath;
651 : fs::path outputTSCborFilePath;
652 : fs::path outputYRCborFilePath;
653 : fs::path outputMNCborFilePath;
654 : fs::path outputDYCborFilePath;
655 : fs::path outputHRCborFilePath;
656 : fs::path outputSMCborFilePath;
657 : fs::path outputMsgPackFilePath;
658 : fs::path outputTSHvacMsgPackFilePath;
659 : fs::path outputTSZoneMsgPackFilePath;
660 : fs::path outputTSMsgPackFilePath;
661 : fs::path outputYRMsgPackFilePath;
662 : fs::path outputMNMsgPackFilePath;
663 : fs::path outputDYMsgPackFilePath;
664 : fs::path outputHRMsgPackFilePath;
665 : fs::path outputSMMsgPackFilePath;
666 : };
667 :
668 : class IOFiles
669 : {
670 : public:
671 : struct OutputControl
672 : {
673 796 : OutputControl() = default;
674 :
675 : void getInput(EnergyPlusData &state);
676 : bool writeTabular(EnergyPlusData &state);
677 :
678 : bool csv = false;
679 : bool mtr = true;
680 : bool eso = true;
681 : bool eio = true;
682 : bool audit = true;
683 : bool spsz = true;
684 : bool zsz = true;
685 : bool ssz = true;
686 : bool dxf = true;
687 : bool bnd = true;
688 : bool rdd = true;
689 : bool mdd = true;
690 : bool mtd = true;
691 : bool end = true;
692 : bool shd = true;
693 : bool dfs = true;
694 : bool glhe = true;
695 : bool delightin = true;
696 : bool delighteldmp = true;
697 : bool delightdfdmp = true;
698 : bool edd = true;
699 : bool dbg = true;
700 : bool perflog = true;
701 : bool sln = true;
702 : bool sci = true;
703 : bool wrl = true;
704 : bool screen = true;
705 : bool tarcog = true;
706 : bool extshd = true;
707 : bool json = true;
708 : bool tabular = true;
709 : bool sqlite = true;
710 : };
711 :
712 : OutputControl outputControl;
713 :
714 : InputOutputFile audit{"eplusout.audit"};
715 : InputOutputFile eio{"eplusout.eio"};
716 : InputOutputFile eso{"eplusout.eso"}; // (hourly data only)
717 :
718 : InputOutputFile zsz{""};
719 : fs::path outputZszCsvFilePath{"epluszsz.csv"};
720 : fs::path outputZszTabFilePath{"epluszsz.tab"};
721 : fs::path outputZszTxtFilePath{"epluszsz.txt"};
722 :
723 : InputOutputFile spsz{""};
724 : fs::path outputSpszCsvFilePath{"eplusspsz.csv"};
725 : fs::path outputSpszTabFilePath{"eplusspsz.tab"};
726 : fs::path outputSpszTxtFilePath{"eplusspsz.txt"};
727 :
728 : InputOutputFile ssz{""};
729 : fs::path outputSszCsvFilePath{"eplusssz.csv"};
730 : fs::path outputSszTabFilePath{"eplusssz.tab"};
731 : fs::path outputSszTxtFilePath{"eplusssz.txt"};
732 :
733 : InputOutputFile map{""};
734 : fs::path outputMapCsvFilePath{"eplusmap.csv"};
735 : fs::path outputMapTabFilePath{"eplusmap.tab"};
736 : fs::path outputMapTxtFilePath{"eplusmap.txt"};
737 :
738 : InputOutputFile mtr{"eplusout.mtr"};
739 : InputOutputFile bnd{"eplusout.bnd"};
740 : InputOutputFile rdd{"eplusout.rdd"};
741 : InputOutputFile mdd{"eplusout.mdd"};
742 :
743 : InputOutputFile debug{"eplusout.dbg"};
744 :
745 : InputOutputFile dfs{"eplusout.dfs"};
746 :
747 : InputOutputFilePath sln{"eplusout.sln"};
748 : InputOutputFilePath dxf{"eplusout.dxf"};
749 : InputOutputFilePath sci{"eplusout.sci"};
750 : InputOutputFilePath wrl{"eplusout.wrl"};
751 :
752 : InputOutputFilePath delightIn{"eplusout.delightin"};
753 :
754 : InputOutputFile mtd{"eplusout.mtd"};
755 : InputOutputFile edd{"eplusout.edd", true}; // write to stdout if no file never opened
756 : InputOutputFile shade{"eplusshading.csv"};
757 :
758 : InputOutputFile csv{"eplusout.csv"};
759 : InputOutputFile mtr_csv{"eplusmtr.csv"};
760 :
761 : InputOutputFilePath screenCsv{"eplusscreen.csv"};
762 : InputOutputFilePath endFile{"eplusout.end"};
763 :
764 : InputFilePath iniFile{"EnergyPlus.ini"};
765 :
766 : InputFilePath outputDelightEldmpFilePath{"eplusout.delighteldmp"};
767 : InputFilePath outputDelightDfdmpFilePath{"eplusout.delightdfdmp"};
768 :
769 : // for transient uses of weather files
770 : // also, keeper of the currently set input weather file name
771 : InputFilePath inputWeatherFilePath{""};
772 :
773 : // for the persistent weather simulation, using the EPW
774 : // uses the file name set in `inputWeatherFilePath`
775 : InputFile inputWeatherFile{""};
776 :
777 : InputFilePath TempFullFilePath{""};
778 : InputFilePath inStatFilePath{""};
779 :
780 : fs::path outputErrFilePath{"eplusout.err"};
781 : std::unique_ptr<std::ostream> err_stream;
782 :
783 : JsonOutputFilePaths json; // Internal streams used for json outputs
784 :
785 : void flushAll(); // For RunningEnergyPlusViaAPI only
786 : };
787 :
788 : class SharedFileHandle
789 : {
790 : std::shared_ptr<InputOutputFile> file;
791 3322 : InputOutputFile *ptr()
792 : {
793 3322 : if (!file) {
794 9 : file = std::make_shared<InputOutputFile>("");
795 : }
796 :
797 3322 : return file.get();
798 : }
799 :
800 : public:
801 3295 : InputOutputFile &operator*()
802 : {
803 3295 : return *ptr();
804 : }
805 :
806 27 : InputOutputFile *operator->()
807 : {
808 27 : return ptr();
809 : }
810 : };
811 :
812 6774997 : template <typename... Args> void vprint(std::ostream &os, std::string_view format_str, const Args &... args)
813 : {
814 : // assert(os.good());
815 13549994 : auto buffer = fmt::memory_buffer();
816 : try {
817 6774997 : fmt::format_to(std::back_inserter(buffer), format_str, args...);
818 0 : } catch (const fmt::format_error &) {
819 0 : throw EnergyPlus::FatalError(fmt::format("Error with format, '{}', passed {} args", format_str, sizeof...(Args)));
820 : }
821 6774997 : os.write(buffer.data(), buffer.size());
822 6774997 : }
823 :
824 32740151 : template <typename... Args> std::string vprint(std::string_view format_str, const Args &... args)
825 : {
826 65480302 : auto buffer = fmt::memory_buffer();
827 : try {
828 32740151 : fmt::format_to(std::back_inserter(buffer), format_str, args...);
829 0 : } catch (const fmt::format_error &) {
830 0 : throw EnergyPlus::FatalError(fmt::format("Error with format, '{}', passed {} args", format_str, sizeof...(Args)));
831 : }
832 65480302 : return fmt::to_string(buffer);
833 32740151 : }
834 :
835 : // Uses lib {fmt} (which has been accepted for C++20)
836 : // Formatting syntax guide is here: https://fmt.dev/latest/syntax.html
837 : // The syntax is similar to printf, but uses {} to indicate parameters to be formatted
838 : // you must escape any {} that you want with {}, like `{{}}`
839 : //
840 : // Defines a custom formatting type 'R' (round_ which chooses between `E` and `G` depending
841 : // on the value being printed.
842 : // This is necessary for parity with the old "RoundSigDigits" utility function
843 : //
844 : // Defines a custom formatting type 'S' that behaves like Fortran's G type, but stripped of whitespace
845 : // 'S' was chosen for "Stripped". It is implemented in terms of 'N'
846 : //
847 : // Defines a custom formatting type 'N' that behaves like Fortran's G type.
848 : // 'N' was chosen for "Number"
849 : //
850 : // Defines a custom formatting type 'Z' that behaves like Fortran's E type.
851 : // 'Z' was chosen because Fortran's 'E' format always starts with a Zero
852 : //
853 : // Defines a custom formatting type 'T' that that truncates the value
854 : // to match the behavior of TrimSigDigits utility function
855 : //
856 :
857 : namespace {
858 6774997 : template <typename... Args> void print_fortran_syntax(std::ostream &os, std::string_view format_str, const Args &... args)
859 : {
860 6774997 : EnergyPlus::vprint<std::conditional_t<std::is_same_v<double, Args>, DoubleWrapper, Args>...>(os, format_str, args...);
861 6774997 : }
862 :
863 32740151 : template <typename... Args> std::string format_fortran_syntax(std::string_view format_str, const Args &... args)
864 : {
865 32740151 : return EnergyPlus::vprint<std::conditional_t<std::is_same_v<double, Args>, DoubleWrapper, Args>...>(format_str, args...);
866 : }
867 : } // namespace
868 :
869 : template <FormatSyntax formatSyntax = FormatSyntax::Fortran, typename... Args>
870 : void print(std::ostream &os, std::string_view format_str, Args &&... args)
871 : {
872 : if constexpr (formatSyntax == FormatSyntax::Fortran) {
873 : print_fortran_syntax(os, format_str, args...);
874 : } else if constexpr (formatSyntax == FormatSyntax::FMT) {
875 : fmt::print(os, format_str, std::forward<Args>(args)...);
876 : } else {
877 : static_assert(!(formatSyntax == FormatSyntax::Fortran || formatSyntax == FormatSyntax::FMT), "Invalid FormatSyntax selection");
878 : }
879 : }
880 :
881 23698261 : template <FormatSyntax formatSyntax, typename... Args> void print(InputOutputFile &outputFile, std::string_view format_str, Args &&... args)
882 : {
883 47396522 : auto *outputStream = [&]() -> std::ostream * {
884 23698261 : if (outputFile.os) {
885 23698261 : return outputFile.os.get();
886 : } else {
887 0 : if (outputFile.defaultToStdOut) {
888 0 : return &std::cout;
889 : } else {
890 0 : assert(outputFile.os);
891 0 : return nullptr;
892 : }
893 : }
894 23698261 : }();
895 : if constexpr (formatSyntax == FormatSyntax::Fortran) {
896 6774997 : print_fortran_syntax(*outputStream, format_str, args...);
897 : } else if constexpr (formatSyntax == FormatSyntax::FMT) {
898 16923264 : fmt::print(*outputStream, format_str, std::forward<Args>(args)...);
899 : } else {
900 : static_assert(!(formatSyntax == FormatSyntax::Fortran || formatSyntax == FormatSyntax::FMT), "Invalid FormatSyntax selection");
901 : }
902 23698261 : }
903 :
904 34736945 : template <FormatSyntax formatSyntax = FormatSyntax::Fortran, typename... Args> std::string format(std::string_view format_str, Args &&... args)
905 : {
906 : if constexpr (formatSyntax == FormatSyntax::Fortran) {
907 32740151 : return format_fortran_syntax(format_str, args...);
908 : } else if constexpr (formatSyntax == FormatSyntax::FMT) {
909 3993588 : return fmt::format(format_str, std::forward<Args>(args)...);
910 : } else if constexpr (formatSyntax == FormatSyntax::Printf) {
911 : return fmt::sprintf(format_str, std::forward<Args>(args)...);
912 : }
913 : }
914 :
915 : } // namespace EnergyPlus
916 :
917 : // extern template the most commonly used format function calls
918 : // to save on compilation time. They will be explicitly instantiated
919 : // in IOFiles.cc
920 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int>(std::string_view, int &&);
921 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const char *const &>(std::string_view, const char *const &);
922 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, std::string &>(std::string_view, int &, std::string &);
923 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, std::string &, std::string &, double &>(
924 : std::string_view, std::string &, std::string &, std::string &, double &);
925 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const std::string_view &>(std::string_view,
926 : const std::string_view &);
927 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const std::string_view &, std::string &>(std::string_view,
928 : const std::string_view &,
929 : std::string &);
930 : extern template std::string
931 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, double &, double &>(std::string_view, std::string &, double &, double &);
932 : extern template std::string
933 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, std::string &, int &>(std::string_view, std::string &, std::string &, int &);
934 : extern template std::string
935 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &, double &, double &>(std::string_view, double &, double &, double &);
936 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &, std::string &>(std::string_view, double &, std::string &);
937 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &>(std::string_view, std::string &);
938 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const int &, int &>(std::string_view, const int &, int &);
939 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double>(std::string_view, double &&);
940 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, int &>(std::string_view, int &, int &);
941 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const double &>(std::string_view, const double &);
942 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, int &>(std::string_view, std::string &, int &);
943 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, std::string &, double &>(std::string_view,
944 : std::string &,
945 : std::string &,
946 : double &);
947 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, double &, std::string &, double &>(
948 : std::string_view, std::string &, double &, std::string &, double &);
949 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const int &>(std::string_view, const int &);
950 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, const std::string &, std::string &>(std::string_view,
951 : int &,
952 : const std::string &,
953 : std::string &);
954 : extern template std::string
955 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, int &, const std::string &>(std::string_view, int &, int &, const std::string &);
956 : extern template std::string
957 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, int &, std::string_view &>(std::string_view, int &, int &, std::string_view &);
958 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, std::string_view &, std::string &>(std::string_view,
959 : int &,
960 : std::string_view &,
961 : std::string &);
962 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &, double &>(std::string_view, double &, double &);
963 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &>(std::string_view, int &);
964 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, double &>(std::string_view, std::string &, double &);
965 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &>(std::string_view, double &);
966 :
967 : #endif
|