Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new people schedule columns to EIO/initialization summary to address #10314 #10437

Merged
merged 25 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a673de6
Add new function for min max by daytype and new EIO/initialization re…
JasonGlazer Mar 13, 2024
60fe6f9
Add unit test
JasonGlazer Mar 14, 2024
26f96b0
Add support for other internal load equipment.
JasonGlazer Mar 14, 2024
ed9c634
Update output-details-and-examples for People
JasonGlazer Mar 18, 2024
8955374
Add new EIO outputs for remaining changes to output-details-and-examples
JasonGlazer Mar 18, 2024
f4280d1
Merge branch 'develop' into Fix10314-AddingScheduleColumns
JasonGlazer Apr 9, 2024
5957438
Using constexpr for dayfilters
JasonGlazer Apr 9, 2024
f32e3c7
clean ups
JasonGlazer Apr 9, 2024
5f0010a
clang format (although not actually better)
JasonGlazer Apr 9, 2024
af8fde5
Incorporate suggested code changes
JasonGlazer Apr 9, 2024
e437006
Store the min and max to speed up multiple calls for same schedule. C…
JasonGlazer Apr 10, 2024
bced9e7
format
JasonGlazer Apr 10, 2024
c4fe92c
Clang, clang, clang
JasonGlazer Apr 11, 2024
c0db5b7
Separate columns on design day to be separate for summer and winter d…
JasonGlazer Apr 11, 2024
eb855df
Merge branch 'develop' into Fix10314-AddingScheduleColumns
JasonGlazer Apr 11, 2024
f9e2315
Update output-details-and-examples/src/output-files/eplusout-eio.tex …
JasonGlazer Apr 11, 2024
a70fe0a
Fix typo
JasonGlazer Apr 11, 2024
90bf684
Trying to outwit clang-format which I cannot replicate locally.
JasonGlazer Apr 12, 2024
7f6925c
Retype code changes [decent_ci_skip]
JasonGlazer Apr 12, 2024
9c6a8e7
Move initialization out of constructor to make clang format happy (I …
JasonGlazer Apr 12, 2024
b912be3
Merge branch 'develop' into Fix10314-AddingScheduleColumns
JasonGlazer Apr 12, 2024
50b7a43
Add OutputChanges24-1-0-to-24-2-0.md [decent_ci_skip] [actions skip]
JasonGlazer Apr 12, 2024
77ed130
Merge branch 'develop' into Fix10314-AddingScheduleColumns
JasonGlazer Apr 25, 2024
b8d9928
Utilize SetScheduleMinMax and optimize loop
JasonGlazer Apr 25, 2024
8475b71
Don't iterate through duplicate week schedules when finding min and max.
JasonGlazer Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 205 additions & 37 deletions doc/output-details-and-examples/src/output-files/eplusout-eio.tex

Large diffs are not rendered by default.

246 changes: 227 additions & 19 deletions src/EnergyPlus/InternalHeatGains.cc

Large diffs are not rendered by default.

199 changes: 148 additions & 51 deletions src/EnergyPlus/ScheduleManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2691,9 +2691,9 @@ namespace ScheduleManager {
// na

// Checking if valid index is passed is necessary
if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
return 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
return 0.0;
} else if (!state.dataScheduleMgr->Schedule(ScheduleIndex).EMSActuatedOn) {
return state.dataScheduleMgr->Schedule(ScheduleIndex)
Expand Down Expand Up @@ -2771,9 +2771,9 @@ namespace ScheduleManager {
// Return value
Real64 scheduleValue(0.0);

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
return 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
return 0.0;
}

Expand Down Expand Up @@ -2990,10 +2990,10 @@ namespace ScheduleManager {
state.dataScheduleMgr->ScheduleInputProcessed = true;
}

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
DayValues({1, state.dataGlobal->NumOfTimeStepInHour}, {1, 24}) = 1.0;
return;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
DayValues({1, state.dataGlobal->NumOfTimeStepInHour}, {1, 24}) = 0.0;
return;
}
Expand Down Expand Up @@ -3706,12 +3706,17 @@ namespace ScheduleManager {
MinValue = min(MinValue, daySched.TSValMin);
MaxValue = max(MaxValue, daySched.TSValMax);
}
int prevWkSch = -999; // set to a value that would never occur
for (int Loop = 2; Loop <= 366; ++Loop) {
auto const &wkSched = state.dataScheduleMgr->WeekSchedule(sched.WeekSchedulePointer(Loop));
for (int DayT = 1; DayT <= maxDayTypes; ++DayT) {
auto const &daySched = state.dataScheduleMgr->DaySchedule(wkSched.DaySchedulePointer(DayT));
MinValue = min(MinValue, daySched.TSValMin);
MaxValue = max(MaxValue, daySched.TSValMax);
int WkSch = sched.WeekSchedulePointer(Loop);
if (WkSch != prevWkSch) { // skip if same as previous week (very common)
auto const &wkSched = state.dataScheduleMgr->WeekSchedule(WkSch);
for (int DayT = 1; DayT <= maxDayTypes; ++DayT) {
auto const &daySched = state.dataScheduleMgr->DaySchedule(wkSched.DaySchedulePointer(DayT));
MinValue = min(MinValue, daySched.TSValMin);
MaxValue = max(MaxValue, daySched.TSValMax);
}
prevWkSch = WkSch;
}
}
sched.MaxMinSet = true;
Expand Down Expand Up @@ -3744,10 +3749,10 @@ namespace ScheduleManager {
Real64 MinValue(0.0); // For total minimum
Real64 MaxValue(0.0); // For total maximum

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
MaxValue = 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
MaxValue = 0.0;
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
Expand Down Expand Up @@ -3810,11 +3815,11 @@ namespace ScheduleManager {
bool MinValueOk = true;
bool MaxValueOk = true;

if (schedNum == -1) {
if (schedNum == ScheduleManager::ScheduleAlwaysOn) {
assert(clusiveMin == Clusivity::Inclusive && clusiveMax == Clusivity::Inclusive);
MinValueOk = (Minimum == 1.0);
MaxValueOk = (Maximum == 1.0);
} else if (schedNum == 0) {
} else if (schedNum == ScheduleManager::ScheduleAlwaysOff) {
assert(clusiveMin == Clusivity::Inclusive && clusiveMax == Clusivity::Inclusive);
MinValueOk = (Minimum == 0.0);
MaxValueOk = (Maximum == 0.0);
Expand Down Expand Up @@ -3853,9 +3858,9 @@ namespace ScheduleManager {

Real64 MinValue(0.0); // For total minimum

if (schedNum == -1) {
if (schedNum == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
} else if (schedNum == 0) {
} else if (schedNum == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
} else if (schedNum > 0 && schedNum > state.dataScheduleMgr->NumSchedules) {
if (!state.dataScheduleMgr->Schedule(schedNum).MaxMinSet) { // Set Minimum/Maximums for this schedule
Expand Down Expand Up @@ -3893,10 +3898,10 @@ namespace ScheduleManager {
bool MinValueOk;
bool MaxValueOk;

if (schedNum == -1) {
if (schedNum == ScheduleManager::ScheduleAlwaysOn) {
MinValueOk = (Minimum == 1.0);
MaxValueOk = (Maximum == 1.0);
} else if (schedNum == 0) {
} else if (schedNum == ScheduleManager::ScheduleAlwaysOff) {
MinValueOk = (Minimum == 0.0);
MaxValueOk = (Maximum == 0.0);
} else if (schedNum > 0 && schedNum <= state.dataScheduleMgr->NumSchedules) {
Expand Down Expand Up @@ -3962,9 +3967,9 @@ namespace ScheduleManager {

CheckScheduleValue = false;

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
CheckScheduleValue = (Value == 1.0);
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
CheckScheduleValue = (Value == 0.0);
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
ShowFatalError(state, "CheckScheduleValue called with ScheduleIndex out of range");
Expand Down Expand Up @@ -4034,9 +4039,9 @@ namespace ScheduleManager {
int WkSch; // Pointer for WeekSchedule value

CheckScheduleValue = false;
if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
CheckScheduleValue = (Value == 1);
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
CheckScheduleValue = (Value == 0);
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
ShowFatalError(state, "CheckScheduleValue called with ScheduleIndex out of range");
Expand Down Expand Up @@ -4109,10 +4114,10 @@ namespace ScheduleManager {
bool MinValueOk;
bool MaxValueOk;

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
MaxValue = 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
MaxValue = 0.0;
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumDaySchedules) {
Expand Down Expand Up @@ -4191,9 +4196,9 @@ namespace ScheduleManager {
Real64 MinValue(0.0); // For total minimum
bool MinValueOk;

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumDaySchedules) {
ShowFatalError(state, "CheckDayScheduleValueMinMax called with ScheduleIndex out of range");
Expand Down Expand Up @@ -4260,7 +4265,7 @@ namespace ScheduleManager {
int Hour;
int TStep;

if (ScheduleIndex == -1 || ScheduleIndex == 0) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn || ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {

} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
ShowFatalError(state, "HasFractionalScheduleValue called with ScheduleIndex out of range");
Expand Down Expand Up @@ -4351,10 +4356,10 @@ namespace ScheduleManager {
int DayT;
int Loop;

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
MaxValue = 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
MaxValue = 0.0;
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
Expand All @@ -4374,15 +4379,21 @@ namespace ScheduleManager {
max(MaxValue,
maxval(state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
}
int prevWkSch = -999; // set to a value that would never occur
for (Loop = 2; Loop <= 366; ++Loop) {
WkSch = state.dataScheduleMgr->Schedule(ScheduleIndex).WeekSchedulePointer(Loop);
for (DayT = 1; DayT <= maxDayTypes; ++DayT) {
MinValue = min(
MinValue,
minval(state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
MaxValue = max(
MaxValue,
maxval(state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
if (WkSch != prevWkSch) { // skip if same as previous week (very common)
for (DayT = 1; DayT <= maxDayTypes; ++DayT) {
MinValue = min(
MinValue,
minval(
state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
MaxValue = max(
MaxValue,
maxval(
state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
}
prevWkSch = WkSch;
}
}
state.dataScheduleMgr->Schedule(ScheduleIndex).MaxMinSet = true;
Expand Down Expand Up @@ -4442,10 +4453,10 @@ namespace ScheduleManager {
int DayT;
int Loop;

if (ScheduleIndex == -1) {
if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
MaxValue = 1.0;
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
MaxValue = 0.0;
} else if (ScheduleIndex < 1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
Expand All @@ -4465,15 +4476,21 @@ namespace ScheduleManager {
max(MaxValue,
maxval(state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
}
int prevWkSch = -999; // set to a value that would never occur
for (Loop = 2; Loop <= 366; ++Loop) {
WkSch = state.dataScheduleMgr->Schedule(ScheduleIndex).WeekSchedulePointer(Loop);
for (DayT = 1; DayT <= maxDayTypes; ++DayT) {
MinValue = min(
MinValue,
minval(state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
MaxValue = max(
MaxValue,
maxval(state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
if (WkSch != prevWkSch) { // skip if same as previous week (very common)
for (DayT = 1; DayT <= maxDayTypes; ++DayT) {
MinValue = min(
MinValue,
minval(
state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
MaxValue = max(
MaxValue,
maxval(
state.dataScheduleMgr->DaySchedule(state.dataScheduleMgr->WeekSchedule(WkSch).DaySchedulePointer(DayT)).TSValue));
}
prevWkSch = WkSch;
}
}
state.dataScheduleMgr->Schedule(ScheduleIndex).MaxMinSet = true;
Expand All @@ -4491,6 +4508,86 @@ namespace ScheduleManager {
return MaximumValue;
}

std::pair<Real64, Real64> getScheduleMinMaxByDayType(EnergyPlusData &state, int const ScheduleIndex, DayTypeGroup const days)
{
// J. Glazer - March 2024
// finds the minimum and maximum for a specific set of day types for a given schedule
Real64 MinValue = Constant::BigNumber;
Real64 MaxValue = -Constant::BigNumber;
// Sun Mon Tues Wed Thur Fri Sat Hol Summer Winter Cust1 Cust2
constexpr std::array<bool, maxDayTypes> dayTypeFilterWkDy = {false, true, true, true, true, true, false, false, false, false, false, false};
constexpr std::array<bool, maxDayTypes> dayTypeFilterWeHo = {true, false, false, false, false, false, true, true, false, false, false, false};
// Sun Mon Tues Wed Thur Fri Sat Hol Summer Winter Cust1 Cust2
constexpr std::array<bool, maxDayTypes> dayTypeFilterSumDsDy = {
false, false, false, false, false, false, false, false, true, false, false, false};
constexpr std::array<bool, maxDayTypes> dayTypeFilterWinDsDy = {
false, false, false, false, false, false, false, false, false, true, false, false};
constexpr std::array<bool, maxDayTypes> dayTypeFilterNone = {
false, false, false, false, false, false, false, false, false, false, false, false};
if (ScheduleIndex > 0 && ScheduleIndex <= state.dataScheduleMgr->NumSchedules) {
Copy link
Contributor

@rraustad rraustad Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to convince myself that it is even possible for ScheduleIndex > NumSchedules. Not all functions in ScheduleManager use this check (i.e., ScheduleIndex <= NumSchedules). The only way to get an index is to call GetScheduleIndex, which uses FindItemInList and will either return a valid index or 0. I'm not convinced yet so nothing to do here. While investigating this I noticed in GetScheduleIndex that WeekSchedule(Schedule().WeekSchedulePointer()).Used and WeekSchedule(Schedule().WeekSchedulePointer()).DaySchedulePointer().Used are set but never used anywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used variables are never used. Alanis would be proud of that irony.

int curDayTypeGroup = static_cast<int>(days);
auto &curSch = state.dataScheduleMgr->Schedule(ScheduleIndex);
if (!curSch.MaxMinSet) {
SetScheduleMinMax(state, ScheduleIndex);
}
if (!curSch.MaxMinByDayTypeSet[curDayTypeGroup]) {
std::array<bool, maxDayTypes> dayTypeFilter;
switch (days) {
case DayTypeGroup::Weekday:
dayTypeFilter = dayTypeFilterWkDy;
break;
case DayTypeGroup::WeekEndHoliday:
dayTypeFilter = dayTypeFilterWeHo;
break;
case DayTypeGroup::SummerDesignDay:
dayTypeFilter = dayTypeFilterSumDsDy;
break;
case DayTypeGroup::WinterDesignDay:
dayTypeFilter = dayTypeFilterWinDsDy;
break;
default:
dayTypeFilter = dayTypeFilterNone;
break;
}
int prevWkSch = -999; // set to a value that would never occur
for (int iDayOfYear = 1; iDayOfYear <= 366; ++iDayOfYear) {
int WkSch = curSch.WeekSchedulePointer(iDayOfYear);
if (WkSch != prevWkSch) { // skip if same as previous week (very common)
auto &weekSch = state.dataScheduleMgr->WeekSchedule(WkSch);
for (int jType = 1; jType <= maxDayTypes; ++jType) {
if (dayTypeFilter[jType - 1]) {
auto &daySch = state.dataScheduleMgr->DaySchedule(weekSch.DaySchedulePointer(jType));
// use precalcuated min and max from SetScheduleMinMax
MinValue = min(MinValue, daySch.TSValMin);
MaxValue = max(MaxValue, daySch.TSValMax);
}
}
prevWkSch - WkSch;
}
}
if (MinValue == Constant::BigNumber) MinValue = 0;
if (MaxValue == -Constant::BigNumber) MaxValue = 0;
// store for the next call of the same schedule
curSch.MaxByDayType[curDayTypeGroup] = MaxValue;
curSch.MinByDayType[curDayTypeGroup] = MinValue;
curSch.MaxMinByDayTypeSet[curDayTypeGroup] = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another loop over people schedules in DetermineSystemPopulationDiversity. Not sure if this could help there or if when checking those people schedules these 3 values could be set to save even more simulation time.

} else {
// retrieve previously found min and max by day type
MaxValue = curSch.MaxByDayType[curDayTypeGroup];
MinValue = curSch.MinByDayType[curDayTypeGroup];
}
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
MinValue = 1.0;
MaxValue = 1.0;
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
MinValue = 0.0;
MaxValue = 0.0;
} else {
ShowFatalError(state, "getScheduleMinMaxByDayType called with ScheduleIndex out of range");
}
return std::make_pair(MinValue, MaxValue);
}

std::string GetScheduleName(EnergyPlusData &state, int const ScheduleIndex)
{
// FUNCTION INFORMATION:
Expand Down Expand Up @@ -4536,9 +4633,9 @@ namespace ScheduleManager {

if (ScheduleIndex > 0) {
ScheduleName = state.dataScheduleMgr->Schedule(ScheduleIndex).Name;
} else if (ScheduleIndex == -1) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOn) {
ScheduleName = "Constant-1.0";
} else if (ScheduleIndex == 0) {
} else if (ScheduleIndex == ScheduleManager::ScheduleAlwaysOff) {
ScheduleName = "Constant-0.0";
} else {
ScheduleName = "N/A-Invalid";
Expand Down Expand Up @@ -4694,7 +4791,7 @@ namespace ScheduleManager {
DaysInYear = 365;
}

if (ScheduleIndex < -1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
if (ScheduleIndex < ScheduleManager::ScheduleAlwaysOn || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
ShowFatalError(state, "ScheduleAnnualFullLoadHours called with ScheduleIndex out of range");
}

Expand Down Expand Up @@ -4741,7 +4838,7 @@ namespace ScheduleManager {
WeeksInYear = 365.0 / 7.0;
}

if (ScheduleIndex < -1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
if (ScheduleIndex < ScheduleManager::ScheduleAlwaysOn || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
ShowFatalError(state, "ScheduleAverageHoursPerWeek called with ScheduleIndex out of range");
}

Expand All @@ -4768,7 +4865,7 @@ namespace ScheduleManager {
DaysInYear = 365;
}

if (ScheduleIndex < -1 || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
if (ScheduleIndex < ScheduleManager::ScheduleAlwaysOn || ScheduleIndex > state.dataScheduleMgr->NumSchedules) {
ShowFatalError(state, "ScheduleHoursGT1perc called with ScheduleIndex out of range");
}

Expand Down
Loading
Loading