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

Enable Mac ARM support #3408

Merged
merged 18 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions .github/workflows/conda_lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: conda-lock

on:
pull_request:
types: [opened, synchronize]
branches: [master]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Install conda-lock with Micromamba
uses: mamba-org/setup-micromamba@v1
with:
environment-name: ci
create-args: conda-lock
init-shell: bash

- name: Run conda-lock
run: conda-lock --mamba -f environment.yml -p osx-64 -p linux-64 -p win-64 --lockfile ${{ runner.temp }}/conda-lock.yml
shell: bash -el {0}
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ include cea/workflows/*.yml
include cea/dev/conda.bat

graft cea/resources/radiation/bin

graft cea/lib
2 changes: 1 addition & 1 deletion bin/test_substation_calc_hex_cooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def calc_HEX_cooling_orig(Q, UA, thi, tho, tci, ch):
else:
tco = 0
cc = 0
return np.float(tco), np.float(cc)
return float(tco), float(cc)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion bin/test_substation_calc_hex_heating.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def calc_HEX_heating_orig(Q, UA, thi, tco, tci, cc):
else:
tho = 0
ch = 0
return np.float(tho), np.float(ch)
return float(tho), float(ch)


if __name__ == '__main__':
Expand Down
7 changes: 5 additions & 2 deletions cea/datamanagement/streets_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import osmnx
import osmnx.utils_graph
import pandas as pd
from geopandas import GeoDataFrame as Gdf

import cea.config
Expand All @@ -27,8 +28,10 @@ def calc_bounding_box(locator):
# connect both files and avoid repetition
data_zone, data_dis = get_zone_and_surr_in_projected_crs(locator)
data_dis = data_dis.loc[~data_dis["Name"].isin(data_zone["Name"])]
data = data_zone.append(data_dis, ignore_index=True, sort=True)
data = data.to_crs(get_geographic_coordinate_system())
data = pd.concat([
data_zone.to_crs(get_geographic_coordinate_system()),
data_dis.to_crs(get_geographic_coordinate_system())
], ignore_index=True, sort=True)
result = data.total_bounds # in float
return result

Expand Down
7 changes: 4 additions & 3 deletions cea/datamanagement/terrain_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ def request_elevation(lon, lat):


def calc_bounding_box_projected_coordinates(locator):

# connect both files and avoid repetition
data_zone, data_dis = get_zone_and_surr_in_projected_crs(locator)
data_dis = data_dis.loc[~data_dis["Name"].isin(data_zone["Name"])]
data = data_zone.append(data_dis, ignore_index = True, sort=True)
data = data.to_crs(get_geographic_coordinate_system())
data = pd.concat([
data_zone.to_crs(get_geographic_coordinate_system()),
data_dis.to_crs(get_geographic_coordinate_system())
], ignore_index=True, sort=True)
lon = data.geometry[0].centroid.coords.xy[0][0]
lat = data.geometry[0].centroid.coords.xy[1][0]
crs = get_projected_coordinate_system(float(lat), float(lon))
Expand Down
19 changes: 11 additions & 8 deletions cea/datamanagement/zone_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,16 +500,19 @@ def polygon_to_zone(buildings_floors, buildings_floors_below_ground, buildings_h
lon = poly.geometry[0].centroid.coords.xy[0][0]
lat = poly.geometry[0].centroid.coords.xy[1][0]
# get all footprints in the district tagged as 'building' or 'building:part' in OSM
shapefile = osmnx.geometries.geometries_from_polygon(polygon=poly['geometry'].values[0], tags={"building": True})
shapefile = osmnx.features.features_from_polygon(polygon=poly['geometry'].values[0], tags={"building": True})
if include_building_parts:
# get all footprints in the district tagged as 'building' or 'building:part' in OSM
building_parts = osmnx.geometries.geometries_from_polygon(polygon=poly['geometry'].values[0],
try:
# get all footprints in the district tagged as 'building' or 'building:part' in OSM
building_parts = osmnx.features.features_from_polygon(polygon=poly['geometry'].values[0],
tags={"building": ["part"]})
shapefile = pd.concat([shapefile, building_parts], ignore_index=True)
# using building:part tags requires fixing overlapping polygons
if not fix_overlapping:
print('Building parts included, fixing overlapping geometries activated.')
fix_overlapping = True
shapefile = pd.concat([shapefile, building_parts], ignore_index=True)
# using building:part tags requires fixing overlapping polygons
if not fix_overlapping:
print('Building parts included, fixing overlapping geometries activated.')
fix_overlapping = True
except osmnx._errors.InsufficientResponseError:
pass

# clean geometries
shapefile = clean_geometries(shapefile)
Expand Down
2 changes: 1 addition & 1 deletion cea/glossary.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def read_glossary_df(plugins):
cd = schemas[lm]["schema"]["columns"][col] # cd: column definition
rows.append(glossary_row(script, file_path, col, lm, cd, worksheet=""))

glossary_df = glossary_df.append(rows, ignore_index=True)
glossary_df = pd.concat([glossary_df, pd.DataFrame(rows)], ignore_index=True)
glossary_df['key'] = glossary_df['FILE_NAME'] + '!!!' + glossary_df['VARIABLE']
glossary_df = glossary_df.set_index(['key'])
glossary_df = glossary_df.sort_values(by=['LOCATOR_METHOD', 'FILE_NAME', 'VARIABLE'])
Expand Down
18 changes: 5 additions & 13 deletions cea/interfaces/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,15 @@
import cea.plots
import cea.plots.cache

socketio = None


def main(config):
config.restricted_to = None # allow access to the whole config file
plot_cache = cea.plots.cache.MemoryPlotCache(config.project)
app = Flask(__name__, static_folder='base/static', )
CORS(app)
app.config.from_mapping({'SECRET_KEY': 'secret'})

global socketio
socketio = SocketIO(app, cors_allowed_origins="*")

def shutdown(signum, frame):
print("Shutting Down...")
socketio.stop()
signal.signal(signal.SIGINT, shutdown)

if config.server.browser:
from cea.interfaces.dashboard.frontend import blueprint as frontend
app.register_blueprint(frontend)
Expand All @@ -44,16 +35,17 @@ def shutdown(signum, frame):
app.plot_cache = plot_cache
app.socketio = socketio

print("start socketio.run")

print("start CEA dashboard server")
if config.server.browser:
url = f"http://{config.server.host}:{config.server.port}"
print(f"Open {url} in your browser to access the GUI")
webbrowser.open(url)

print("Press Ctrl+C to stop server")
socketio.run(app, host=config.server.host, port=config.server.port)
print("done socketio.run")
socketio.run(app, host=config.server.host, port=config.server.port, allow_unsafe_werkzeug=True)

print("\nserver exited")


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion cea/interfaces/dashboard/server/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

from flask import request, current_app
from flask_restx import Namespace, Resource, fields
from flask_restx import Namespace, Resource

__author__ = "Daren Thomas"
__copyright__ = "Copyright 2019, Architecture and Building Systems - ETH Zurich"
Expand Down
Binary file added cea/lib/Darwin/libepanet.dylib
Binary file not shown.
33 changes: 33 additions & 0 deletions cea/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
This only works for wntr v0.2.3

Apple Silicon lib builds from:
WNTR (https://github.com/USEPA/WNTR/tree/0.2.3)
set `use_swig` and `build` to True in setup.py
make sure swig is installed
python setup.py build_ext --inplace
copy _evaluator and _network from build

EPANET (https://github.com/OpenWaterAnalytics/EPANET/tree/v2.0.12)
replace all `malloc.h` with `stdlib.h`
gcc -dynamiclib *.c -o libepanet.dylib
copy libepanet.dylib to Darwin folder
"""

import platform
import sys
import os

if sys.platform == "darwin" and platform.processor() == "arm":
# Add required shared objects to path before importing wntr
sys.path.append(os.path.dirname(__file__))

# Make sure correct version of wntr is installed
import wntr
if wntr.__version__ != '0.2.3':
raise ImportError(f"Require wntr version 0.2.3, found {wntr.__version__}")

print("Applying fix for Apple Silicon")
# Change where wntr looks for libepanet
import wntr.epanet.toolkit
wntr.epanet.toolkit.epanet_toolkit = "cea.lib"
Binary file added cea/lib/_evaluator.cpython-38-darwin.so
Binary file not shown.
Binary file added cea/lib/_network_isolation.cpython-38-darwin.so
Binary file not shown.
22 changes: 12 additions & 10 deletions cea/optimization/preprocessing/decentralized_buildings_cooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ def disconnected_cooling_for_building(building_name, supply_systems, lca, locato
# capacity of cooling technologies
operation_results[2][0] = Qc_nom_AHU_ARU_SCU_W
operation_results[2][4] = Qc_nom_AHU_ARU_SCU_W # 4: ACH_SC_FP
q_total_load = q_chw_single_ACH_Wh[None, :] + q_sc_gen_FP_Wh[None,
:] + q_load_Boiler_FP_to_single_ACH_to_AHU_ARU_SCU_Wh[None, :]
system_COP_list = np.divide(q_total_load, (
el_total_Wh[None, :] + q_gas_Boiler_FP_to_single_ACH_to_AHU_ARU_SCU_Wh[None, :])).flatten()
system_COP = np.nansum(q_total_load * system_COP_list) / np.nansum(
q_total_load) # weighted average of the system efficiency
q_total_load = (q_chw_single_ACH_Wh[None, :] +
q_sc_gen_FP_Wh[None, :] +
q_load_Boiler_FP_to_single_ACH_to_AHU_ARU_SCU_Wh[None, :])
system_COP_list = np.divide(q_total_load,
(el_total_Wh[None, :] + q_gas_Boiler_FP_to_single_ACH_to_AHU_ARU_SCU_Wh[None, :])
).flatten()
system_COP = (np.nansum(q_total_load * system_COP_list) /
np.nansum(q_total_load)) # weighted average of the system efficiency
operation_results[2][9] += system_COP

# 3: SC_ET + single-effect ACH (AHU + ARU + SCU) + CT + Boiler + SC_ET
Expand Down Expand Up @@ -286,8 +288,8 @@ def disconnected_cooling_for_building(building_name, supply_systems, lca, locato
operation_results[3][5] = Qc_nom_AHU_ARU_SCU_W
q_total_load = (q_burner_load_Wh[None, :] + q_chw_single_ACH_Wh[None, :] + q_sc_gen_ET_Wh[None, :])
system_COP_list = np.divide(q_total_load, (el_total_Wh[None, :] + q_gas_for_burner_Wh[None, :])).flatten()
system_COP = np.nansum(q_total_load * system_COP_list) / np.nansum(
q_total_load) # weighted average of the system efficiency
system_COP = (np.nansum(q_total_load * system_COP_list) /
np.nansum(q_total_load)) # weighted average of the system efficiency
operation_results[3][9] += system_COP

# these two configurations are only activated when SCU is in use
Expand Down Expand Up @@ -673,9 +675,9 @@ def get_SC_data(building_name, locator, panel_type):
SC_data = pd.read_csv(locator.SC_results(building_name, panel_type),
usecols=["T_SC_sup_C", "T_SC_re_C", "mcp_SC_kWperC", "Q_SC_gen_kWh", "Area_SC_m2",
"Eaux_SC_kWh"])
q_sc_gen_Wh = SC_data['Q_SC_gen_kWh'] * 1000
q_sc_gen_Wh = (SC_data['Q_SC_gen_kWh'] * 1000).values
q_sc_gen_Wh = np.where(q_sc_gen_Wh < 0.0, 0.0, q_sc_gen_Wh)
el_aux_SC_Wh = SC_data['Eaux_SC_kWh'] * 1000
el_aux_SC_Wh = (SC_data['Eaux_SC_kWh'] * 1000).values
if panel_type == "FP":
T_hw_in_C = [x if x > T_GENERATOR_FROM_FP_C else T_GENERATOR_FROM_FP_C for x in SC_data['T_SC_re_C']]
elif panel_type == "ET":
Expand Down
4 changes: 2 additions & 2 deletions cea/optimization/slave/cooling_resource_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,15 @@ def activate_CCandACH_trigen(Q_cooling_unmet_W,
if Qh_CCGT_req_W <= Q_output_CC_max_W: # Normal operation possible within part load regime
Q_CHP_gen_W = float(Qh_CCGT_req_W)
NG_Trigen_req_W = Q_used_prim_CC_fn_W(Q_CHP_gen_W)
E_Trigen_NG_gen_W = np.float(eta_elec_interpol(NG_Trigen_req_W)) * NG_Trigen_req_W
E_Trigen_NG_gen_W = float(eta_elec_interpol(NG_Trigen_req_W)) * NG_Trigen_req_W

else: # Only part of the demand can be delivered as 100% load achieved
# This case should never occur, since it means that another heat source would be required to run
# the absorption chiller at the required capacity.
print("WARNING: Gas turbine can not output enough heat to power the absorption chiller!")
Q_CHP_gen_W = Q_output_CC_max_W
NG_Trigen_req_W = Q_used_prim_CC_fn_W(Q_CHP_gen_W)
E_Trigen_NG_gen_W = np.float(eta_elec_interpol(NG_Trigen_req_W)) * NG_Trigen_req_W
E_Trigen_NG_gen_W = float(eta_elec_interpol(NG_Trigen_req_W)) * NG_Trigen_req_W

# if the operation of the combined cycle is not possible due to its limited capacity, the absorption chiller
# (i.e. the cooling technology of the tri-generation plant) can not be powered either
Expand Down
4 changes: 2 additions & 2 deletions cea/optimization/slave/heating_resource_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ def heating_source_activator(Q_therm_req_W,
if Q_heat_unmet_W <= Q_output_CC_max_W: # Normal operation Possible within partload regime
Q_CHP_gen_W = Q_heat_unmet_W
NG_CHP_req_W = Q_used_prim_CC_fn_W(Q_CHP_gen_W)
E_CHP_gen_W = np.float(eta_elec_interpol(NG_CHP_req_W)) * NG_CHP_req_W
E_CHP_gen_W = float(eta_elec_interpol(NG_CHP_req_W)) * NG_CHP_req_W
else: # Only part of the demand can be delivered as 100% load achieved
Q_CHP_gen_W = Q_output_CC_max_W
NG_CHP_req_W = Q_used_prim_CC_fn_W(Q_CHP_gen_W)
E_CHP_gen_W = np.float(eta_elec_interpol(NG_CHP_req_W)) * NG_CHP_req_W
E_CHP_gen_W = float(eta_elec_interpol(NG_CHP_req_W)) * NG_CHP_req_W
else:
NG_CHP_req_W = 0.0
E_CHP_gen_W = 0.0
Expand Down
6 changes: 4 additions & 2 deletions cea/optimization_new/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(self, identifier, demands_file_path):
self.identifier = identifier
self.demands_file_path = demands_file_path
self.stand_alone_supply_system = SupplySystem()
self.crs = None
self._demand_flow = EnergyFlow()
self._footprint = None
self._location = None
Expand Down Expand Up @@ -98,6 +99,7 @@ def load_building_location(self, domain_shp_file):
reference system).
"""
if self.location is None:
self.crs = domain_shp_file.crs
self.footprint = domain_shp_file[domain_shp_file.Name == self.identifier].geometry.iloc[0]
self.location = self.footprint.representative_point()
else:
Expand Down Expand Up @@ -190,7 +192,7 @@ def distribute_building_potentials(domain_energy_potentials, domain_buildings):

# group the potentials by energy carriers and sum them up if necessary
for potential in building_scale_energy_potentials:
for building, profile in potential.main_building_profiles.iteritems():
for building, profile in potential.main_building_profiles.items():
if potential.main_potential.energy_carrier.code in building_energy_potentials[building].keys():
building_energy_potentials[building][potential.main_potential.energy_carrier.code] += \
EnergyFlow('source', 'secondary',
Expand All @@ -199,7 +201,7 @@ def distribute_building_potentials(domain_energy_potentials, domain_buildings):
building_energy_potentials[building][potential.main_potential.energy_carrier.code] = \
EnergyFlow('source', 'secondary',
potential.main_potential.energy_carrier.code, profile)
for building, profile in potential.auxiliary_building_profiles.iteritems():
for building, profile in potential.auxiliary_building_profiles.items():
if potential.auxiliary_potential.energy_carrier.code in building_energy_potentials[building].keys():
building_energy_potentials[building][potential.auxiliary_potential.energy_carrier.code] += \
EnergyFlow('source', 'secondary',
Expand Down
9 changes: 6 additions & 3 deletions cea/optimization_new/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
__email__ = "mathias.niffeler@sec.ethz.ch"
__status__ = "Production"

import cea.lib

import os.path
import tempfile

Expand Down Expand Up @@ -245,7 +247,8 @@ def _load_pot_network(domain):
# join building locations (shapely.POINTS) and the corresponding identifiers in a DataFrame
building_identifiers = [building.identifier for building in domain.buildings]
building_locations = [building.location for building in domain.buildings]
buildings_df = pd.DataFrame(list(zip(building_locations, building_identifiers)), columns=['geometry', 'Name'])
buildings_df = Gdf(list(zip(building_locations, building_identifiers)), columns=['geometry', 'Name'],
crs=domain.buildings[0].crs, geometry="geometry")

# create a potential network grid with orthogonal connections between buildings and their closest street
network_grid_shp = calc_connectivity_network(domain.locator.get_street_network(),
Expand Down Expand Up @@ -450,7 +453,7 @@ def _set_plant_next_to_building(self, anchor_building):
plant_terminal = plant_terminal.rename({plant_terminal.index[0]: plant_terminal_node})
plant_terminal['Type'][0] = "PLANT"

self.network_nodes = self.network_nodes.append(plant_terminal)
self.network_nodes = pd.concat([self.network_nodes, plant_terminal])

# create new edge
point1 = (plant_terminal.geometry[0].x, plant_terminal.geometry[0].y)
Expand All @@ -461,7 +464,7 @@ def _set_plant_next_to_building(self, anchor_building):
'end node': plant_terminal_node},
index=['PIPE' + str(len(self.network_edges.index))],
crs=Network._coordinate_reference_system)
self.network_edges = self.network_edges.append(plant_to_network)
self.network_edges = pd.concat([self.network_edges, plant_to_network])

def _run_water_network_model(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion cea/technologies/boiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def calc_Cop_boiler(q_load_Wh, Q_nom_W, T_return_to_boiler_K):
phi = float(q_load_Wh) / float(Q_nom_W)
if phi >=1.0: # avoid rounding error
phi = 0.98
T_return_C = np.float(T_return_to_boiler_K - 273.15)
T_return_C = float(T_return_to_boiler_K - 273.15)
eff_score = eff_of_phi(phi) / eff_of_phi(1)
boiler_eff = (eff_score * eff_of_T_return([T_return_C]))[0] / 100.0
else:
Expand Down
4 changes: 2 additions & 2 deletions cea/technologies/chiller_absorption.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import pandas as pd
import numpy as np
from math import log, ceil
import sympy
# import sympy
from cea.constants import HEAT_CAPACITY_OF_WATER_JPERKGK
from cea.analysis.costs.equations import calc_capex_annualized, calc_opex_annualized
from cea.analysis.costs.equations import calc_capex_annualized

__author__ = "Shanshan Hsieh"
__copyright__ = "Copyright 2015, Architecture and Building Systems - ETH Zurich"
Expand Down
Loading