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 : // C++ Headers
49 : #include <thread>
50 :
51 : // CLI Headers
52 : #include <CLI/CLI11.hpp>
53 :
54 : // Project headers
55 : #include <EnergyPlus/CommandLineInterface.hh>
56 : #include <EnergyPlus/Data/EnergyPlusData.hh>
57 : #include <EnergyPlus/DataStringGlobals.hh>
58 : #include <EnergyPlus/DisplayRoutines.hh>
59 : #include <EnergyPlus/EnergyPlus.hh>
60 : #include <EnergyPlus/FileSystem.hh>
61 : #include <EnergyPlus/PluginManager.hh>
62 : #include <EnergyPlus/UtilityRoutines.hh>
63 :
64 : namespace EnergyPlus {
65 :
66 : namespace CommandLineInterface {
67 :
68 796 : int ProcessArgs(EnergyPlusData &state, const std::vector<std::string> &args)
69 : {
70 : using size_type = std::string::size_type;
71 :
72 : // Expand long-name options using "=" sign in to two arguments
73 : // and expand multiple short options into separate arguments
74 796 : std::vector<std::string> arguments;
75 :
76 796 : std::string const dash("-");
77 :
78 7194 : for (const auto &inputArg : args) {
79 :
80 6398 : size_type const doubleDashPosition = inputArg.find("--");
81 6398 : size_type const equalsPosition = inputArg.find("=");
82 :
83 6398 : if (doubleDashPosition == 0 && equalsPosition != std::string::npos) { // --option=value
84 0 : arguments.push_back(inputArg.substr(0, equalsPosition));
85 0 : arguments.push_back(inputArg.substr(equalsPosition + 1, inputArg.size() - 1));
86 6398 : } else if (doubleDashPosition == 0 && inputArg.size() == 2) {
87 : // Filter it out, it's a bash-like separator (end of the command options, before positionals arguments are accepted)
88 6398 : } else if ((inputArg.size() > 2) && (inputArg[0] == '-') && (inputArg[1] != '-')) { // -abc style
89 0 : for (size_type c = 1; c < inputArg.size(); ++c) {
90 0 : arguments.push_back(dash + inputArg[c]);
91 : }
92 : } else { // ?
93 6398 : arguments.push_back(inputArg);
94 : }
95 796 : }
96 :
97 : // erase the first element, which is the name of the program
98 796 : const std::string programName = std::move(arguments.front());
99 796 : arguments.erase(arguments.begin());
100 :
101 796 : size_type const argCount = arguments.size();
102 796 : bool const legacyMode = (argCount == 0);
103 :
104 : // Set path of EnergyPlus program path (if we aren't overriding it)
105 796 : if (!state.dataGlobal->installRootOverride) {
106 796 : state.dataStrGlobals->exeDirectoryPath = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath()));
107 : }
108 :
109 1592 : CLI::App app{"energyplus", programName};
110 : // opt.add("", false, 0, 0, "Display version information", "-v", "--version");
111 796 : app.set_version_flag("-v,--version", EnergyPlus::DataStringGlobals::VerString);
112 :
113 : std::string const description = fmt::format(R"({}
114 : PythonLinkage: {}
115 : Built on Platform: {}
116 : )",
117 796 : state.dataStrGlobals->VerStringVar,
118 796 : PluginManagement::pythonStringForUsage(state),
119 796 : DataStringGlobals::BuildPlatformString);
120 796 : app.description(description);
121 :
122 796 : auto *annualOpt = app.add_flag("-a,--annual", state.dataGlobal->AnnualSimulation, "Force annual simulation");
123 :
124 796 : app.add_flag("-D,--design-day", state.dataGlobal->DDOnlySimulation, "Force design-day-only simulation")->excludes(annualOpt);
125 :
126 796 : app.add_option("-d,--output-directory", state.dataStrGlobals->outDirPath, "Output directory path (default: current directory)")
127 2388 : ->option_text("DIR")
128 796 : ->required(false);
129 : // ->check(CLI::ExistingDirectory) // We don't require it to exist, we make it if needed
130 :
131 796 : if (legacyMode) {
132 0 : state.dataStrGlobals->inputIddFilePath = "Energy+.idd";
133 : } else {
134 796 : state.dataStrGlobals->inputIddFilePath = state.dataStrGlobals->exeDirectoryPath / "Energy+.idd";
135 : }
136 2388 : app.add_option(
137 796 : "-i,--idd", state.dataStrGlobals->inputIddFilePath, "Input data dictionary path (default: Energy+.idd in executable directory)")
138 : ->required(false)
139 : ->option_text("IDD")
140 796 : ->check(CLI::ExistingFile);
141 :
142 796 : bool runEPMacro = false;
143 796 : app.add_flag("-m,--epmacro", runEPMacro, "Run EPMacro prior to simulation");
144 :
145 796 : std::string prefixOutName = "eplus";
146 796 : app.add_option("-p,--output-prefix", prefixOutName, "Prefix for output file names (default: eplus)")->required(false)->option_text("PRE");
147 :
148 796 : app.add_flag("-r,--readvars", state.dataGlobal->runReadVars, "Run ReadVarsESO after simulation");
149 :
150 796 : app.add_flag("-c,--convert", state.dataGlobal->outputEpJSONConversion, "Output IDF->epJSON or epJSON->IDF, dependent on input file type");
151 :
152 1592 : app.add_flag("--convert-only",
153 796 : state.dataGlobal->outputEpJSONConversionOnly,
154 : "Only convert IDF->epJSON or epJSON->IDF, dependent on input file type. No simulation");
155 :
156 796 : std::string suffixType = "L";
157 796 : std::string const suffixHelp = R"help(Suffix style for output file names (default: L)
158 : L: Legacy (e.g., eplustbl.csv)
159 : C: Capital (e.g., eplusTable.csv)
160 : D: Dash (e.g., eplus-table.csv))help";
161 : app.add_option("-s,--output-suffix", suffixType, suffixHelp)
162 1592 : ->option_text("SUFFIX")
163 : ->required(false)
164 796 : ->check(CLI::IsMember({"L", "C", "D"}, CLI::ignore_case));
165 :
166 : // TODO: maybe delay validation to output a better error message?
167 796 : const int MAX_N = static_cast<int>(std::thread::hardware_concurrency());
168 : app.add_option("-j,--jobs",
169 796 : state.dataGlobal->numThread,
170 : "Multi-thread with N threads; 1 thread with no arg. (Currently only for G-Function generation)")
171 : ->option_text("N")
172 : // ->check(CLI::Range(1, MAX_N) // Tempted to just do that... much simpler
173 : // ->check(CLI::Number)
174 1592 : ->transform([MAX_N, &state](std::string input) -> std::string {
175 0 : int number_of_threads = -1;
176 0 : bool const converted = CLI::detail::lexical_cast(input, number_of_threads);
177 0 : if (!converted) {
178 : // CLI::ValidationError
179 0 : return fmt::format("Argument should be an integer, not '{}'", input);
180 : }
181 0 : if (number_of_threads <= 0) {
182 0 : DisplayString(state, "Invalid value for -j arg. Defaulting to 1.");
183 0 : return "1";
184 : }
185 0 : if (number_of_threads > MAX_N) {
186 0 : DisplayString(state,
187 0 : fmt::format("Invalid value for -j arg. Value exceeds num available. Defaulting to num available. -j {}", MAX_N));
188 0 : return std::to_string(MAX_N);
189 : }
190 0 : return input;
191 : });
192 :
193 796 : state.files.inputWeatherFilePath.filePath = "in.epw";
194 : // Note: we can't do check(CLI::ExistingFile) here since passing a non-existing /path/to/myfile.epw file
195 : // when there exists a /path/to/myfile.stat would mean the Weather File Statistics are still parsed and reported to the tabular output
196 : // We still report a message + fail later if DDOnlySimulation is false
197 : auto *weatherPathOpt =
198 1592 : app.add_option("-w,--weather", state.files.inputWeatherFilePath.filePath, "Weather file path (default: in.epw in current directory)")
199 : ->required(false)
200 796 : ->option_text("EPW");
201 :
202 796 : bool runExpandObjects = false;
203 796 : app.add_flag("-x,--expandobjects", runExpandObjects, "Run ExpandObjects prior to simulation");
204 :
205 : // Positional
206 796 : state.dataStrGlobals->inputFilePath = "in.idf";
207 1592 : app.add_option("input_file", state.dataStrGlobals->inputFilePath, "Input file (default: in.idf in current directory)")
208 : ->required(false)
209 796 : ->check(CLI::ExistingFile);
210 :
211 : // Catching it myself, so I can print the arguments vector before it's mutated
212 7194 : bool debugCLI = std::any_of(args.begin(), args.end(), [](const auto &arg) { return arg == "--debug-cli"; });
213 796 : if (debugCLI) {
214 : {
215 0 : fmt::print("ProcessArgs: received args\n");
216 0 : int na = 0;
217 0 : for (const auto &a : args) {
218 0 : fmt::print("* {}: '{}'\n", na++, a);
219 0 : }
220 : }
221 : {
222 0 : fmt::print("\nAfter massaging/expanding of args\n");
223 0 : int na = 0;
224 0 : for (const auto &a : arguments) {
225 0 : fmt::print("* {}: '{}'\n", na++, a);
226 0 : }
227 : }
228 0 : fmt::print("\n");
229 : }
230 : // bool debugCLI = false;
231 796 : app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it
232 :
233 796 : app.footer("Example: energyplus -w weather.epw -r input.idf");
234 :
235 796 : const bool eplusRunningViaAPI = state.dataGlobal->eplusRunningViaAPI;
236 :
237 : try {
238 : // app.parse(argc, argv);
239 : // CLI11 when passing argc, argv creates a vector<string> but **in reverse** order:
240 : // https://github.com/CLIUtils/CLI11/blob/291c58789c031208f08f4f261a858b5b7083e8e2/include/CLI/impl/App_inl.hpp#L476-L488
241 796 : std::reverse(arguments.begin(), arguments.end());
242 796 : app.parse(arguments);
243 0 : } catch (const CLI::Success &e) {
244 0 : int const return_code = app.exit(e);
245 0 : if (eplusRunningViaAPI) {
246 0 : return static_cast<int>(ReturnCodes::SuccessButHelper);
247 : } else {
248 0 : exit(return_code);
249 : }
250 0 : } catch (const CLI::ParseError &e) {
251 0 : int const return_code = app.exit(e);
252 0 : if (eplusRunningViaAPI) {
253 0 : return static_cast<int>(ReturnCodes::Failure);
254 : } else {
255 0 : exit(return_code);
256 : }
257 0 : }
258 :
259 796 : if (debugCLI) {
260 0 : fmt::print(stderr,
261 : R"debug(
262 : state.dataGlobal->AnnualSimulation = {},
263 : state.dataGlobal->DDOnlySimulation = {},
264 : state.dataStrGlobals->outDirPath = '{:g}',
265 : state.dataStrGlobals->inputIddFilePath= '{:g}',
266 :
267 : runEPMacro = {},
268 : prefixOutName = {},
269 :
270 : state.dataGlobal->runReadVars={},
271 : state.dataGlobal->outputEpJSONConversion={},
272 : state.dataGlobal->outputEpJSONConversionOnly={},
273 :
274 : suffixType={},
275 :
276 : state.dataGlobal->numThread={},
277 : state.files.inputWeatherFilePath.filePath='{:g}',
278 : state.dataStrGlobals->inputFilePath='{:g}',
279 : )debug",
280 0 : state.dataGlobal->AnnualSimulation,
281 0 : state.dataGlobal->DDOnlySimulation,
282 0 : state.dataStrGlobals->outDirPath,
283 0 : state.dataStrGlobals->inputIddFilePath,
284 :
285 : runEPMacro,
286 : prefixOutName,
287 0 : state.dataGlobal->runReadVars,
288 0 : state.dataGlobal->outputEpJSONConversion,
289 0 : state.dataGlobal->outputEpJSONConversionOnly,
290 : suffixType,
291 0 : state.dataGlobal->numThread,
292 0 : state.files.inputWeatherFilePath.filePath,
293 0 : state.dataStrGlobals->inputFilePath);
294 :
295 0 : fmt::print(stderr, "--debug-cli passed: exiting early\n");
296 :
297 0 : exit(0);
298 : }
299 :
300 : // Convert all paths to native paths
301 796 : state.dataStrGlobals->inputFilePath = FileSystem::makeNativePath(state.dataStrGlobals->inputFilePath);
302 796 : state.files.inputWeatherFilePath.filePath = FileSystem::makeNativePath(state.files.inputWeatherFilePath.filePath);
303 796 : state.dataStrGlobals->inputIddFilePath = FileSystem::makeNativePath(state.dataStrGlobals->inputIddFilePath);
304 796 : state.dataStrGlobals->outDirPath = FileSystem::makeNativePath(state.dataStrGlobals->outDirPath);
305 :
306 796 : state.dataStrGlobals->inputFilePathNameOnly = FileSystem::getFileName(state.dataStrGlobals->inputFilePath);
307 796 : state.dataStrGlobals->inputDirPath = FileSystem::getParentDirectoryPath(state.dataStrGlobals->inputFilePath);
308 :
309 : {
310 796 : FileSystem::FileTypes const fileType = FileSystem::getFileType(state.dataStrGlobals->inputFilePath);
311 796 : state.dataGlobal->isEpJSON = FileSystem::is_all_json_type(fileType);
312 796 : switch (fileType) {
313 796 : case FileSystem::FileTypes::IDF:
314 : case FileSystem::FileTypes::IMF:
315 : case FileSystem::FileTypes::EpJSON:
316 : case FileSystem::FileTypes::JSON:
317 796 : break;
318 0 : case FileSystem::FileTypes::CBOR:
319 0 : DisplayString(state, "CBOR input format is experimental and unsupported.");
320 0 : break;
321 0 : case FileSystem::FileTypes::MsgPack:
322 0 : DisplayString(state, "MsgPack input format is experimental and unsupported.");
323 0 : break;
324 0 : case FileSystem::FileTypes::UBJSON:
325 0 : DisplayString(state, "UBJSON input format is experimental and unsupported.");
326 0 : break;
327 0 : case FileSystem::FileTypes::BSON:
328 0 : DisplayString(state, "BSON input format is experimental and unsupported.");
329 0 : break;
330 0 : default:
331 0 : DisplayString(state,
332 0 : fmt::format("ERROR: Input file must have IDF, IMF, or epJSON extension: {:g}", state.dataStrGlobals->inputFilePath));
333 0 : if (eplusRunningViaAPI) {
334 0 : return static_cast<int>(ReturnCodes::Failure);
335 : } else {
336 0 : exit(EXIT_FAILURE);
337 : }
338 : }
339 : }
340 :
341 796 : if (!state.dataStrGlobals->outDirPath.empty()) {
342 : // Create directory if it doesn't already exist
343 796 : FileSystem::makeDirectory(state.dataStrGlobals->outDirPath);
344 : }
345 :
346 : // File naming scheme
347 1592 : fs::path outputFilePrefixFullPath = state.dataStrGlobals->outDirPath / prefixOutName;
348 :
349 1592 : fs::path outputEpmdetFilePath;
350 1592 : fs::path outputEpmidfFilePath;
351 :
352 1592 : fs::path outputExpidfFilePath;
353 1592 : fs::path outputExperrFilePath;
354 :
355 1592 : std::string normalSuffix;
356 1592 : std::string tableSuffix;
357 1592 : std::string mapSuffix;
358 1592 : std::string zszSuffix;
359 1592 : std::string spszSuffix;
360 1592 : std::string sszSuffix;
361 1592 : std::string meterSuffix;
362 1592 : std::string sqliteSuffix;
363 1592 : std::string adsSuffix;
364 1592 : std::string screenSuffix;
365 1592 : std::string shdSuffix;
366 :
367 1592 : std::string const errorFollowUp = "Type 'energyplus --help' for usage.";
368 : {
369 796 : std::transform(suffixType.begin(), suffixType.end(), suffixType.begin(), ::toupper);
370 :
371 796 : if (suffixType == "L") {
372 :
373 796 : normalSuffix = "out";
374 796 : tableSuffix = "tbl";
375 796 : mapSuffix = "map";
376 796 : zszSuffix = "zsz";
377 796 : spszSuffix = "spsz";
378 796 : sszSuffix = "ssz";
379 796 : meterSuffix = "mtr";
380 796 : sqliteSuffix = "sqlite";
381 796 : adsSuffix = "ADS";
382 796 : screenSuffix = "screen";
383 796 : shdSuffix = "shading";
384 :
385 0 : } else if (suffixType == "D") {
386 :
387 0 : normalSuffix = "";
388 0 : tableSuffix = "-table";
389 0 : mapSuffix = "-map";
390 0 : zszSuffix = "-zsz";
391 0 : spszSuffix = "-spsz";
392 0 : sszSuffix = "-ssz";
393 0 : meterSuffix = "-meter";
394 0 : sqliteSuffix = "-sqlite";
395 0 : adsSuffix = "-ads";
396 0 : screenSuffix = "-screen";
397 0 : shdSuffix = "-shading";
398 :
399 0 : } else if (suffixType == "C") {
400 :
401 0 : normalSuffix = "";
402 0 : tableSuffix = "Table";
403 0 : mapSuffix = "Map";
404 0 : zszSuffix = "Zsz";
405 0 : spszSuffix = "Spsz";
406 0 : sszSuffix = "Ssz";
407 0 : meterSuffix = "Meter";
408 0 : sqliteSuffix = "Sqlite";
409 0 : adsSuffix = "Ads";
410 0 : screenSuffix = "Screen";
411 0 : shdSuffix = "Shading";
412 : }
413 : // No else needed, this is validated at CLI level above
414 : }
415 :
416 : // Helper to construct output file path
417 60496 : auto composePath = [&outputFilePrefixFullPath](const std::string &suffix) -> fs::path {
418 60496 : return FileSystem::appendSuffixToPath(outputFilePrefixFullPath, suffix);
419 796 : };
420 :
421 : // EnergyPlus files
422 796 : state.files.audit.filePath = composePath(normalSuffix + ".audit");
423 796 : state.files.bnd.filePath = composePath(normalSuffix + ".bnd");
424 796 : state.files.dxf.filePath = composePath(normalSuffix + ".dxf");
425 796 : state.files.eio.filePath = composePath(normalSuffix + ".eio");
426 796 : state.files.endFile.filePath = composePath(normalSuffix + ".end");
427 796 : state.files.outputErrFilePath = composePath(normalSuffix + ".err");
428 796 : state.files.eso.filePath = composePath(normalSuffix + ".eso");
429 :
430 796 : state.files.json.outputJsonFilePath = composePath(normalSuffix + ".json");
431 796 : state.files.json.outputTSZoneJsonFilePath = composePath(normalSuffix + "_detailed_zone.json");
432 796 : state.files.json.outputTSHvacJsonFilePath = composePath(normalSuffix + "_detailed_HVAC.json");
433 796 : state.files.json.outputTSJsonFilePath = composePath(normalSuffix + "_timestep.json");
434 796 : state.files.json.outputYRJsonFilePath = composePath(normalSuffix + "_yearly.json");
435 796 : state.files.json.outputMNJsonFilePath = composePath(normalSuffix + "_monthly.json");
436 796 : state.files.json.outputDYJsonFilePath = composePath(normalSuffix + "_daily.json");
437 796 : state.files.json.outputHRJsonFilePath = composePath(normalSuffix + "_hourly.json");
438 796 : state.files.json.outputSMJsonFilePath = composePath(normalSuffix + "_runperiod.json");
439 796 : state.files.json.outputCborFilePath = composePath(normalSuffix + ".cbor");
440 796 : state.files.json.outputTSZoneCborFilePath = composePath(normalSuffix + "_detailed_zone.cbor");
441 796 : state.files.json.outputTSHvacCborFilePath = composePath(normalSuffix + "_detailed_HVAC.cbor");
442 796 : state.files.json.outputTSCborFilePath = composePath(normalSuffix + "_timestep.cbor");
443 796 : state.files.json.outputYRCborFilePath = composePath(normalSuffix + "_yearly.cbor");
444 796 : state.files.json.outputMNCborFilePath = composePath(normalSuffix + "_monthly.cbor");
445 796 : state.files.json.outputDYCborFilePath = composePath(normalSuffix + "_daily.cbor");
446 796 : state.files.json.outputHRCborFilePath = composePath(normalSuffix + "_hourly.cbor");
447 796 : state.files.json.outputSMCborFilePath = composePath(normalSuffix + "_runperiod.cbor");
448 796 : state.files.json.outputMsgPackFilePath = composePath(normalSuffix + ".msgpack");
449 796 : state.files.json.outputTSZoneMsgPackFilePath = composePath(normalSuffix + "_detailed_zone.msgpack");
450 796 : state.files.json.outputTSHvacMsgPackFilePath = composePath(normalSuffix + "_detailed_HVAC.msgpack");
451 796 : state.files.json.outputTSMsgPackFilePath = composePath(normalSuffix + "_timestep.msgpack");
452 796 : state.files.json.outputYRMsgPackFilePath = composePath(normalSuffix + "_yearly.msgpack");
453 796 : state.files.json.outputMNMsgPackFilePath = composePath(normalSuffix + "_monthly.msgpack");
454 796 : state.files.json.outputDYMsgPackFilePath = composePath(normalSuffix + "_daily.msgpack");
455 796 : state.files.json.outputHRMsgPackFilePath = composePath(normalSuffix + "_hourly.msgpack");
456 796 : state.files.json.outputSMMsgPackFilePath = composePath(normalSuffix + "_runperiod.msgpack");
457 :
458 796 : state.files.mtd.filePath = composePath(normalSuffix + ".mtd");
459 796 : state.files.mdd.filePath = composePath(normalSuffix + ".mdd");
460 796 : state.files.mtr.filePath = composePath(normalSuffix + ".mtr");
461 796 : state.files.rdd.filePath = composePath(normalSuffix + ".rdd");
462 796 : state.dataStrGlobals->outputShdFilePath = composePath(normalSuffix + ".shd");
463 796 : state.files.dfs.filePath = composePath(normalSuffix + ".dfs");
464 796 : state.dataStrGlobals->outputGLHEFilePath = composePath(normalSuffix + ".glhe");
465 796 : state.files.edd.filePath = composePath(normalSuffix + ".edd");
466 796 : state.dataStrGlobals->outputIperrFilePath = composePath(normalSuffix + ".iperr");
467 796 : state.files.sln.filePath = composePath(normalSuffix + ".sln");
468 796 : state.files.sci.filePath = composePath(normalSuffix + ".sci");
469 796 : state.files.wrl.filePath = composePath(normalSuffix + ".wrl");
470 796 : state.dataStrGlobals->outputSqlFilePath = composePath(normalSuffix + ".sql");
471 796 : state.files.debug.filePath = composePath(normalSuffix + ".dbg");
472 796 : state.dataStrGlobals->outputPerfLogFilePath = composePath(normalSuffix + "_perflog.csv");
473 796 : state.dataStrGlobals->outputTblCsvFilePath = composePath(tableSuffix + ".csv");
474 796 : state.dataStrGlobals->outputTblHtmFilePath = composePath(tableSuffix + ".htm");
475 796 : state.dataStrGlobals->outputTblTabFilePath = composePath(tableSuffix + ".tab");
476 796 : state.dataStrGlobals->outputTblTxtFilePath = composePath(tableSuffix + ".txt");
477 796 : state.dataStrGlobals->outputTblXmlFilePath = composePath(tableSuffix + ".xml");
478 796 : state.files.outputMapTabFilePath = composePath(mapSuffix + ".tab");
479 796 : state.files.outputMapCsvFilePath = composePath(mapSuffix + ".csv");
480 796 : state.files.outputMapTxtFilePath = composePath(mapSuffix + ".txt");
481 796 : state.files.outputZszCsvFilePath = composePath(zszSuffix + ".csv");
482 796 : state.files.outputZszTabFilePath = composePath(zszSuffix + ".tab");
483 796 : state.files.outputZszTxtFilePath = composePath(zszSuffix + ".txt");
484 796 : state.files.outputSpszCsvFilePath = composePath(spszSuffix + ".csv");
485 796 : state.files.outputSpszTabFilePath = composePath(spszSuffix + ".tab");
486 796 : state.files.outputSpszTxtFilePath = composePath(spszSuffix + ".txt");
487 796 : state.files.outputSszCsvFilePath = composePath(sszSuffix + ".csv");
488 796 : state.files.outputSszTabFilePath = composePath(sszSuffix + ".tab");
489 796 : state.files.outputSszTxtFilePath = composePath(sszSuffix + ".txt");
490 796 : state.dataStrGlobals->outputAdsFilePath = composePath(adsSuffix + ".out");
491 796 : state.files.shade.filePath = composePath(shdSuffix + ".csv");
492 796 : if (suffixType == "L") {
493 : // out/sqlite.err
494 796 : state.dataStrGlobals->outputSqliteErrFilePath = state.dataStrGlobals->outDirPath / fs::path{sqliteSuffix + ".err"};
495 : } else {
496 : // if 'D': out/eplus-sqlite.err
497 0 : state.dataStrGlobals->outputSqliteErrFilePath = composePath(sqliteSuffix + ".err");
498 : }
499 :
500 796 : state.files.screenCsv.filePath = composePath(screenSuffix + ".csv");
501 : // TODO, why are these relative paths?
502 796 : state.files.delightIn.filePath = "eplusout.delightin";
503 796 : state.dataStrGlobals->outputDelightOutFilePath = "eplusout.delightout";
504 :
505 : // TODO: why is this relative?
506 796 : state.files.iniFile.filePath = "Energy+.ini";
507 :
508 : // Stat next to epw
509 : // Careful! fs::path::replace_extension will **mutate the original object**
510 796 : state.files.inStatFilePath.filePath = state.files.inputWeatherFilePath.filePath;
511 796 : state.files.inStatFilePath.filePath.replace_extension(".stat");
512 : // Or is it better to provide a helper that does not mutate like this?
513 : // state.files.inStatFilePath.filePath = FileSystem::replaceFileExtension(state.inputWeatherFilePath.filePath, ".stat");
514 :
515 796 : state.dataStrGlobals->eplusADSFilePath = state.dataStrGlobals->inputDirPath / "eplusADS.inp";
516 :
517 : // Readvars files
518 796 : state.files.csv.filePath = composePath(normalSuffix + ".csv");
519 796 : state.files.mtr_csv.filePath = composePath(meterSuffix + ".csv");
520 796 : state.dataStrGlobals->outputRvauditFilePath = composePath(normalSuffix + ".rvaudit");
521 :
522 : // EPMacro files
523 796 : outputEpmdetFilePath = composePath(normalSuffix + ".epmdet");
524 796 : outputEpmidfFilePath = composePath(normalSuffix + ".epmidf");
525 :
526 : // ExpandObjects files
527 796 : outputExpidfFilePath = composePath(normalSuffix + ".expidf");
528 796 : outputExperrFilePath = composePath(normalSuffix + ".experr");
529 :
530 : // Read path from INI file if it exists
531 :
532 : // Check for IDD and IDF files
533 796 : if (FileSystem::fileExists(state.files.iniFile.filePath)) {
534 0 : EnergyPlus::InputFile iniFile = state.files.iniFile.try_open();
535 0 : if (!iniFile.good()) {
536 0 : DisplayString(state, fmt::format("ERROR: Could not open file {} for input (read).", iniFile.filePath));
537 0 : if (eplusRunningViaAPI) {
538 0 : return static_cast<int>(ReturnCodes::Failure);
539 : } else {
540 0 : exit(EXIT_FAILURE);
541 : }
542 : }
543 0 : state.dataStrGlobals->CurrentWorkingFolder = iniFile.filePath;
544 : // Relying on compiler to supply full path name here
545 : // TODO: not sure I understand this block
546 : // const int TempIndx = index(state.dataStrGlobals->CurrentWorkingFolder, state.dataStrGlobals->pathChar, true);
547 : // if (TempIndx == std::string::npos) {
548 : // state.dataStrGlobals->CurrentWorkingFolder = "";
549 : //} else {
550 : // state.dataStrGlobals->CurrentWorkingFolder.erase(TempIndx + 1);
551 : //}
552 : // Get directories from ini file
553 0 : std::string programPathStr;
554 0 : ReadINIFile(iniFile, "program", "dir", programPathStr);
555 0 : state.dataStrGlobals->ProgramPath = fs::path(programPathStr);
556 :
557 0 : state.dataStrGlobals->inputIddFilePath = state.dataStrGlobals->ProgramPath / "Energy+.idd";
558 0 : }
559 :
560 : // Check if specified files exist
561 796 : if (!FileSystem::fileExists(state.dataStrGlobals->inputFilePath)) {
562 0 : DisplayString(
563 0 : state, fmt::format("ERROR: Could not find input data file: {}.", FileSystem::getAbsolutePath(state.dataStrGlobals->inputFilePath)));
564 0 : DisplayString(state, errorFollowUp);
565 0 : if (eplusRunningViaAPI) {
566 0 : return static_cast<int>(ReturnCodes::Failure);
567 : } else {
568 0 : exit(EXIT_FAILURE);
569 : }
570 : }
571 :
572 796 : if ((weatherPathOpt->count() > 0) && !state.dataGlobal->DDOnlySimulation) {
573 6 : if (!FileSystem::fileExists(state.files.inputWeatherFilePath.filePath)) {
574 0 : DisplayString(
575 : state,
576 0 : fmt::format("ERROR: Could not find weather file: {}.", FileSystem::getAbsolutePath(state.files.inputWeatherFilePath.filePath)));
577 0 : DisplayString(state, errorFollowUp);
578 0 : if (eplusRunningViaAPI) {
579 0 : return static_cast<int>(ReturnCodes::Failure);
580 : } else {
581 0 : exit(EXIT_FAILURE);
582 : }
583 : }
584 : }
585 :
586 : // TODO: might be able to convert epJSON->IDF, run preprocessors, then go back IDF->epJSON
587 :
588 : // Preprocessors (These will likely move to a new file)
589 796 : if (runEPMacro) {
590 2 : fs::path epMacroPath = (state.dataStrGlobals->exeDirectoryPath / "EPMacro").replace_extension(FileSystem::exeExtension);
591 1 : if (!FileSystem::fileExists(epMacroPath)) {
592 0 : DisplayString(state, fmt::format("ERROR: Could not find EPMacro executable: {}.", FileSystem::getAbsolutePath(epMacroPath)));
593 0 : if (eplusRunningViaAPI) {
594 0 : return static_cast<int>(ReturnCodes::Failure);
595 : } else {
596 0 : exit(EXIT_FAILURE);
597 : }
598 : }
599 2 : std::string epMacroCommand = "\"" + FileSystem::toString(epMacroPath) + "\"";
600 1 : bool inputFilePathdIn = (FileSystem::getAbsolutePath(state.dataStrGlobals->inputFilePath) == FileSystem::getAbsolutePath("in.imf"));
601 :
602 1 : if (!inputFilePathdIn) {
603 1 : FileSystem::linkFile(state.dataStrGlobals->inputFilePath, "in.imf");
604 : }
605 1 : DisplayString(state, "Running EPMacro...");
606 1 : FileSystem::systemCall(epMacroCommand);
607 1 : if (!inputFilePathdIn) {
608 1 : FileSystem::removeFile("in.imf");
609 : }
610 1 : FileSystem::moveFile("audit.out", outputEpmdetFilePath);
611 1 : FileSystem::moveFile("out.idf", outputEpmidfFilePath);
612 1 : state.dataStrGlobals->inputFilePath = outputEpmidfFilePath;
613 1 : }
614 :
615 796 : if (runExpandObjects) {
616 : fs::path expandObjectsPath =
617 58 : (state.dataStrGlobals->exeDirectoryPath / fs::path("ExpandObjects")).replace_extension(FileSystem::exeExtension);
618 29 : if (!FileSystem::fileExists(expandObjectsPath)) {
619 0 : DisplayString(state,
620 0 : fmt::format("ERROR: Could not find ExpandObjects executable: {}.", FileSystem::getAbsolutePath(expandObjectsPath)));
621 0 : if (eplusRunningViaAPI) {
622 0 : return static_cast<int>(ReturnCodes::Failure);
623 : } else {
624 0 : exit(EXIT_FAILURE);
625 : }
626 : }
627 58 : std::string expandObjectsCommand = "\"" + FileSystem::toString(expandObjectsPath) + "\"";
628 29 : bool inputFilePathdIn = (FileSystem::getAbsolutePath(state.dataStrGlobals->inputFilePath) == FileSystem::getAbsolutePath("in.idf"));
629 :
630 : // check if IDD actually exists since ExpandObjects still requires it
631 29 : if (!FileSystem::fileExists(state.dataStrGlobals->inputIddFilePath)) {
632 0 : DisplayString(state,
633 0 : fmt::format("ERROR: Could not find input data dictionary: {}.",
634 0 : FileSystem::getAbsolutePath(state.dataStrGlobals->inputIddFilePath)));
635 0 : DisplayString(state, errorFollowUp);
636 0 : if (eplusRunningViaAPI) {
637 0 : return static_cast<int>(ReturnCodes::Failure);
638 : } else {
639 0 : exit(EXIT_FAILURE);
640 : }
641 : }
642 :
643 : bool iddFilePathdEnergy =
644 29 : (FileSystem::getAbsolutePath(state.dataStrGlobals->inputIddFilePath) == FileSystem::getAbsolutePath("Energy+.idd"));
645 :
646 29 : if (!inputFilePathdIn) {
647 29 : FileSystem::linkFile(state.dataStrGlobals->inputFilePath, "in.idf");
648 : }
649 29 : if (!iddFilePathdEnergy) {
650 29 : FileSystem::linkFile(state.dataStrGlobals->inputIddFilePath, "Energy+.idd");
651 : }
652 :
653 29 : FileSystem::systemCall(expandObjectsCommand);
654 29 : if (!inputFilePathdIn) {
655 29 : FileSystem::removeFile("in.idf");
656 : }
657 29 : if (!iddFilePathdEnergy) {
658 29 : FileSystem::removeFile("Energy+.idd");
659 : }
660 :
661 29 : FileSystem::moveFile("expandedidf.err", outputExperrFilePath);
662 29 : if (FileSystem::fileExists("expanded.idf")) {
663 29 : FileSystem::moveFile("expanded.idf", outputExpidfFilePath);
664 29 : state.dataStrGlobals->inputFilePath = outputExpidfFilePath;
665 : }
666 29 : }
667 :
668 796 : return static_cast<int>(ReturnCodes::Success);
669 796 : }
670 :
671 : // Fix This is Fortranic code that needs to be brought up to C++ style
672 : // All the index and len and strip should be eliminated and replaced by string calls only where needed
673 : // I/o with std::string should not be pulling in trailing blanks so stripping should not be needed, etc.
674 : // Rewinding is a big performance hit and should be avoided if possible
675 : // Case-insensitive comparison is much faster than converting strings to upper or lower case
676 : // Each strip and case conversion is a heap hit and should be avoided if possible
677 0 : void ReadINIFile(InputFile &inputFile, // Unit number of the opened INI file
678 : std::string const &Heading, // Heading for the parameters ('[heading]')
679 : std::string const &KindofParameter, // Kind of parameter to be found (String)
680 : std::string &DataOut // Output from the retrieval
681 : )
682 : {
683 :
684 : // SUBROUTINE INFORMATION:
685 : // AUTHOR Linda K. Lawrie
686 : // DATE WRITTEN September 1997
687 : // MODIFIED na
688 : // RE-ENGINEERED na
689 :
690 : // PURPOSE OF THIS SUBROUTINE:
691 : // This routine reads the .ini file and retrieves
692 : // the path names for the files from it.
693 :
694 : // METHODOLOGY EMPLOYED:
695 : // Duplicate the kind of reading the Windows "GetINISetting" would
696 : // do.
697 :
698 : // REFERENCES:
699 : // na
700 :
701 : // Using/Aliasing
702 : using namespace EnergyPlus;
703 : using namespace DataStringGlobals;
704 :
705 : // Locals
706 : // SUBROUTINE ARGUMENT DEFINITIONS:
707 :
708 : // SUBROUTINE PARAMETER DEFINITIONS:
709 :
710 : // INTERFACE BLOCK SPECIFICATIONS
711 : // na
712 :
713 : // DERIVED TYPE DEFINITIONS
714 : // na
715 :
716 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
717 :
718 0 : std::string Param;
719 : std::string::size_type ILB;
720 : std::string::size_type IRB;
721 : std::string::size_type IEQ;
722 : std::string::size_type IPAR;
723 : std::string::size_type IPOS;
724 : std::string::size_type ILEN;
725 :
726 : // Formats
727 :
728 0 : DataOut.clear();
729 :
730 : // I tried ADJUSTL(TRIM(KindofParameter)) and got an internal compiler error
731 :
732 0 : Param = KindofParameter;
733 0 : strip(Param);
734 0 : ILEN = len(Param);
735 0 : inputFile.rewind();
736 0 : bool Found = false;
737 0 : bool NewHeading = false;
738 :
739 0 : while (inputFile.good() && !Found) {
740 0 : EnergyPlus::InputFile::ReadResult const readResult = inputFile.readLine();
741 :
742 0 : if (readResult.eof) {
743 0 : break;
744 : }
745 :
746 0 : if (readResult.data.empty()) {
747 0 : continue;
748 : } // Ignore Blank Lines
749 :
750 0 : std::string LINEOut;
751 0 : ConvertCaseToLower(readResult.data, LINEOut); // Turn line into lower case
752 : // LINE=LINEOut
753 :
754 0 : if (!has(LINEOut, Heading)) continue;
755 :
756 : // See if [ and ] are on line
757 0 : ILB = index(LINEOut, '[');
758 0 : IRB = index(LINEOut, ']');
759 0 : if (ILB == std::string::npos && IRB == std::string::npos) continue;
760 0 : if (!has(LINEOut, '[' + Heading + ']')) continue; // Must be really correct heading line
761 :
762 : // Heading line found, now looking for Kind
763 0 : while (inputFile.good() && !NewHeading) {
764 0 : const auto innerReadResult = inputFile.readLine(); // readLine returns a ReadResult<std::string>, hence no & (THIS_AUTO_OK)
765 0 : if (innerReadResult.eof) {
766 0 : break;
767 : }
768 :
769 0 : std::string line = innerReadResult.data;
770 0 : strip(line);
771 :
772 0 : if (line.empty()) continue; // Ignore Blank Lines
773 :
774 0 : ConvertCaseToLower(line, LINEOut); // Turn line into lower case
775 : // LINE=LINEOut
776 :
777 0 : ILB = index(LINEOut, '[');
778 0 : IRB = index(LINEOut, ']');
779 0 : NewHeading = (ILB != std::string::npos && IRB != std::string::npos);
780 :
781 : // Should be a parameter line
782 : // KindofParameter = string
783 0 : IEQ = index(LINEOut, '=');
784 0 : IPAR = index(LINEOut, Param);
785 0 : if (IEQ == std::string::npos) continue;
786 0 : if (IPAR == std::string::npos) continue;
787 0 : if (IPAR != 0) continue;
788 0 : if (!has(LINEOut, Param + '=')) continue; // needs to be param=
789 :
790 : // = found and parameter found.
791 0 : if (IPAR > IEQ) continue;
792 :
793 : // parameter = found
794 : // Set output string to start with non-blank character
795 :
796 0 : DataOut = stripped(line.substr(IEQ + 1));
797 0 : Found = true;
798 0 : break;
799 0 : }
800 0 : }
801 :
802 0 : if (Param == "dir") {
803 0 : IPOS = len(DataOut);
804 0 : if (IPOS != 0) {
805 : // Non-blank make sure last position is valid path character
806 : // (Set in DataStringGlobals)
807 :
808 0 : if (DataOut[IPOS - 1] != pathChar) {
809 0 : DataOut += pathChar;
810 : }
811 : }
812 : }
813 0 : }
814 :
815 794 : int runReadVarsESO(EnergyPlusData &state)
816 : {
817 1588 : fs::path readVarsPath = (state.dataStrGlobals->exeDirectoryPath / "ReadVarsESO").replace_extension(FileSystem::exeExtension);
818 :
819 794 : if (!FileSystem::fileExists(readVarsPath)) {
820 0 : readVarsPath = (state.dataStrGlobals->exeDirectoryPath / "PostProcess" / "ReadVarsESO").replace_extension(FileSystem::exeExtension);
821 0 : if (!FileSystem::fileExists(readVarsPath)) {
822 : // should report the error differently if the user is calling into E+ through EXE or DLL
823 0 : if (state.dataGlobal->eplusRunningViaAPI) {
824 0 : DisplayString(
825 : state,
826 : "ERROR: Could not find ReadVarsESO executable. When calling through C API, make sure to call setEnergyPlusRootDirectory");
827 : } else {
828 0 : DisplayString(state, fmt::format("ERROR: Could not find ReadVarsESO executable: {}.", FileSystem::getAbsolutePath(readVarsPath)));
829 : }
830 0 : return static_cast<int>(ReturnCodes::Failure);
831 : }
832 : }
833 :
834 1588 : fs::path const RVIfile = (state.dataStrGlobals->inputDirPath / state.dataStrGlobals->inputFilePathNameOnly).replace_extension(".rvi");
835 1588 : fs::path const MVIfile = (state.dataStrGlobals->inputDirPath / state.dataStrGlobals->inputFilePathNameOnly).replace_extension(".mvi");
836 :
837 794 : const bool rviFileExists = FileSystem::fileExists(RVIfile);
838 794 : if (!rviFileExists) {
839 641 : std::ofstream ofs{RVIfile};
840 641 : if (!ofs.good()) {
841 0 : ShowFatalError(state, format("EnergyPlus: Could not open file \"{}\" for output (write).", RVIfile));
842 : } else {
843 641 : ofs << FileSystem::toString(state.files.eso.filePath) << '\n';
844 641 : ofs << FileSystem::toString(state.files.csv.filePath) << '\n';
845 : }
846 641 : }
847 :
848 794 : const bool mviFileExists = FileSystem::fileExists(MVIfile);
849 794 : if (!mviFileExists) {
850 781 : std::ofstream ofs{MVIfile};
851 781 : if (!ofs.good()) {
852 0 : ShowFatalError(state, format("EnergyPlus: Could not open file \"{}\" for output (write).", RVIfile));
853 : } else {
854 781 : ofs << FileSystem::toString(state.files.mtr.filePath) << '\n';
855 781 : ofs << FileSystem::toString(state.files.mtr_csv.filePath) << '\n';
856 : }
857 781 : }
858 :
859 : // We quote the paths in case we have spaces
860 : // "/Path/to/ReadVarEso" "/Path/to/folder with spaces/file.rvi" unlimited
861 1588 : std::string const readVarsRviCommand = "\"" + FileSystem::toString(readVarsPath) + "\" \"" + FileSystem::toString(RVIfile) + "\" unlimited";
862 1588 : std::string const readVarsMviCommand = "\"" + FileSystem::toString(readVarsPath) + "\" \"" + FileSystem::toString(MVIfile) + "\" unlimited";
863 :
864 : // systemCall will be responsible to handle to above command on Windows versus Unix
865 794 : FileSystem::systemCall(readVarsRviCommand);
866 794 : FileSystem::systemCall(readVarsMviCommand);
867 :
868 794 : if (!rviFileExists) {
869 641 : FileSystem::removeFile(RVIfile);
870 : }
871 :
872 794 : if (!mviFileExists) {
873 781 : FileSystem::removeFile(MVIfile);
874 : }
875 :
876 794 : FileSystem::moveFile("readvars.audit", state.dataStrGlobals->outputRvauditFilePath);
877 794 : return static_cast<int>(ReturnCodes::Success);
878 794 : }
879 :
880 : } // namespace CommandLineInterface
881 : } // namespace EnergyPlus
|