-
Notifications
You must be signed in to change notification settings - Fork 75
Allow running pv plots without running demand #3911
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
Changes from 20 commits
772d7dd
471bf9f
cecbac8
89f30a8
e4368a0
d94b8cf
d59270a
ca9dc84
261e502
3c6908b
f49e9d8
2378c8d
a3c5083
9555831
5b160d7
a3a0cc6
869dbc8
0f7f254
6dfdf54
6b4c12a
fbe780d
261ab2c
15d3817
a48c124
c5650eb
46e4390
7b26b9b
2eb9c7b
22d9c8e
7a25a5d
00812aa
f82c3d1
0447871
49313cf
bf104c5
03cc1c1
38901a9
0db063b
60e2758
8c64beb
93e8b12
47390bb
0f0bee5
bdba1c5
38621da
d8f9ce3
565f8f8
f9890f6
4944435
f6d3239
45c1853
96e8071
c59da2a
46b7eb7
ab9746a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -24,6 +24,7 @@ | |||||||||||||||||||
from cea.datamanagement.format_helper.cea4_verify import cea4_verify, verify_shp, \ | ||||||||||||||||||||
COLUMNS_ZONE_4, print_verification_results_4, path_to_input_file_without_db_4, CSV_BUILDING_PROPERTIES_3_CSV | ||||||||||||||||||||
from cea.datamanagement.format_helper.cea4_verify_db import check_directory_contains_csv | ||||||||||||||||||||
from cea.datamanagement.utils import migrate_void_deck_data | ||||||||||||||||||||
from cea.utilities.dbf import dbf_to_dataframe | ||||||||||||||||||||
|
||||||||||||||||||||
COLUMNS_ZONE_3 = ['Name', 'floors_bg', 'floors_ag', 'height_bg', 'height_ag'] | ||||||||||||||||||||
|
@@ -550,9 +551,12 @@ def migrate_cea3_to_cea4(scenario, verbose=False): | |||||||||||||||||||
pass | ||||||||||||||||||||
# print('For Scenario: {scenario}, '.format(scenario=scenario_name), 'zone.shp already follows the CEA-4 format.') | ||||||||||||||||||||
else: | ||||||||||||||||||||
raise ValueError('! zone.shp exists but follows neither the CEA-3 nor CEA-4 format. CEA cannot proceed with the data migration.' | ||||||||||||||||||||
'Check the following column(s) for CEA-3 format: {list_missing_attributes_zone_3}.'.format(list_missing_attributes_zone_3=list_missing_attributes_zone_3), | ||||||||||||||||||||
'Check the following column(s) for CEA-4 format: {list_missing_attributes_zone_4}.'.format(list_missing_attributes_zone_4=list_missing_attributes_zone_4) | ||||||||||||||||||||
if list_missing_attributes_zone_4[0] == 'void_deck' and len(list_missing_attributes_zone_4) == 1: | ||||||||||||||||||||
config = cea.config.Configuration() | ||||||||||||||||||||
locator = cea.inputlocator.InputLocator(config.scenario) | ||||||||||||||||||||
migrate_void_deck_data(locator) | ||||||||||||||||||||
else: | ||||||||||||||||||||
raise ValueError('! zone.shp exists but follows neither the CEA-3 nor CEA-4 format. CEA cannot proceed with the data migration. Check the following column(s) for CEA-3 format: {list_missing_attributes_zone_3}.'.format(list_missing_attributes_zone_3=list_missing_attributes_zone_3), 'Check the following column(s) for CEA-4 format: {list_missing_attributes_zone_4}.'.format(list_missing_attributes_zone_4=list_missing_attributes_zone_4) | ||||||||||||||||||||
) | ||||||||||||||||||||
else: | ||||||||||||||||||||
print("! Ensure zone.shp (CEA-3 format) is present in building-geometry folder.") | ||||||||||||||||||||
|
@@ -571,9 +575,12 @@ def migrate_cea3_to_cea4(scenario, verbose=False): | |||||||||||||||||||
pass | ||||||||||||||||||||
# print('For Scenario: {scenario}, '.format(scenario=scenario_name), 'surroundings.shp already follows the CEA-4 format.') | ||||||||||||||||||||
else: | ||||||||||||||||||||
raise ValueError('surroundings.shp exists but follows neither the CEA-3 nor CEA-4 format. CEA cannot proceed with the data migration.' | ||||||||||||||||||||
'Check the following column(s) for CEA-3 format: {list_missing_attributes_surroundings_3}.'.format(list_missing_attributes_surroundings_3=list_missing_attributes_surroundings_3), | ||||||||||||||||||||
'Check the following column(s) for CEA-4 format: {list_missing_attributes_surroundings_4}.'.format(list_missing_attributes_surroundings_4=list_missing_attributes_surroundings_4) | ||||||||||||||||||||
if list_missing_attributes_zone_4[0] == 'void_deck' and len(list_missing_attributes_zone_4) == 1: | ||||||||||||||||||||
config = cea.config.Configuration() | ||||||||||||||||||||
locator = cea.inputlocator.InputLocator(config.scenario) | ||||||||||||||||||||
migrate_void_deck_data(locator) | ||||||||||||||||||||
else: | ||||||||||||||||||||
raise ValueError('surroundings.shp exists but follows neither the CEA-3 nor CEA-4 format. CEA cannot proceed with the data migration. Check the following column(s) for CEA-3 format: {list_missing_attributes_surroundings_3}.'.format(list_missing_attributes_surroundings_3=list_missing_attributes_surroundings_3), 'Check the following column(s) for CEA-4 format: {list_missing_attributes_surroundings_4}.'.format(list_missing_attributes_surroundings_4=list_missing_attributes_surroundings_4) | ||||||||||||||||||||
) | ||||||||||||||||||||
Comment on lines
+578
to
584
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong list referenced in surroundings branch; remove void_deck migration here.
Apply this diff: - else:
- if list_missing_attributes_zone_4[0] == 'void_deck' and len(list_missing_attributes_zone_4) == 1:
- config = cea.config.Configuration()
- locator = cea.inputlocator.InputLocator(config.scenario)
- migrate_void_deck_data(locator)
- else:
- raise ValueError('surroundings.shp exists but follows neither the CEA-3 nor CEA-4 format. CEA cannot proceed with the data migration. Check the following column(s) for CEA-3 format: {list_missing_attributes_surroundings_3}.'.format(list_missing_attributes_surroundings_3=list_missing_attributes_surroundings_3), 'Check the following column(s) for CEA-4 format: {list_missing_attributes_surroundings_4}.'.format(list_missing_attributes_surroundings_4=list_missing_attributes_surroundings_4)
- )
+ else:
+ raise ValueError('surroundings.shp exists but follows neither the CEA-3 nor CEA-4 format. CEA cannot proceed with the data migration. Check the following column(s) for CEA-3 format: {list_missing_attributes_surroundings_3}.'.format(list_missing_attributes_surroundings_3=list_missing_attributes_surroundings_3), 'Check the following column(s) for CEA-4 format: {list_missing_attributes_surroundings_4}.'.format(list_missing_attributes_surroundings_4=list_missing_attributes_surroundings_4)) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||
else: | ||||||||||||||||||||
print('! (optional) Run Surroundings Helper to generate surroundings.shp after the data migration.') | ||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
from __future__ import annotations | ||
from typing import TYPE_CHECKING | ||
import warnings | ||
|
||
import pandas as pd | ||
import geopandas as gpd | ||
|
||
if TYPE_CHECKING: | ||
from cea.inputlocator import InputLocator | ||
|
||
|
||
def migrate_void_deck_data(locator: InputLocator) -> None: | ||
"""Check if void_deck exists in zone.shp and copy it from envelope.csv if necessary. | ||
:param locator: the input locator object. | ||
:type locator: cea.inputlocator.InputLocator | ||
""" | ||
|
||
zone_gdf = gpd.read_file(locator.get_zone_geometry()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing zone geometry update after void deck migration. The Apply this diff to save the updated zone geometry: zone_gdf["void_deck"] = zone_gdf["void_deck"].fillna(0)
print("Migrated void_deck data from envelope.csv to zone.shp.")
+zone_gdf.to_file(locator.get_zone_geometry())
envelope_df.drop(columns=["void_deck"], inplace=True)
envelope_df.to_csv(locator.get_building_architecture(), index=False) And for the case where void_deck is initialized to 0: zone_gdf["void_deck"] = 0
warnings.warn(
"No void_deck data found in envelope.csv, setting to 0 in zone.shp"
)
+zone_gdf.to_file(locator.get_zone_geometry()) Also applies to: 27-27 🤖 Prompt for AI Agents
|
||
isin_zone = "void_deck" in zone_gdf.columns | ||
|
||
if not isin_zone: | ||
envelope_df = pd.read_csv(locator.get_building_architecture()) | ||
|
||
if "void_deck" in envelope_df.columns: | ||
# assign void_deck from envelope.csv to zone.shp and remove it from envelope.csv | ||
zone_gdf = zone_gdf.merge( | ||
envelope_df[["name", "void_deck"]], on="name", how="left" | ||
) | ||
zone_gdf["void_deck"] = zone_gdf["void_deck"].fillna(0) | ||
print("Migrated void_deck data from envelope.csv to zone.shp.") | ||
envelope_df.drop(columns=["void_deck"], inplace=True) | ||
envelope_df.to_csv(locator.get_building_architecture(), index=False) | ||
|
||
else: # cannot find void_deck anywhere, just initialize it to 0 | ||
zone_gdf["void_deck"] = 0 | ||
warnings.warn( | ||
"No void_deck data found in envelope.csv, setting to 0 in zone.shp" | ||
) | ||
|
||
# Validate that floors_ag is larger than void_deck for each building | ||
actual_floors = zone_gdf["floors_ag"] - zone_gdf["void_deck"] | ||
invalid_floors = zone_gdf[actual_floors <= 0] | ||
if len(invalid_floors) > 0: | ||
invalid_buildings = invalid_floors["name"].tolist() | ||
warnings.warn(f"Some buildings have void_deck greater than floors_ag: {invalid_buildings}", | ||
RuntimeWarning) | ||
|
||
|
||
def generate_architecture_csv(locator: InputLocator, building_typology_df: gpd.GeoDataFrame): | ||
""" | ||
Generate an architecture CSV file with geometric properties | ||
Includes: | ||
- Af_m2: Conditioned floor area [m2] | ||
- Aroof_m2: Roof area [m2] | ||
- GFA_m2: Gross floor area [m2] | ||
- Aocc_m2: Occupied floor area [m2] | ||
:param locator: InputLocator instance | ||
:param building_typology_df: GeoDataFrame containing building geometry data | ||
""" | ||
# Get architecture database to access Hs, Ns, Es, occupied_bg values | ||
architecture_DB = pd.read_csv(locator.get_database_archetypes_construction_type()) | ||
prop_architecture_df = building_typology_df.merge(architecture_DB, left_on='const_type', right_on='const_type', | ||
# avoid column name conflicts and keep left ones | ||
# possible conflicts: 'void_deck' | ||
suffixes=('', '_y')) | ||
|
||
# Calculate architectural properties | ||
# Calculate areas based on geometry | ||
footprint = prop_architecture_df.geometry.area # building footprint area | ||
floors_ag = prop_architecture_df['floors_ag'] # above-ground floors | ||
floors_bg = prop_architecture_df['floors_bg'] # below-ground floors | ||
void_deck = prop_architecture_df['void_deck'] # void deck floors | ||
|
||
|
||
# Get shares from architecture database | ||
Hs = prop_architecture_df['Hs'] # Share of GFA that is conditioned | ||
Ns = prop_architecture_df['Ns'] # Share of GFA that is occupied | ||
occupied_bg = prop_architecture_df['occupied_bg'] # Whether basement is occupied | ||
|
||
# Calculate GFA components using proper equations | ||
gfa_ag_m2 = footprint * (floors_ag - void_deck) # Above-ground GFA | ||
gfa_bg_m2 = footprint * floors_bg # Below-ground GFA | ||
gfa_m2 = gfa_ag_m2 + gfa_bg_m2 # Total GFA | ||
|
||
# Split shares between above and below ground areas | ||
# Using the same logic as in useful_areas.py split_above_and_below_ground_shares | ||
effective_floors_ag = floors_ag - void_deck | ||
denominator = effective_floors_ag + floors_bg * occupied_bg | ||
share_ag = effective_floors_ag / denominator | ||
# Handle division by zero case | ||
share_ag = share_ag.fillna(1.0).where(denominator > 0, 1.0) | ||
share_bg = 1 - share_ag | ||
|
||
Hs_ag = Hs * share_ag | ||
Hs_bg = Hs * share_bg | ||
Ns_ag = Ns * share_ag | ||
Ns_bg = Ns * share_bg | ||
|
||
# Calculate areas using proper equations from useful_areas.py | ||
af_m2 = gfa_ag_m2 * Hs_ag + gfa_bg_m2 * Hs_bg # Conditioned floor area | ||
aocc_m2 = gfa_ag_m2 * Ns_ag + gfa_bg_m2 * Ns_bg # Occupied floor area | ||
aroof_m2 = footprint # Roof area equals footprint | ||
|
||
# Create DataFrame directly from vectorized calculations | ||
architecture_df = pd.DataFrame({ | ||
'name': prop_architecture_df['name'], | ||
'Af_m2': af_m2, | ||
'Aroof_m2': aroof_m2, | ||
'GFA_m2': gfa_m2, | ||
'Aocc_m2': aocc_m2, | ||
}) | ||
|
||
# Ensure parent folder exists | ||
locator.ensure_parent_folder_exists(locator.get_architecture_csv()) | ||
|
||
# Save to CSV file | ||
architecture_df.to_csv(locator.get_architecture_csv(), index=False, float_format='%.3f') | ||
print(f"Architecture data generated and saved to: {locator.get_architecture_csv()}") | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -87,7 +87,7 @@ def results_to_hdf5(self, tsd: TimeSeriesData, bpr: BuildingPropertiesRow, locat | |||||||||||||||||||||||||||||||||||||||
self.write_to_hdf5(building_name, columns, hourly_data, locator) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# save total for the year | ||||||||||||||||||||||||||||||||||||||||
columns, data = self.calc_yearly_dataframe(bpr, building_name, tsd) | ||||||||||||||||||||||||||||||||||||||||
columns, data = self.calc_yearly_dataframe(bpr, building_name, tsd, locator) | ||||||||||||||||||||||||||||||||||||||||
# save to disc | ||||||||||||||||||||||||||||||||||||||||
partial_total_data = pd.DataFrame(data, index=[0]) | ||||||||||||||||||||||||||||||||||||||||
partial_total_data.drop('name', inplace=True, axis=1) | ||||||||||||||||||||||||||||||||||||||||
|
@@ -101,12 +101,12 @@ def results_to_csv(self, tsd: TimeSeriesData, bpr: BuildingPropertiesRow, locato | |||||||||||||||||||||||||||||||||||||||
self.write_to_csv(building_name, columns, hourly_data, locator) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
# save annual values to a temp file for YearlyDemandWriter | ||||||||||||||||||||||||||||||||||||||||
columns, data = self.calc_yearly_dataframe(bpr, building_name, tsd) | ||||||||||||||||||||||||||||||||||||||||
columns, data = self.calc_yearly_dataframe(bpr, building_name, tsd, locator) | ||||||||||||||||||||||||||||||||||||||||
pd.DataFrame(data, index=[0]).to_csv( | ||||||||||||||||||||||||||||||||||||||||
locator.get_temporary_file('%(building_name)sT.csv' % locals()), | ||||||||||||||||||||||||||||||||||||||||
index=False, columns=columns, float_format='%.3f', na_rep='nan') | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def calc_yearly_dataframe(self, bpr: BuildingPropertiesRow, building_name, tsd: TimeSeriesData): | ||||||||||||||||||||||||||||||||||||||||
def calc_yearly_dataframe(self, bpr: BuildingPropertiesRow, building_name, tsd: TimeSeriesData, locator): | ||||||||||||||||||||||||||||||||||||||||
# if printing total values is necessary | ||||||||||||||||||||||||||||||||||||||||
# treating timeseries data from W to MWh | ||||||||||||||||||||||||||||||||||||||||
data = dict((x + '_MWhyr', np.nan_to_num(tsd.get_load_value(x)).sum() / 1000000) for x in self.load_vars) | ||||||||||||||||||||||||||||||||||||||||
|
@@ -115,9 +115,17 @@ def calc_yearly_dataframe(self, bpr: BuildingPropertiesRow, building_name, tsd: | |||||||||||||||||||||||||||||||||||||||
keys = data.keys() | ||||||||||||||||||||||||||||||||||||||||
columns = self.OTHER_VARS | ||||||||||||||||||||||||||||||||||||||||
columns.extend(keys) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mutating shared class list (self.OTHER_VARS) causes column duplication across buildings columns = self.OTHER_VARS extends the shared list in-place, so subsequent buildings will accumulate duplicate keys. Apply this diff: - columns = self.OTHER_VARS
+ columns = self.OTHER_VARS.copy()
columns.extend(keys) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||
# get the architecture data | ||||||||||||||||||||||||||||||||||||||||
architecture_df = pd.read_csv(locator.get_architecture_csv()) | ||||||||||||||||||||||||||||||||||||||||
Af_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'Af_m2'].iloc[0]) | ||||||||||||||||||||||||||||||||||||||||
Aroof_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'Aroof_m2'].iloc[0]) | ||||||||||||||||||||||||||||||||||||||||
GFA_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'GFA_m2'].iloc[0]) | ||||||||||||||||||||||||||||||||||||||||
Aocc_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'Aocc_m2'].iloc[0]) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
# get the architecture data | |
architecture_df = pd.read_csv(locator.get_architecture_csv()) | |
Af_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'Af_m2'].iloc[0]) | |
Aroof_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'Aroof_m2'].iloc[0]) | |
GFA_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'GFA_m2'].iloc[0]) | |
Aocc_m2 = float(architecture_df.loc[architecture_df['name'] == building_name, 'Aocc_m2'].iloc[0]) | |
# get the architecture data | |
import os | |
arch_path = locator.get_architecture_csv() | |
if not os.path.exists(arch_path): | |
raise FileNotFoundError(f"Missing architecture CSV: {arch_path}. Generate it before running demand.") | |
architecture_df = pd.read_csv(arch_path) | |
row = architecture_df.loc[architecture_df['name'] == building_name] | |
if row.empty: | |
raise KeyError(f"Building '{building_name}' not found in {arch_path}.") | |
Af_m2 = float(row['Af_m2'].iloc[0]) | |
Aroof_m2 = float(row['Aroof_m2'].iloc[0]) | |
GFA_m2 = float(row['GFA_m2'].iloc[0]) | |
Aocc_m2 = float(row['Aocc_m2'].iloc[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard the ‘void_deck-only’ check and build the locator from the passed
scenario
(also avoids indexing errors).list_missing_attributes_zone_4[0]
will crash when the list is empty.['void_deck']
and instantiateInputLocator
with the function’sscenario
argument (not the global config). Also,cea.inputlocator
isn’t imported in this file.Apply this diff:
Add the missing import near the top of the file:
🤖 Prompt for AI Agents