Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, 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 : // clang-format off
79 17311186 : DoubleWrapper(double val) : value(val) {};
80 : // clang-format on
81 17344837 : operator double() const
82 : {
83 17344837 : return value;
84 : };
85 : DoubleWrapper &operator=(const double &other)
86 : {
87 : value = other;
88 : return *this;
89 : }
90 :
91 : private:
92 : double value;
93 : };
94 : } // namespace
95 :
96 : namespace fmt {
97 : template <> struct formatter<DoubleWrapper>
98 : {
99 : private:
100 : fmt::detail::dynamic_format_specs<char> specs_;
101 : const char *format_str_;
102 : fmt::memory_buffer buffer = fmt::memory_buffer();
103 :
104 : struct null_handler : detail::error_handler
105 : {
106 17344837 : void on_align(align_t)
107 : {
108 17344837 : }
109 0 : void on_sign(sign_t)
110 : {
111 0 : }
112 2629495 : void on_hash()
113 : {
114 2629495 : }
115 : };
116 :
117 3414317 : static constexpr bool should_be_fixed_output(const double value)
118 : {
119 3414317 : return (value >= 0.099999999999999995 || value <= -0.099999999999999995) || (value == 0.0) || (value == -0.0);
120 : }
121 :
122 36929 : static constexpr bool fixed_will_fit(const double value, const int places)
123 : {
124 36929 : if (value < 1.0 && value > -1.0) {
125 8882 : return true;
126 : } else {
127 28047 : return static_cast<int>(std::log10(std::abs(value))) < places;
128 : }
129 : }
130 :
131 204707 : static std::string &zero_pad_exponent(std::string &str)
132 : {
133 : // if necessary, pad the exponent with a 0 to match the old formatting from Objexx
134 204707 : if (str.size() > 3) {
135 204707 : if (!std::isdigit(str[str.size() - 3])) {
136 : // wants a 0 inserted
137 204707 : str.insert(str.size() - 2, "0");
138 : }
139 : }
140 204707 : return str;
141 : }
142 :
143 17344837 : std::string_view spec_builder()
144 : {
145 17344837 : buffer.clear();
146 17344837 : buffer.push_back('{');
147 17344837 : buffer.push_back(':');
148 : // [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
149 :
150 : // [[fill]align]
151 17344837 : switch (specs_.align) {
152 0 : case align_t::left:
153 0 : if (specs_.fill.size()) {
154 0 : buffer.append(specs_.fill);
155 : }
156 0 : buffer.push_back('<');
157 0 : break;
158 0 : case align_t::right:
159 0 : if (specs_.fill.size()) {
160 0 : buffer.append(specs_.fill);
161 : }
162 0 : buffer.push_back('>');
163 0 : break;
164 0 : case align_t::center:
165 0 : if (specs_.fill.size()) {
166 0 : buffer.append(specs_.fill);
167 : }
168 0 : buffer.push_back('^');
169 0 : break;
170 17344837 : case align_t::none:
171 : case align_t::numeric:
172 17344837 : break;
173 0 : default:
174 0 : throw fmt::format_error("Bad alignment");
175 : }
176 :
177 : // [sign]
178 17344837 : switch (specs_.sign) {
179 0 : case sign_t::plus:
180 0 : buffer.push_back('+');
181 0 : break;
182 0 : case sign_t::minus:
183 0 : buffer.push_back('-');
184 0 : break;
185 0 : case sign_t::space:
186 0 : buffer.push_back(' ');
187 0 : break;
188 17344837 : case sign_t::none:
189 17344837 : break;
190 0 : default:
191 0 : throw fmt::format_error("Bad sign");
192 : }
193 :
194 : // [alt]
195 17344837 : if (specs_.alt) {
196 2663274 : buffer.push_back('#');
197 : }
198 :
199 : // [width]
200 17344837 : if (specs_.width >= 0) {
201 17344837 : if (specs_.fill[0] == '0') {
202 0 : buffer.push_back('0');
203 : }
204 17344837 : auto fmt_int = fmt::format_int(specs_.width);
205 17344837 : buffer.append(fmt_int.data(), fmt_int.data() + fmt_int.size());
206 : }
207 :
208 : // [precision]
209 17344837 : if (specs_.precision >= 0) {
210 17343867 : buffer.push_back('.');
211 :
212 17343867 : auto fmt_int = fmt::format_int(specs_.precision);
213 17343867 : buffer.append(fmt_int.data(), fmt_int.data() + fmt_int.size());
214 : }
215 :
216 : // [locale]
217 17344837 : if (specs_.localized) {
218 0 : buffer.push_back('L');
219 : }
220 :
221 : // [type]
222 17344837 : buffer.push_back(specs_.type);
223 :
224 17344837 : buffer.push_back('}');
225 :
226 17344837 : return {buffer.data(), buffer.size()};
227 : }
228 :
229 17344837 : template <typename Context> void handle_specs(Context &ctx)
230 : {
231 17344837 : detail::handle_dynamic_spec<detail::width_checker>(specs_.width, specs_.width_ref, ctx);
232 17344837 : detail::handle_dynamic_spec<detail::precision_checker>(specs_.precision, specs_.precision_ref, ctx);
233 17344837 : }
234 :
235 : public:
236 17344837 : template <typename ParseContext> constexpr auto parse(ParseContext &ctx)
237 : {
238 17344837 : auto begin = ctx.begin(), end = ctx.end();
239 17344837 : format_str_ = begin;
240 17344837 : if (begin == end) {
241 638 : return begin;
242 : }
243 : using handler_type = fmt::detail::dynamic_specs_handler<ParseContext>;
244 17344199 : auto it = fmt::detail::parse_format_specs(begin, end, handler_type(specs_, ctx));
245 17344199 : return it;
246 : }
247 :
248 17344837 : template <typename FormatContext> auto format(const DoubleWrapper &doubleWrapper, FormatContext &ctx)
249 : {
250 4819074 : const auto next_float = [](const double value) {
251 4819074 : if (std::signbit(value)) {
252 1085515 : if (value == -0.0) {
253 0 : return value;
254 : } else {
255 1085515 : return std::nextafter(value, std::numeric_limits<decltype(value)>::lowest());
256 : }
257 : } else {
258 3733559 : if (value == 0.0) {
259 0 : return value;
260 : } else {
261 3733559 : return std::nextafter(value, std::numeric_limits<decltype(value)>::max());
262 : }
263 : }
264 : };
265 :
266 17344837 : double val = doubleWrapper;
267 :
268 17344837 : handle_specs(ctx);
269 17344837 : detail::specs_checker<null_handler> checker(null_handler(), detail::mapped_type_constant<double, FormatContext>::value);
270 17344837 : checker.on_align(specs_.align);
271 17344837 : if (specs_.sign != sign::none) {
272 0 : checker.on_sign(specs_.sign);
273 : }
274 17344837 : if (specs_.alt) {
275 2629495 : checker.on_hash();
276 : }
277 17344837 : if (specs_.precision >= 0) {
278 17343867 : checker.end_precision();
279 : }
280 :
281 : // matches Fortran's 'E' format
282 17344837 : if (specs_.type == 'Z') {
283 : // The Fortran 'G' format insists on a leading 0, even though
284 : // that actually means losing data
285 33779 : specs_.type = 'E';
286 :
287 : // 0 pad the end
288 33779 : specs_.alt = true;
289 :
290 33779 : bool initialPrecisionWas1 = false;
291 33779 : if (specs_.precision > 1) {
292 : // reduce the precision to get rounding behavior
293 33779 : --specs_.precision;
294 : } else {
295 : // We need AT LEAST one in precision so we capture a '.' below
296 0 : initialPrecisionWas1 = true;
297 0 : specs_.precision = 1;
298 0 : ++specs_.width;
299 : }
300 :
301 : // multiply by 10 to get the exponent we want
302 33779 : auto str = fmt::format(spec_builder(), val * 10);
303 : // auto str = write_to_string(value * 10);
304 :
305 : // we need "space" to insert our leading 0
306 33779 : if (str.front() != ' ') {
307 0 : str.insert(str.begin(), ' ');
308 : }
309 :
310 67558 : auto begin = std::find(std::begin(str), std::end(str), '.');
311 33779 : if (initialPrecisionWas1) {
312 : // 123.45 => 1.2E+03, except we asked for precision = 1. So we delete the thing after the dot
313 : // and this is why we manually increased the specs_.width by one above
314 0 : str.erase(std::next(begin));
315 : }
316 : // if (begin != std::end(str)) {
317 : // ' -1.2345E15'
318 : // ^
319 33779 : std::swap(*begin, *std::prev(begin));
320 : // ' -.12345E15'
321 : // ^
322 : std::advance(begin, -2);
323 : // ' -.12345E15'
324 : // ^
325 33779 : if (*begin != ' ') {
326 : // found a sign
327 10114 : std::swap(*begin, *std::prev(begin));
328 : // '- .12345E15'
329 : // ^
330 : }
331 : // '-0.12345E15'
332 : // ^
333 33779 : *begin = '0';
334 67558 : return fmt::format_to(ctx.out(), "{}", str);
335 17344837 : } else if (specs_.type == 'S') {
336 : // matches Fortran's 'G', but stripped of whitespace
337 0 : specs_.type = 'N';
338 : // Need to rerun with double wrapper since 'N' is one of our custom ones
339 0 : auto str = fmt::format(spec_builder(), doubleWrapper);
340 :
341 0 : auto strip_whitespace = [](std::string_view const s) -> std::string {
342 0 : if (s.empty()) {
343 0 : return std::string{};
344 : }
345 0 : auto const first = s.find_first_not_of(' ');
346 0 : auto const last = s.find_last_not_of(' ');
347 0 : if ((first == std::string::npos) || (last == std::string::npos)) {
348 0 : return std::string{};
349 : } else {
350 0 : return std::string{s.substr(first, last - first + 1)};
351 : }
352 : };
353 :
354 0 : return fmt::format_to(ctx.out(), "{}", strip_whitespace(str));
355 17311058 : } else if (specs_.type == 'N') {
356 : // matches Fortran's 'G' format
357 :
358 70573 : if (specs_.width == 0 && specs_.precision == -1) {
359 : // Need to rerun with double wrapper since 'N' is one of our custom ones
360 0 : return fmt::format_to(ctx.out(), "{:20N}", doubleWrapper);
361 70573 : } else if (should_be_fixed_output(val) && fixed_will_fit(val, specs_.width - 5)) {
362 36922 : specs_.type = 'F';
363 :
364 : // account for alignment with E formatted
365 36922 : specs_.width -= 4;
366 36922 : if (val == 0.0) {
367 566 : --specs_.precision;
368 36356 : } else if (val < 1.0 && val > -1.0) {
369 : // No adjustment necessary
370 28040 : } else if (specs_.precision == -1) {
371 0 : const auto order_of_magnitude = static_cast<int>(std::log10(std::abs(val)));
372 0 : specs_.precision = specs_.width - (order_of_magnitude + 2);
373 : } else {
374 28040 : const auto order_of_magnitude = static_cast<int>(std::log10(std::abs(val)));
375 28040 : specs_.precision -= (order_of_magnitude + 1);
376 : }
377 :
378 : // if precision adjustment would result in negative, make it 0 to get rounding
379 : // and adjust spacing
380 36922 : if (specs_.precision <= 0) {
381 2 : specs_.width -= 1;
382 2 : specs_.precision = 0;
383 : }
384 :
385 36922 : auto str = fmt::format(spec_builder(), val);
386 :
387 : // When precision hit 0, add . to match Fortran formatting
388 36922 : if (specs_.precision == 0) {
389 : // write the last 4 chars
390 4 : return fmt::format_to(ctx.out(), "{}. ", str);
391 : } else {
392 : // write the last 4 chars
393 73840 : return fmt::format_to(ctx.out(), "{} ", str);
394 : }
395 36922 : } else {
396 : // The Fortran 'G' format insists on a leading 0, even though
397 : // that actually means losing data
398 33651 : specs_.type = 'Z';
399 : // Need to rerun with double wrapper since 'Z' is one of our custom ones
400 100953 : return fmt::format_to(ctx.out(), spec_builder(), doubleWrapper);
401 : }
402 17240485 : } else if (specs_.type == 'R') { // matches RoundSigDigits() behavior
403 : // push the value up a tad to get the same rounding behavior as Objexx
404 2030257 : const auto fixed_output = should_be_fixed_output(val);
405 :
406 2030257 : if (fixed_output) {
407 1914937 : specs_.type = 'F';
408 :
409 1914937 : if (val > 100000.0) {
410 4168 : const auto digits10 = static_cast<int>(std::log10(val));
411 : // we cannot represent this val to the required precision, truncate the floating
412 : // point portion
413 4168 : if (digits10 + specs_.precision >= std::numeric_limits<decltype(val)>::max_digits10) {
414 3 : specs_.precision = 0;
415 3 : spec_builder();
416 : // add '.' to match old RoundSigDigits
417 3 : buffer.push_back('.');
418 3 : std::string_view fmt_buffer(buffer.data(), buffer.size());
419 6 : return fmt::format_to(ctx.out(), fmt_buffer, val);
420 : } else {
421 12495 : return fmt::format_to(ctx.out(), spec_builder(), val);
422 : }
423 : } else {
424 1910769 : if (val == 0.0 || val == -0.0) {
425 1028553 : return fmt::format_to(ctx.out(), spec_builder(), 0.0);
426 : } else {
427 : // nudge up to next rounded val
428 4703754 : return fmt::format_to(ctx.out(), spec_builder(), next_float(next_float(next_float(val))));
429 : }
430 : }
431 : } else {
432 115320 : specs_.type = 'E';
433 115320 : auto str = fmt::format(spec_builder(), next_float(val));
434 230640 : return fmt::format_to(ctx.out(), "{}", zero_pad_exponent(str));
435 115320 : }
436 15210228 : } else if (specs_.type == 'T') { // matches TrimSigDigits behavior
437 1313487 : const auto fixed_output = should_be_fixed_output(val);
438 :
439 1313487 : if (fixed_output) {
440 1224100 : const auto magnitude = std::pow(10, specs_.precision);
441 1224100 : const auto adjusted = (val * magnitude) + 0.0001;
442 1224100 : const auto truncated = std::trunc(adjusted) / magnitude;
443 1224100 : specs_.type = 'F';
444 3672300 : return fmt::format_to(ctx.out(), spec_builder(), truncated);
445 : } else {
446 89387 : specs_.type = 'E';
447 89387 : specs_.precision += 2;
448 :
449 : // write the `E` formatted float to a std::string
450 89387 : auto str = fmt::format(spec_builder(), val);
451 89387 : str = zero_pad_exponent(str);
452 :
453 : // Erase last 2 numbers to truncate the value
454 178774 : const auto E_itr = std::find(begin(str), end(str), 'E');
455 89387 : if (E_itr != str.end()) {
456 178774 : str.erase(std::prev(E_itr, 2), E_itr);
457 : }
458 :
459 178774 : return fmt::format_to(ctx.out(), "{}", str);
460 89387 : }
461 : }
462 41690223 : return fmt::format_to(ctx.out(), spec_builder(), val);
463 : }
464 : };
465 : } // namespace fmt
466 :
467 : namespace EnergyPlus {
468 :
469 : // Forward declarations
470 : struct EnergyPlusData;
471 :
472 : enum class FormatSyntax
473 : {
474 : Invalid = -1,
475 : Fortran,
476 : FMT,
477 : Printf,
478 : Num
479 : };
480 :
481 : inline constexpr bool is_fortran_syntax(const std::string_view format_str)
482 : {
483 : bool within_fmt_str = false;
484 : for (auto const c : format_str) {
485 : switch (c) {
486 : case '{':
487 : within_fmt_str = true;
488 : break;
489 : case '}':
490 : within_fmt_str = false;
491 : break;
492 : case 'R':
493 : case 'S':
494 : case 'N':
495 : case 'Z':
496 : case 'T':
497 : if (within_fmt_str) {
498 : return true;
499 : } else {
500 : break;
501 : }
502 : default:
503 : break;
504 : }
505 : }
506 : return false;
507 : }
508 :
509 : class InputOutputFile;
510 : template <FormatSyntax formatSyntax = FormatSyntax::Fortran, typename... Args>
511 : void print(InputOutputFile &outputFile, std::string_view format_str, Args &&...args);
512 :
513 : inline constexpr FormatSyntax check_syntax(const std::string_view format_str)
514 : {
515 : if (is_fortran_syntax(format_str)) {
516 : return FormatSyntax::Fortran;
517 : } else {
518 : return FormatSyntax::FMT;
519 : }
520 : }
521 :
522 : class InputFile
523 : {
524 : public:
525 : template <typename Type> struct ReadResult
526 : {
527 2946212 : ReadResult(Type data_, bool eof_, bool good_) : data{std::move(data_)}, eof{eof_}, good{good_}
528 : {
529 2946212 : }
530 :
531 : // Update the current eof/good state from the incoming value
532 : // but only update the `data` member if the state is good
533 : // The idea is to keep consistency with the operator>> that was used
534 : // from gio
535 2255265 : void update(ReadResult &&other)
536 : {
537 2255265 : eof = other.eof;
538 2255265 : good = other.good;
539 2255265 : if (good) {
540 2255265 : data = std::move(other.data);
541 : }
542 2255265 : }
543 :
544 : Type data;
545 : bool eof;
546 : bool good;
547 : };
548 :
549 : void close();
550 :
551 : // 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)
552 : // this operate like `operator bool(istream& is)` <=> `!is.bad() && !is.fail()`
553 : bool good() const noexcept;
554 :
555 : bool is_open() const noexcept;
556 :
557 : void backspace() noexcept;
558 :
559 : std::string error_state_to_string() const;
560 :
561 : // opens the file if it is not currently open and returns
562 : // a reference back to itself
563 : InputFile &ensure_open(EnergyPlusData &state, const std::string &caller, bool output_to_file = true);
564 : std::istream::iostate rdstate() const noexcept;
565 :
566 : fs::path filePath;
567 : void open(bool = false, bool = true);
568 : std::fstream::pos_type position() const noexcept;
569 :
570 1238 : void rewind() noexcept
571 : {
572 1238 : if (is) {
573 1238 : is->clear(); // clear potentially failbit and badbit (seekg would only clear eofbit)
574 1238 : is->seekg(0, std::ios::beg);
575 : }
576 1238 : }
577 :
578 : ReadResult<std::string> readLine() noexcept;
579 :
580 6111 : template <typename T> ReadResult<T> read() noexcept
581 : {
582 6111 : if (is) {
583 : T result;
584 6111 : *is >> result;
585 : // Use operator bool, see ReadResult::good() docstring
586 6111 : return ReadResult<T>{result, is->eof(), bool(is)};
587 : } else {
588 0 : return ReadResult<T>{T{}, true, false};
589 : }
590 : }
591 :
592 : std::string readFile();
593 :
594 : nlohmann::json readJSON();
595 :
596 : explicit InputFile(fs::path FilePath);
597 :
598 : private:
599 : std::uintmax_t file_size{};
600 : std::unique_ptr<std::istream> is;
601 : friend class IOFiles;
602 : };
603 :
604 : class InputOutputFile
605 : {
606 : public:
607 : fs::path filePath;
608 : bool defaultToStdOut = false;
609 :
610 : void close();
611 : void del();
612 : bool good() const;
613 :
614 : // opens the file if it is not currently open and returns
615 : // a reference back to itself
616 : InputOutputFile &ensure_open(EnergyPlusData &state, const std::string &caller, bool output_to_file = true);
617 :
618 : void open(const bool forAppend = false, bool output_to_file = true);
619 : std::fstream::pos_type position() const noexcept;
620 : std::vector<std::string> getLines();
621 : void open_as_stringstream();
622 : std::string get_output();
623 : void flush();
624 : explicit InputOutputFile(fs::path FilePath, const bool DefaultToStdOut = false);
625 :
626 : private:
627 : std::unique_ptr<std::iostream> os;
628 : bool print_to_dev_null = false;
629 : template <FormatSyntax, typename... Args> friend void print(InputOutputFile &outputFile, std::string_view format_str, Args &&...args);
630 : friend class IOFiles;
631 : };
632 :
633 : template <typename FileType> struct IOFilePath
634 : {
635 : fs::path filePath;
636 1425 : FileType open(EnergyPlusData &state, const std::string &caller, bool output_to_file = true)
637 : {
638 1425 : FileType file{filePath};
639 1425 : file.ensure_open(state, caller, output_to_file);
640 1425 : return file;
641 0 : }
642 2838 : FileType try_open(bool output_to_file = true)
643 : {
644 2838 : FileType file{filePath};
645 2838 : file.open(false, output_to_file);
646 2838 : return file;
647 0 : }
648 : };
649 :
650 : using InputOutputFilePath = IOFilePath<InputOutputFile>;
651 : using InputFilePath = IOFilePath<InputFile>;
652 :
653 : struct JsonOutputFilePaths
654 : {
655 : fs::path outputJsonFilePath;
656 : fs::path outputTSHvacJsonFilePath;
657 : fs::path outputTSZoneJsonFilePath;
658 : fs::path outputTSJsonFilePath;
659 : fs::path outputYRJsonFilePath;
660 : fs::path outputMNJsonFilePath;
661 : fs::path outputDYJsonFilePath;
662 : fs::path outputHRJsonFilePath;
663 : fs::path outputSMJsonFilePath;
664 : fs::path outputCborFilePath;
665 : fs::path outputTSHvacCborFilePath;
666 : fs::path outputTSZoneCborFilePath;
667 : fs::path outputTSCborFilePath;
668 : fs::path outputYRCborFilePath;
669 : fs::path outputMNCborFilePath;
670 : fs::path outputDYCborFilePath;
671 : fs::path outputHRCborFilePath;
672 : fs::path outputSMCborFilePath;
673 : fs::path outputMsgPackFilePath;
674 : fs::path outputTSHvacMsgPackFilePath;
675 : fs::path outputTSZoneMsgPackFilePath;
676 : fs::path outputTSMsgPackFilePath;
677 : fs::path outputYRMsgPackFilePath;
678 : fs::path outputMNMsgPackFilePath;
679 : fs::path outputDYMsgPackFilePath;
680 : fs::path outputHRMsgPackFilePath;
681 : fs::path outputSMMsgPackFilePath;
682 : };
683 :
684 : class IOFiles
685 : {
686 : public:
687 : struct OutputControl
688 : {
689 801 : OutputControl() = default;
690 :
691 : void getInput(EnergyPlusData &state);
692 : bool writeTabular(EnergyPlusData &state);
693 :
694 : bool csv = false;
695 : bool mtr = true;
696 : bool eso = true;
697 : bool eio = true;
698 : bool audit = true;
699 : bool spsz = true;
700 : bool zsz = true;
701 : bool ssz = true;
702 : bool dxf = true;
703 : bool bnd = true;
704 : bool rdd = true;
705 : bool mdd = true;
706 : bool mtd = true;
707 : bool end = true;
708 : bool shd = true;
709 : bool dfs = true;
710 : bool glhe = true;
711 : bool delightin = true;
712 : bool delighteldmp = true;
713 : bool delightdfdmp = true;
714 : bool edd = true;
715 : bool dbg = true;
716 : bool perflog = true;
717 : bool sln = true;
718 : bool sci = true;
719 : bool wrl = true;
720 : bool screen = true;
721 : bool tarcog = true;
722 : bool extshd = true;
723 : bool json = true;
724 : bool tabular = true;
725 : bool sqlite = true;
726 : };
727 :
728 : OutputControl outputControl;
729 :
730 : InputOutputFile audit{"eplusout.audit"};
731 : InputOutputFile eio{"eplusout.eio"};
732 : InputOutputFile eso{"eplusout.eso"}; // (hourly data only)
733 :
734 : InputOutputFile zsz{""};
735 : fs::path outputZszCsvFilePath{"epluszsz.csv"};
736 : fs::path outputZszTabFilePath{"epluszsz.tab"};
737 : fs::path outputZszTxtFilePath{"epluszsz.txt"};
738 :
739 : InputOutputFile spsz{""};
740 : fs::path outputSpszCsvFilePath{"eplusspsz.csv"};
741 : fs::path outputSpszTabFilePath{"eplusspsz.tab"};
742 : fs::path outputSpszTxtFilePath{"eplusspsz.txt"};
743 :
744 : InputOutputFile ssz{""};
745 : fs::path outputSszCsvFilePath{"eplusssz.csv"};
746 : fs::path outputSszTabFilePath{"eplusssz.tab"};
747 : fs::path outputSszTxtFilePath{"eplusssz.txt"};
748 :
749 : InputOutputFile map{""};
750 : fs::path outputMapCsvFilePath{"eplusmap.csv"};
751 : fs::path outputMapTabFilePath{"eplusmap.tab"};
752 : fs::path outputMapTxtFilePath{"eplusmap.txt"};
753 :
754 : InputOutputFile mtr{"eplusout.mtr"};
755 : InputOutputFile bnd{"eplusout.bnd"};
756 : InputOutputFile rdd{"eplusout.rdd"};
757 : InputOutputFile mdd{"eplusout.mdd"};
758 :
759 : InputOutputFile debug{"eplusout.dbg"};
760 :
761 : InputOutputFile dfs{"eplusout.dfs"};
762 :
763 : InputOutputFilePath sln{"eplusout.sln"};
764 : InputOutputFilePath dxf{"eplusout.dxf"};
765 : InputOutputFilePath sci{"eplusout.sci"};
766 : InputOutputFilePath wrl{"eplusout.wrl"};
767 :
768 : InputOutputFilePath delightIn{"eplusout.delightin"};
769 :
770 : InputOutputFile mtd{"eplusout.mtd"};
771 : InputOutputFile edd{"eplusout.edd", true}; // write to stdout if no file never opened
772 : InputOutputFile shade{"eplusshading.csv"};
773 :
774 : InputOutputFile csv{"eplusout.csv"};
775 : InputOutputFile mtr_csv{"eplusmtr.csv"};
776 :
777 : InputOutputFilePath screenCsv{"eplusscreen.csv"};
778 : InputOutputFilePath endFile{"eplusout.end"};
779 :
780 : InputFilePath iniFile{"EnergyPlus.ini"};
781 :
782 : InputFilePath outputDelightEldmpFilePath{"eplusout.delighteldmp"};
783 : InputFilePath outputDelightDfdmpFilePath{"eplusout.delightdfdmp"};
784 :
785 : // for transient uses of weather files
786 : // also, keeper of the currently set input weather file name
787 : InputFilePath inputWeatherFilePath{""};
788 :
789 : // for the persistent weather simulation, using the EPW
790 : // uses the file name set in `inputWeatherFilePath`
791 : InputFile inputWeatherFile{""};
792 :
793 : InputFilePath TempFullFilePath{""};
794 : InputFilePath inStatFilePath{""};
795 :
796 : fs::path outputErrFilePath{"eplusout.err"};
797 : std::unique_ptr<std::ostream> err_stream;
798 :
799 : JsonOutputFilePaths json; // Internal streams used for json outputs
800 :
801 : void flushAll(); // For RunningEnergyPlusViaAPI only
802 : };
803 :
804 : class SharedFileHandle
805 : {
806 : std::shared_ptr<InputOutputFile> file;
807 3322 : InputOutputFile *ptr()
808 : {
809 3322 : if (!file) {
810 9 : file = std::make_shared<InputOutputFile>("");
811 : }
812 :
813 3322 : return file.get();
814 : }
815 :
816 : public:
817 3295 : InputOutputFile &operator*()
818 : {
819 3295 : return *ptr();
820 : }
821 :
822 27 : InputOutputFile *operator->()
823 : {
824 27 : return ptr();
825 : }
826 : };
827 :
828 6968360 : template <typename... Args> void vprint(std::ostream &os, std::string_view format_str, const Args &...args)
829 : {
830 : // assert(os.good());
831 13936720 : auto buffer = fmt::memory_buffer();
832 : try {
833 6968360 : fmt::format_to(std::back_inserter(buffer), format_str, args...);
834 0 : } catch (const fmt::format_error &) {
835 0 : throw EnergyPlus::FatalError(fmt::format("Error with format, '{}', passed {} args", format_str, sizeof...(Args)));
836 : }
837 6968360 : os.write(buffer.data(), buffer.size());
838 6968360 : }
839 :
840 22394033 : template <typename... Args> std::string vprint(std::string_view format_str, const Args &...args)
841 : {
842 44788066 : auto buffer = fmt::memory_buffer();
843 : try {
844 22394033 : fmt::format_to(std::back_inserter(buffer), format_str, args...);
845 0 : } catch (const fmt::format_error &) {
846 0 : throw EnergyPlus::FatalError(fmt::format("Error with format, '{}', passed {} args", format_str, sizeof...(Args)));
847 : }
848 44788066 : return fmt::to_string(buffer);
849 22394033 : }
850 :
851 : // Uses lib {fmt} (which has been accepted for C++20)
852 : // Formatting syntax guide is here: https://fmt.dev/latest/syntax.html
853 : // The syntax is similar to printf, but uses {} to indicate parameters to be formatted
854 : // you must escape any {} that you want with {}, like `{{}}`
855 : //
856 : // Defines a custom formatting type 'R' (round_ which chooses between `E` and `G` depending
857 : // on the value being printed.
858 : // This is necessary for parity with the old "RoundSigDigits" utility function
859 : //
860 : // Defines a custom formatting type 'S' that behaves like Fortran's G type, but stripped of whitespace
861 : // 'S' was chosen for "Stripped". It is implemented in terms of 'N'
862 : //
863 : // Defines a custom formatting type 'N' that behaves like Fortran's G type.
864 : // 'N' was chosen for "Number"
865 : //
866 : // Defines a custom formatting type 'Z' that behaves like Fortran's E type.
867 : // 'Z' was chosen because Fortran's 'E' format always starts with a Zero
868 : //
869 : // Defines a custom formatting type 'T' that that truncates the value
870 : // to match the behavior of TrimSigDigits utility function
871 : //
872 :
873 : namespace {
874 6968360 : template <typename... Args> void print_fortran_syntax(std::ostream &os, std::string_view format_str, const Args &...args)
875 : {
876 6968360 : EnergyPlus::vprint<std::conditional_t<std::is_same_v<double, Args>, DoubleWrapper, Args>...>(os, format_str, args...);
877 6968360 : }
878 :
879 22394033 : template <typename... Args> std::string format_fortran_syntax(std::string_view format_str, const Args &...args)
880 : {
881 22394033 : return EnergyPlus::vprint<std::conditional_t<std::is_same_v<double, Args>, DoubleWrapper, Args>...>(format_str, args...);
882 : }
883 : } // namespace
884 :
885 : template <FormatSyntax formatSyntax = FormatSyntax::Fortran, typename... Args>
886 : void print(std::ostream &os, std::string_view format_str, Args &&...args)
887 : {
888 : if constexpr (formatSyntax == FormatSyntax::Fortran) {
889 : print_fortran_syntax(os, format_str, args...);
890 : } else if constexpr (formatSyntax == FormatSyntax::FMT) {
891 : fmt::print(os, format_str, std::forward<Args>(args)...);
892 : } else {
893 : static_assert(!(formatSyntax == FormatSyntax::Fortran || formatSyntax == FormatSyntax::FMT), "Invalid FormatSyntax selection");
894 : }
895 : }
896 :
897 23936323 : template <FormatSyntax formatSyntax, typename... Args> void print(InputOutputFile &outputFile, std::string_view format_str, Args &&...args)
898 : {
899 71808969 : auto *outputStream = [&]() -> std::ostream * {
900 23936323 : if (outputFile.os) {
901 23936323 : return outputFile.os.get();
902 : } else {
903 0 : if (outputFile.defaultToStdOut) {
904 0 : return &std::cout;
905 : } else {
906 0 : assert(outputFile.os);
907 0 : return nullptr;
908 : }
909 : }
910 23936323 : }();
911 : if constexpr (formatSyntax == FormatSyntax::Fortran) {
912 6968360 : print_fortran_syntax(*outputStream, format_str, args...);
913 : } else if constexpr (formatSyntax == FormatSyntax::FMT) {
914 16967963 : fmt::print(*outputStream, format_str, std::forward<Args>(args)...);
915 : } else {
916 : static_assert(!(formatSyntax == FormatSyntax::Fortran || formatSyntax == FormatSyntax::FMT), "Invalid FormatSyntax selection");
917 : }
918 23936323 : }
919 :
920 24427085 : template <FormatSyntax formatSyntax = FormatSyntax::Fortran, typename... Args> std::string format(std::string_view format_str, Args &&...args)
921 : {
922 : if constexpr (formatSyntax == FormatSyntax::Fortran) {
923 22394033 : return format_fortran_syntax(format_str, args...);
924 : } else if constexpr (formatSyntax == FormatSyntax::FMT) {
925 4066104 : return fmt::format(format_str, std::forward<Args>(args)...);
926 : } else if constexpr (formatSyntax == FormatSyntax::Printf) {
927 : return fmt::sprintf(format_str, std::forward<Args>(args)...);
928 : }
929 : }
930 :
931 : } // namespace EnergyPlus
932 :
933 : // extern template the most commonly used format function calls
934 : // to save on compilation time. They will be explicitly instantiated
935 : // in IOFiles.cc
936 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int>(std::string_view, int &&);
937 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const char *const &>(std::string_view, const char *const &);
938 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, std::string &>(std::string_view, int &, std::string &);
939 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, std::string &, std::string &, double &>(
940 : std::string_view, std::string &, std::string &, std::string &, double &);
941 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const std::string_view &>(std::string_view,
942 : const std::string_view &);
943 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const std::string_view &, std::string &>(std::string_view,
944 : const std::string_view &,
945 : std::string &);
946 : extern template std::string
947 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, double &, double &>(std::string_view, std::string &, double &, double &);
948 : extern template std::string
949 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, std::string &, int &>(std::string_view, std::string &, std::string &, int &);
950 : extern template std::string
951 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &, double &, double &>(std::string_view, double &, double &, double &);
952 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &, std::string &>(std::string_view, double &, std::string &);
953 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &>(std::string_view, std::string &);
954 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const int &, int &>(std::string_view, const int &, int &);
955 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double>(std::string_view, double &&);
956 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, int &>(std::string_view, int &, int &);
957 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const double &>(std::string_view, const double &);
958 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, int &>(std::string_view, std::string &, int &);
959 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, std::string &, double &>(std::string_view,
960 : std::string &,
961 : std::string &,
962 : double &);
963 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, double &, std::string &, double &>(
964 : std::string_view, std::string &, double &, std::string &, double &);
965 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, const int &>(std::string_view, const int &);
966 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, const std::string &, std::string &>(std::string_view,
967 : int &,
968 : const std::string &,
969 : std::string &);
970 : extern template std::string
971 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, int &, const std::string &>(std::string_view, int &, int &, const std::string &);
972 : extern template std::string
973 : EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, int &, std::string_view &>(std::string_view, int &, int &, std::string_view &);
974 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &, std::string_view &, std::string &>(std::string_view,
975 : int &,
976 : std::string_view &,
977 : std::string &);
978 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &, double &>(std::string_view, double &, double &);
979 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, int &>(std::string_view, int &);
980 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, std::string &, double &>(std::string_view, std::string &, double &);
981 : extern template std::string EnergyPlus::format<EnergyPlus::FormatSyntax::Fortran, double &>(std::string_view, double &);
982 :
983 : #endif
|