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