diff --git a/BuildObject/BuildingObject.py b/BuildObject/BuildingObject.py new file mode 100644 index 0000000..4106321 --- /dev/null +++ b/BuildObject/BuildingObject.py @@ -0,0 +1,879 @@ +# @Author : Xavier Faure +# @Email : xavierf@kth.se + +from shapely.geometry.polygon import Polygon, Point, LineString +import CoreFiles.GeneralFunctions as GrlFct +import CoreFiles.MUBES_pygeoj as MUBES_pygeoj +from CoreFiles import setConfig as setConfig +from geomeppy.geom.polygons import Polygon2D, break_polygons, Polygon3D +from geomeppy import IDF +from geomeppy.geom import core_perim +import os +import json +import shutil +import BuildObject.GeomUtilities as GeomUtilities +import re +import CoreFiles.ProbGenerator as ProbGenerator + +import matplotlib.pyplot as plt +#this class defines the building characteristics regarding available data in the geojson file + +#function that checks if value is out of limits +def checkLim(val, ll, ul): + if val < ll: + val = ll + elif val > ul: + val = round(val/10) + if val > ul: + val = ul + return val + +#get the value from the correct key +def getDBValue(DB, Keys): + Val = '' + IdKey = '' + if type(Keys) ==list: + for key in Keys: + try: + Val = DB[key] + IdKey = key + break + except: + pass + else: + try: + Val = DB[Keys] + IdKey = Keys + except: pass + return Val,IdKey + + +#find the wall id for the shading surfaces from surrounding buildings +def findWallId(Id, Shadingsfile, ref,GE): + finished = 0 + ii = 0 + ShadeWall = {} + while finished == 0: + if Id in Shadingsfile[ii].properties[GE['ShadingIdKey']]: + ShadeWall[GE['BuildingIdKey']] = Shadingsfile[ii].properties[GE['BuildingIdKey']] + ShadeWall[GE['ShadingIdKey']] = Shadingsfile[ii].properties[GE['ShadingIdKey']] + try: + ShadeWall['height'] = float(Shadingsfile[ii].properties['zmax'])-float(Shadingsfile[ii].properties['zmin']) + except: + pass + ShadeWall[GE['VertexKey']] = [] + for jj in Shadingsfile[ii].geometry.coordinates: + ShadeWall[GE['VertexKey']].append(tuple([jj[0]-ref[0],jj[1]-ref[1]])) + finished = 1 + else: + ii = ii+1 + if ii>len(Shadingsfile): + print('No finded Wall Id ....') + finished = 1 + return ShadeWall + +#find the height the building's ID +def findBuildId(Id, Buildingsfile,GE): + finished = 0 + ii = 0 + height = 0 + while finished == 0: + if Id == Buildingsfile[ii].properties[GE['BuildingIdKey']]: + height = Buildingsfile[ii].properties['height'] + finished = 1 + else: + ii = ii+1 + if ii>len(Buildingsfile): + print('No finded Build Id ....') + finished = 1 + return height + +class BuildingList: + def __init__(self): + self.building = [] + + def addBuilding(self,name,DataBaseInput,nbcase,MainPath,keypath,LogFile,PlotOnly, DebugMode): + #idf object is created here + IDF.setiddname(os.path.join(keypath['epluspath'],"Energy+.idd")) + idf = IDF(os.path.normcase(os.path.join(keypath['epluspath'],"ExampleFiles/Minimal.idf"))) + idf.idfname = name + #building object is created here + building = Building(name, DataBaseInput, nbcase, MainPath,keypath['Buildingsfile'],LogFile,PlotOnly, DebugMode) + #both are append as dict in the globa studied case list + self.building.append({ + 'BuildData' : building, + 'BuildIDF' : idf, + } + ) + +class Building: + def __init__(self,name,DataBaseInput,nbcase,MainPath,BuildingFilePath,LogFile,PlotOnly,DebugMode): + import time + Buildingsfile = DataBaseInput['Build'] + DB = Buildingsfile[nbcase] + config = setConfig.read_yaml('ConfigFile.yml') + DBL = config['3_SIM']['DBLimits'] + BE = config['3_SIM']['3_BasisElement'] + self.GE = config['3_SIM']['GeomElement'] #these element might be needed afterward for parametric simulation, thus the keys words are needed + EPC = config['3_SIM']['EPCMeters'] + SD = config['3_SIM']['2_SimuData'] + ExEn = config['3_SIM']['ExtraEnergy'] + WeatherData = config['3_SIM']['1_WeatherData'] + try: + self.CRS = Buildingsfile.crs['properties']['name'] #this is the coordinates reference system for the polygons + except: + self.CRS = 'Null' + self.getBEData(BE) + self.getSimData(SD) + self.getSimData(WeatherData) + self.name = name + self.BuildingFilePath = BuildingFilePath + self.BuildID = self.getBuildID(DB,LogFile) + self.Multipolygon = self.getMultipolygon(DB) + self.nbfloor = self.getnbfloor(DB, DBL,LogFile,DebugMode) + self.nbBasefloor = self.getnbBasefloor(DB, DBL) + self.height = self.getheight(DB, DBL) + self.DistTol = self.GE['DistanceTolerance'] + self.footprint, self.BlocHeight, self.BlocNbFloor = self.getfootprint(DB,LogFile,self.nbfloor,DebugMode) + self.AggregFootprint = self.getAggregatedFootprint() + self.RefCoord = self.getRefCoord() + self.DB_Surf = self.getsurface(DB, DBL,LogFile,DebugMode) + self.SharedBld, self.VolumeCorRatio = self.IsSameFormularIdBuilding(Buildingsfile, nbcase, LogFile, DBL,DebugMode) + self.BlocHeight, self.BlocNbFloor, self.StoreyHeigth = self.EvenFloorCorrection(self.BlocHeight, self.nbfloor, self.BlocNbFloor, self.footprint, LogFile,DebugMode) + self.EPHeatedArea = self.getEPHeatedArea(LogFile,DebugMode) + self.AdjacentWalls = [] #this will be appended in the getshade function if any present + self.shades = self.getshade(nbcase, Buildingsfile,LogFile, PlotOnly=PlotOnly,DebugMode=DebugMode) + self.Materials = config['3_SIM']['BaseMaterial'] + self.InternalMass = config['3_SIM']['InternalMass'] + self.MakeRelativeCoord(roundfactor = 4)# we need to convert into local coordinate in order to compute adjacencies with more precision than keeping thousand of km for x and y + #self.edgesHeights = self.getEdgesHeights(roundfactor= 4) + if not PlotOnly: + #the attributres above are needed in all case, the one below are needed only if energy simulation is asked for + self.VentSyst = self.getVentSyst(DB, config['3_SIM']['VentSyst'], LogFile,DebugMode) + self.AreaBasedFlowRate = self.getAreaBasedFlowRate(DB, DBL, BE) + self.OccupType = self.getOccupType(DB, config['3_SIM']['OccupType'], LogFile,DebugMode) + self.nbStairwell = self.getnbStairwell(DB, DBL) + #self.WeatherDataFile = WeatherData + self.year = self.getyear(DB, DBL) + self.EPCMeters = self.getEPCMeters(DB, EPC, LogFile,DebugMode) + if len(self.SharedBld) > 0: + self.CheckAndCorrEPCs(Buildingsfile, LogFile, nbcase, EPC,DebugMode) + self.nbAppartments = self.getnbAppartments(DB, DBL) + #we define the internal load only if it's not for making picture + self.IntLoad = self.getIntLoad(MainPath,LogFile,DebugMode) + self.DHWInfos = self.getExtraEnergy(ExEn, MainPath) + #if there are no cooling comsumption, lets considerer a set point at 50deg max + # for key in self.EPCMeters['Cooling']: + # if self.EPCMeters['Cooling'][key]>0: + # self.setTempUpL = BE['setTempUpL'] + # self.intT_freecool = 50 + # else: + # self.setTempUpL = [50]*len(BE['setTempUpL']) + + #No more needed, embedded in the MakeShadowingWallFile + # def getEdgesHeights(self,roundfactor = 8): + # GlobalFootprint = Polygon2D(self.AggregFootprint[:-1]) + # EdgesHeights = {'Height':[],'Edge':[],'BlocNum': []} + # for edge in GlobalFootprint.edges: + # EdgesHeights['Edge'].append([(round(x+self.RefCoord[0],roundfactor),round(y+self.RefCoord[1],roundfactor)) for x,y in edge.vertices]) + # EdgesHeights['Height'].append(0) + # EdgesHeights['BlocNum'].append(0) + # for idx,poly in enumerate(self.footprint): + # localBloc = Polygon2D(poly) + # for edge,edge_reversed in zip(localBloc.edges,localBloc.edges_reversed): + # Heightidx1 = [idx for idx,val in enumerate(GlobalFootprint.edges) if edge == val] + # Heightidx2 = [idx for idx, val in enumerate(GlobalFootprint.edges_reversed) if edge == val] + # if Heightidx1 or Heightidx2: + # Heigthidx = Heightidx1 if Heightidx1 else Heightidx2 + # EdgesHeights['Height'][Heigthidx[0]] = self.BlocHeight[idx] + # EdgesHeights['BldID']= self.BuildID + # return EdgesHeights + + def MakeRelativeCoord(self,roundfactor= 8): + # we need to convert change the reference coordinate because precision is needed for boundary conditions definition: + newfoot = [] + x,y = self.RefCoord + self.RefCoord = (round(x,roundfactor),round(y,roundfactor)) + for foot in self.footprint: + newfoot.append([(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in foot]) + self.footprint = newfoot + for shade in self.shades.keys(): + newcoord = [(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in + self.shades[shade]['Vertex']] + self.shades[shade]['Vertex'] = newcoord + newwalls = [] + new_Agreg = [(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in self.AggregFootprint] + self.AggregFootprint = new_Agreg + for Wall in self.AdjacentWalls: + newcoord = [(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in Wall['geometries']] + Wall['geometries'] = newcoord + + def CheckAndCorrEPCs(self,Buildingsfile,LogFile,nbcase,EPC,DebugMode): + totHeat = [] + tocheck = [nbcase]+self.SharedBld + for share in tocheck: + val = 0 + Meas = self.getEPCMeters(Buildingsfile[share],EPC) + for key in Meas['Heating'].keys(): + val += Meas['Heating'][key] + totHeat.append(val) + # correction on the DDB_SurfBSurf if it is the same on all (should be) + HeatDiff = [totHeat[idx + 1] - A for idx, A in enumerate(totHeat[:-1])] + if all(v == 0 for v in HeatDiff): + newval = 0 + for keyType in self.EPCMeters.keys(): + for key in self.EPCMeters[keyType].keys(): + try: + self.EPCMeters[keyType][key] *= self.VolumeCorRatio + except: + pass + if 'Heating' == keyType: + newval += self.EPCMeters['Heating'][key] + msg = '[EPCs correction] The EPCs total heat needs for the each shared buildings is :'+str(totHeat)+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[EPCs correction] All EPCs metrix will be modified by the Volume ratio as for the DBSurface\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[EPCs correction] For example, the Heat needs is corrected from : '+ str(totHeat[0])+ ' to : '+ str(newval)+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + + def IsSameFormularIdBuilding(self,Buildingsfile,nbcase,LogFile,DBL,DebugMode): + SharedBld = [] + VolumeCorRatio = 1 + Correction = False + for nb,Build in enumerate(Buildingsfile): + try: + if Build.properties['FormularId'] == self.BuildID['FormularId'] and nb != nbcase: + SharedBld.append(nb) + Correction = True + except: + pass + maxHeight=[max(self.BlocHeight)] + floors = [self.nbfloor] + DB_Surf = [self.DB_Surf] + Volume = [sum([Polygon(foot).area * self.BlocHeight[idx] for idx,foot in enumerate(self.footprint)])] + for nb in SharedBld: + DB_Surf.append(self.getsurface(Buildingsfile[nb], DBL)) + floors.append(self.getnbfloor(Buildingsfile[nb],DBL)) + Bldfootprint, BldBlocHeight, BldBlocNbFloor = self.getfootprint(Buildingsfile[nb],[],floors[-1]) + maxHeight.append(max(BldBlocHeight)) + Volume.append(sum([Polygon(foot).area * BldBlocHeight[idx] for idx,foot in enumerate(Bldfootprint)])) + if Correction: + #some correction is needed on the nb of floor because a higher one, with the same FormularId is higher + newfloor = max(int(floors[maxHeight.index(max(maxHeight))] / (max(maxHeight) / maxHeight[0])),1) + msg = '[Shared EPC] Buildings are found with same FormularId: '+str(SharedBld)+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[Nb Floor Cor] The nb of floors will be corrected by the height ratio of this building with the highests one with same FormularId (but cannot be lower than 1)\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[Nb Floor Cor] nb of floors is thus corrected from : '+ str(self.nbfloor)+ ' to : '+ str(newfloor)+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + self.nbfloor = newfloor + #correction on the DB_Surf if it is the same on all (should be) + Adiff = [DB_Surf[idx+1]-A for idx,A in enumerate(DB_Surf[:-1])] + if all(v == 0 for v in Adiff): + VolumeCorRatio = Volume[0] / sum(Volume) + newATemp = self.DB_Surf * VolumeCorRatio + msg = '[DB_Surf Cor] The DB_Surf will also be modified by the volume ratio of this building over the volume sum of all concerned building \n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[DB_Surf Cor] The DB_Surf is thus corrected from : '+ str(self.DB_Surf)+ ' to : '+ str(newATemp)+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + self.DB_Surf = newATemp + return SharedBld, VolumeCorRatio + + + def getBEData(self,BE): + for key in BE.keys(): + setattr(self, key, BE[key]) + + def getExtraEnergy(self,ExEn,MainPath): + output={} + for key in ExEn.keys(): + try: + ifFile = os.path.join(os.path.dirname(MainPath),os.path.normcase(ExEn[key])) + if os.path.isfile(ifFile): + AbsInputFileDir,InputFileDir = self.isInputDir() + iflocFile = os.path.join(AbsInputFileDir,os.path.basename(ifFile)) + if not os.path.isfile(iflocFile): + shutil.copy(ifFile,iflocFile) + output[key] = os.path.join(InputFileDir,os.path.basename(ifFile)) + else: + output[key] = ExEn[key] + except: + output[key] = ExEn[key] + return output + + def getSimData(self,SD): + for key in SD.keys(): + setattr(self, key, SD[key]) + + def getBuildID(self,DB,LogFile): + BuildID={} + for key in self.GE['BuildIDKey']: + try: + BuildID[key] = DB.properties[key] + BuildID['BldIDKey'] = key + break + except: pass + if not BuildID: + BuildID['BldIDKey'] = 'NoBldID' + BuildID['NoBldID'] = 'NoBldID' + msg = '[Bld ID] '+ 'BldIDKey'+' : ' + str(BuildID['BldIDKey']) + '\n' + GrlFct.Write2LogFile(msg, LogFile) + msg = '[Bld ID] '+ str(BuildID['BldIDKey'])+' : ' + str(BuildID[BuildID['BldIDKey']]) + '\n' + GrlFct.Write2LogFile(msg, LogFile) + return BuildID + + # def getMultipolygon(self,DB): + # test = DB.geometry.coordinates[0][0][0] + # if type(test) is list: + # Multipolygon = True + # else: + # Multipolygon = False + # return Multipolygon + def getMultipolygon(self,DB): + Multipolygon = False + try: + DB.geometry.poly3rdcoord + Multipolygon = True + except: pass + return Multipolygon + + def getRefCoord(self): + "get the reference coodinates for visualisation afterward" + #check for Multipolygon first + if self.Multipolygon: + centroide = [list(Polygon(foot).centroid.coords) for foot in self.footprint] #same reason than below, foot print is computed before now [list(Polygon(DB.geometry.coordinates[i][0]).centroid.coords) for i in range(len(DB.geometry.coordinates))] + x = sum([centroide[i][0][0] for i in range(len(centroide))])/len(centroide) + y = sum([centroide[i][0][1] for i in range(len(centroide))])/len(centroide) + else: + centroide = list(Polygon(self.footprint[0]).centroid.coords)# now the foot print is computed nbefore the reference. before it was defined with list(Polygon(DB.geometry.coordinates[0]).centroid.coords) + x = centroide[0][0] + y = centroide[0][1] + #ref = (round(x,8), round(y,8)) + offset = ((2*Polygon(self.AggregFootprint).area)**0.5)/8 + ref = (x-offset, y-offset) #there might be not a true need for suche precision.... + return ref + + def getfootprint(self,DB,LogFile=[],nbfloor=0,DebugMode = False): + "get the footprint coordinate and the height of each building bloc" + DistTol = self.GE['DistanceTolerance'] + coord = [] + node2remove =[] + BlocHeight = [] + BlocNbFloor = [] + #we first need to check if it is Multipolygon + if self.Multipolygon: + #then we append all the floor and roof fottprints into one with associate height + MatchedPoly = [0]*len((DB.geometry.coordinates)) + for idx1,poly1 in enumerate(DB.geometry.coordinates[:-1]): + for idx2,poly2 in enumerate(DB.geometry.coordinates[idx1+1:]): + if GeomUtilities.chekIdenticalpoly(poly1[0], poly2[0]) and \ + round(abs(DB.geometry.poly3rdcoord[idx1]-DB.geometry.poly3rdcoord[idx2+idx1+1]),1) >0: + MatchedPoly[idx1] = 1 + MatchedPoly[idx2+idx1+1] = 1 + newpolycoor,node = GeomUtilities.CleanPoly(poly1[0],DistTol) + node2remove.append(node) + #polycoor.reverse() + #test of identical polygone maybe (encountered from geojon made out of skethup + skipit = False + for donPoly in coord: + if GeomUtilities.chekIdenticalpoly(donPoly, newpolycoor): + skipit = True #no need to store the same polygone.... + if not skipit: + coord.append(newpolycoor) + BlocHeight.append(round(abs(DB.geometry.poly3rdcoord[idx1]-DB.geometry.poly3rdcoord[idx2+idx1+1]),1)) + else: + #for dealing with 2D files + singlepoly = False + for j in DB.geometry.coordinates[0]: + if len(j)==2: + new = (j[0], j[1]) + coord.append(tuple(new)) + singlepoly = True + else: + # new = [] + # for jj in j: + # new.append(tuple(jj)) + # # even before skewed angle, we need to check for tiny edge below the tolerance onsdered aftward (0.5m) + # pt2remove = [] + # for edge in Polygon2D(new).edges: + # if edge.length < DistTol: + # pt2remove.append(edge.p2) + # for pt in pt2remove: + # if len(new) > 3: + # new.remove(pt) + # newpolycoor, node = core_perim.CheckFootprintNodes(new, 5) + newpolycoor, node = GeomUtilities.CleanPoly(j, DistTol) + coord.append(newpolycoor) + node2remove.append(node) + if singlepoly: + newpolycoor, node = core_perim.CheckFootprintNodes(coord, 5) + node2remove.append(node) + coord = [coord] + for i in range(len(coord)): + BlocNbFloor.append(nbfloor) + BlocHeight.append(self.height) + + #if a polygonew has been seen alone, it means that it should be exruded down to the floor + if self.Multipolygon: + for idx,val in enumerate(MatchedPoly): + if val ==0: + missedPoly,node = GeomUtilities.CleanPoly(DB.geometry.coordinates[idx][0], DistTol) + coord.append(missedPoly) + node2remove.append(node) + BlocHeight.append(DB.geometry.poly3rdcoord[idx]) + # we need to clean the footprint from the node2remove but not if there are part of another bloc + newbloccoor = [] + for idx, coor in enumerate(coord): + newcoor = [] + FilteredNode2remove = [] + single = False + for node in node2remove[idx]: + single = True + for idx1, coor1 in enumerate(coord): + if idx != idx1: + if coor[node] in coor1 and coor[node] not in [n for i, n in enumerate(coor1[idx1]) if + i in node2remove[idx1]]: + single = False + if single: + FilteredNode2remove.append(node) + for nodeIdx, node in enumerate(coor): + if not nodeIdx in FilteredNode2remove: + newcoor.append(node) + newbloccoor.append(newcoor) + coord = newbloccoor + # these following lines are here to highlight holes in footprint and split it into two blocs... + # it may appear some errors for other building with several blocs and some with holes (these cases havn't been checked) + poly2merge = [] + for idx, coor in enumerate(coord): + for i in range(len(coord) - idx - 1): + if Polygon(coor).contains(Polygon(coord[idx + i + 1])): + poly2merge.append([idx, idx + i + 1]) + if Polygon(coord[idx + i + 1]).contains(Polygon(coor)): + poly2merge.append([idx + i + 1,idx]) + try: + for i, idx in enumerate(poly2merge): + #lets check if it's a tower of smaller foorptin than the base: + SmallerTower = False + if BlocHeight[idx[1]]-BlocHeight[idx[0]]>0: + SmallerTower = True + newtry = False + + if newtry : + + newSurface = GeomUtilities.mergeHole(coord[idx[0]],coord[idx[1]]) + coord[idx[0]] = newSurface[0] + coord[idx[1]] = newSurface[1] + else: + new_surfaces = GeomUtilities.mergeGeomeppy(coord[idx[0]], coord[idx[1]]) + #new_surfaces = break_polygons(Polygon3D(coord[idx[0]]), Polygon3D(coord[idx[1]])) + xs, ys, zs = zip(*list(new_surfaces[0])) + coord[idx[0]] = [(xs[nbv], ys[nbv]) for nbv in range(len(xs))] + if len(new_surfaces) > 1: + xs, ys, zs = zip(*list(new_surfaces[1])) + if SmallerTower: + coord.append([(xs[nbv], ys[nbv]) for nbv in range(len(xs))]) + BlocHeight.append(BlocHeight[idx[0]]) + else: + coord[idx[1]] = [(xs[nbv], ys[nbv]) for nbv in range(len(xs))] + BlocHeight[idx[1]] = BlocHeight[idx[0]] + else: + coord.pop(idx[1]) + BlocHeight.pop(idx[1]) + + + msg = '[Geom Cor] There is a hole that will split the main surface in two blocs \n' + GrlFct.Write2LogFile(msg, LogFile) + except: + msg = '[Poly Error] Some error are present in the polygon parts. Some are identified as being inside others...\n' + print(msg[:-1]) + GrlFct.Write2LogFile(msg, LogFile) + import matplotlib.pyplot as plt + fig = plt.figure(0) + for i in coord: + xs, ys = zip(*i) + plt.plot(xs, ys, '-.') + #plt.show() + # titre = 'FormularId : '+str(DB.properties['FormularId'])+'\n 50A_UUID : '+str(DB.properties['50A_UUID']) + # plt.title(titre) + # plt.savefig(self.name+ '.png') + # plt.close(fig) + #before submitting the full coordinates, we need to check correspondance in case of multibloc + coord, validFootprint = GeomUtilities.CheckMultiBlocFootprint(coord,tol = DistTol) + if not validFootprint: + msg = '[Poly Error] The different bloc are not adjacent...\n' + #print(msg[:-1]) + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return + # multibloc should share at least one edge and not a polygon as bld:a3848e24-d29e-44bc-a395-a25b5fd26598 in area : 0180C3170 of Sodermalm_V4 + SmallEdge = False + for bloc in coord: + if [val for val in Polygon2D(bloc).edges_length if val < 2]: + SmallEdge = True + if SmallEdge: + msg = '[Geom Warning] This building has at least one edge length below 2m\n' + #print(msg[:-1]) + if DebugMode : GrlFct.Write2LogFile(msg, LogFile) + #lets make a final check of the polygon orientation + #for energy plu inputs, floor needs to be clockwise oriented + for poly in coord: + if GeomUtilities.is_clockwise(poly): + poly.reverse() + return coord, BlocHeight, BlocNbFloor + + def EvenFloorCorrection(self,BlocHeight,nbfloor,BlocNbFloor,coord,LogFile,DebugMode=False): + # we compute a storey height as well to choosen the one that correspond to the highest part of the building afterward + BlocNbFloor=[] #the number of blocks is reset to comply with the old 2D geojson files is anyway empty for multipolygons files + StoreyHeigth = 3 + if nbfloor !=0: + storeyRatio = StoreyHeigth / (max(BlocHeight) / nbfloor) if (max(BlocHeight) / nbfloor) > 0.5 else 1 + msg = '[Geom Info] The max bloc height is : ' + str(round(max(BlocHeight), 2)) + ' for ' + str( + nbfloor) + ' floors declared in the EPC \n' + else: + nbfloor= round(max(BlocHeight)/StoreyHeigth) + try: + storeyRatio = StoreyHeigth / (max(BlocHeight) / nbfloor) if (max(BlocHeight) / nbfloor) > 0.5 else 1 + except: + storeyRatio = 0 + msg = '[Geom Info] The max bloc height is : ' + str(round(max(BlocHeight), 2)) + ' for ' + str( + nbfloor) + ' floors computed from max bloc height\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[Geom Cor] A ratio of ' + str(storeyRatio) + ' will be applied on each bloc height\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + + for height in range(len(BlocHeight)): + BlocHeight[height] *= storeyRatio + for idx, Height in enumerate(BlocHeight): + val = int(round(Height, 1) / StoreyHeigth) + BlocNbFloor.append(max(1, val)) # the height is ed to the closest 10cm + BlocHeight[idx] = BlocNbFloor[-1] * StoreyHeigth + msg = '[Geom Info] Bloc height : ' + str(BlocHeight[idx]) + ' with ' + str(BlocNbFloor[-1]) + ' nb of floors\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[Geom Info] This bloc has a footprint with : ' + str(len(coord[idx])) + ' vertexes\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + if val == 0: + try: + LogFile.write( + '[WARNING] /!\ This bloc as a height below 3m, it has been raized to 3m to enable construction /!\ \n') + except: + pass + return BlocHeight, BlocNbFloor, StoreyHeigth + + def getEPHeatedArea(self,LogFile,DebugMode): + "get the heated area based on the footprint and the number of floors" + self.BlocFootprintArea=[] + EPHeatedArea = 0 + for i,foot in enumerate(self.footprint): + EPHeatedArea += Polygon(foot).area*self.BlocNbFloor[i] + self.BlocFootprintArea.append(Polygon(foot).area) + msg = '[Geom Info] Blocs footprint areas : '+ str(self.BlocFootprintArea)+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + msg = '[Geom Info] The total heated area is : ' + str(EPHeatedArea)+' for a declared DB_Surf of : '+str(self.DB_Surf)+' --> discrepancy of : '+str(round((self.DB_Surf-EPHeatedArea)/self.DB_Surf*100,2))+'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return EPHeatedArea + + def getsurface(self,DB, DBL,LogFile = [],DebugMode = False): + "Get the surface from the input file, DB_Surf" + DB_Surf, IdKey = getDBValue(DB.properties, DBL['surface_key']) + try: DB_Surf = int(DB_Surf) + except: DB_Surf = 1 + if DB_Surf == 1: + msg = '[Geom Error] Surface ID not recognized as number, fixed to 1\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + DB_Surf = checkLim(DB_Surf,DBL['surface_lim'][0],DBL['surface_lim'][1]) + self.DBSurfOriginal= DB_Surf #this is to keep the original value as some correction might done afterward if more then 1 bld is present in 1 Id + return int(DB_Surf) + + def getnbfloor(self,DB, DBL,LogFile = [], DebugMode = False): + "Get the number of floor above ground" + nbfloor, IdKey = getDBValue(DB.properties, DBL['nbfloor_key']) + try: nbfloor = int(nbfloor) + except: nbfloor = 0 + if nbfloor == 0: + msg = '[EPCs Warning] The nb of floors is 0. It will be defined using the max bloc height and a storey height of 3m\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + nbfloor = checkLim(nbfloor,DBL['nbfloor_lim'][0],DBL['nbfloor_lim'][1]) + return int(nbfloor) + + def getnbStairwell(self,DB, DBL): + "Get the number of stariwell, need for natural stack effect on infiltration" + nbStairwell, IdKey = getDBValue(DB.properties, DBL['nbStairwell_key']) + try: nbStairwell = int(nbStairwell) + except: nbStairwell=0 + nbStairwell = checkLim(nbStairwell,DBL['nbStairwell_lim'][0],DBL['nbStairwell_lim'][1]) + return int(nbStairwell) + + + def getnbBasefloor(self,DB, DBL): + "Get the number of floor below ground" + nbBasefloor, IdKey = getDBValue(DB.properties, DBL['nbBasefloor_key']) + try: nbBasefloor = int(nbBasefloor) + except: nbBasefloor = 0 + nbBasefloor = checkLim(nbBasefloor,DBL['nbBasefloor_lim'][0],DBL['nbBasefloor_lim'][1]) + return int(nbBasefloor) + + def getyear(self,DB, DBL): + "Get the year of construction in the input file" + year, IdKey = getDBValue(DB.properties, DBL['year_key']) + try: year = int(year) + except: year = 1900 + year = checkLim(year,DBL['year_lim'][0],DBL['year_lim'][1]) + return int(year) + + def getEPCMeters(self,DB,EPC,LogFile = [], DebugMode = False): + "Get the EPC meters values" + Meters = {} + for key1 in EPC: + Meters[key1] = {} + for key2 in EPC[key1]: + if '_key' in key2: + try: + Meters[key1][key2[:-4]] = DB.properties[EPC[key1][key2]] + Meters[key1][key2[:-4]] = int(DB.properties[EPC[key1][key2]])*EPC[key1][key2[:-4]+'COP'] + except: + pass + return Meters + + def getnbAppartments(self, DB, DBL): + "Get the number of appartment in the building" + nbApp, IdKey = getDBValue(DB.properties, DBL['nbAppartments_key']) + try: nbApp = int(nbApp) + except: nbApp = 0 + nbApp = checkLim(nbApp,DBL['nbAppartments_lim'][0],DBL['nbAppartments_lim'][1]) + return int(nbApp) + + def getheight(self, DB, DBL): + "Get the building height from the input file, but not used if 3D coordinates in the footprints" + height, IdKey = getDBValue(DB.properties, DBL['height_key']) + try: height = int(height) + except: height = 3 + height = checkLim(height,DBL['height_lim'][0],DBL['height_lim'][1]) + return int(height) + + def getAggregatedFootprint(self): + # lets compute the aggregaded external footprint of the different blocs + # starting with the first one + AggregFootprint = self.footprint[0] + RemainingBlocs = self.footprint[1:] + idx = 0 + while RemainingBlocs: + Intersectionline = Polygon(AggregFootprint).intersection(Polygon(RemainingBlocs[idx])) + if Intersectionline and type(Intersectionline) != Point: + AggregFootprint = list(Polygon(AggregFootprint).union(Polygon(RemainingBlocs[idx])).exterior.coords) + RemainingBlocs.remove(RemainingBlocs[idx]) + idx = 0 + else: + idx += 1 + # in order to close the loop if not already done + if AggregFootprint[0] != AggregFootprint[-1]: + AggregFootprint.append(AggregFootprint[0]) + return AggregFootprint + + def getShadesFromJson(self,ShadowingWalls): + # shades: dict of dict with key being shade ID and sub key are + # 'Vertex (list of tuple)' \ + # 'height (in m)' \ + # 'distance' + # and that should be enough + shades = {} + RelativeAgregFootprint = [(node[0] - self.RefCoord[0], node[1] - self.RefCoord[1]) for node in + self.AggregFootprint] + Meancoordx = list(Polygon(RelativeAgregFootprint).centroid.coords)[0][0] + Meancoordy = list(Polygon(RelativeAgregFootprint).centroid.coords)[0][1] + for key in ShadowingWalls: + if self.BuildID[self.BuildID['BldIDKey']] in ShadowingWalls[key]['RecepientBld_ID']: + x,y = zip(*ShadowingWalls[key]['AbsCoord']) + meanPx = sum(x)/2- self.RefCoord[0] + meanPy = sum(y) / 2 - self.RefCoord[1] + dist = (abs(meanPx - Meancoordx) ** 2 + abs(meanPy - Meancoordy) ** 2) ** 0.5 + shades[key] = {} + shades[key]['Vertex'] = ShadowingWalls[key]['AbsCoord'] + shades[key]['height'] = ShadowingWalls[key]['Height'] + shades[key]['distance'] = dist + return shades + + def getshade(self, nbcase, Buildingsfile,LogFile, PlotOnly=True, DebugMode=False): + "Get all the shading surfaces to be build for surrounding building effect" + JSONFile = [] + GeJsonFile = [] + shades = {} + BuildingFileName = os.path.basename(self.BuildingFilePath) + JSonTest = os.path.join(os.path.dirname(self.BuildingFilePath),BuildingFileName[:BuildingFileName.index('.')] + '_Walls.json') + GeoJsonTest = os.path.join(os.path.dirname(self.BuildingFilePath),BuildingFileName.replace('Buildings', 'Walls')) + GeoJsonTest1 = True if 'Walls' in GeoJsonTest else False + if os.path.isfile(JSonTest): + JSONFile = JSonTest + elif GeoJsonTest and GeoJsonTest1: + GeJsonFile = GeoJsonTest + else: + msg = '[Shadowing Info] No Shadowing wall file were found\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return shades + + if JSONFile: + msg = '[Shadowing Info] Shadowing walls are taken from a json file\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + with open(JSONFile) as json_file: + ShadowingWalls = json.load(json_file) + return self.getShadesFromJson(ShadowingWalls) + if GeJsonFile: + msg = '[Shadowing Info] Shadowing walls are taken from a GeoJson file\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + Shadingsfile = MUBES_pygeoj.load(GeJsonFile) + Shadingsfile = GrlFct.checkRefCoordinates(Shadingsfile) + try: + GE = self.GE + shadesID = Buildingsfile[nbcase].properties[GE['ShadingIdKey']] + except: + return shades + ModifiedShadeVertexes ={'ShadeId' : [], 'OldCoord': [], 'NewCoord' : []} #this dict will log the changes in the vertex coordinate to adjust other shading if necesseray afterward + + RelativeAgregFootprint = [(node[0] - self.RefCoord[0], node[1] - self.RefCoord[1]) for node in self.AggregFootprint] + Meancoordx = list(Polygon(RelativeAgregFootprint).centroid.coords)[0][0] + Meancoordy = list(Polygon(RelativeAgregFootprint).centroid.coords)[0][1] + + + currentRef = self.getRefCoord() + ref = (0, 0) if currentRef==self.RefCoord else self.RefCoord + idlist = [-1] + for m in re.finditer(';', shadesID): + idlist.append(m.start()) + for ii, sh in enumerate(idlist): + if ii == len(idlist) - 1: + wallId = shadesID[idlist[ii] + 1:] + else: + wallId = shadesID[idlist[ii] + 1:idlist[ii + 1]] + ShadeWall = findWallId(wallId, Shadingsfile, ref, GE) + if not 'height' in ShadeWall.keys(): + ShadeWall['height'] = findBuildId(ShadeWall[GE['BuildingIdKey']], Buildingsfile,GE) + if ShadeWall['height']==None: + ShadeWall['height'] = self.height + currentShadingElement = [(node[0]-self.RefCoord[0],node[1]-self.RefCoord[1]) for node in ShadeWall[GE['VertexKey']]] + meanPx = (currentShadingElement[0][0] + currentShadingElement[1][0]) / 2 + meanPy = (currentShadingElement[0][1] + currentShadingElement[1][1]) / 2 + edgelength = LineString(currentShadingElement).length + if edgelength<2: + msg = '[Shadowing Info] This one is dropped, less than 2m wide ('+str(round(edgelength,2))+'m), shading Id : '+ ShadeWall[GE['ShadingIdKey']] +'\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + #print(msg[:-1]) + continue + confirmed,currentShadingElement,OverlapCode = GeomUtilities.checkShadeWithFootprint(RelativeAgregFootprint, + currentShadingElement,ShadeWall[GE['ShadingIdKey']],tol = self.GE['DistanceTolerance']) + if confirmed: + if ShadeWall['height']<=(max(self.BlocHeight)+self.StoreyHeigth): + OverlapCode +=1 + ShadeWall['height'] = self.StoreyHeigth*round(ShadeWall['height'] / self.StoreyHeigth) #max(self.BlocHeight)# + self.AdjacentWalls.append(ShadeWall) + shades[wallId] = {} + shades[wallId]['Vertex'] = [(node[0]+self.RefCoord[0],node[1]+self.RefCoord[1]) for node in currentShadingElement] + shades[wallId]['height'] = ShadeWall['height'] + shades[wallId]['distance'] = 0 + ModifiedShadeVertexes['ShadeId'].append(ShadeWall[GE['ShadingIdKey']]) + ModifiedShadeVertexes['OldCoord'].append(ShadeWall[GE['VertexKey']]) + ModifiedShadeVertexes['NewCoord'].append(shades[wallId]['Vertex']) + msg = '[Adjacent Wall] This Shading wall is considered as adjacent with an overlap code of '+str(OverlapCode)+', shading Id : ' + ShadeWall[ + GE['ShadingIdKey']] + '\n' + #print(msg[:-1]) + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + continue + if OverlapCode== 999: + msg = '[Shadowing Error] This Shading wall goes inside the building...It is dropped, shading Id : ' + ShadeWall[ + GE['ShadingIdKey']] + '\n' + #print(msg[:-1]) + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + continue + dist = (abs(meanPx - Meancoordx) ** 2 + abs(meanPy - Meancoordy) ** 2) ** 0.5 + shades[wallId] = {} + shades[wallId]['Vertex'] = ShadeWall[GE['VertexKey']] + shades[wallId]['height'] = round(ShadeWall['height'],2) + shades[wallId]['distance'] = dist + return shades + + + + + def getVentSyst(self, DB,VentSystDict,LogFile,DebugMode): + "Get ventilation system type" + VentSyst = {} + for key in VentSystDict: + try: + VentSyst[key] = True if 'Ja' in DB.properties[VentSystDict[key]] else False + except: + VentSyst[key] = False + nbVentSyst = [idx for idx, key in enumerate(VentSyst) if VentSyst[key]] + nbVentSystWithHR = [idx for idx, key in enumerate(VentSyst) if + VentSyst[key] and key[-1] == 'X'] + if len(nbVentSyst) > 1: + msg = '[Vent Warning] This building has '+str(len(nbVentSyst))+' ventilation systems declared\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + if len(nbVentSystWithHR)>1: + msg = '[Vent Warning] This building has '+str(len(nbVentSystWithHR))+' ventilation systems with heat recovery\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return VentSyst + + def getAreaBasedFlowRate(self, DB, DBL, BE): + "Get the airflow rates based on the floor area" + val,key = getDBValue(DB.properties, DBL['AreaBasedFlowRate_key']) + try: AreaBasedFlowRate = float(val) + except : AreaBasedFlowRate = BE['AreaBasedFlowRate'] + AreaBasedFlowRate = checkLim(AreaBasedFlowRate,DBL['AreaBasedFlowRate_lim'][0],DBL['AreaBasedFlowRate_lim'][1]) + return AreaBasedFlowRate + + def getOccupType(self,DB,OccupTypeDict,LogFile,DebugMode): + "get the occupency type of the building" + OccupType = {} + self.OccupRate = {} + for key in OccupTypeDict: + if '_key' in key: + try: + OccupType[key[:-4]] = int(DB.properties[OccupTypeDict[key]])/100 + except: + OccupType[key[:-4]] = 0 + if '_Rate' in key: + self.OccupRate[key[:-5]] = OccupTypeDict[key] + msg = '[Usage Info] This building has ' + str(1 - OccupType['Residential']) + ' % of none residential occupancy type\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return OccupType + + def isInputDir(self): + InputFileDir = 'InputFiles' + AbsInputFileDir = os.path.join(os.getcwd(),InputFileDir) + if not os.path.exists(AbsInputFileDir): + os.mkdir(AbsInputFileDir) + return AbsInputFileDir,AbsInputFileDir #both values are identicial since the relative path was still creating issues with FMUs afterward... + + def getIntLoad(self, MainPath,LogFile,DebugMode = False): + "get the internal load profil or value" + #we should integrate the loads depending on the number of appartemnent in the building + type = self.IntLoadType + # Input_path = os.path.join(MainPath,'InputFiles') + # #lets used StROBE package by defaults (average over 10 profile + # IntLoad = os.path.join(Input_path, 'P_Mean_over_10.txt') + try: + IntLoad = self.ElecYearlyLoad #this value is in W\m2 prescies in DB_Data + except: + IntLoad = 0 + #now we compute power time series in order to match the measures form EPCs + eleval = 0 + try : + for x in self.EPCMeters['ElecLoad']: + if self.EPCMeters['ElecLoad'][x]: + eleval += self.EPCMeters['ElecLoad'][x]*1000 #to convert kW in W + except: + pass + if eleval>0: + try: + if 'Cste' in type: + IntLoad = eleval/self.EPHeatedArea/8760 #this value is thus in W/m2 #division by number of hours to convert Wh into W + else: + AbsInputFileDir,InputFileDir = self.isInputDir() + if 'winter' in type: + IntLoad = os.path.join(InputFileDir, self.name + '_winter.txt') + ProbGenerator.SigmoFile('winter', self.IntLoadCurveShape, eleval/self.EPHeatedArea * 100, IntLoad) #the *100 is because we have considered 100m2 for the previous file + if 'summer' in type: + IntLoad = os.path.join(InputFileDir, self.name + '_summer.txt') + ProbGenerator.SigmoFile('summer', self.IntLoadCurveShape, eleval / self.EPHeatedArea * 100,IntLoad) # the *100 is because we have considered 100m2 for the previous file + except: + msg = '[Int Load Error] Unable to write the internal load file...\n' + #print(msg[:-1]) + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return IntLoad \ No newline at end of file diff --git a/BuildObject/DB_Building.py b/BuildObject/DB_Building.py deleted file mode 100644 index c08b8d8..0000000 --- a/BuildObject/DB_Building.py +++ /dev/null @@ -1,890 +0,0 @@ -# @Author : Xavier Faure -# @Email : xavierf@kth.se - -from shapely.geometry.polygon import Polygon, Point, LineString -import CoreFiles.GeneralFunctions as GrlFct -from geomeppy.geom.polygons import Polygon2D, Polygon3D,break_polygons -from geomeppy import IDF -from geomeppy.geom import core_perim -import os -import shutil -import BuildObject.DB_Data as DB_Data -import re -import CoreFiles.ProbGenerator as ProbGenerator -import itertools - -import matplotlib.pyplot as plt -#this class defines the building characteristics regarding available data in the geojson file - -#function that checks if value is out of limits -def checkLim(val, ll, ul): - if val < ll: - val = ll - elif val > ul: - val = round(val/10) - if val > ul: - val = ul - return val - -#get the value from the correct key -def getDBValue(DB, Keys): - Val = '' - if type(Keys) ==list: - for key in Keys: - try: - Val = DB[key] - break - except: - pass - else: - try: Val = DB[Keys] - except: pass - return Val - - -#find the wall id for the shading surfaces from surrounding buildings -def findWallId(Id, Shadingsfile, ref,GE): - finished = 0 - ii = 0 - ShadeWall = {} - while finished == 0: - if Id in Shadingsfile[ii].properties[GE['ShadingIdKey']]: - ShadeWall[GE['BuildingIdKey']] = Shadingsfile[ii].properties[GE['BuildingIdKey']] - ShadeWall[GE['ShadingIdKey']] = Shadingsfile[ii].properties[GE['ShadingIdKey']] - try: - ShadeWall['height'] = float(Shadingsfile[ii].properties['zmax'])-float(Shadingsfile[ii].properties['zmin']) - except: - pass - ShadeWall[GE['VertexKey']] = [] - for jj in Shadingsfile[ii].geometry.coordinates: - ShadeWall[GE['VertexKey']].append(tuple([jj[0]-ref[0],jj[1]-ref[1]])) - finished = 1 - else: - ii = ii+1 - if ii>len(Shadingsfile): - print('No finded Wall Id ....') - finished = 1 - return ShadeWall - -#find the height the building's ID -def findBuildId(Id, Buildingsfile,GE): - finished = 0 - ii = 0 - height = 0 - while finished == 0: - if Id == Buildingsfile[ii].properties[GE['BuildingIdKey']]: - height = Buildingsfile[ii].properties['height'] - finished = 1 - else: - ii = ii+1 - if ii>len(Buildingsfile): - print('No finded Build Id ....') - finished = 1 - return height - -class BuildingList: - def __init__(self): - self.building = [] - - def addBuilding(self,name,DataBaseInput,nbcase,MainPath,epluspath,LogFile,PlotOnly): - #idf object is created here - IDF.setiddname(os.path.join(epluspath,"Energy+.idd")) - idf = IDF(os.path.normcase(os.path.join(epluspath,"ExampleFiles/Minimal.idf"))) - idf.idfname = name - #building object is created here - building = Building(name, DataBaseInput, nbcase, MainPath,LogFile,PlotOnly) - #both are append as dict in the globa studied case list - self.building.append({ - 'BuildData' : building, - 'BuildIDF' : idf, - } - ) - -class Building: - - def __init__(self,name,DataBaseInput,nbcase,MainPath,LogFile,PlotOnly): - Buildingsfile = DataBaseInput['Build'] - Shadingsfile = DataBaseInput['Shades'] - DB = Buildingsfile[nbcase] - DBL = DB_Data.DBLimits - BE = DB_Data.BasisElement - GE = DB_Data.GeomElement - EPC = DB_Data.EPCMeters - SD = DB_Data.SimuData - ExEn = DB_Data.ExtraEnergy - try: - self.CRS = Buildingsfile.crs['properties']['name'] #this is the coordinates reference system for the polygons - except: - self.CRS = 'Null' - - self.getBEData(BE) - self.getSimData(SD) - self.name = name - self.BuildID = self.getBuildID(DB, GE,LogFile) - self.Multipolygon = self.getMultipolygon(DB) - - self.nbfloor = self.getnbfloor(DB, DBL,LogFile) - self.nbBasefloor = self.getnbBasefloor(DB, DBL) - self.height = self.getheight(DB, DBL) - self.DistTol = GE['DistanceTolerance'] - self.footprint, self.BlocHeight, self.BlocNbFloor = self.getfootprint(DB,LogFile,self.nbfloor) - self.AggregFootprint = self.getAggregatedFootprint() - self.RefCoord = self.getRefCoord() - self.ATemp = self.getsurface(DB, DBL,LogFile) - self.SharedBld, self.VolumeCorRatio = self.IsSameFormularIdBuilding(Buildingsfile, nbcase, LogFile, DBL) - self.BlocHeight, self.BlocNbFloor, self.StoreyHeigth = self.EvenFloorCorrection(self.BlocHeight, self.nbfloor, self.BlocNbFloor, self.footprint, LogFile) - - self.EPHeatedArea = self.getEPHeatedArea(LogFile) - self.MaxShadingDist = GE['MaxShadingDist'] - self.AdjacentWalls = [] #this will be appended in the getshade function if any present - self.shades = self.getshade(DB,Shadingsfile,Buildingsfile,GE,LogFile) - - self.Materials = DB_Data.BaseMaterial - - self.InternalMass = DB_Data.InternalMass - self.MakeRelativeCoord() # we need to convert into local coordinate in order to compute adjacencies with more precision than keeping thousand of km for x and y - if not PlotOnly: - #the attributres above are needed in all case, the one below are needed only if energy simulation is asked for - self.VentSyst = self.getVentSyst(DB, LogFile) - self.AreaBasedFlowRate = self.getAreaBasedFlowRate(DB, DBL, BE) - self.OccupType = self.getOccupType(DB, LogFile) - self.nbStairwell = self.getnbStairwell(DB, DBL) - self.WeatherDataFile = DB_Data.WeatherFile['Loc'] - self.year = self.getyear(DB, DBL) - self.EPCMeters = self.getEPCMeters(DB, EPC, LogFile) - if len(self.SharedBld) > 0: - self.CheckAndCorrEPCs(Buildingsfile, LogFile, nbcase, EPC) - self.nbAppartments = self.getnbAppartments(DB, DBL) - #we define the internal load only if it's not for making picture - self.IntLoad = self.getIntLoad(MainPath,LogFile) - self.DHWInfos = self.getExtraEnergy(ExEn, MainPath) - - #if there are no cooling comsumption, lets considerer a set point at 50deg max - # for key in self.EPCMeters['Cooling']: - # if self.EPCMeters['Cooling'][key]>0: - # self.setTempUpL = BE['setTempUpL'] - # self.intT_freecool = 50 - # else: - # self.setTempUpL = [50]*len(BE['setTempUpL']) - - def MakeRelativeCoord(self): - # we need to convert change the reference coordinate because precision is needed for boundary conditions definition: - newfoot = [] - roundfactor = 4 - for foot in self.footprint: - newfoot.append([(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in foot]) - self.footprint = newfoot - for shade in self.shades.keys(): - newcoord = [(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in - self.shades[shade]['Vertex']] - self.shades[shade]['Vertex'] = newcoord - newwalls = [] - for Wall in self.AdjacentWalls: - newcoord = [(round(node[0] - self.RefCoord[0],roundfactor), round(node[1] - self.RefCoord[1],roundfactor)) for node in Wall['geometries']] - Wall['geometries'] = newcoord - - def CheckAndCorrEPCs(self,Buildingsfile,LogFile,nbcase,EPC): - totHeat = [] - tocheck = [nbcase]+self.SharedBld - for share in tocheck: - val = 0 - Meas = self.getEPCMeters(Buildingsfile[share],EPC,[]) - for key in Meas['Heating'].keys(): - val += Meas['Heating'][key] - totHeat.append(val) - # correction on the ATemp if it is the same on all (should be) - HeatDiff = [totHeat[idx + 1] - A for idx, A in enumerate(totHeat[:-1])] - if all(v == 0 for v in HeatDiff): - newval = 0 - for keyType in self.EPCMeters.keys(): - for key in self.EPCMeters[keyType].keys(): - try: - self.EPCMeters[keyType][key] *= self.VolumeCorRatio - except: - pass - if 'Heating' == keyType: - newval += self.EPCMeters['Heating'][key] - msg = '[EPCs correction] The EPCs total heat needs for the each shared buildings is :'+str(totHeat)+'\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[EPCs correction] All EPCs metrix will be modified by the Volume ratio as for ATemp\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[EPCs correction] For example, the Heat needs is corrected from : '+ str(totHeat[0])+ ' to : '+ str(newval)+'\n' - GrlFct.Write2LogFile(msg, LogFile) - - def IsSameFormularIdBuilding(self,Buildingsfile,nbcase,LogFile,DBL): - SharedBld = [] - VolumeCorRatio = 1 - Correction = False - for nb,Build in enumerate(Buildingsfile): - try: - if Build.properties['FormularId'] == self.BuildID['FormularId'] and nb != nbcase: - SharedBld.append(nb) - Correction = True - except: - pass - maxHeight=[max(self.BlocHeight)] - floors = [self.nbfloor] - ATemp = [self.ATemp] - Volume = [sum([Polygon(foot).area * self.BlocHeight[idx] for idx,foot in enumerate(self.footprint)])] - for nb in SharedBld: - ATemp.append(self.getsurface(Buildingsfile[nb], DBL,[])) - - floors.append(self.getnbfloor(Buildingsfile[nb],DBL,[])) - Bldfootprint, BldBlocHeight, BldBlocNbFloor = self.getfootprint(Buildingsfile[nb],[],floors[-1]) - maxHeight.append(max(BldBlocHeight)) - Volume.append(sum([Polygon(foot).area * BldBlocHeight[idx] for idx,foot in enumerate(Bldfootprint)])) - if Correction: - #some correction is needed on the nb of floor because a higher one, with the same FormularId is higher - newfloor = max(int(floors[maxHeight.index(max(maxHeight))] / (max(maxHeight) / maxHeight[0])),1) - msg = '[Shared EPC] Buildings are found with same FormularId: '+str(SharedBld)+'\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[Nb Floor Cor] The nb of floors will be corrected by the height ratio of this building with the highests one with same FormularId (but cannot be lower than 1)\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[Nb Floor Cor] nb of floors is thus corrected from : '+ str(self.nbfloor)+ ' to : '+ str(newfloor)+'\n' - GrlFct.Write2LogFile(msg, LogFile) - self.nbfloor = newfloor - #correction on the ATemp if it is the same on all (should be) - Adiff = [ATemp[idx+1]-A for idx,A in enumerate(ATemp[:-1])] - if all(v == 0 for v in Adiff): - VolumeCorRatio = Volume[0] / sum(Volume) - newATemp = self.ATemp * VolumeCorRatio - msg = '[ATemp Cor] The ATemp will also be modified by the volume ratio of this building over the volume sum of all concerned building \n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[ATemp Cor] The ATemp is thus corrected from : '+ str(self.ATemp)+ ' to : '+ str(newATemp)+'\n' - GrlFct.Write2LogFile(msg, LogFile) - self.ATemp = newATemp - return SharedBld, VolumeCorRatio - - - def getBEData(self,BE): - for key in BE.keys(): - setattr(self, key, BE[key]) - - def getExtraEnergy(self,ExEn,MaintPath): - output={} - for key in ExEn.keys(): - try: - ifFile = os.path.join(os.path.dirname(MaintPath),os.path.normcase(ExEn[key])) - if os.path.isfile(ifFile): - AbsInputFileDir,InputFileDir = self.isInputDir() - iflocFile = os.path.join(AbsInputFileDir,os.path.basename(ifFile)) - if not os.path.isfile(iflocFile): - shutil.copy(ifFile,iflocFile) - output[key] = os.path.join(InputFileDir,os.path.basename(ifFile)) - else: - output[key] = ExEn[key] - except: - output[key] = ExEn[key] - return output - - def getSimData(self,SD): - for key in SD.keys(): - setattr(self, key, SD[key]) - - def getBuildID(self,DB,GE,LogFile): - BuildID={} - for key in GE['BuildIDKey']: - try: - BuildID[key] = DB.properties[key] - except: - pass #BuildID[key] = None - if BuildID: - for key in BuildID: - msg = '[Bld ID] '+ key+' : ' + str(BuildID[key]) + '\n' - GrlFct.Write2LogFile(msg, LogFile) - return BuildID - - def getMultipolygon(self,DB): - test = DB.geometry.coordinates[0][0][0] - if type(test) is list: - Multipolygon = True - else: - Multipolygon = False - return Multipolygon - - def getRefCoord(self): - "get the reference coodinates for visualisation afterward" - #check for Multipolygon first - if self.Multipolygon: - centroide = [list(Polygon(foot).centroid.coords) for foot in self.footprint] #same reason than below, foot print is computed before now [list(Polygon(DB.geometry.coordinates[i][0]).centroid.coords) for i in range(len(DB.geometry.coordinates))] - x = sum([centroide[i][0][0] for i in range(len(centroide))])/len(centroide) - y = sum([centroide[i][0][1] for i in range(len(centroide))])/len(centroide) - else: - centroide = list(Polygon(self.footprint[0]).centroid.coords)# now the foot print is computed nbefore the reference. before it was defined with list(Polygon(DB.geometry.coordinates[0]).centroid.coords) - x = centroide[0][0] - y = centroide[0][1] - offset = ((2*Polygon(self.AggregFootprint).area)**0.5)/8 - ref = (x-offset, y-offset) #there might be not a true need for suche precision.... - return ref - - def getfootprint(self,DB,LogFile=[],nbfloor=0): - "get the footprint coordinate and the height of each building bloc" - DistTol = self.DistTol - coord = [] - node2remove =[] - BlocHeight = [] - BlocNbFloor = [] - #we first need to check if it is Multipolygon - if self.Multipolygon: - #then we append all the floor and roof fottprints into one with associate height - for idx1,poly1 in enumerate(DB.geometry.coordinates[:-1]): - for idx2,poly2 in enumerate(DB.geometry.coordinates[idx1+1:]): - if poly1 == poly2: - polycoor = [] - for j in poly1[0]: - new = (j[0], j[1]) - new_coor = new#[] - # for ii in range(len(RefCoord)): - # new_coor.append((new[ii] - RefCoord[ii])) - polycoor.append(tuple(new_coor)) - if polycoor[0]==polycoor[-1]: - polycoor = polycoor[:-1] - #even before skewed angle, we need to check for tiny edge below the tolerance onsdered aftward (0.5m) - pt2remove = [] - for edge in Polygon2D(polycoor).edges: - if edge.length < DistTol: - pt2remove.append(edge.p2) - for pt in pt2remove: - if len(polycoor)>3: - polycoor.remove(pt) - newpolycoor, node = core_perim.CheckFootprintNodes(polycoor,5) - node2remove.append(node) - #polycoor.reverse() - coord.append(polycoor) - BlocHeight.append(round(abs(DB.geometry.poly3rdcoord[idx1]-DB.geometry.poly3rdcoord[idx2+idx1+1]),1)) - #these following lines are here to highlight holes in footprint and split it into two blocs... - #it may appear some errors for other building with several blocs and some with holes (these cases havn't been checked) - poly2merge = [] - for idx, coor in enumerate(coord): - for i in range(len(coord)-idx-1): - if Polygon(coor).contains(Polygon(coord[idx+i+1])): - poly2merge.append([idx,idx+i+1]) - try: - for i,idx in enumerate(poly2merge): - new_surfaces = break_polygons(Polygon3D(coord[idx[0]]), Polygon3D(coord[idx[1]])) - xs,ys,zs = zip(*list(new_surfaces[0])) - coord[idx[0]] = [(xs[nbv],ys[nbv]) for nbv in range(len(xs))] - xs,ys,zs = zip(*list(new_surfaces[1])) - coord[idx[1]] = [(xs[nbv],ys[nbv]) for nbv in range(len(xs))] - BlocHeight[idx[1]] = BlocHeight[idx[0]] - msg ='[Geom Cor] There is a hole that will split the main surface in two blocs \n' - GrlFct.Write2LogFile(msg, LogFile) - except: - msg = '[Poly Error] Some error are present in the polygon parts. Some are identified as being inside others...\n' - print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - import matplotlib.pyplot as plt - fig = plt.figure(0) - for i in coord: - xs,ys = zip(*i) - plt.plot(xs,ys,'-.') - #titre = 'FormularId : '+str(DB.properties['FormularId'])+'\n 50A_UUID : '+str(DB.properties['50A_UUID']) - # plt.title(titre) - # plt.savefig(self.name+ '.png') - # plt.close(fig) - - #we need to clean the footprint from the node2remove but not if there are part of another bloc - newbloccoor= [] - for idx,coor in enumerate(coord): - newcoor = [] - FilteredNode2remove = [] - single = False - for node in node2remove[idx]: - single = True - for idx1,coor1 in enumerate(coord): - if idx!=idx1: - if coor[node] in coor1 and coor[node] not in [n for i,n in enumerate(coor1[idx1]) if i in node2remove[idx1]]: - single =False - if single: - FilteredNode2remove.append(node) - for nodeIdx,node in enumerate(coor): - if not nodeIdx in FilteredNode2remove: - newcoor.append(node) - newbloccoor.append(newcoor) - coord = newbloccoor - else: - #for dealing with 2D files - for j in DB.geometry.coordinates[0]: - new = (j[0], j[1]) - new_coor = new#[] - # for ii in range(len(self.RefCoord)): - # new_coor.append((new[ii] - self.RefCoord[ii])) - coord.append(tuple(new_coor)) - BlocNbFloor.append(nbfloor) - BlocHeight.append(self.height) - newpolycoor, node = core_perim.CheckFootprintNodes(coord, 5) - coord= [newpolycoor] - #before submitting the full coordinates, we need to check correspondance in case of multibloc - coord, validFootprint = CheckMultiBlocFootprint(coord,tol = DistTol) - if not validFootprint: - msg = '[Poly Error] The different bloc are not adjacent...\n' - #print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - return - # multibloc should share at least one edge and not a polygon as bld:a3848e24-d29e-44bc-a395-a25b5fd26598 in area : 0180C3170 of Sodermalm_V4 - SmallEdge = False - for bloc in coord: - if [val for val in Polygon2D(bloc).edges_length if val < 2]: - SmallEdge = True - if SmallEdge: - msg = '[Geom Warning] This building has at least one edge length below 2m\n' - #print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - return coord, BlocHeight, BlocNbFloor - - def EvenFloorCorrection(self,BlocHeight,nbfloor,BlocNbFloor,coord,LogFile): - # we compute a storey height as well to choosen the one that correspond to the highest part of the building afterward - BlocNbFloor=[] #the number of blocks is reset to comply with the old 2D geojson files is anyway empty for multipolygons files - StoreyHeigth = 3 - if nbfloor !=0: - storeyRatio = StoreyHeigth / (max(BlocHeight) / nbfloor) if (max(BlocHeight) / nbfloor) > 0.5 else 1 - msg = '[Geom Info] The max bloc height is : ' + str(round(max(BlocHeight), 2)) + ' for ' + str( - nbfloor) + ' floors declared in the EPC \n' - else: - nbfloor= round(max(BlocHeight)/StoreyHeigth) - try: - storeyRatio = StoreyHeigth / (max(BlocHeight) / nbfloor) if (max(BlocHeight) / nbfloor) > 0.5 else 1 - except: - storeyRatio = 0 - msg = '[Geom Info] The max bloc height is : ' + str(round(max(BlocHeight), 2)) + ' for ' + str( - nbfloor) + ' floors computed from max bloc height\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[Geom Cor] A ratio of ' + str(storeyRatio) + ' will be applied on each bloc height\n' - GrlFct.Write2LogFile(msg, LogFile) - - for height in range(len(BlocHeight)): - BlocHeight[height] *= storeyRatio - for idx, Height in enumerate(BlocHeight): - val = int(round(Height, 1) / StoreyHeigth) - BlocNbFloor.append(max(1, val)) # the height is ed to the closest 10cm - BlocHeight[idx] = BlocNbFloor[-1] * StoreyHeigth - msg = '[Geom Info] Bloc height : ' + str(BlocHeight[idx]) + ' with ' + str(BlocNbFloor[-1]) + ' nb of floors\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[Geom Info] This bloc has a footprint with : ' + str(len(coord[idx])) + ' vertexes\n' - GrlFct.Write2LogFile(msg, LogFile) - if val == 0: - try: - LogFile.write( - '[WARNING] /!\ This bloc as a height below 3m, it has been raized to 3m to enable construction /!\ \n') - except: - pass - return BlocHeight, BlocNbFloor, StoreyHeigth - - def getEPHeatedArea(self,LogFile): - "get the heated area based on the footprint and the number of floors" - self.BlocFootprintArea=[] - - EPHeatedArea = 0 - for i,foot in enumerate(self.footprint): - EPHeatedArea += Polygon(foot).area*self.BlocNbFloor[i] - self.BlocFootprintArea.append(Polygon(foot).area) - msg = '[Geom Info] Blocs footprint areas : '+ str(self.BlocFootprintArea)+'\n' - GrlFct.Write2LogFile(msg, LogFile) - msg = '[Geom Info] The total heated area is : ' + str(EPHeatedArea)+' for a declared ATemp of : '+str(self.ATemp)+' --> discrepancy of : '+str(round((self.ATemp-EPHeatedArea)/self.ATemp*100,2))+'\n' - GrlFct.Write2LogFile(msg, LogFile) - - return EPHeatedArea - - def getsurface(self,DB, DBL,LogFile): - "Get the surface from the input file, ATemp" - - try: ATemp = int(getDBValue(DB.properties, DBL['surface_key'])) - except: ATemp = 1 - if ATemp == 1: - msg = '[Geom Error] Atemp not recognized as number, fixed to 1\n' - GrlFct.Write2LogFile(msg, LogFile) - ATemp = checkLim(ATemp,DBL['surface_lim'][0],DBL['surface_lim'][1]) - - self.ATempOr= ATemp #this is to keep the original value as some correction might done afterward if more then 1 bld is present in 1 Id - return ATemp - - def getnbfloor(self,DB, DBL,LogFile): - "Get the number of floor above ground" - - try: nbfloor=int(getDBValue(DB.properties, DBL['nbfloor_key'])) - except: nbfloor = 0 - if nbfloor == 0: - msg = '[EPCs Warning] The nb of floors is 0. It will be defined using the max bloc height and a storey height of 3m\n' - GrlFct.Write2LogFile(msg, LogFile) - nbfloor = checkLim(nbfloor,DBL['nbfloor_lim'][0],DBL['nbfloor_lim'][1]) - return nbfloor - - def getnbStairwell(self,DB, DBL): - "Get the number of stariwell, need for natural stack effect on infiltration" - - try: nbStairwell = int(getDBValue(DB.properties, DBL['nbStairwell_key'])) - except: nbStairwell=0 - nbStairwell = checkLim(nbStairwell,DBL['nbStairwell_lim'][0],DBL['nbStairwell_lim'][1]) - return nbStairwell - - - def getnbBasefloor(self,DB, DBL): - "Get the number of floor below ground" - - try: nbBasefloor = int(getDBValue(DB.properties, DBL['nbBasefloor_key'])) - except: nbBasefloor = 0 - nbBasefloor = checkLim(nbBasefloor,DBL['nbBasefloor_lim'][0],DBL['nbBasefloor_lim'][1]) - return nbBasefloor - - def getyear(self,DB, DBL): - "Get the year of construction in the input file" - - try: year = int(getDBValue(DB.properties, DBL['year_key'])) - except: year = 1900 - year = checkLim(year,DBL['year_lim'][0],DBL['year_lim'][1]) - return year - - def getEPCMeters(self,DB,EPC,LogFile): - "Get the EPC meters values" - Meters = {} - - for key1 in EPC: - Meters[key1] = {} - for key2 in EPC[key1]: - if '_key' in key2: - try: - Meters[key1][key2[:-4]] = DB.properties[EPC[key1][key2]] - Meters[key1][key2[:-4]] = int(DB.properties[EPC[key1][key2]])*EPC[key1][key2[:-4]+'COP'] - except: - pass - return Meters - - def getnbAppartments(self, DB, DBL): - "Get the number of appartment in the building" - try: nbApp = int(getDBValue(DB.properties, DBL['nbAppartments_key'])) - except: nbApp = 0 - nbApp = checkLim(nbApp,DBL['nbAppartments_lim'][0],DBL['nbAppartments_lim'][1]) - return nbApp - - def getheight(self, DB, DBL): - "Get the building height from the input file, but not used if 3D coordinates in the footprints" - try: height = int(getDBValue(DB.properties, DBL['height_key'])) - except: height = 0 - height = checkLim(height,DBL['height_lim'][0],DBL['height_lim'][1]) - return height - - def getAggregatedFootprint(self): - # lets compute the aggregaded external footprint of the different blocs - # starting with the first one - AggregFootprint = self.footprint[0] - RemainingBlocs = self.footprint[1:] - idx = 0 - while RemainingBlocs: - Intersectionline = Polygon(AggregFootprint).intersection(Polygon(RemainingBlocs[idx])) - if Intersectionline and type(Intersectionline) != Point: - AggregFootprint = list(Polygon(AggregFootprint).union(Polygon(RemainingBlocs[idx])).exterior.coords) - RemainingBlocs.remove(RemainingBlocs[idx]) - idx = 0 - else: - idx += 1 - # in order to close the loop if not already done - if AggregFootprint[0] != AggregFootprint[-1]: - AggregFootprint.append(AggregFootprint[0]) - return AggregFootprint - def getshade(self, DB,Shadingsfile,Buildingsfile,GE,LogFile,PlotOnly = True): - "Get all the shading surfaces to be build for surrounding building effect" - shades = {} - - try: - shadesID = DB.properties[GE['ShadingIdKey']] - except: - return shades - ModifiedShadeVertexes ={'ShadeId' : [], 'OldCoord': [], 'NewCoord' : []} #this dict will log the changes in the vertex coordinate to adjust other shading if necesseray afterward - - RelativeAgregFootprint = [(node[0] - self.RefCoord[0], node[1] - self.RefCoord[1]) for node in self.AggregFootprint] - Meancoordx = list(Polygon(RelativeAgregFootprint).centroid.coords)[0][0] - Meancoordy = list(Polygon(RelativeAgregFootprint).centroid.coords)[0][1] - - - currentRef = self.getRefCoord() - ref = (0, 0) if currentRef==self.RefCoord else self.RefCoord - idlist = [-1] - for m in re.finditer(';', shadesID): - idlist.append(m.start()) - for ii, sh in enumerate(idlist): - if ii == len(idlist) - 1: - wallId = shadesID[idlist[ii] + 1:] - else: - wallId = shadesID[idlist[ii] + 1:idlist[ii + 1]] - ShadeWall = findWallId(wallId, Shadingsfile, ref, GE) - if not 'height' in ShadeWall.keys(): - ShadeWall['height'] = findBuildId(ShadeWall[GE['BuildingIdKey']], Buildingsfile,GE) - if ShadeWall['height']==None: - ShadeWall['height'] = self.height - currentShadingElement = [(node[0]-self.RefCoord[0],node[1]-self.RefCoord[1]) for node in ShadeWall[GE['VertexKey']]] - meanPx = (currentShadingElement[0][0] + currentShadingElement[1][0]) / 2 - meanPy = (currentShadingElement[0][1] + currentShadingElement[1][1]) / 2 - edgelength = LineString(currentShadingElement).length - if edgelength<2: - msg = '[Shading Info] This one is dropped, less than 2m wide ('+str(round(edgelength,2))+'m), shading Id : '+ ShadeWall[GE['ShadingIdKey']] +'\n' - GrlFct.Write2LogFile(msg, LogFile) - - #print(msg[:-1]) - continue - if ShadeWall[GE['ShadingIdKey']] =='V67656-3': - a=1 - confirmed,currentShadingElement,OverlapCode = checkShadeWithFootprint(RelativeAgregFootprint,currentShadingElement,ShadeWall[GE['ShadingIdKey']],tol = self.DistTol) - if confirmed: - if ShadeWall['height']<=(max(self.BlocHeight)+self.StoreyHeigth): - OverlapCode +=1 - ShadeWall['height'] = self.StoreyHeigth*round(ShadeWall['height'] / self.StoreyHeigth) #max(self.BlocHeight)# - self.AdjacentWalls.append(ShadeWall) - shades[wallId] = {} - shades[wallId]['Vertex'] = [(node[0]+self.RefCoord[0],node[1]+self.RefCoord[1]) for node in currentShadingElement] - shades[wallId]['height'] = ShadeWall['height'] - shades[wallId]['distance'] = 0 - ModifiedShadeVertexes['ShadeId'].append(ShadeWall[GE['ShadingIdKey']]) - ModifiedShadeVertexes['OldCoord'].append(ShadeWall[GE['VertexKey']]) - ModifiedShadeVertexes['NewCoord'].append(shades[wallId]['Vertex']) - msg = '[Adjacent Wall] This Shading wall is considered as adjacent with an overlap code of '+str(OverlapCode)+', shading Id : ' + ShadeWall[ - GE['ShadingIdKey']] + '\n' - #print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - continue - if OverlapCode== 999: - msg = '[Shading Error] This Shading wall goes inside the building...It is dropped, shading Id : ' + ShadeWall[ - GE['ShadingIdKey']] + '\n' - #print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - continue - dist = (abs(meanPx - Meancoordx) ** 2 + abs(meanPy - Meancoordy) ** 2) ** 0.5 - - shades[wallId] = {} - shades[wallId]['Vertex'] = ShadeWall[GE['VertexKey']] - shades[wallId]['height'] = round(ShadeWall['height'],2) - shades[wallId]['distance'] = dist - return shades - - - def getVentSyst(self, DB,LogFile): - "Get ventilation system type" - VentSyst = {} - for key in DB_Data.VentSyst: - try: - VentSyst[key] = True if 'Ja' in DB.properties[DB_Data.VentSyst[key]] else False - except: - VentSyst[key] = False - nbVentSyst = [idx for idx, key in enumerate(VentSyst) if VentSyst[key]] - nbVentSystWithHR = [idx for idx, key in enumerate(VentSyst) if - VentSyst[key] and key[-1] == 'X'] - if len(nbVentSyst) > 1: - msg = '[Vent Warning] This building has '+str(len(nbVentSyst))+' ventilation systems declared\n' - GrlFct.Write2LogFile(msg, LogFile) - if len(nbVentSystWithHR)>1: - msg = '[Vent Warning] This building has '+str(len(nbVentSystWithHR))+' ventilation systems with heat recovery\n' - GrlFct.Write2LogFile(msg, LogFile) - return VentSyst - - def getAreaBasedFlowRate(self, DB, DBL, BE): - "Get the airflow rates based on the floor area" - try: AreaBasedFlowRate = float(getDBValue(DB.properties, DBL['AreaBasedFlowRate_key'])) - except : AreaBasedFlowRate = BE['AreaBasedFlowRate'] - AreaBasedFlowRate = checkLim(AreaBasedFlowRate,DBL['AreaBasedFlowRate_lim'][0],DBL['AreaBasedFlowRate_lim'][1]) - return AreaBasedFlowRate - - def getOccupType(self,DB,LogFile): - "get the occupency type of the building" - OccupType = {} - self.OccupRate = {} - for key in DB_Data.OccupType: - if '_key' in key: - try: - OccupType[key[:-4]] = int(DB.properties[DB_Data.OccupType[key]])/100 - except: - OccupType[key[:-4]] = 0 - if '_Rate' in key: - self.OccupRate[key[:-5]] = DB_Data.OccupType[key] - msg = '[Usage Info] This building has ' + str(1 - OccupType['Residential']) + ' % of none residential occupancy type\n' - GrlFct.Write2LogFile(msg, LogFile) - return OccupType - - def isInputDir(self): - InputFileDir = 'InputFiles' - AbsInputFileDir = os.path.join(os.getcwd(),InputFileDir) - if not os.path.exists(AbsInputFileDir): - os.mkdir(AbsInputFileDir) - return AbsInputFileDir,AbsInputFileDir #both values are identicial since the relative path was still creating issues with FMUs afterward... - - def getIntLoad(self, MainPath,LogFile): - "get the internal load profil or value" - #we should integrate the loads depending on the number of appartemnent in the building - type = self.IntLoadType - # Input_path = os.path.join(MainPath,'InputFiles') - # #lets used StROBE package by defaults (average over 10 profile - # IntLoad = os.path.join(Input_path, 'P_Mean_over_10.txt') - try: - IntLoad = self.ElecYearlyLoad #this value is in W\m2 prescies in DB_Data - except: - IntLoad = 0 - #now we compute power time series in order to match the measures form EPCs - eleval = 0 - try : - for x in self.EPCMeters['ElecLoad']: - if self.EPCMeters['ElecLoad'][x]: - eleval += self.EPCMeters['ElecLoad'][x]*1000 #to convert kW in W - except: - pass - if eleval>0: - try: - if 'Cste' in type: - IntLoad = eleval/self.EPHeatedArea/8760 #this value is thus in W/m2 #division by number of hours to convert Wh into W - else: - AbsInputFileDir,InputFileDir = self.isInputDir() - if 'winter' in type: - IntLoad = os.path.join(InputFileDir, self.name + '_winter.txt') - ProbGenerator.SigmoFile('winter', self.IntLoadCurveShape, eleval/self.EPHeatedArea * 100, IntLoad) #the *100 is because we have considered 100m2 for the previous file - if 'summer' in type: - IntLoad = os.path.join(InputFileDir, self.name + '_summer.txt') - ProbGenerator.SigmoFile('summer', self.IntLoadCurveShape, eleval / self.EPHeatedArea * 100,IntLoad) # the *100 is because we have considered 100m2 for the previous file - except: - msg = '[Int Load Error] Unable to write the internal load file...\n' - - #print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - return IntLoad - -def CheckMultiBlocFootprint(blocs,tol =1): - validMultibloc = True - if len(blocs)>1: - validMultibloc = False - for bloc1,bloc2 in itertools.product(blocs,repeat = 2): - if bloc1 != bloc2: - for ptidx,pt in enumerate(bloc1): - edge = [bloc1[ptidx],bloc1[(ptidx+1)%len(bloc1)]] - comEdges = [] - for ptidx1,pt1 in enumerate(bloc2): - edge1 = [bloc2[ptidx1], bloc2[(ptidx1+1)%len(bloc2)]] - if is_parallel(edge,edge1,10) and confirmMatch(edge, edge1, tol): - validMultibloc = True - pt1 = False - pt2 = False - if LineString(edge1).distance(Point(edge[0])) < tol: - edge[0] = point_on_line(edge1[0], edge1[1],edge[0]) - edge[0],conf= CoordAdjustement(edge1, edge[0], tol) - pt1 = True - if LineString(edge1).distance(Point(edge[1])) < tol: - edge[1] = point_on_line(edge1[0], edge1[1],edge[1]) - edge[1],conf = CoordAdjustement(edge1, edge[1], tol) - pt2 = True - if pt1 and pt2: - if abs(getAngle(edge1, edge) -180) < 5: - comEdges.append([edge[1],edge[0]]) - else: - comEdges.append(edge) - bloc1[ptidx] = edge[0] - bloc1[(ptidx + 1) % len(bloc1)] = edge[1] - #lets check if these nodes are also on bloc2 - #first which bloc is concerned - for comEdge in comEdges: - if comEdge[0] in bloc2 and comEdge[1] not in bloc2: - index = bloc2.index(comEdge[0])+1 - bloc2.insert(index,comEdge[1]) - #bloc2 = bloc2[:index]+[comEdge[1]]+bloc2[index:] - if comEdge[1] in bloc2 and comEdge[0] not in bloc2: - index = bloc2.index(comEdge[1]) - bloc2.insert(index,comEdge[0]) - #bloc2 = bloc2[:index]+[comEdge[0]]+bloc2[index:] - return blocs,validMultibloc - - -def point_on_line(a, b, p): - import numpy as np - a = np.array(a) - b = np.array(b) - p = np.array(p) - ap = p - a - ab = b - a - result = a + np.dot(ap, ab) / np.dot(ab, ab) * ab - return tuple(result) - -def getAngle(line1,line2): - vector_a_x = line1[1][0] - line1[0][0] - vector_a_y = line1[1][1] - line1[0][1] - vector_b_x = line2[1][0] - line2[0][0] - vector_b_y = line2[1][1] - line2[0][1] - import numpy as np - v = np.array([vector_a_x, vector_a_y]) - w = np.array([vector_b_x, vector_b_y]) - return abs(np.rad2deg(np.arccos(round(v.dot(w) / (np.linalg.norm(v) * np.linalg.norm(w)), 4)))) - -def is_parallel(line1, line2, tol = 5): - angledeg = getAngle(line1, line2) - if angledeg 1 zone per building bloc - } - -#files are needed to be located in the eather folder of EnergyPlus asthe same path is used afterward to launch the simulation -WeatherFile = \ - {'Loc' : 'WeatherData/USA_CA_San.Francisco.Intl.AP.724940_TMY3.epw', #WE_Stockholm.Arlanda.024600_IWEC.epw' - } - -#Thisdict gives all the materials characteristics. -# There are 2 layer maximum, the word Inertia and Insulation or key factor further in the code. If one layer is wanted, just comment the other one. -#the basement is considered not heated and thus never insulated layer -BaseMaterial = \ - {'Window' : {'UFactor' : 1.9, - 'Solar_Heat_Gain_Coefficient' : 0.7, - 'Visible_Transmittance' : 0.8, - }, -'Wall Inertia' : {'Thickness' : 0.2, #this layer will be considered also for the basement walls - 'Conductivity' : 0.9, - 'Roughness' : "Rough", - 'Density' : 2300, - 'Specific_Heat' : 1000, - }, -'Wall Insulation' : {'Thickness' : 0.2, - 'Conductivity' : 0.03, - 'Roughness' : "Rough", - 'Density' : 150, - 'Specific_Heat' : 1000, - #'Thermal_Absorptance' : 0.001, - }, -'Basement Floor' : {'Thickness' : 0.1, #this layer will be considered also for the basement floor - 'Conductivity' : 0.9, - 'Roughness' : "Rough", - 'Density' : 2300, - 'Specific_Heat' : 1000, - }, -# 'Basement Floor Insulation' : {'Thickness' : 0.05, #not needed as even without basement the Heated1rstFloor is taken for the first floor -# 'Conductivity' : 0.25*0.1, -# 'Roughness' : "Rough", -# 'Density' : 1000, -# 'Specific_Heat' : 1000, -# }, -# 'Roof Inertia' : {'Thickness' : 0.05, #not needed unless one wants to have inertia in the roof layer -# 'Conductivity' : 0.15*0.1, -# 'Roughness' : "Rough", -# 'Density' : 1000, -# 'Specific_Heat' : 1000, -# }, -'Roof Insulation' : {'Thickness' : 0.3, - 'Conductivity' : 0.03, - 'Roughness' : "Rough", - 'Density' : 150, - 'Specific_Heat' : 1000, - #'Thermal_Absorptance' : 0.001, - }, -'Heated1rstFloor Inertia' : {'Thickness' : 0.1, - 'Conductivity' : 0.9, - 'Roughness' : "Rough", - 'Density' : 2300, - 'Specific_Heat' : 1000, - }, -'Heated1rstFloor Insulation' : {'Thickness' : 0.15, - 'Conductivity' : 0.035, - 'Roughness' : "Rough", - 'Density' : 150, - 'Specific_Heat' : 1000, - #'Thermal_Absorptance' : 0.001, - }, - } - -#this dict is for specification of internalMass equivalence. -#the material should represent the overall mean material of all partition and furnitures -#the weight par zone area gives the quentity and the Average thickness enable to compute the surface for heat transfer -#the mass gives a volume thanks to the density that gives a surface thanks to the average thickness -InternalMass = \ - {'HeatedZoneIntMass' : { - 'Thickness' : 0.1, #m this will define the surface in contact with the zone - 'Conductivity' : 0.3, - 'Roughness' : "Rough", - 'Density' : 600, - 'Specific_Heat' : 1400, - 'WeightperZoneArea' : 40, #kg/m2 - }, -'NonHeatedZoneIntMass' : { - 'Thickness' : 0.1, #m this will define the surface in contact with the zone - 'Conductivity' : 0.3, - 'Roughness' : "Rough", - 'Density' : 600, - 'Specific_Heat' : 1400, - 'WeightperZoneArea' : 40, #kg/m2 - }, - } - -#this dict give element for the Domestic Hot water. it gives the externail file for the water taps and the inlet cold water temp. -#if empty it is no longer taken into account. if new file are given, thses should be present in the Externael file folder -ExtraEnergy = \ - {} #if no DomesticHot Water is to be consdered, it still needs an empty dict -# ExtraEnergy = \ -# {'Name' : 'DHW', -# 'WatertapsFile':'ExternalFiles/mDHW_Sum_over_40.txt', #this file is in l/mnin and will be converted into m3/s afertward. it needs to have hourly values -# 'ColdWaterTempFile' :'ExternalFiles/ColdWaterTemp.txt', -# 'HotWaterSetTemp': 55, -# 'WaterTapsMultiplier':1e-4/6/40, #this is because the file given above is for 40 apartment and is in l/min where we need m3/s. in the code in is afterward multiplied by the number of apartement in the building -# } - -#this dict is for the shading paradigm. There are two files that we need. the firt one is the main geojson that contains all buildings and their propreties -#the other one contains for each shading surface id the vertex point and the building Id in order to catch the height of it. -#to externalize as much as possible, these elements are reported in the dict below -GeomElement = \ - {'BuildIDKey' : ['50A_UUID', 'FormularId','objectid','OBJECTID'], - 'ShadingIdKey' : 'vaggid', - 'BuildingIdKey' : 'byggnadsid', - 'VertexKey':'geometries', - 'MaxShadingDist': 200, - 'DistanceTolerance': 0.2, #this is a threshold below every edge are removed and vertexes merged - } -#this dict gives information on occupancy times each day. If DCV = True, the airflow will follow the number of person -# and the schedule. if not it will be based only on the extra airflow rate but without schedule (all the time) -#if some separation (ventilation and people) is needed than people heat generation should be converted inteo Electric Load as thus ariflow can be -# related to a schedule, other wise...impossible -BasisElement = \ -{'Office_Open': '08:00', - 'Office_Close': '18:00', - 'DemandControlledVentilation' : True, - 'OccupBasedFlowRate': 7, # l/s/person - 'OccupHeatRate' : 70, #W per person - 'EnvLeak': 0.8, # l/s/m2 at 50Pa - 'BasementAirLeak': 1, #in Air change rate [vol/hour] - 'wwr': 0.25, - 'ExternalInsulation' : False, - 'ElecYearlyLoad' :15, #this is the W\m2 value that will be applied constantly for appliances and occupancy consumptipon impact. It is replace by the values in EPCs if available - 'IntLoadType' : 'Cste', #change either by 'Cste', 'winter', or 'summer' for reversed sigmoid or sigmoid this will generate hourly values file in the InputFiles folder - 'IntLoadMultiplier': 1, #this is a multiplier the modeler would like to play with for calibration - 'IntLoadCurveShape':3, #this defines the slop of the curves - 'OffOccRandom' : False, - 'AreaBasedFlowRate' : 0.35, #l/s/m2 - 'AreaBasedFlowRateDefault' : 0.35, #l/s/m2 This will not be changed by EPCs and is needed if EPCs report only the balcned ventilation flow with HR for building that have 2 system and one wihtout recovery. - 'setTempUpL' : [25,25], #only one have to be defined for none temperature modulation - 'setTempLoL' : [21,21], #only one have to be defined for none temperature modulation - 'ComfortTempOff' :'23:00', #hours at wich the first temperature set point is considered - 'ComfortTempOn': '06:00', #hours at wich the second temperature set point is considered - 'ACH_freecool' :4, #this the the vol/hr of extra ventilation when free cooling is on - 'intT_freecool' : 26, #internal temperature threshold for free coolong (opening windows with fixed ACH) - 'dT_freeCool': 1, #Tint-Text to authorize free cooling to be turned on - 'AirRecovEff' : 0.65, #efficiency if heat is recovered from ventilation - 'HVACLimitMode': 'NoLimit', #'LimitCapacity', #can be NoLimit or LimitFlowRate or LimitFlowRateAndCpacity - 'HVACPowLimit' : 25, #in Watt/m2 - - } - -# definition of person/m2...complytely abritrary, but we still need some vaalues -# these proposales are taken from Marc Nohra report and from personnal suggestions -# to be enhanced !!!!!BBR gives also some -OccupType = \ - {'Residential_key' : 'EgenAtempBostad', 'Residential_Rate': [0.02, 0.02], - 'Hotel_key' : 'EgenAtempHotell', 'Hotel_Rate': [0.01, 0.02], - 'Restaurant_key' : 'EgenAtempRestaurang', 'Restaurant_Rate': [0.01, 0.09], - 'Office_key' : 'EgenAtempKontor', 'Office_Rate': [0.01, 0.09], - 'FoodMarket_key' : 'EgenAtempLivsmedel', 'FoodMarket_Rate': [0.01, 0.09], - 'GoodsMarket_key' : 'EgenAtempButik', 'GoodsMarket_Rate': [0.01, 0.09], - 'Shopping_key' : 'EgenAtempKopcentrum', 'Shopping_Rate': [0.01, 0.09], #'I still wonder what is the difference with goods' - 'Hospital24h_key' : 'EgenAtempVard', 'Hospital24h_Rate': [0.01, 0.09], - 'Hospitalday_key' : 'EgenAtempHotell', 'Hospitalday_Rate': [0.01, 0.09], - 'School_key' : 'EgenAtempSkolor', 'School_Rate': [0.01, 0.1], - 'IndoorSports_key' : 'EgenAtempBad', 'IndoorSports_Rate': [0.01, 0.1], - 'Other_key' : 'EgenAtempOvrig', 'Other_Rate': [0.01, 0.1], - 'AssmbPlace_key' : 'EgenAtempTeater', 'AssmbPlace_Rate': [0.01, 0.2], - # 'OccupRate': OccupRate, - } - -#this dict deals with the ventilation systems -VentSyst = \ - {'BalX' : 'VentTypFTX', - 'Exh' : 'VentTypF', - 'Bal' : 'VentTypFT', - 'Nat' : 'VentTypSjalvdrag', - 'ExhX' : 'VentTypFmed', - } - - -#this dict defines the acceptable limits for the element precised as well as the swedish key for the DataBase -DBLimits = \ -{'surface_key': ['EgenAtemp','SHAPE.AREA'], 'surface_lim': [0, 50000], - 'nbfloor_key': 'EgenAntalPlan', 'nbfloor_lim': [0, 100], - 'nbBasefloor_key': 'EgenAntalKallarplan', 'nbBasefloor_lim': [0, 4], - 'year_key': 'EgenNybyggAr', 'year_lim': [0, 2022], - 'nbAppartments_key': 'EgenAntalBolgh', 'nbAppartments_lim':[0, 100], - 'height_key': ['height', 'SHAPE.LEN','st_lengthshape'], 'height_lim': [0, 100], - 'AreaBasedFlowRate_key': 'EgenProjVentFlode', 'AreaBasedFlowRate_lim': [0.35, 10], - 'nbStairwell_key': 'EgenAntalTrapphus', 'nbStairwell_lim': [0, 100], - } - -#this dict defines the EPC measured key word -EPCMeters = \ - {'Heating': - {'OilHeating_key' : 'EgiOljaUPPV', 'OilHeatingCOP' : 0.85, - 'GasHeating_key' : 'EgiGasUPPV', 'GasHeatingCOP' : 0.9, - 'WoodHeating_key' : 'EgiVedUPPV', 'WoodHeatingCOP' : 0.75, - 'PelletHeating_key' : 'EgiFlisUPPV', 'PelletHeatingCOP' : 0.75, - 'BioFuelHeating_key' : 'EgiOvrBiobransleUPPV', 'BioFuelHeatingCOP' : 0.75, - 'ElecWHeating_key' : 'EgiElVattenUPPV', 'ElecWHeatingCOP' : 1, - 'ElecHeating_key' : 'EgiElDirektUPPV', 'ElecHeatingCOP' : 1, - 'GSHPHeating_key' : 'EgiPumpMarkUPPV', 'GSHPHeatingCOP' : 3, - 'EASHPHeating_key' : 'EgiPumpFranluftUPPV', 'EASHPHeatingCOP' : 2, - 'AASHPHeating_key' : 'EgiPumpLuftLuftUPPV', 'AASHPHeatingCOP' : 2.5, - 'DistrictHeating_key' : 'EgiFjarrvarmeUPPV', 'DistrictHeatingCOP' : 1, - 'ElecAirHeating_key' : 'EgiElLuftUPPV', 'ElecAirHeatingCOP' : 1, - 'AWSHPHeating_key' : 'EgiPumpLuftVattenUPPV', 'AWSHPHeatingCOP' : 3.1, - }, - 'DHW' : - {'OilHeating_key' : 'EgiOljaVV', 'OilHeatingCOP' : 0.85, - 'GasHeating_key' : 'EgiGasVV', 'GasHeatingCOP' : 0.9, - 'WoodHeating_key' : 'EgiVedVV', 'WoodHeatingCOP' : 0.75, - 'PelletHeating_key' : 'EgiFlisVV', 'PelletHeatingCOP' : 0.75, - 'BioFuelHeating_key' : 'EgiOvrBiobransleVV', 'BioFuelHeatingCOP' : 0.75, - 'ElecHeating_key' : 'EgiElVV', 'ElecHeatingCOP' : 1, - 'DistrictHeating_key' : 'EgiFjarrvarmeVV', 'DistrictHeatingCOP' : 1, - - }, - 'Cooling': - {'DistCooling_key': 'EgiFjarrkyla', 'DistCoolingCOP' : 1, - 'ElecCooling_key' : 'EgiKomfort', 'ElecCoolingCOP' : 1, #should be HP no?! - }, - 'ElecLoad': - {'BuildingLev_key' : 'EgiFastighet', 'BuildingLevCOP' : 1, - 'HousholdLev_key' : 'EgiHushall', 'HousholdLevCOP' : 1, - 'OperationLev_key' : 'EgiVerksamhet', 'OperationLevCOP' : 1, - }, - 'NRJandClass': - {'WeatherCorrectedNRJ_key': 'EgiEnergianvandning', 'WeatherCorrectedNRJCOP' : 1, - 'WeatherCorrectedPNRJ_key': 'EgiPrimarenergianvandning', 'WeatherCorrectedPNRJCOP' : 1, - 'EnClasseVersion_key' : 'EgiVersion', 'EnClasseVersionCOP' : 1, - 'NRJ_Class_key' : 'EgiEnergiklass', 'NRJ_ClassCOP' : 1, - } - } \ No newline at end of file diff --git a/BuildObject/DB_Filter4Simulations.py b/BuildObject/Filter4BldProcess.py similarity index 53% rename from BuildObject/DB_Filter4Simulations.py rename to BuildObject/Filter4BldProcess.py index 586cf8d..1ec314d 100644 --- a/BuildObject/DB_Filter4Simulations.py +++ b/BuildObject/Filter4BldProcess.py @@ -1,16 +1,23 @@ # @Author : Xavier Faure # @Email : xavierf@kth.se +import CoreFiles.GeneralFunctions as GrlFct - -def checkBldFilter(building): +def checkBldFilter(building,LogFile = [],DebugMode = False): CaseOk = len(building.BlocHeight) if building.Multipolygon else building.height + msg ='' # if the building have bloc with no Height or if the hiegh is below 1m (shouldn't be as corrected in the Building class now) if len(building.BlocHeight) > 0 and min(building.BlocHeight) < 1: CaseOk = 0 + msg = '[Error] The building has a given height below 1m...\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) # is heated area is below 50m2, we just drop the building if building.EPHeatedArea < 50: CaseOk = 0 + msg = '[Error] The building has a given heated area below 50m2...\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) # is no floor is present...(shouldn't be as corrected in the Building class now) if 0 in building.BlocNbFloor: CaseOk = 0 - return CaseOk \ No newline at end of file + msg = '[Error] The building has a given number of floor equal to 0...\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return CaseOk,msg \ No newline at end of file diff --git a/BuildObject/GeomUtilities.py b/BuildObject/GeomUtilities.py new file mode 100644 index 0000000..f7dabe4 --- /dev/null +++ b/BuildObject/GeomUtilities.py @@ -0,0 +1,324 @@ +# @Author : Xavier Faure +# @Email : xavierf@kth.se + +import itertools +from shapely.geometry.polygon import Polygon, Point, LineString +from geomeppy.geom.polygons import Polygon3D, Polygon2D +from geomeppy.utilities import almostequal +from geomeppy.geom import core_perim + +def is_clockwise(points): + # points is your list (or array) of 2d points. + assert len(points) > 0 + s = 0.0 + for p1, p2 in zip(points, points[1:] + [points[0]]): + s += (p2[0] - p1[0]) * (p2[1] + p1[1]) + return s > 0.0 + +def RotatePolyOrder(poly): + #poly is a list of tuple or list of two coordinates + return poly[1:]+[poly[0]] + +def chekIdenticalpoly(poly1,poly2): + Identical = False + if poly1[-1]==poly1[0]: + poly1 = poly1[:-1] + if poly2[-1] == poly2[0]: + poly2 = poly2[:-1] + tries = 0 + finished = False + if is_clockwise(poly1) != is_clockwise(poly2): + poly2.reverse() + while not finished: + if poly1 == poly2: + Identical = True + finished = True + elif tries == len(poly2): + finished = True + else: + tries += 1 + poly2 = RotatePolyOrder(poly2) + return Identical + +def mergeHole(poly,hole): + #the two polygons needs to be in opposite direction to be merge into one with the hole + if is_clockwise(poly) == is_clockwise(poly): + hole.reverse() + #lets computs the closes distance between each + links = list(itertools.product(poly, hole)) + links = sorted( + links, key=lambda x: getDistance(x[0],x[1]) + ) + #the two first vertexes are considered as starting points and ending point for the first polygon + first_on_poly = [links[0][0]] + last_on_hole = [links[0][1]] + for i,link in enumerate(links): + if link != links[0]: + first_on_poly.append(links[i][0]) + last_on_hole.append(links[i][1]) + break + # #now lets go along the poly and get the mid point of poly list to break the polygons + # for i, link in enumerate(links): + # if link[0] == poly[(poly.index(first_on_poly[0]) + round(len(poly) / 2)) % len(poly)]: + # break + # #both are catched, the two polygons can be created + # first_on_poly.append(links[i][0]) + # last_on_hole.append(links[i][1]) + + section_on_poly = getSection(poly,first_on_poly) + section_on_hole = getSection(hole, last_on_hole) + hole.reverse() + section_on_holerev = getSection(hole, last_on_hole) + final_poly1 = section_on_poly + section_on_hole + final_poly1rev = section_on_poly + section_on_holerev + first_on_poly.reverse() + last_on_hole.reverse() + section_on_poly = getSection(poly, first_on_poly) + section_on_hole = getSection(hole, last_on_hole) + if is_clockwise(section_on_poly) == is_clockwise(section_on_hole): + section_on_hole.reverse() + final_poly2 = section_on_poly + section_on_hole + return [final_poly1,final_poly2] + +def CleanPoly(poly,DistTol): + polycoor = [] + for j in poly: + new = (j[0], j[1]) + new_coor = new # [] + # for ii in range(len(RefCoord)): + # new_coor.append((new[ii] - RefCoord[ii])) + polycoor.append(tuple(new_coor)) + if polycoor[0] == polycoor[-1]: + polycoor = polycoor[:-1] + # even before skewed angle, we need to check for tiny edge below the tolerance onsdered aftward (0.5m) + pt2remove = [] + for edge in Polygon2D(polycoor).edges: + if edge.length < DistTol: + pt2remove.append(edge.p2) + for pt in pt2remove: + if len(polycoor) > 3: + polycoor.remove(pt) + newpolycoor, node = core_perim.CheckFootprintNodes(polycoor, 5) #the returned poly is not used finally investigation are to be done ! + return polycoor, node + +def mergeGeomeppy(poly,hole): + poly = Polygon3D(poly) + hole = Polygon3D(hole) + links = list(itertools.product(poly, hole)) + links = sorted( + links, key=lambda x: x[0].relative_distance(x[1]) + ) # fast distance check + + # first_on_poly = links[0][0] + # if links[1][0] == first_on_poly: + # last_on_poly = links[2][0] + # else: + # last_on_poly = links[1][0] + # + # first_on_hole = links[1][1] + # if links[0][1]== first_on_hole: + # last_on_hole = links[-1][1] + # else: + # last_on_hole = links[0][1] + + first_on_poly = links[0][0] + last_on_hole = links[0][1] + for i, link in enumerate(links): + if link[0] != first_on_poly and link[1] != last_on_hole: + last_on_poly = link[0] + first_on_hole = link[1] + break + + + new_poly = section(first_on_poly, last_on_poly, poly[:] + poly[:]) + section( + first_on_hole, last_on_hole, reversed(hole[:] + hole[:]) + ) + #for a simple case it, didn't work because of the position of the first points, the last was the one after the first withtout taking any other nodes + #the hole was filled... the workaround is to compute both and take the smalest area + new_poly_try = section(first_on_poly, last_on_poly, poly[:] + poly[:]) + section( + first_on_hole, last_on_hole, (hole[:] + hole[:]) + ) + if Polygon3D(new_poly).area > Polygon3D(new_poly_try).area: + new_poly = new_poly_try + + new_poly = Polygon3D(new_poly) + union = hole.union(new_poly)[0] + try: new_poly2 = poly.difference(union)[0] + except: + return [new_poly] + if not almostequal(new_poly.normal_vector, poly.normal_vector): + new_poly = new_poly.invert_orientation() + if not almostequal(new_poly2.normal_vector, poly.normal_vector): + new_poly2 = new_poly2.invert_orientation() + + return [new_poly, new_poly2] + +def section(first, last, coords): + section_on_hole = [] + for item in coords: + if item == first: + section_on_hole.append(item) + elif section_on_hole: + section_on_hole.append(item) + if item == last: + break + return section_on_hole + +def getDistance(pt1,pt2): + return ((((pt2[0] - pt1[0]) ** 2) + ((pt2[1] - pt1[1]) ** 2)) ** 0.5) + +def getSection(poly,mainNode): + section = [] + for idx in range(2*len(poly)): + if poly[idx%len(poly)] == mainNode[0] and not section: + section.append(poly[idx]) + elif section: + section.append(poly[idx%len(poly)]) + if poly[idx%len(poly)] == mainNode[1]: + break + return section + +def CheckMultiBlocFootprint(blocs,tol =1): + validMultibloc = True + if len(blocs)>1: + validMultibloc = False + for bloc1,bloc2 in itertools.product(blocs,repeat = 2): + if bloc1 != bloc2: + for ptidx,pt in enumerate(bloc1): + edge = [bloc1[ptidx],bloc1[(ptidx+1)%len(bloc1)]] + comEdges = [] + for ptidx1,pt1 in enumerate(bloc2): + edge1 = [bloc2[ptidx1], bloc2[(ptidx1+1)%len(bloc2)]] + if is_parallel(edge,edge1,10) and confirmMatch(edge, edge1, tol): + validMultibloc = True + pt1 = False + pt2 = False + if LineString(edge1).distance(Point(edge[0])) < tol: + edge[0] = point_on_line(edge1[0], edge1[1],edge[0]) + edge[0],conf= CoordAdjustement(edge1, edge[0], tol) + pt1 = True + if LineString(edge1).distance(Point(edge[1])) < tol: + edge[1] = point_on_line(edge1[0], edge1[1],edge[1]) + edge[1],conf = CoordAdjustement(edge1, edge[1], tol) + pt2 = True + if pt1 and pt2: + if abs(getAngle(edge1, edge) -180) < 5: + comEdges.append([edge[1],edge[0]]) + else: + comEdges.append(edge) + bloc1[ptidx] = edge[0] + bloc1[(ptidx + 1) % len(bloc1)] = edge[1] + #lets check if these nodes are also on bloc2 + #first which bloc is concerned + for comEdge in comEdges: + if comEdge[0] in bloc2 and comEdge[1] not in bloc2: + index = bloc2.index(comEdge[0])+1 + bloc2.insert(index,comEdge[1]) + #bloc2 = bloc2[:index]+[comEdge[1]]+bloc2[index:] + if comEdge[1] in bloc2 and comEdge[0] not in bloc2: + index = bloc2.index(comEdge[1]) + bloc2.insert(index,comEdge[0]) + #bloc2 = bloc2[:index]+[comEdge[0]]+bloc2[index:] + return blocs,validMultibloc + +def point_on_line(a, b, p): + import numpy as np + a = np.array(a) + b = np.array(b) + p = np.array(p) + ap = p - a + ab = b - a + result = a + np.dot(ap, ab) / np.dot(ab, ab) * ab + return tuple(result) + +def getAngle(line1,line2): + vector_a_x = line1[1][0] - line1[0][0] + vector_a_y = line1[1][1] - line1[0][1] + vector_b_x = line2[1][0] - line2[0][0] + vector_b_y = line2[1][1] - line2[0][1] + import numpy as np + v = np.array([vector_a_x, vector_a_y]) + w = np.array([vector_b_x, vector_b_y]) + return abs(np.rad2deg(np.arccos(round(v.dot(w) / (np.linalg.norm(v) * np.linalg.norm(w)), 4)))) + +def is_parallel(line1, line2, tol = 5): + angledeg = getAngle(line1, line2) + if angledeg 19 to 22. + y_transformed = [] + for i in range(len(y[:, 0])): + #full_range = ParamSample[i, :].max() - ParamSample[i, :].min() + full_range = BoundLim[i][1]-BoundLim[i][0] + y_transformed.append(np.interp(y[i], (y[i].min(), y[i].max()), ( + max(BoundLim[i][0], ParamSample[i, :].min() - 0.1 * full_range), + min(BoundLim[i][1], ParamSample[i, :].max() + 0.1 * full_range)))) + + Param2keep = np.array(y_transformed) + return Param2keep.transpose() + +def getBootStrapedParam(Data, VarName2Change, nbruns, BoundLim): + ##################NO MORE USED########################## + import openturns as ot + ParamSample = [] + NormalizedParam = [] + for key in VarName2Change: + ParamSample.append(Data[key]) + NormalizedParam.append((Data[key]-Data[key].min())/(Data[key].max()-Data[key].min())) + ParamSample = np.array(ParamSample).transpose() + NormalizedParam = np.array(NormalizedParam).transpose() + BottObject = ot.BootstrapExperiment(NormalizedParam) + NewSampleAsArray = [] + finished = False + while not finished: + NewSample = BottObject.generate() + try: + if not NewSampleAsArray: + NewSampleAsArray = np.array(list(NewSample)) + except: + NewSampleAsArray = np.append(NewSampleAsArray,np.array(list(NewSample)),axis = 0) + if NewSampleAsArray.shape[0]>nbruns: + finished = True + y = np.array([NewSampleAsArray[i,:] for i in np.random.randint(0, NewSampleAsArray.shape[0], nbruns)]) + y_transformed = [] + for i in range(y.shape[1]): + #full_range = ParamSample[i, :].max() - ParamSample[i, :].min() + full_range = BoundLim[i][1]-BoundLim[i][0] + y_transformed.append(np.interp(y[:,i], (y[:,i].min(), y[:,i].max()), ( + max(BoundLim[i][0], ParamSample[:, i].min() - 0.1 * full_range), + min(BoundLim[i][1], ParamSample[:, i].max() + 0.1 * full_range)))) + + Param2keep = np.array(y_transformed) + + return Param2keep.transpose() + + +def getNewBounds(Bounds,BoundLim): + newBounds = [] + for idx, bd in enumerate(Bounds): + newBounds.append( + [max(bd[0] - 0.1 * (bd[1] - bd[0]),BoundLim[idx][0]), min(BoundLim[idx][1], bd[1] + 0.1 * (bd[1] - bd[0]))]) + return newBounds + +def getTheWinners(VarName2Change,Matches20, Matches10, Matches5): + if len(Matches5[VarName2Change[0]]) > 20: + Matches = Matches5 + elif len(Matches10[VarName2Change[0]]) > 20: + Matches = Matches10 + else: + Matches = Matches20 + return Matches, len(Matches[VarName2Change[0]]) + +def getTheWeightedWinners(VarName2Change,Matches20, Matches10, Matches5): + Matches = {} + #the number of winners taken for defning the sample size is kept as for the non weighted function + if len(Matches5[VarName2Change[0]]) > 20: + nbwinners = len(Matches5[VarName2Change[0]]) + for key in VarName2Change: + Matches[key] = np.array(Matches5[key]) + elif len(Matches10[VarName2Change[0]]) > 20: + for key in VarName2Change: + Matches[key] = np.array(Matches10[key]) + for weigth in range(2): + Matches[key] = np.append(Matches[key], Matches5[key]) + nbwinners = max(10,len(Matches10[VarName2Change[0]])/2) + else: + nbwinners = 10 #len(Matches20[CalibBasis][VarName2Change[0]]) this way, there will be half of the next sample + for key in VarName2Change: + Matches[key] = np.array(Matches20[key]) + # a weight of 3 is applied to 10% matches (the good ones are also in 20% so there is the need to add only 2) + for weigth in range(2): + Matches[key] = np.append(Matches[key], Matches10[key]) + # a weight of 5 is applied to 5% matches (the good ones are also in 20% and 10% so there is the need to add only 3) + for weigth in range(3): + Matches[key] = np.append(Matches[key], Matches5[key]) + return Matches, int(nbwinners) + +def CompareSample(Finished,idx_offset, SimDir,CurrentPath,nbBuild,VarName2Change,CalibBasis,MeasPath,ParamSample, + Bounds,BoundLim,ParamMethods,NbRun): + # once every run has been computed, lets get the matche and compute the covariance depending on the number of matches + extraVar = ['nbAppartments', 'ATempOr', 'SharedBld', 'height', 'StoreyHeigth', 'nbfloor','BlocHeight','BlocFootprintArea','BlocNbFloor', + 'HeatedArea', 'AreaBasedFlowRate','NonHeatedArea', 'Other'] + Res = Utilities.GetData(os.path.join(SimDir, 'Sim_Results'), extraVar) + os.chdir(CurrentPath) + ComputfFilePath = os.path.normcase(MeasPath) + #'C:\\Users\\xav77\\Documents\\FAURE\\prgm_python\\UrbanT\\Eplus4Mubes\\MUBES_SimResults\\ComputedElem4Calibration') + with open(os.path.join(ComputfFilePath, 'Building_' + str(nbBuild) + '_Meas.pickle'), + 'rb') as handle: + Meas = pickle.load(handle) + + Error = getErrorMatches(Res, Meas, CalibBasis) + Matches20 = getGoodParamList(Error,CalibBasis, VarName2Change, ParamSample, REMax=20, CVRMSMax = 30) + Matches10 = getGoodParamList(Error,CalibBasis, VarName2Change, ParamSample, REMax=10, CVRMSMax = 20) + Matches5 = getGoodParamList(Error, CalibBasis, VarName2Change, ParamSample, REMax=5, CVRMSMax=15) + print('Nb of matches at 20% is : ' + str(len(Matches20[VarName2Change[0]]))) + print('Nb of matches at 10% is : ' + str(len(Matches10[VarName2Change[0]]))) + print('Nb of matches at 5% is : ' + str(len(Matches5[VarName2Change[0]]))) + #Matches, NbWinners = getTheWinners(VarName2Change,Matches20, Matches10, Matches5) + Matches, NbWinners = getTheWeightedWinners(VarName2Change, Matches20, Matches10, Matches5) + + try: + if len(ParamSample[:, 0]) >= 2000 or len(Matches5[VarName2Change[0]]) > 100: + Finished = True + elif len(ParamSample[:, 0]) >= 1000 and len(Matches5[VarName2Change[0]]) < 5: + Finished = True + else: + print('New runs loop') + if len(Matches[VarName2Change[0]]) > 10: + try: + NBskewedRuns = min(NbRun,NbWinners+90) + print('Nd of skewed runs : '+str(NBskewedRuns)) + NbNewRuns = NbRun-NBskewedRuns + #NewSample1 = getBootStrapedParam(Matches, VarName2Change, NBskewedRuns, BoundLim) + #NewSample1 = getOpenTurnsCorrelated(Matches, VarName2Change, NBskewedRuns, BoundLim) + NewSample1 = getOpenTurnsCorrelatedFromSample(Matches, VarName2Change, NBskewedRuns, BoundLim) + #NewSample1 = getCovarCalibratedParam(Matches, VarName2Change, NBskewedRuns, BoundLim) + + if NbNewRuns > 0: + #lets make new bounds for non correlated sample, being in the same range as for correlated ones + openRange = 0 + if len(Matches5[VarName2Change[0]]) < 10: + openRange = 1 + ModifiedBounds = [] + for i in range(len(NewSample1[0,:])): + fullRange = (BoundLim[i][1]-BoundLim[i][0])*openRange + ModifiedBounds.append([max(BoundLim[i][0], NewSample1[:, i].min() - 0.1*fullRange), + min(BoundLim[i][1], NewSample1[:, i].max() + 0.1*fullRange)]) + NewSample2 = GrlFct.getParamSample(VarName2Change, ModifiedBounds, NbNewRuns,ParamMethods) + NewSample = np.append(NewSample1, NewSample2, axis=0) + else: + NewSample = NewSample1 + print('Covariance worked !') + except: + print('Covariance did not work...') + Bounds = getNewBounds(Bounds, BoundLim) + NewSample = GrlFct.getParamSample(VarName2Change, Bounds, NbRun,ParamMethods) + else: + Bounds = getNewBounds(Bounds, BoundLim) + NewSample = GrlFct.getParamSample(VarName2Change, Bounds, NbRun,ParamMethods) + idx_offset = len(ParamSample[:, 0]) + ParamSample = np.concatenate((ParamSample, NewSample)) + Paramfile = os.path.join(SimDir, 'ParamSample.pickle') + with open(Paramfile, 'wb') as handle: + pickle.dump(ParamSample, handle, protocol=pickle.HIGHEST_PROTOCOL) + except: + print('No matches at all from now...') + if len(ParamSample[:, 0]) >= 2000: + Finished = True + else: + Bounds = getNewBounds(Bounds, BoundLim) + NewSample = GrlFct.getParamSample(VarName2Change, Bounds, NbRun,ParamMethods) + idx_offset = len(ParamSample[:, 0]) + ParamSample = np.concatenate((ParamSample, NewSample)) + Paramfile = os.path.join(SimDir, 'ParamSample.pickle') + with open(Paramfile, 'wb') as handle: + pickle.dump(ParamSample, handle, protocol=pickle.HIGHEST_PROTOCOL) + return Finished,idx_offset,ParamSample + +if __name__ == '__main__' : + print('CalibUtilities.py') \ No newline at end of file diff --git a/CoreFiles/CaseBuilder_OAT.py b/CoreFiles/CaseBuilder_OAT.py index f74ac76..8ec4f2f 100644 --- a/CoreFiles/CaseBuilder_OAT.py +++ b/CoreFiles/CaseBuilder_OAT.py @@ -1,8 +1,7 @@ # @Author : Xavier Faure # @Email : xavierf@kth.se -import os -import sys +import os, sys, platform #add the required path path2addgeom = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())),'geomeppy') sys.path.append(path2addgeom) @@ -11,27 +10,31 @@ from subprocess import check_call from geomeppy import IDF import pickle#5 as pickle -import pickle5 +#import pickle5 import CoreFiles.GeneralFunctions as GrlFct -from BuildObject.DB_Building import BuildingList -from BuildObject.DB_Filter4Simulations import checkBldFilter -import BuildObject.DB_Data as DB_Data +from BuildObject.BuildingObject import BuildingList +from BuildObject.Filter4BldProcess import checkBldFilter +#import BuildObject.DB_Data as DB_Data import re import time -def LaunchOAT(MainInputs,SimDir,nbBuild,ParamVal,currentRun,pythonpath=[]): +def LaunchOAT(MainInputs,SimDir,keypath,nbBuild,ParamVal,currentRun,pythonpath=[],BldObj=[], + MakePlotOnly = False): #this function was made to enable either to launch a process in a seperate terminal or not, given a python path to a virtualenv #but if kept in seperate terminal, the inputfile needs to be read for each simulation...not really efficient, #thus, the first option, being fully in the same envirnment is used with the optionnal argument 'DataBaseInput' if not pythonpath: - LaunchProcess(SimDir, MainInputs['FirstRun'], MainInputs['TotNbRun'], currentRun, - MainInputs['PathInputFiles'], nbBuild, MainInputs['CorePerim'], - MainInputs['FloorZoning'], ParamVal,MainInputs['VarName2Change'],MainInputs['CreateFMU'], - MainInputs['OutputsFile'],DataBaseInput = MainInputs['DataBaseInput']) + return LaunchProcess(SimDir, MainInputs['FirstRun'], MainInputs['NbRuns'], currentRun,keypath, nbBuild, + MainInputs['CorePerim'], MainInputs['FloorZoning'], ParamVal,MainInputs['VarName2Change'], + MainInputs['CreateFMU'],MainInputs['OutputsFile'],DataBaseInput = MainInputs['DataBaseInput'], + DebugMode = MainInputs['DebugMode'],MakePlotOnly = MakePlotOnly,Verbose = MainInputs['Verbose']) # if willing to launch each run in seperate terminals, all arguments must be given in text form and the pythonpath is required else: - virtualenvline = os.path.join(pythonpath,'python.exe') + if platform.system() == "Windows": + virtualenvline = os.path.join(pythonpath, "python.exe") + else: + virtualenvline = os.path.join(pythonpath, "python") scriptpath =os.path.join(os.path.dirname(os.getcwd()),'CoreFiles') cmdline = [virtualenvline, os.path.join(scriptpath, 'CaseBuilder_OAT.py')] for key in MainInputs.keys(): @@ -41,6 +44,13 @@ def LaunchOAT(MainInputs,SimDir,nbBuild,ParamVal,currentRun,pythonpath=[]): else: cmdline.append(str(MainInputs[key])) + for key in keypath.keys(): + cmdline.append('-'+key) + if type(keypath[key]) == str: + cmdline.append(keypath[key]) + else: + cmdline.append(str(keypath[key])) + cmdline.append('-SimDir') cmdline.append(str(SimDir)) cmdline.append('-nbBuild') @@ -51,26 +61,24 @@ def LaunchOAT(MainInputs,SimDir,nbBuild,ParamVal,currentRun,pythonpath=[]): cmdline.append(str(currentRun)) check_call(cmdline,stdout=open(os.devnull, "w")) -def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,CorePerim,FloorZoning,ParamVal,VarName2Change, - CreateFMU,OutputsFile,DataBaseInput = []): +def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,keyPath,nbcase,CorePerim,FloorZoning,ParamVal,VarName2Change, + CreateFMU,OutputsFile,DataBaseInput = [], DebugMode = False,MakePlotOnly = False,Verbose = False): #This function builds the idf file, a log file is generated if the buildiung is run for the first time, #the idf file will be saved as well as the building object as a pickle. the latter could be commented as not required - MainPath = os.getcwd() - keyPath = GrlFct.readPathfile(PathInputFiles) if not DataBaseInput: - # Building and Shading objects from reading the geojson file as input for further functionsif not given as arguments + # Buildingobjects from reading the geojson file as input for further functions if not given as arguments DataBaseInput = GrlFct.ReadGeoJsonFile(keyPath) Buildingsfile = DataBaseInput['Build'] - Shadingsfile = DataBaseInput['Shades'] epluspath = keyPath['epluspath'] os.chdir(SimDir) + start = time.time() #Creating the log file for this building if it's his frist run if FirstRun: LogFile = open(os.path.join(SimDir, 'Build_'+str(nbcase)+'_Logs.log'), 'w') msg = 'Building ' + str(nbcase) + ' is starting\n' - print(msg[:-1]) + if Verbose: print(msg[:-1]) GrlFct.Write2LogFile(msg,LogFile) else: LogFile = False @@ -80,24 +88,37 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core if FirstRun: StudiedCase = BuildingList() #lets build the two main object we'll be playing with in the following : the idf and the building - idf, building = GrlFct.appendBuildCase(StudiedCase, epluspath, nbcase, DataBaseInput, MainPath,LogFile) - #Rounds of check if we continue with this building or not, see DB_Filter4Simulation.py if other filter are to add - CaseOK = checkBldFilter(building) - if not CaseOK: - msg = '[Error] This Building/bloc is not valid to continue, please check DB_Filter4Simulation.py to see what is of concerned\n' + try: + if DebugMode: startIniti = time.time() + idf, building = GrlFct.appendBuildCase(StudiedCase, keyPath, nbcase, DataBaseInput, MainPath,LogFile, + DebugMode = DebugMode,PlotOnly=MakePlotOnly) + except: + msg = '[Error] The Building Object Initialisation has failed...\n' print(msg[:-1]) os.chdir(MainPath) if FirstRun: GrlFct.Write2LogFile(msg, LogFile) GrlFct.Write2LogFile('##############################################################\n', LogFile) - return + return [], [], 'NOK' + + #Rounds of check if we continue with this building or not, see DB_Filter4Simulation.py if other filter are to add + CaseOK,msg = checkBldFilter(building,LogFile,DebugMode = DebugMode) + if not CaseOK: + print(msg[:-1]) + os.chdir(MainPath) + if FirstRun: + GrlFct.Write2LogFile('##############################################################\n', LogFile) + return building,idf, 'NOK' + if DebugMode: GrlFct.Write2LogFile('[Time report] Building Initialisation phase : '+ + str(round(time.time()-startIniti,2))+' sec\n', LogFile) # The simulation parameters are assigned here - GrlFct.setSimLevel(idf, building) + if not MakePlotOnly: + GrlFct.setSimLevel(idf, building) # The geometry is assigned here try: - # start = time.time() - GrlFct.setBuildingLevel(idf, building,LogFile,CorePerim,FloorZoning) + if DebugMode: startIniti = time.time() + GrlFct.setBuildingLevel(idf, building,LogFile,CorePerim,FloorZoning,DebugMode = DebugMode,ForPlots=MakePlotOnly) # end = time.time() # print('[Time Report] : The setBuildingLevel took : ',round(end-start,2),' sec') except: @@ -107,9 +128,11 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core if FirstRun: GrlFct.Write2LogFile(msg, LogFile) GrlFct.Write2LogFile('##############################################################\n', LogFile) - return + return building,idf, 'NOK' + if DebugMode: GrlFct.Write2LogFile('[Time report] Building level (geometry) phase : ' + + str(round(time.time() - startIniti, 2)) + ' sec\n', LogFile) # if the number of run for one building is greater than 1 it means parametric simulation, a template file will be saved - if TotNbRun>1: + if TotNbRun>1 and not MakePlotOnly: Case = {} Case['BuildData'] = building idf.saveas('Building_' + str(nbcase) + '_template.idf') @@ -133,8 +156,9 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core # assignement of the building name for the simulation building.name = 'Building_' + str(nbcase) + 'v'+str(currentRun) - #in order to make parametric simulation, lets go along the VarName2Change list and change the building object attributes accordingly - GrlFct.setChangedParam(building,ParamVal,VarName2Change,MainPath,Buildingsfile,Shadingsfile,nbcase,DB_Data) + if DebugMode: startIniti = time.time() + if not MakePlotOnly: #in order to make parametric simulation, lets go along the VarName2Change list and change the building object attributes accordingly + GrlFct.setChangedParam(building, ParamVal, VarName2Change, MainPath, Buildingsfile, nbcase) # lets assign the material and finalize the envelope definition try: @@ -149,11 +173,16 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core if FirstRun: GrlFct.Write2LogFile(msg, LogFile) GrlFct.Write2LogFile('##############################################################\n', LogFile) - return - #uncomment only to have a look at the splitting surfaces function effect. it will make a figure for each building created - #idf.view_model(test=True, FigCenter=(0,0)) + return building,idf, 'NOK' + #the following is only to make building plots, so no need to go feeding the indoor building inputs + if DebugMode: GrlFct.Write2LogFile('[Time report] Building level (Envelope) phase : ' + + str(round(time.time() - startIniti, 2)) + ' sec\n', LogFile) + + if MakePlotOnly: + return building,idf, 'OK' # lets define the zone level now + if DebugMode: startIniti = time.time() try: # start = time.time() GrlFct.setZoneLevel(idf, building,FloorZoning) @@ -168,11 +197,23 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core GrlFct.Write2LogFile('##############################################################\n', LogFile) return + if DebugMode: GrlFct.Write2LogFile('[Time report] Zone level phase : ' + + str(round(time.time() - startIniti, 2)) + ' sec\n', LogFile) + try: # add some extra energy loads like domestic Hot water # start = time.time() GrlFct.setExtraEnergyLoad(idf,building) - + except: + msg = '[Error] The setExtraEnergyLoad definition failed...\n' + print(msg[:-1]) + os.chdir(MainPath) + if FirstRun: + GrlFct.Write2LogFile(msg, LogFile) + GrlFct.Write2LogFile('##############################################################\n', LogFile) + return + if DebugMode: startIniti = time.time() + try: #lets add the main gloval variable : Mean temperautre over the heated areas and the total building power consumption #and if present, the heating needs for DHW production as heated by direct heating #these are added using EMS option of EnergyPlus, and used for the FMU option @@ -190,32 +231,43 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core # end = time.time() # print('[Time Report] : The setOutputLevel took : ', round(end - start, 2), ' sec') #special ending process if FMU is wanted - if CreateFMU: - GrlFct.CreatFMU(idf,building,nbcase,epluspath,SimDir, currentRun,EMSOutputs,LogFile) - else: - # saving files and objects - idf.saveas('Building_' + str(nbcase) + 'v'+str(currentRun)+'.idf') - - #the data object is saved as needed afterward aside the Eplus results (might be not needed, to be checked) - with open('Building_' + str(nbcase) + 'v'+str(currentRun)+ '.pickle', 'wb') as handle: - pickle.dump(Case, handle, protocol=pickle.HIGHEST_PROTOCOL) - - msg = 'Building_' + str(nbcase)+' IDF file ' + str(currentRun+1)+ '/' + str(TotNbRun)+ ' is done\n' - print(msg[:-1]) - GrlFct.Write2LogFile('##############################################################\n', LogFile) - if FirstRun: - LogFile.close() - # lets get back to the Main Folder we were at the very beginning - os.chdir(MainPath) except: - msg = '[Error] The process after the Zonelevel definition failed...\n' + msg = '[Error] The Output definition failed...\n' print(msg[:-1]) os.chdir(MainPath) if FirstRun: GrlFct.Write2LogFile(msg, LogFile) GrlFct.Write2LogFile('##############################################################\n', LogFile) return + if DebugMode: GrlFct.Write2LogFile('[Time report] Output level phase : ' + + str(round(time.time() - startIniti, 2)) + ' sec\n', LogFile) + if CreateFMU: + if DebugMode: startIniti = time.time() + GrlFct.CreatFMU(idf,building,nbcase,epluspath,SimDir, currentRun,EMSOutputs,LogFile,DebugMode) + if DebugMode: GrlFct.Write2LogFile('[Time report] Creating FMU phase : ' + + str(round(time.time() - startIniti, 2)) + ' sec\n', LogFile) + if DebugMode: startIniti = time.time() + else: + # saving files and objects + if DebugMode: startIniti = time.time() + idf.saveas('Building_' + str(nbcase) + 'v'+str(currentRun)+'.idf') + + #the data object is saved as needed afterward aside the Eplus results (might be not needed, to be checked) + with open('Building_' + str(nbcase) + 'v'+str(currentRun)+ '.pickle', 'wb') as handle: + pickle.dump(Case, handle, protocol=pickle.HIGHEST_PROTOCOL) + if DebugMode: GrlFct.Write2LogFile('[Time report] Writing file and data phase : ' + + str(round(time.time() - startIniti, 2)) + ' sec\n', LogFile) + msg = 'Building_' + str(nbcase)+' IDF file ' + str(currentRun+1)+ '/' + str(TotNbRun)+ ' is done\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + if Verbose: print(msg[:-1]) + end = time.time() + GrlFct.Write2LogFile('[Reported Time] Input File : '+str(round(end-start,2))+' seconds\n',LogFile) + GrlFct.Write2LogFile('##############################################################\n', LogFile) + if FirstRun: + LogFile.close() + # lets get back to the Main Folder we were at the very beginning + os.chdir(MainPath) if __name__ == '__main__' : @@ -242,9 +294,15 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core if (currArg.startswith('-SimDir')): currIdx += 1 SimDir = sys.argv[currIdx] - elif (currArg.startswith('-PathInputFiles')): + elif (currArg.startswith('-epluspath')): currIdx += 1 PathInputFiles = sys.argv[currIdx] + elif (currArg.startswith('-BuildingsFile')): + currIdx += 1 + BuildingsFile = sys.argv[currIdx] + elif (currArg.startswith('-ShadingsFile')): + currIdx += 1 + ShadingsFile = sys.argv[currIdx] elif (currArg.startswith('-nbBuild')): currIdx += 1 nbcase = int(sys.argv[currIdx]) @@ -266,7 +324,7 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core elif (currArg.startswith('-CreateFMU')): currIdx += 1 CreateFMU = eval(sys.argv[currIdx]) - elif (currArg.startswith('-TotNbRun')): + elif (currArg.startswith('-NbRuns')): currIdx += 1 TotNbRun = int(sys.argv[currIdx]) elif (currArg.startswith('-OutputsFile')): @@ -284,5 +342,6 @@ def LaunchProcess(SimDir,FirstRun,TotNbRun,currentRun,PathInputFiles,nbcase,Core ParamVal = [float(val) for val in ParamVal] VarName2Change = re.split(r'\W+', VarName2Change)[1:-1] - LaunchProcess(SimDir, FirstRun, TotNbRun, currentRun,PathInputFiles, nbcase, CorePerim, FloorZoning, ParamVal,VarName2Change, + keypath = {'epluspath':epluspath, 'BuildingsFile':BuildingsFile, 'ShadingsFile':ShadingsFile} + LaunchProcess(SimDir, FirstRun, TotNbRun, currentRun,keypath, nbcase, CorePerim, FloorZoning, ParamVal,VarName2Change, CreateFMU, OutputsFile) \ No newline at end of file diff --git a/CoreFiles/DefaultConfig.yml b/CoreFiles/DefaultConfig.yml new file mode 100644 index 0000000..267bc84 --- /dev/null +++ b/CoreFiles/DefaultConfig.yml @@ -0,0 +1,319 @@ +#This files provides the default settings for MUBES +0_APP : + #PATH_TO_MUBES_UBEM : ../MUBES_UBEM/ #[Str] this is not currently used + #PATH_TO_MUBES_UBEM_PYTHON : ../venv/bin/python #[Str] this is not currently used + PATH_TO_ENERGYPLUS : C:/EnergyPlusV9-1-0 #[Str] this is for energyplus.exe path + PATH_TO_RESULTS : ../../MUBES_SimResults/ #[Str] this is the main folder where the results will be stored. it will be created if not exists + +1_DATA : + PATH_TO_DATA : '../ModelerFolder/Minneberg_Buildings_v1.geojson' #[Str] Input file or folder in case of several files + +2_CASE : + 0_GrlChoices : + CaseName : 'ForTest' #[Str] Name of the studied Case, a subfolder of this name will be created in the main folder defined in '0_APP:PATH_TO_RESULTS' + OutputsFile : 'Outputs_Template.txt' #[Str] Output file to identify the outputs the modeller would want and their frequency + RefreshFolder : True #[Bool] True : the folder CaseName, if exist, is emptied whatever could be in it + MakePlotsOnly : False #[Bool] True : a figure will be created with the buildings specified in BldID + MakePlotsPerBld : False #[Bool] True : a figure is created for each building including it's shadowing surfaces (if present) + Verbose : True #[Bool] True : Gives messages in the prompt window along the ongoing process + CPUusage : 0.8 #[Float] Factor of CPU allowed to be used during parallel computing + DebugMode : False #[Bool] The log file will be feeded by detailed information along the process + 1_SimChoices : + ZoneOfInterest : '' #[Str] BldID can be given from an external file if generated by a third program. + BldID : [] #[List of Str] list of Building's ID if defined as attribute in the geojson file, index of the building in the geojson file otherwise + DESO : [] #[List of Str] not currently used + VarName2Change : [] #[List of Str] parameters to be changed for parametric simulations + Bounds : [] #[List of list of 2 floats] bounds between the parameter are to be changed [Lower Bound,Upper Bounds] a pair of value is to be given for each parameter in VarName2Change + BoundsLim : [] #[List of list of 2 floats] used for calibration : external strict bounds + ParamMethods : [] #[List of Str] probabilistic law from which LHS will be generated (one for each parameter in VarName2Change. Can be 'Uniform', 'Normal', 'Triangular'. if 'Linear' : constant increase is ensure along the bounds + NbRuns : 1 #[Int] sample size for parametric simulation + CorePerim : False #[Bool] True : core and perimeter zone thermal modelling + FloorZoning : True #[Bool] True : each floor is considered as a zone or for core zone construction + 2_AdvancedChoices : + API : False #[Bool] in case the simulation is launched trough an API. Option under development + CreateFMU : False #[Bool] True: creates FMU for each building specified in the BldID. No simulation is launched afterward. FMU will be found in the CaseName folder + Calibration : False #[Bool] if calibration process is desired to be launched (several other inputs are required + CalibTimeBasis : '' #[Str] can currently be either 'MonthlyBasis','WeeklyBasis','YearlyBasis'. + MeasurePath4Calibration : '' #[Str] path to the measured data. File names currently need to quite specific. Under development for full deployment + FromPosteriors : False #[Bool] True : Outputs form already made calibration phase will be created. Under development for full deployment + PosteriorsDataPath : '' #[Str] path to the pickle file to be used to catch the posteriors + ECMParam : '' #[Str] Energy Conservation Measure. Currently done from available posteriors + ECMChange : 1 #[Float] factor of enhancement of the default value (the default value could also be changed (useful for ECM applied to distributions) + PassBldObject : False #[Bool] key word for reading only once the geojson file. but no launch through consol then for each bld +3_SIM : + #files are needed to be located in the eather folder of EnergyPlus asthe same path is used afterward to launch the simulation + 1_WeatherData : + WeatherDataFile : 'WeatherData/USA_CA_San.Francisco.Intl.AP.724940_TMY3.epw' #[Str] Need to be a epw file. This one is given because installed by default when installing EnergyPlus + Latitude: 37.62 #[Float] Latitude of the location (+ is North, - is South) [-90,+90] + Longitude: -122.40 #[Float] Longitude of the location (- is West, + is East [-180,180] + Time_Zone: -8.0 #[Float] Time relative to GMT [-12,14] + Elevation: 2.0 #[Float] Elevation + YearRoundGroundTemp : 15 #Float] Year round ground temperature. considered constant afterward + 2_SimuData : + Begin_Day_of_Month : 1 #[Int] + Begin_Month : 1 #[Int] + End_Day_of_Month : 31 #[Int] + End_Month : 12 #[Int] + SaveLogFiles : False #[Bool] True: computing folder is not removed thus all energyplus outputs files are preserved + + #this dict gives information on occupancy times each day. If DCV = True, the airflow will follow the number of person + # and the schedule. if not it will be based only on the extra airflow rate but without schedule (all the time) + #if some separation (ventilation and people) is needed than people heat generation should be converted inteo Electric Load as thus ariflow can be + # related to a schedule, other wise...impossible + 3_BasisElement : + Office_Open : '08:00' #[Str] + Office_Close : '18:00' #[Str] + DemandControlledVentilation : True #[Bool] + OccupBasedFlowRate : 7 #[Float] l/s/person + OccupHeatRate : 70 #[Float] W per person + EnvLeak : 1 #[Float] l/s/m2 at 50Pa + BasementAirLeak : 1 #[Float] in Air change rate [vol/hour] + wwr : 0.3 #[Float] + ExternalInsulation : False #[Bool] + ElecYearlyLoad : 15 #[Float] this is the W\m2 value that will be applied constantly for appliances and occupancy consumptipon impact. It is replace by the values in EPCs if available + IntLoadType : 'winter' #[Str] change either by 'Cste', 'winter', or 'summer' for reversed sigmoid or sigmoid this will generate hourly values file in the InputFiles folder + IntLoadMultiplier : 1 #[Float] this is a multiplier the modeler would like to play with for calibration + IntLoadCurveShape : 3 #[Float] this defines the slop of the curves + OffOccRandom : False #[Bool] + AreaBasedFlowRate : 0.35 #[Float] l/s/m2 + AreaBasedFlowRateDefault : 0.35 #[Float] l/s/m2 This will not be changed by EPCs and is needed if EPCs report only the balcned ventilation flow with HR for building that have 2 system and one wihtout recovery. + setTempUpL : [50,50] #[List of 2 Floats] only one have to be defined for none temperature modulation + setTempLoL : [21,21] #[List of 2 Floats]#only one have to be defined for none temperature modulation + ComfortTempOff : '23:00' #[Str] hours at wich the first temperature set point is considered + ComfortTempOn : '06:00' #[Str] hours at wich the second temperature set point is considered + ACH_freecool : 4 #[Float] this the the vol/hr of extra ventilation when free cooling is on + intT_freecool : 26 #[Float] internal temperature threshold for free coolong (opening windows with fixed ACH) + dT_freeCool : 1 #[Float] Tint-Text to authorize free cooling to be turned on + AirRecovEff : 0.65 #[Float] efficiency if heat is recovered from ventilation + HVACLimitMode : 'NoLimit' #[Str] 'LimitCapacity', #can be NoLimit or LimitFlowRate or LimitFlowRateAndCpacity + HVACPowLimit : 25 #[Float] in Watt/m2 + + #Thisdict gives all the materials characteristics. + # There are 2 layer maximum, the word Inertia and Insulation or key factor further in the code. If one layer is wanted, just comment the other one. + #the basement is considered not heated and thus never insulated layer + BaseMaterial : + Window : + UFactor : 1.9 #[Float] + Solar_Heat_Gain_Coefficient : 0.7 #[Float] + Visible_Transmittance : 0.8 #[Float] + Wall Inertia : + Thickness : 0.2 #[Float] #this layer will be considered also for the basement walls + Conductivity : 0.9 #[Float] + Roughness : "Rough" #[Str] + Density : 2300 #[Float] + Specific_Heat : 1000 #[Float] + Wall Insulation : + Thickness : 0.2 #[Float] + Conductivity : 0.03 #[Float] + Roughness : "Rough" #[Str] + Density : 150 #[Float] + Specific_Heat : 1000 #[Float] + #'Thermal_Absorptance' : 0.001 + Basement Floor : + Thickness : 0.1 #[Float] #this layer will be considered also for the basement floor + Conductivity : 0.9 #[Float] + Roughness : "Rough" #[Str] + Density : 2300 #[Float] + Specific_Heat : 1000 #[Float] + # Basement Floor Insulation : + # Thickness : 0.05 #not needed as even without basement the Heated1rstFloor is taken for the first floor + # Conductivity : 0.25*0.1 + # Roughness : "Rough" + # Density : 1000 + # Specific_Heat : 1000 + # Roof Inertia : + # Thickness : 0.05 #not needed unless one wants to have inertia in the roof layer + # Conductivity : 0.15*0.1 + # Roughness : "Rough" + # Density : 1000 + # Specific_Heat : 1000 + Roof Insulation : + Thickness : 0.3 #[Float] + Conductivity : 0.03 #[Float] + Roughness : "Rough" #[Str] + Density : 150 #[Float] + Specific_Heat : 1000 #[Float] + #Thermal_Absorptance : 0.001 + Heated1rstFloor Inertia : + Thickness : 0.1 #[Float] + Conductivity : 0.9 #[Float] + Roughness : "Rough" #[Str] + Density : 2300 #[Float] + Specific_Heat : 1000 #[Float] + Heated1rstFloor Insulation : + Thickness : 0.15 #[Float] + Conductivity : 0.035 #[Float] + Roughness : "Rough" #[Str] + Density : 150 #[Float] + Specific_Heat : 1000 #[Float] + #Thermal_Absorptance : 0.001 + + #this dict is for specification of internalMass equivalence. + #the material should represent the overall mean material of all partition and furnitures + #the weight par zone area gives the quentity and the Average thickness enable to compute the surface for heat transfer + #the mass gives a volume thanks to the density that gives a surface thanks to the average thickness + InternalMass : + HeatedZoneIntMass : + Thickness : 0.1 #[Float] #m this will define the surface in contact with the zone + Conductivity : 0.3 #[Float] + Roughness : "Rough" #[Str] + Density : 600 #[Float] + Specific_Heat : 1400 #[Float] + WeightperZoneArea : 40 #[Float] #kg/m2 + NonHeatedZoneIntMass : + Thickness : 0.1 #[Float] #m this will define the surface in contact with the zone + Conductivity : 0.3 #[Float] + Roughness : "Rough" #[Str] + Density : 600 #[Float] + Specific_Heat : 1400 #[Float] + WeightperZoneArea : 40 #[Float] #kg/m2 + + #this dict give element for the Domestic Hot water. it gives the externail file for the water taps and the inlet cold water temp. + #if empty it is no longer taken into account. if new file are given, thses should be present in the Externael file folder + ExtraEnergy : + Present : True #[Bool] True: will consider the following information. Currently only used for DHW + Name : 'DHW' #[Str] + WatertapsFile : 'ExternalFiles/mDHW_Sum_over_40.txt' #[Str] path to external file #this file is in l/mnin and will be converted into m3/s afertward. it needs to have hourly values + ColdWaterTempFile : 'ExternalFiles/ColdWaterTemp.txt' #[Str] path to external file + HotWaterSetTemp : 55 #[Float] + TargetWaterTapTemp : 37 #[Float] + WaterTapsMultiplier : 4.167e-7 #[Float] eval(1e-3/60/40) is ensured in the scriptsso operation van be given as well this is because the file given above is for 40 apartment and is in l/min where we need m3/s. in the code in is afterward multiplied by the number of apartement in the building + + #this dict is for the shading paradigm. There are two files that we need. the firt one is the main geojson that contains all buildings and their propreties + #the other one contains for each shading surface id the vertex point and the building Id in order to catch the height of it. + #to externalize as much as possible, these elements are reported in the dict below + GeomElement : + BuildIDKey : ['50A_UUID', 'objectid','OBJECTID','gid'] #[List of Str] key word that can be used for the building ID + FormularIdKey : 'FormularId' #[Str] key word for the the EPC correspondance + ShadingIdKey : 'vaggid' #[Str] key word for the shadowing wall given in the geojson file only + BuildingIdKey : 'byggnadsid' #[Str] key word for the owner of the shadowing wall given in the geojson file only + VertexKey : 'geometries' #[str] key for the attribute of the coordinates given in the geojson file only + MaxShadingDist : 200 #[Float] Maximum distance allowed to consider shadowing walls + DistanceTolerance : 0.2 #[Float] Geometric threshold from which below every edge are removed and vertexes merged + + + # definition of person/m2...complytely abritrary, but we still need some vaalues + # these proposales are taken from Marc Nohra report and from personnal suggestions + # to be enhanced !!!!!BBR gives also some + OccupType : + Residential_key : 'EgenAtempBostad' + Residential_Rate : [0.02, 0.02] + Hotel_key : 'EgenAtempHotell' + Hotel_Rate : [0.01, 0.02] + Restaurant_key : 'EgenAtempRestaurang' + Restaurant_Rate : [0.01, 0.09] + Office_key : 'EgenAtempKontor' + Office_Rate : [0.01, 0.09] + FoodMarket_key : 'EgenAtempLivsmedel' + FoodMarket_Rate : [0.01, 0.09] + GoodsMarket_key : 'EgenAtempButik' + GoodsMarket_Rate : [0.01, 0.09] + Shopping_key : 'EgenAtempKopcentrum' + Shopping_Rate : [0.01, 0.09] #'I still wonder what is the difference with goods' + Hospital24h_key : 'EgenAtempVard' + Hospital24h_Rate : [0.01, 0.09] + Hospitalday_key : 'EgenAtempHotell' + Hospitalday_Rate : [0.01, 0.09] + School_key : 'EgenAtempSkolor' + School_Rate : [0.01, 0.1] + IndoorSports_key : 'EgenAtempBad' + IndoorSports_Rate : [0.01, 0.1] + Other_key : 'EgenAtempOvrig' + Other_Rate : [0.01, 0.1] + AssembPlace_key : 'EgenAtempTeater' + AssembPlace_Rate : [0.01, 0.2] + #this dict deals with the ventilation systems + VentSyst : + BalX : 'VentTypFTX' + Exh : 'VentTypF' + Bal : 'VentTypFT' + Nat : 'VentTypSjalvdrag' + ExhX : 'VentTypFmed' + #this dict defines the acceptable limits for the element precised as well as the swedish key for the DataBase + DBLimits : + surface_key : ['EgenAtemp','SHAPE.AREA','st_areashape'] + surface_lim : [0, 50000] + nbfloor_key : 'EgenAntalPlan' + nbfloor_lim : [0, 100] + nbBasefloor_key : 'EgenAntalKallarplan' + nbBasefloor_lim : [0, 4] + year_key : 'EgenNybyggAr' + year_lim : [0, 2022] + nbAppartments_key : 'EgenAntalBolgh' + nbAppartments_lim : [0, 100] + height_key : ['height', 'Height','SHAPE.LEN','st_lengthshape','Shape__Length','hauteur'] + height_lim : [0, 100] + AreaBasedFlowRate_key : 'EgenProjVentFlode' + AreaBasedFlowRate_lim : [0.35, 10] + nbStairwell_key : 'EgenAntalTrapphus' + nbStairwell_lim : [0, 100] + + + #this dict defines the EPC measured key word + EPCMeters : + Heating : + OilHeating_key : 'EgiOljaUPPV' + OilHeatingCOP : 0.85 + GasHeating_key : 'EgiGasUPPV' + GasHeatingCOP : 0.9 + WoodHeating_key : 'EgiVedUPPV' + WoodHeatingCOP : 0.75 + PelletHeating_key : 'EgiFlisUPPV' + PelletHeatingCOP : 0.75 + BioFuelHeating_key : 'EgiOvrBiobransleUPPV' + BioFuelHeatingCOP : 0.75 + ElecWHeating_key : 'EgiElVattenUPPV' + ElecWHeatingCOP : 1 + ElecHeating_key : 'EgiElDirektUPPV' + ElecHeatingCOP : 1 + GSHPHeating_key : 'EgiPumpMarkUPPV' + GSHPHeatingCOP : 3 + EASHPHeating_key : 'EgiPumpFranluftUPPV' + EASHPHeatingCOP : 2 + AASHPHeating_key : 'EgiPumpLuftLuftUPPV' + AASHPHeatingCOP : 2.5 + DistrictHeating_key : 'EgiFjarrvarmeUPPV' + DistrictHeatingCOP : 1 + ElecAirHeating_key : 'EgiElLuftUPPV' + ElecAirHeatingCOP : 1 + AWSHPHeating_key : 'EgiPumpLuftVattenUPPV' + AWSHPHeatingCOP : 3.1 + + DHW : + OilHeating_key : 'EgiOljaVV' + OilHeatingCOP : 0.85 + GasHeating_key : 'EgiGasVV' + GasHeatingCOP : 0.9 + WoodHeating_key : 'EgiVedVV' + WoodHeatingCOP : 0.75 + PelletHeating_key : 'EgiFlisVV' + PelletHeatingCOP : 0.75 + BioFuelHeating_key : 'EgiOvrBiobransleVV' + BioFuelHeatingCOP : 0.75 + ElecHeating_key : 'EgiElVV' + ElecHeatingCOP : 1 + DistrictHeating_key : 'EgiFjarrvarmeVV' + DistrictHeatingCOP : 1 + Cooling : + DistCooling_key : 'EgiFjarrkyla' + DistCoolingCOP : 1 + ElecCooling_key : 'EgiKomfort' + ElecCoolingCOP : 1 #should be HP no?! + + ElecLoad : + BuildingLev_key : 'EgiFastighet' + BuildingLevCOP : 1 + HousholdLev_key : 'EgiHushall' + HousholdLevCOP : 1 + OperationLev_key : 'EgiVerksamhet' + OperationLevCOP : 1 + + NRJandClass : + WeatherCorrectedNRJ_key : 'EgiEnergianvandning' + WeatherCorrectedNRJCOP : 1 + WeatherCorrectedPNRJ_key : 'EgiPrimarenergianvandning' + WeatherCorrectedPNRJCOP : 1 + EnClasseVersion_key : 'EgiVersion' + EnClasseVersionCOP : 1 + NRJ_Class_key : 'EgiEnergiklass' + NRJ_ClassCOP : 1 \ No newline at end of file diff --git a/CoreFiles/DefaultConfigKeyUnit.yml b/CoreFiles/DefaultConfigKeyUnit.yml new file mode 100644 index 0000000..90ac1f7 --- /dev/null +++ b/CoreFiles/DefaultConfigKeyUnit.yml @@ -0,0 +1,315 @@ +#This files provides the default settings for MUBES +0_APP : + #PATH_TO_MUBES_UBEM : ../MUBES_UBEM/ #[Str] this is not currently used + #PATH_TO_MUBES_UBEM_PYTHON : ../venv/bin/python #[Str] this is not currently used + PATH_TO_ENERGYPLUS : [str] #[Str] this is for energyplus.exe path + PATH_TO_RESULTS : [str] #[Str] this is the main folder where the results will be stored. Subfolder are created for each studied Case + +1_DATA : + PATH_TO_DATA : [str] #Input file or folder in case of several files + +2_CASE: + 0_GrlChoices : + CaseName : [str] #[Str] Name of the studied Case, a subfolder of this name will be created in the main folder defined in '0_APP:PATH_TO_RESULTS' + OutputsFile : [str] #[Str] Output file to identify the outputs the modeller would want and their frequency + RefreshFolder : [bool] #[Bool] True : the folder CaseName, if exist, is emptied whatever could be in it + MakePlotsOnly : [bool] #[Bool] True : a figure will be created with the buildings specified in BldID + MakePlotsPerBld : [bool] #[Bool] True : a figure is created for each building including it's shadowing surfaces (if present) + Verbose : [bool] #[Bool] True : Gives messages in the prompt window along the ongoing process + CPUusage : [float,int] #[Float] Factor of CPU allowed to be used during parallel computing + DebugMode : [bool] #[Bool] The log file will be feeded by detailed information along the process + 1_SimChoices: + ZoneOfInterest : [str] #[Str] BldID can be given from an external file if generated by a third program. + BldID : [str] #[List of Str] list of Building's ID if defined as attribute in the geojson file, index of the building in the geojson file otherwise + DESO : [str] #[Str] not currently used + VarName2Change : [str] #[List of Str] parameters to be changed for parametric simulations + Bounds : [float,int] #[List of list of 2 floats] bounds between the parameter are to be changed [Lower Bound,Upper Bounds] a pair of value is to be given for each parameter in VarName2Change + BoundsLim : [float,int] #[List of list of 2 floats] used for calibration : external strict bounds + ParamMethods : [str] #[List of Str] probabilistic law from which LHS will be generated (one for each parameter in VarName2Change. Can be 'Uniform', 'Normal', 'Triangular'. if 'Linear' : constant increase is ensure along the bounds + NbRuns : [int] #[Int] sample size for parametric simulation + CorePerim : [bool] #[Bool] True : core and perimeter zone thermal modelling + FloorZoning : [bool] #[Bool] True : each floor in consider as a zone or for core zone construction + 2_AdvancedChoices : + API : [bool] #[Bool] in case the simulation is launched trough an API. Option under development + CreateFMU : [bool] #[Bool] True: creates FMU for each building specified in the BldID. No simulation is launched afterward. FMU will be found in the CaseName folder + Calibration : [bool] #[Bool] if calibration process is desired to be launched (several other inputs are required + CalibTimeBasis : [str] #[Str] can currently be either 'MonthlyBasis','WeeklyBasis','YearlyBasis'. + MeasurePath4Calibration : [str] #[Str] path to the measured data. File names currently need to quite specific. Under development for full deployment + FromPosteriors : [bool] #[Bool] True : Outputs form already made calibration phase will be created. Under development for full deployment + PosteriorsDataPath : [str] #[Str] path to the pickle file to be used to catch the posteriors + ECMParam : [str] #[Str] Energy Conservation Measure. Currently done from available posteriors + ECMChange : [float,int] #[Float] factor of enhancement of the default value (the default value could also be changed (useful for ECM applied to distributions) +3_SIM : + #files are needed to be located in the eather folder of EnergyPlus asthe same path is used afterward to launch the simulation + 1_WeatherData : + WeatherDataFile : [str] #[Str] Need to be a epw file. This one is given because installed by default when installing EnergyPlus + Latitude: [float,int] #[Float] Latitude of the location (+ is North, - is South) [-90,+90] + Longitude: [float,int] #[Float] Longitude of the location (- is West, + is East [-180,180] + Time_Zone: [float,int] #[Float] Time relative to GMT [-12,14] + Elevation: [float,int] #[Float] Elevation + YearRoundGroundTemp : [float,int] #Float] Year round ground temperature. considered constant afterward + 2_SimuData : + Begin_Day_of_Month : [int] #[Int] + Begin_Month : [int] #[Int] + End_Day_of_Month : [int] #[Int] + End_Month : [int] #[Int] + SaveLogFiles : [bool] #[Bool] True: computing folder is not removed thus all energyplus outputs files are preserved +#this dict gives information on occupancy times each day. If DCV = True, the airflow will follow the number of person + # and the schedule. if not it will be based only on the extra airflow rate but without schedule (all the time) + #if some separation (ventilation and people) is needed than people heat generation should be converted inteo Electric Load as thus ariflow can be + # related to a schedule, other wise...impossible + 3_BasisElement : + Office_Open : [str] #[Str] + Office_Close : [str] #[Str] + DemandControlledVentilation : [bool] #[Bool] + OccupBasedFlowRate : [float,int] #[Float] l/s/person + OccupHeatRate : [float,int] #[Float] W per person + EnvLeak : [float,int] #[Float] l/s/m2 at 50Pa + BasementAirLeak : [float,int] #[Float] in Air change rate [vol/hour] + wwr : [float,int] #[Float] + ExternalInsulation : [bool] #[Bool] + ElecYearlyLoad : [float,int] #[Float] this is the W\m2 value that will be applied constantly for appliances and occupancy consumptipon impact. It is replace by the values in EPCs if available + IntLoadType : [str] #[Str] change either by 'Cste', 'winter', or 'summer' for reversed sigmoid or sigmoid this will generate hourly values file in the InputFiles folder + IntLoadMultiplier : [float,int] #[Float] this is a multiplier the modeler would like to play with for calibration + IntLoadCurveShape : [float,int] #[Float] this defines the slop of the curves + OffOccRandom : [bool] #[Bool] + AreaBasedFlowRate : [float,int] #[Float] l/s/m2 + AreaBasedFlowRateDefault : [float,int] #[Float] l/s/m2 This will not be changed by EPCs and is needed if EPCs report only the balcned ventilation flow with HR for building that have 2 system and one wihtout recovery. + setTempUpL : [float,int] #[List of 2 Floats] only one have to be defined for none temperature modulation + setTempLoL : [float,int] #[List of 2 Floats]#only one have to be defined for none temperature modulation + ComfortTempOff : [str] #[Str] hours at wich the first temperature set point is considered + ComfortTempOn : [str] #[Str] hours at wich the second temperature set point is considered + ACH_freecool : [float,int] #[Float] this the the vol/hr of extra ventilation when free cooling is on + intT_freecool : [float,int] #[Float] internal temperature threshold for free coolong (opening windows with fixed ACH) + dT_freeCool : [float,int] #[Float] Tint-Text to authorize free cooling to be turned on + AirRecovEff : [float,int] #[Float] efficiency if heat is recovered from ventilation + HVACLimitMode : [str] #[Str] 'LimitCapacity', #can be NoLimit or LimitFlowRate or LimitFlowRateAndCpacity + HVACPowLimit : [float,int] #[Float] in Watt/m2 + #Thisdict gives all the materials characteristics. + # There are 2 layer maximum, the word Inertia and Insulation or key factor further in the code. If one layer is wanted, just comment the other one. + #the basement is considered not heated and thus never insulated layer + BaseMaterial : + Window : + UFactor : [float,int] #[Float] + Solar_Heat_Gain_Coefficient : [float,int] #[Float] + Visible_Transmittance : [float,int] #[Float] + Wall Inertia : + Thickness : [float,int] #[Float] #this layer will be considered also for the basement walls + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + Wall Insulation : + Thickness : [float,int] #[Float] + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + #'Thermal_Absorptance' : [float,int] + Basement Floor : + Thickness : [float,int] #[Float] #this layer will be considered also for the basement floor + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + # Basement Floor Insulation : + # Thickness : [float,int] #not needed as even without basement the Heated1rstFloor is taken for the first floor + # Conductivity : [float,int] + # Roughness : "Rough" + # Density : [float,int] + # Specific_Heat : [float,int] + # Roof Inertia : + # Thickness : [float,int] #not needed unless one wants to have inertia in the roof layer + # Conductivity : [float,int] + # Roughness : "Rough" + # Density : [float,int] + # Specific_Heat : [float,int] + Roof Insulation : + Thickness : [float,int] #[Float] + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + #Thermal_Absorptance : [float,int] + Heated1rstFloor Inertia : + Thickness : [float,int] #[Float] + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + Heated1rstFloor Insulation : + Thickness : [float,int] #[Float] + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + #Thermal_Absorptance : [float,int] + + #this dict is for specification of internalMass equivalence. + #the material should represent the overall mean material of all partition and furnitures + #the weight par zone area gives the quentity and the Average thickness enable to compute the surface for heat transfer + #the mass gives a volume thanks to the density that gives a surface thanks to the average thickness + InternalMass : + HeatedZoneIntMass : + Thickness : [float,int] #[Float] #m this will define the surface in contact with the zone + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + WeightperZoneArea : [float,int] #[Float] #kg/m2 + NonHeatedZoneIntMass : + Thickness : [float,int] #[Float] #m this will define the surface in contact with the zone + Conductivity : [float,int] #[Float] + Roughness : [str] #[Str] + Density : [float,int] #[Float] + Specific_Heat : [float,int] #[Float] + WeightperZoneArea : [float,int] #[Float] #kg/m2 + + #this dict give element for the Domestic Hot water. it gives the externail file for the water taps and the inlet cold water temp. + #if empty it is no longer taken into account. if new file are given, thses should be present in the Externael file folder + ExtraEnergy : + Present : [bool] #[Bool] True: will consider the following information. Currently only used for DHW + Name : [str] #[Str] + WatertapsFile : [str] #[Str] path to external file #this file is in l/mnin and will be converted into m3/s afertward. it needs to have hourly values + ColdWaterTempFile : [str] #[Str] path to external file + HotWaterSetTemp : [float,int] #[Float] + TargetWaterTapTemp : [float,int] #[Float] + WaterTapsMultiplier : [float,int] #[Float] eval() is ensured in the scriptsso operation van be given as well this is because the file given above is for 40 apartment and is in l/min where we need m3/s. in the code in is afterward multiplied by the number of apartement in the building + + #this dict is for the shading paradigm. There are two files that we need. the firt one is the main geojson that contains all buildings and their propreties + #the other one contains for each shading surface id the vertex point and the building Id in order to catch the height of it. + #to externalize as much as possible, these elements are reported in the dict below + GeomElement : + BuildIDKey : [str] #[List of Str] key word that can be used for the building ID + FormularIdKey : [str] #[Str] key word for the the EPC correspondance + ShadingIdKey : [str] #[Str] key word for the shadowing wall given in the geojson file only + BuildingIdKey : [str] #[Str] key word for the owner of the shadowing wall given in the geojson file only + VertexKey : [str] #[str] key for the attribute of the coordinates given in the geojson file only + MaxShadingDist : [float,int] #[Float] Maximum distance allowed to consider shadowing walls + DistanceTolerance : [float,int] #[Float] Geometric threshold from which below every edge are removed and vertexes merged + + + # definition of person/m2...complytely abritrary, but we still need some vaalues + # these proposales are taken from Marc Nohra report and from personnal suggestions + # to be enhanced !!!!!BBR gives also some + OccupType : + Residential_key : [str] + Residential_Rate : [float,int] + Hotel_key : [str] + Hotel_Rate : [float,int] + Restaurant_key : [str] + Restaurant_Rate : [float,int] + Office_key : [str] + Office_Rate : [float,int] + FoodMarket_key : [str] + FoodMarket_Rate : [float,int] + GoodsMarket_key : [str] + GoodsMarket_Rate : [float,int] + Shopping_key : [str] + Shopping_Rate : [float,int] #'I still wonder what is the difference with goods' + Hospital24h_key : [str] + Hospital24h_Rate : [float,int] + Hospitalday_key : [str] + Hospitalday_Rate : [float,int] + School_key : [str] + School_Rate : [float,int] + IndoorSports_key : [str] + IndoorSports_Rate : [float,int] + Other_key : [str] + Other_Rate : [float,int] + AssembPlace_key : [str] + AssembPlace_Rate : [float,int] + #this dict deals with the ventilation systems + VentSyst : + BalX : [str] + Exh : [str] + Bal : [str] + Nat : [str] + ExhX : [str] + #this dict defines the acceptable limits for the element precised as well as the swedish key for the DataBase + DBLimits : + surface_key : [str] + surface_lim : [float,int] + nbfloor_key : [str] + nbfloor_lim : [int] + nbBasefloor_key : [str] + nbBasefloor_lim : [int] + year_key : [str] + year_lim : [int] + nbAppartments_key : [str] + nbAppartments_lim : [int] + height_key : [str] + height_lim : [float,int] + AreaBasedFlowRate_key : [str] + AreaBasedFlowRate_lim : [float,int] + nbStairwell_key : [str] + nbStairwell_lim : [float,int] + + #this dict defines the EPC measured key word + EPCMeters : + Heating : + OilHeating_key : [str] + OilHeatingCOP : [float,int] + GasHeating_key : [str] + GasHeatingCOP : [float,int] + WoodHeating_key : [str] + WoodHeatingCOP : [float,int] + PelletHeating_key : [str] + PelletHeatingCOP : [float,int] + BioFuelHeating_key : [str] + BioFuelHeatingCOP : [float,int] + ElecWHeating_key : [str] + ElecWHeatingCOP : [float,int] + ElecHeating_key : [str] + ElecHeatingCOP : [float,int] + GSHPHeating_key : [str] + GSHPHeatingCOP : [float,int] + EASHPHeating_key : [str] + EASHPHeatingCOP : [float,int] + AASHPHeating_key : [str] + AASHPHeatingCOP : [float,int] + DistrictHeating_key : [str] + DistrictHeatingCOP : [float,int] + ElecAirHeating_key : [str] + ElecAirHeatingCOP : [float,int] + AWSHPHeating_key : [str] + AWSHPHeatingCOP : [float,int] + + DHW : + OilHeating_key : [str] + OilHeatingCOP : [float,int] + GasHeating_key : [str] + GasHeatingCOP : [float,int] + WoodHeating_key : [str] + WoodHeatingCOP : [float,int] + PelletHeating_key : [str] + PelletHeatingCOP : [float,int] + BioFuelHeating_key : [str] + BioFuelHeatingCOP : [float,int] + ElecHeating_key : [str] + ElecHeatingCOP : [float,int] + DistrictHeating_key : [str] + DistrictHeatingCOP : [float,int] + Cooling : + DistCooling_key : [str] + DistCoolingCOP : [float,int] + ElecCooling_key : [str] + ElecCoolingCOP : [float,int] #should be HP no?! + + ElecLoad : + BuildingLev_key : [str] + BuildingLevCOP : [float,int] + HousholdLev_key : [str] + HousholdLevCOP : [float,int] + OperationLev_key : [str] + OperationLevCOP : [float,int] + + NRJandClass : + WeatherCorrectedNRJ_key : [str] + WeatherCorrectedNRJCOP : [float,int] + WeatherCorrectedPNRJ_key : [str] + WeatherCorrectedPNRJCOP : [float,int] + EnClasseVersion_key : [str] + EnClasseVersionCOP : [float,int] + NRJ_Class_key : [str] + NRJ_ClassCOP : [float,int] \ No newline at end of file diff --git a/CoreFiles/DomesticHotWater.py b/CoreFiles/DomesticHotWater.py index 62dcb22..952a046 100644 --- a/CoreFiles/DomesticHotWater.py +++ b/CoreFiles/DomesticHotWater.py @@ -2,6 +2,7 @@ # @Email : xavierf@kth.se import CoreFiles.Load_and_occupancy as Load_and_occupancy +import os def createWaterEqpt(idf,building): if not (idf.getobject('SCHEDULETYPELIMITS', 'Any Number')): @@ -10,29 +11,73 @@ def createWaterEqpt(idf,building): Load_and_occupancy.create_ScheduleFile(idf, 'Watertaps', building.DHWInfos['WatertapsFile']) Load_and_occupancy.create_ScheduleFile(idf, 'ColdWaterTemp', building.DHWInfos['ColdWaterTempFile']) Load_and_occupancy.ScheduleCompact(idf, 'HotWaterTemp', building.DHWInfos['HotWaterSetTemp']) + Load_and_occupancy.ScheduleCompact(idf, 'TargetWaterTemp', building.DHWInfos['TargetWaterTapTemp']) #now lets create the water equipment object + multiplier = building.DHWInfos['WaterTapsMultiplier'] idf.newidfobject( 'WATERUSE:EQUIPMENT', Name = building.DHWInfos['Name'], - Peak_Flow_Rate = building.nbAppartments*building.DHWInfos['WaterTapsMultiplier']*CallCorrectionFactor(building.name),#the flow rate should be in m3/s and we are using schedul file in l/min, thus we need this transformation, + Peak_Flow_Rate = building.nbAppartments*multiplier*CallCorrectionFactor(building), Flow_Rate_Fraction_Schedule_Name = 'WaterTaps', + Target_Temperature_Schedule_Name='TargetWaterTemp', Hot_Water_Supply_Temperature_Schedule_Name = 'HotWaterTemp', Cold_Water_Supply_Temperature_Schedule_Name='ColdWaterTemp', ) return idf -def CallCorrectionFactor(BuildName): - return 1 +def CallCorrectionFactor(building): + try: + Cp = 4200 #J/Kg/K water specific heat + Waterdensity = 1000 #kg/m3 + try: multiplier = eval(building.DHWInfos['WaterTapsMultiplier']) + except: multiplier = building.DHWInfos['WaterTapsMultiplier'] + #lets comput the cumulative value and make it match with thr EPC data of DHW consumption + watertaps, waterList = getVal(building.DHWInfos['WatertapsFile']) + coldTemp, coldTempList = getVal(building.DHWInfos['ColdWaterTempFile']) + targetTemp, TargetTempList = getVal(building.DHWInfos['TargetWaterTapTemp']) + DHWfromEPC = getDHW_EPC(building.EPCMeters['DHW']) + targetTemp = [targetTemp]*len(watertaps) + #from now we know that water taps and cold temp are from file, so in the futur there should be some check to make the floowing operation ! + TotalDHW = sum([val * Waterdensity * building.nbAppartments * multiplier * Cp * (targetTemp[idx]-coldTemp[idx]) for idx,val in enumerate(watertaps)]) + return DHWfromEPC/TotalDHW + except: + return 1 + +def getVal(var): + if os.path.isfile(var): + with open(var, 'r') as f: + Lines = f.readlines() + return [float(val) for val in Lines], True + else: + return float(var), False + +def getDHW_EPC(DHW_Meas): + TotDHW = 0 + for key in DHW_Meas.keys(): + TotDHW += DHW_Meas[key] + return TotDHW + +def CallCorrectionFactorMade4Calib(building): + BuildName = building.name #all this below was done for the calibration study using the calibrated values to make Strobe package comply with measurements #cf paper on the calibration study - # BuildNumber = int(BuildName[BuildName.index('_')+1:BuildName.index('v')]) - # # Lets read the correction factors - # import os - # pth2corfactors = os.path.normcase('C:\\Users\\xav77\\Documents\\FAURE\\prgm_python\\UrbanT\\Eplus4Mubes\\MUBES_SimResults\\ComputedElem4Calibration\\') - # CorFactPath = os.path.normcase(os.path.join(pth2corfactors, 'DHWCorFact.txt')) - # with open(CorFactPath, 'r') as handle: - # FileLines = handle.readlines() - # CorFact = {} - # for line in FileLines: - # CorFact[int(line[:line.index('\t')])] = float(line[line.index('\t')+1:line.index('\n')]) - # return CorFact[BuildNumber] \ No newline at end of file + BuildNumber = int(BuildName[BuildName.index('_')+1:BuildName.index('v')]) + # Lets read the correction factors + import os + pth2corfactors = os.path.normcase('C:\\Users\\xav77\\Documents\\FAURE\\prgm_python\\UrbanT\\Eplus4Mubes\\MUBES_SimResults\\ComputedElem4Calibration\\') + CorFactPath = os.path.normcase(os.path.join(pth2corfactors, 'DHWCorFact.txt')) + with open(CorFactPath, 'r') as handle: + FileLines = handle.readlines() + CorFact = {} + for line in FileLines: + CorFact[int(line[:line.index('\t')])] = float(line[line.index('\t')+1:line.index('\n')]) + + # #the following lines are added to apply an extra correction factor for year 2014 and 2015 compared to 2012 + CorFactPath = os.path.normcase(os.path.join(pth2corfactors, 'DHWCorFact2014.txt')) + with open(CorFactPath, 'r') as handle: + FileLines = handle.readlines() + ExtraCorFact = {} + for line in FileLines: + ExtraCorFact[int(line[:line.index('\t')])] = float(line[line.index('\t') + 1:line.index('\n')]) + + return CorFact[BuildNumber] #* ExtraCorFact[BuildNumber] \ No newline at end of file diff --git a/CoreFiles/GeneralFunctions.py b/CoreFiles/GeneralFunctions.py index 9c0956a..6edfd9f 100644 --- a/CoreFiles/GeneralFunctions.py +++ b/CoreFiles/GeneralFunctions.py @@ -10,13 +10,14 @@ import CoreFiles.MUBES_pygeoj as MUBES_pygeoj import CoreFiles.BuildFMUs as BuildFMUs from openpyxl import load_workbook -from SALib.sample import latin +import openturns as ot import shutil import pickle import pyproj +import numpy as np -def appendBuildCase(StudiedCase,epluspath,nbcase,DataBaseInput,MainPath,LogFile,PlotOnly = False): - StudiedCase.addBuilding('Building'+str(nbcase),DataBaseInput,nbcase,MainPath,epluspath,LogFile,PlotOnly) +def appendBuildCase(StudiedCase,keypath,nbcase,DataBaseInput,MainPath,LogFile,PlotOnly = False, DebugMode = False): + StudiedCase.addBuilding('Building'+str(nbcase),DataBaseInput,nbcase,MainPath,keypath,LogFile,PlotOnly, DebugMode) idf = StudiedCase.building[-1]['BuildIDF'] building = StudiedCase.building[-1]['BuildData'] return idf, building @@ -28,13 +29,12 @@ def setSimLevel(idf,building): Sim_param.Location_and_weather(idf,building) Sim_param.setSimparam(idf,building) -def setBuildingLevel(idf,building,LogFile,CorePerim = False,FloorZoning = False,ForPlots = False): +def setBuildingLevel(idf,building,LogFile,CorePerim = False,FloorZoning = False,ForPlots = False,DebugMode = False): ###################################################################################### #Building Level ###################################################################################### #this is the function that requires the longest time - GeomScripts.createBuilding(LogFile,idf,building, perim = CorePerim,FloorZoning = FloorZoning,ForPlots=ForPlots) - + GeomScripts.createBuilding(LogFile,idf,building, perim = CorePerim,FloorZoning = FloorZoning,ForPlots=ForPlots,DebugMode = DebugMode) def setEnvelopeLevel(idf,building): ###################################################################################### @@ -54,7 +54,6 @@ def setExtraEnergyLoad(idf,building): if building.DHWInfos: DomesticHotWater.createWaterEqpt(idf,building) - def setOutputLevel(idf,building,MainPath,EMSOutputs,OutputsFile): #ouputs definitions Set_Outputs.AddOutputs(idf,building,MainPath,EMSOutputs,OutputsFile) @@ -69,39 +68,53 @@ def readPathfile(Pathways): keyPath[key] = os.path.normcase(line[line.find(':') + 1:-1]) return keyPath -def ReadGeoJsonFile(keyPath): - print('Reading Input files,...') +def ReadGeoJsonFile(keyPath,toBuildPool = False): + #print('Reading Input files,...') try: BuildObjectDict = ReadGeojsonKeyNames(keyPath['GeojsonProperties']) Buildingsfile = MUBES_pygeoj.load(keyPath['Buildingsfile']) - Shadingsfile = MUBES_pygeoj.load(keyPath['Shadingsfile']) - Buildingsfile = checkRefCoordinates(Buildingsfile) - Shadingsfile = checkRefCoordinates(Shadingsfile) - return {'BuildObjDict':BuildObjectDict,'Build' :Buildingsfile, 'Shades': Shadingsfile} + #Shadingsfile = MUBES_pygeoj.load(keyPath['Shadingsfile']) + if not toBuildPool: Buildingsfile = checkRefCoordinates(Buildingsfile) + #if not toBuildPool: Shadingsfile = checkRefCoordinates(Shadingsfile) + return {'BuildObjDict':BuildObjectDict,'Build' :Buildingsfile}#, 'Shades': Shadingsfile} except: Buildingsfile = MUBES_pygeoj.load(keyPath['Buildingsfile']) - Shadingsfile = MUBES_pygeoj.load(keyPath['Shadingsfile']) - Buildingsfile = checkRefCoordinates(Buildingsfile) - Shadingsfile = checkRefCoordinates(Shadingsfile) - return {'Build': Buildingsfile, 'Shades': Shadingsfile} + #Shadingsfile = MUBES_pygeoj.load(keyPath['Shadingsfile']) + if not toBuildPool: Buildingsfile = checkRefCoordinates(Buildingsfile) + #if not toBuildPool: Shadingsfile = checkRefCoordinates(Shadingsfile) + return {'Build': Buildingsfile}#, 'Shades': Shadingsfile} + +def ListAvailableFiles(keyPath): + # reading the pathfiles and the geojsonfile + GlobKey = [keyPath] + # lets see if the input file is a dir with several geojson files + multipleFiles = [] + BuildingFiles = ReadGeoJsonDir(GlobKey[0]) + if BuildingFiles: + if len(BuildingFiles)>1: + multipleFiles = [FileName[:-8] for FileName in BuildingFiles] + MainRootPath = GlobKey[0]['Buildingsfile'] + GlobKey[0]['Buildingsfile'] = os.path.join(MainRootPath, BuildingFiles[0]) + for nb, file in enumerate(BuildingFiles[1:]): + GlobKey.append(GlobKey[-1].copy()) + GlobKey[-1]['Buildingsfile'] = os.path.join(MainRootPath, file) + return GlobKey, multipleFiles def ReadGeoJsonDir(keyPath): - print('Reading Input dir,...') + #print('Reading Input dir,...') BuildingFiles = [] - ShadingWallFiles = [] if os.path.isdir(keyPath['Buildingsfile']): FileList = os.listdir(keyPath['Buildingsfile']) for nb,file in enumerate(FileList): - if 'Buildings' in file: - print('Building main input file with file nb: ' + str(nb)) - BuildingFiles.append(file) - ShadingWallFiles.append(file.replace('Buildings', 'Walls')) - - return BuildingFiles,ShadingWallFiles - - + if file[-8:] == '.geojson': + #print('Building main input file with file nb: ' + str(nb)) + if not 'Wall' in file: + BuildingFiles.append(file) + return BuildingFiles def checkRefCoordinates(GeojsonFile): + if not GeojsonFile: + return GeojsonFile if 'EPSG' in GeojsonFile.crs['properties']['name']: return GeojsonFile ##The coordinate system depends on the input file, thus, if specific filter or conversion from one to another, @@ -122,36 +135,38 @@ def checkRefCoordinates(GeojsonFile): def ComputeDistance(v1,v2): return ((v2[0]-v1[0])**2+(v2[1]-v1[1])**2)**0.5 -def MakeAbsoluteCoord(idf,building): +def MakeAbsoluteCoord(building,idf = [],roundfactor = 8): # we need to convert change the reference coordinate because precision is needed for boundary conditions definition: newfoot = [] for foot in building.footprint: - newfoot.append([(node[0] + building.RefCoord[0], node[1] + building.RefCoord[1]) for node in foot]) + newfoot.append([(round(node[0] + building.RefCoord[0],roundfactor), round(node[1] + building.RefCoord[1],roundfactor)) for node in foot]) building.footprint = newfoot for shade in building.shades.keys(): - newcoord = [(node[0] + building.RefCoord[0], node[1] + building.RefCoord[1]) for node in + newcoord = [(round(node[0] + building.RefCoord[0],roundfactor), round(node[1] + building.RefCoord[1],roundfactor)) for node in building.shades[shade]['Vertex']] building.shades[shade]['Vertex'] = newcoord - newwalls = [] + new_Agreg = [(round(node[0] + building.RefCoord[0],roundfactor), round(node[1] + building.RefCoord[1],roundfactor)) for node in building.AggregFootprint] + building.AggregFootprint = new_Agreg for Wall in building.AdjacentWalls: - newcoord = [(node[0] - building.RefCoord[0], node[1] - building.RefCoord[1]) for node in Wall['geometries']] + newcoord = [(round(node[0] + building.RefCoord[0],roundfactor), round(node[1] + building.RefCoord[1],roundfactor)) for node in Wall['geometries']] Wall['geometries'] = newcoord - surfaces = idf.getsurfaces() + idf.getshadingsurfaces() + idf.getsubsurfaces() - for surf in surfaces: - for i,node in enumerate(surf.coords): - try: - x,y,z = node[0], node[1], node[2] - varx = 'Vertex_' + str(i+1) + '_Xcoordinate' - vary = 'Vertex_' + str(i+1) + '_Ycoordinate' - varz = 'Vertex_' + str(i+1) + '_Zcoordinate' - setattr(surf, varx, x + building.RefCoord[0]) - setattr(surf, vary, y + building.RefCoord[1]) - setattr(surf, varz, z) - except: - a=1 - return idf,building - - + if idf: + surfaces = idf.getsurfaces() + idf.getshadingsurfaces() + idf.getsubsurfaces() + for surf in surfaces: + for i,node in enumerate(surf.coords): + try: + x,y,z = node[0], node[1], node[2] + varx = 'Vertex_' + str(i+1) + '_Xcoordinate' + vary = 'Vertex_' + str(i+1) + '_Ycoordinate' + varz = 'Vertex_' + str(i+1) + '_Zcoordinate' + setattr(surf, varx, round(x + building.RefCoord[0],roundfactor)) + setattr(surf, vary, round(y + building.RefCoord[1],roundfactor)) + setattr(surf, varz, round(z,roundfactor)) + except: + a=1 + return building, idf + else: + return building def SaveCase(MainPath,SepThreads,CaseName,nbBuild): SaveDir = os.path.join(os.path.dirname(os.path.dirname(MainPath)), 'SimResults') @@ -173,56 +188,98 @@ def SaveCase(MainPath,SepThreads,CaseName,nbBuild): except: pass -def CreateSimDir(CurrentPath,CaseName,SepThreads,nbBuild,idx,MultipleFile = '',Refresh = False): - if not os.path.exists(os.path.join(os.path.dirname(os.path.dirname(CurrentPath)),'MUBES_SimResults')): - os.mkdir(os.path.join(os.path.dirname(os.path.dirname(CurrentPath)),'MUBES_SimResults')) - SimDir = os.path.normcase( - os.path.join(os.path.dirname(os.path.dirname(CurrentPath)), os.path.join('MUBES_SimResults', CaseName))) +def CreateSimDir(CurrentPath,DestinationPath,CaseName,SepThreads,nbBuild,idx,MultipleFile = '',Refresh = False,Verbose = False): + if not os.path.exists(DestinationPath): + os.mkdir(os.path.abspath(DestinationPath)) + if Verbose: print(DestinationPath +' folder is created') + SimDir = os.path.join(os.path.abspath(DestinationPath), CaseName) if not os.path.exists(SimDir): os.mkdir(SimDir) + if Verbose: print('[Prep. phase] '+CaseName + ' folder is created') elif idx == 0 and Refresh: shutil.rmtree(SimDir) os.mkdir(SimDir) + if Verbose: print('[Prep. phase] '+CaseName + ' folder is emptied') if SepThreads: - SimDir = os.path.normcase( - os.path.join(SimDir, 'Build_' + str(nbBuild))) + SimDir = os.path.join(SimDir, 'Build_' + str(nbBuild)) if not os.path.exists(SimDir): os.mkdir(SimDir) + if Verbose: print('[Prep. phase] '+CaseName + '/Build_' + str(nbBuild)+' folder is created') elif idx == 0 and Refresh: shutil.rmtree(SimDir) os.mkdir(SimDir) + if Verbose: print('[Prep. phase] '+CaseName + '/Build_' + str(nbBuild)+' folder is emptied') if len(MultipleFile)> 0 : SimDir = os.path.normcase( os.path.join(SimDir, MultipleFile)) if not os.path.exists(SimDir): os.mkdir(SimDir) + if Verbose: print('[Prep. phase] '+CaseName + '/'+MultipleFile+ ' folder is created') elif idx == 0 and Refresh: shutil.rmtree(SimDir) os.mkdir(SimDir) + if Verbose: print('[Prep. phase] '+CaseName + '/' + MultipleFile + ' folder is emptied') return SimDir -def getParamSample(VarName2Change,Bounds,nbruns): +def getDistType(ParamMethod,Bounds): + if 'Normal' in ParamMethod: + return ot.Normal((Bounds[1]+Bounds[0])/2,(Bounds[1]-Bounds[0])/6) + elif 'Triangular' in ParamMethod: + return ot.Triangular(Bounds[0],(Bounds[1]+Bounds[0])/2, Bounds[1]) + else: + #the Uniform law is by default + return ot.Uniform(Bounds[0],Bounds[1]) + +def getParamSample(VarName2Change,Bounds,nbruns,ParamMethods): # Sampling process if someis define int eh function's arguments # It is currently using the latin hyper cube methods for the sampling generation (latin.sample) - Param = [1] - if len(VarName2Change) > 0: - problem = {} - problem['names'] = VarName2Change - problem['bounds'] = Bounds # , - problem['num_vars'] = len(VarName2Change) - # problem = read_param_file(MainPath+'\\liste_param.txt') - Param = latin.sample(problem, nbruns) - return Param - -def CreatFMU(idf,building,nbcase,epluspath,SimDir, i,varOut,LogFile): + Dist = {} + LinearVal = {} + varMethodIdx = {'Idx':[],'Method':[]} + LinearIdx = 0 + DistIdx = 0 + for idx,param in enumerate(VarName2Change): + if 'Linear' in ParamMethods[idx]: + LinearVal[param] = np.linspace(Bounds[idx][0],Bounds[idx][1],nbruns) + varMethodIdx['Method'].append('Linear') + varMethodIdx['Idx'].append(LinearIdx) + LinearIdx+=1 + else: + Dist[param] = getDistType(ParamMethods[idx],Bounds[idx]) + varMethodIdx['Method'].append('Dist') + varMethodIdx['Idx'].append(DistIdx) + DistIdx +=1 + if Dist: + MakeDist = ot.ComposedDistribution([Dist[x] for x in Dist.keys()]) + OTSample = np.array(ot.LHSExperiment(MakeDist, nbruns).generate()) + if LinearVal: + LinSample = np.array([[LinearVal[key][x] for key in LinearVal.keys()] for x in range(nbruns)]) + if Dist and LinearVal: + #both dict need to be implemented but keeping the order of VarName2change list + newSample = [] + for idx,mthd in enumerate(varMethodIdx['Method']): + if idx==0: + newSample = LinSample[:,varMethodIdx['Idx'][idx]].reshape(nbruns,1) if mthd=='Linear' else \ + OTSample[:,varMethodIdx['Idx'][idx]].reshape(nbruns,1) + else: + newSample = np.append(newSample,LinSample[:,varMethodIdx['Idx'][idx]].reshape(nbruns,1) if \ + mthd=='Linear' else OTSample[:,varMethodIdx['Idx'][idx]].reshape(nbruns,1),axis = 1) + return newSample + elif Dist: + return OTSample + elif LinearVal: + return LinSample + return [1] + +def CreatFMU(idf,building,nbcase,epluspath,SimDir, i,varOut,LogFile,DebugMode): print('Building FMU under process...Please wait around 30sec') #get the heated zones first and set them into a zonelist BuildFMUs.setFMUsINOut(idf, building,varOut) idf.saveas('Building_' + str(nbcase) + 'v' + str(i) + '.idf') BuildFMUs.buildEplusFMU(epluspath, building.WeatherDataFile, os.path.join(SimDir,'Building_' + str(nbcase) + 'v' + str(i) + '.idf')) print('FMU created for this building') - Write2LogFile('FMU created for this building\n',LogFile) - Write2LogFile('##############################################################\n',LogFile) + if DebugMode: Write2LogFile('FMU created for this building\n',LogFile) + if DebugMode: Write2LogFile('##############################################################\n',LogFile) def ReadGeojsonKeyNames(GeojsonProperties): #this is currently ot used.... @@ -287,10 +344,50 @@ def CleanUpLogFiles(MainPath): file1.close() for line in Lines: Write2LogFile(line,MainLogFile) + Write2LogFile('#############################################################\n', MainLogFile) os.remove(os.path.join(MainPath,file)) MainLogFile.close() -def setChangedParam(building,ParamVal,VarName2Change,MainPath,Buildingsfile,Shadingsfile,nbcase,DB_Data,LogFile=[]): +def AppendLogFiles(MainPath,BldIDKey): + file2del = [] + try: + with open(os.path.join(MainPath, 'AllLogs.log'), 'r') as file: + Lines = file.readlines() + file2del = 'AllLogs.log' + except: + Liste = os.listdir(MainPath) + for file in Liste: + if 'Logs.log' in file: + with open(os.path.join(MainPath, file), 'r') as extrafile: + Lines = extrafile.readlines() + file2del = file + break + if file2del: + NewLines = [] + flagON = False + for line in Lines: + NewLines.append(line) + if '[Bld ID] '+BldIDKey+' : ' in line: + flagON = True + id = line[len('[Bld ID] '+BldIDKey+' : '):-1] + try: + with open(os.path.join(MainPath,'Sim_Results', BldIDKey+'_'+str(id) + '.txt'), 'r') as file: + extralines = file.readlines() + os.remove(os.path.join(MainPath,'Sim_Results', BldIDKey+'_'+str(id) + '.txt')) + except: + extralines = ['ERROR : No simulations found for this building\n'] + if '[Reported Time]' in line and flagON: + for extraline in extralines: + NewLines.append(extraline) + flagON = False + FinalLogFile = open(os.path.join(MainPath, 'FinalLogsCompiled.log'), 'w') + for line in NewLines: + Write2LogFile(line, FinalLogFile) + FinalLogFile.close() + os.remove(os.path.join(MainPath, file2del)) + +#def setChangedParam(building,ParamVal,VarName2Change,MainPath,Buildingsfile,Shadingsfile,nbcase,LogFile=[]): +def setChangedParam(building, ParamVal, VarName2Change, MainPath, Buildingsfile, nbcase, LogFile=[]): #there is a loop file along the variable name to change and if specific ation are required it should be define here # if the variable to change are embedded into several layer of dictionnaries than there is a need to make checks and change accordingly to the correct element # here are examples for InternalMass impact using 'InternalMass' keyword in the VarName2Change list to play with the 'WeightperZoneArea' parameter @@ -319,7 +416,8 @@ def setChangedParam(building,ParamVal,VarName2Change,MainPath,Buildingsfile,Shad setattr(building, var, exttmass) elif 'MaxShadingDist' in var: building.MaxShadingDist = round(ParamVal[varnum], roundVal) - building.shades = building.getshade(Buildingsfile[nbcase], Shadingsfile, Buildingsfile,DB_Data.GeomElement,LogFile,PlotOnly = False) + #building.shades = building.getshade(Buildingsfile[nbcase], Shadingsfile, Buildingsfile,LogFile,PlotOnly = False) + building.shades = building.getshade(nbcase, Buildingsfile, LogFile,PlotOnly=False) elif 'IntLoadCurveShape' in var: building.IntLoadCurveShape = max(round(ParamVal[varnum], roundVal),1e-6) building.IntLoad = building.getIntLoad(MainPath, LogFile) @@ -332,15 +430,19 @@ def setChangedParam(building,ParamVal,VarName2Change,MainPath,Buildingsfile,Shad except: print('This one needs special care : '+var) -def SetParamSample(SimDir,nbruns,VarName2Change,Bounds,SepThreads): +def SetParamSample(SimDir,CaseChoices,SepThreads): #the parameter are constructed. the oupute gives a matrix ofn parameter to change with nbruns values to simulate + nbruns = CaseChoices['NbRuns'] + VarName2Change = CaseChoices['VarName2Change'] + Bounds = CaseChoices['Bounds'] + ParamMethods = CaseChoices['ParamMethods'] if SepThreads: Paramfile = os.path.join(SimDir, 'ParamSample.pickle')#os.path.join(os.path.dirname(SimDir), 'ParamSample.pickle') if os.path.isfile(Paramfile): with open(Paramfile, 'rb') as handle: ParamSample = pickle.load(handle) else: - ParamSample = getParamSample(VarName2Change,Bounds,nbruns) + ParamSample = getParamSample(VarName2Change,Bounds,nbruns,ParamMethods) if nbruns>1: with open(Paramfile, 'wb') as handle: pickle.dump(ParamSample, handle, protocol=pickle.HIGHEST_PROTOCOL) @@ -350,11 +452,104 @@ def SetParamSample(SimDir,nbruns,VarName2Change,Bounds,SepThreads): with open(Paramfile, 'rb') as handle: ParamSample = pickle.load(handle) else: - ParamSample = getParamSample(VarName2Change, Bounds, nbruns) + ParamSample = getParamSample(VarName2Change, Bounds, nbruns,ParamMethods) if nbruns > 1: with open(Paramfile, 'wb') as handle: pickle.dump(ParamSample, handle, protocol=pickle.HIGHEST_PROTOCOL) - return ParamSample + #lets add a sepcial case for making a sample from posteriors + if CaseChoices['FromPosteriors']: + CaseChoices['VarName2Change'] = [] + BldNum = int(SimDir[-SimDir[::-1].index('_'):]) + with open(os.path.join(CaseChoices['PosteriorsDataPath'], 'GlobalMatchedParam.pickle'), 'rb') as handle: + MatchedData = pickle.load(handle) + for paramName in MatchedData[CaseChoices['CalibTimeBasis']][BldNum].keys(): + if type(MatchedData[CaseChoices['CalibTimeBasis']][BldNum][paramName]) == np.ndarray: + CaseChoices['VarName2Change'].append(paramName) + ParamSample = [] + if CaseChoices['VarName2Change']: + nbmatches = len(MatchedData[CaseChoices['CalibTimeBasis']][BldNum][CaseChoices['VarName2Change'][0]]) + if nbmatches < 100: + return ParamSample,CaseChoices + else: + for i in range(nbmatches): + ParamSet = [float(MatchedData[CaseChoices['CalibTimeBasis']][BldNum][key][i]) for key in CaseChoices['VarName2Change']] + if CaseChoices['ECMParam']: + try: ParamSet[int(CaseChoices['VarName2Change'].index(CaseChoices['ECMParam']))] *= float(CaseChoices['ECMChange']) + except: pass + ParamSample.append(ParamSet) + if CaseChoices['ECMParam']: + import random + random.shuffle(ParamSample) + ParamSample = ParamSample[:100] + ParamSample = np.array(ParamSample) + CaseChoices['NbRuns'] = len(ParamSample[:,0]) + with open(Paramfile, 'wb') as handle: + pickle.dump(ParamSample, handle, protocol=pickle.HIGHEST_PROTOCOL) + return ParamSample,CaseChoices + +def ReadData(line,seperator,header = False): + Val = [line[:line.index(seperator)]] + remainline = line[line.index(seperator) + 1:] + stillhere = 1 + while stillhere == 1: + try: + Val.append(remainline[:remainline.index(seperator)]) + remainline = remainline[remainline.index(seperator) + 1:] + except: + stillhere = 0 + Val.append(remainline[:-1]) + if header: + Outputs = {} + for i in Val: + Outputs[i] =[] + else: + Outputs = Val + return Outputs + +def getInputFile(path,seperator): + with open(path, 'r') as handle: + FileLines = handle.readlines() + Header = ReadData(FileLines[0],seperator,header=True) + for i,line in enumerate(FileLines[1:]): + Val = ReadData(line,seperator) + try: float(Val[0].replace(',','.')) + except: break + for id,key in enumerate(Header.keys()): + Header[key].append(Val[id].replace(',','.')) + return Header + +def ManageGlobalPlots(BldObj,IdfObj,FigCenter,WindSize, PlotBldOnly,nbcase = [],LastBld = False): + FigCenter.append(BldObj.RefCoord) + refx = sum([center[0] for center in FigCenter]) / len(FigCenter) + refy = sum([center[1] for center in FigCenter]) / len(FigCenter) + FigCentroid = BldObj.RefCoord if PlotBldOnly else (refx, refy) + # we need to transform the prvious relatve coordinates into absolute one in order to make plot of several building keeping their location + if PlotBldOnly: + FigCentroid = (0,0) + else: + BldObj,IdfObj = MakeAbsoluteCoord(BldObj,IdfObj) + + # compåuting the window size for visualization + for poly in BldObj.footprint: + for vertex in poly: + WindSize = max(ComputeDistance(FigCentroid, vertex), WindSize) + surf = IdfObj.getsurfaces() + ok2plot = False + nbadiab = 0 + adiabsurf = [] + for s in surf: + if s.Outside_Boundary_Condition == 'adiabatic': + ok2plot = True + if s.Name[:s.Name.index('_')] not in adiabsurf: + adiabsurf.append(s.Name[:s.Name.index('_')]) + nbadiab += 1 + RoofSpecialColor = "firebrick" + if nbcase in [39]: + RoofSpecialColor = 'limegreen' + IdfObj.view_model(test= True if PlotBldOnly+LastBld>0 else False, FigCenter=FigCentroid, WindSize=2 * WindSize, + RoofSpecialColor=RoofSpecialColor) + return FigCenter,WindSize + if __name__ == '__main__' : print('GeneralFunctions.py') \ No newline at end of file diff --git a/CoreFiles/GeomScripts.py b/CoreFiles/GeomScripts.py index b6a9979..d14cc20 100644 --- a/CoreFiles/GeomScripts.py +++ b/CoreFiles/GeomScripts.py @@ -5,6 +5,7 @@ from shapely.geometry import Polygon from shapely.ops import cascaded_union import CoreFiles.Envelope_Param as Envelope_Param +import CoreFiles.GeneralFunctions as GrlFct import itertools def BuildBloc(idf,perim,bloc,bloc_coord,Height,nbstories,nbBasementstories,BasementstoriesHeight,Perim_depth): @@ -31,7 +32,7 @@ def BuildBloc(idf,perim,bloc,bloc_coord,Height,nbstories,nbBasementstories,Basem below_ground_storey_height=BasementstoriesHeight if nbBasementstories > 0 else 0 ) -def createBuilding(LogFile,idf,building,perim,FloorZoning,ForPlots =False): +def createBuilding(LogFile,idf,building,perim,FloorZoning,ForPlots =False,DebugMode = False): #here, the building geometry is created and extruded for each bloc composing the builing, it uses the function above as well Full_coord = building.footprint Nb_blocs = len(Full_coord) @@ -49,18 +50,12 @@ def createBuilding(LogFile,idf,building,perim,FloorZoning,ForPlots =False): matched = True except: Perim_depth = Perim_depth/2 - print('I reduce half the perim depth') - try: - LogFile.write('I reduce half the perim depth\n') - except: - pass + msg = '[Core Perimeter] the given perimeter depth had to be reduced by 2...\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) if Perim_depth<0.5: - try: - LogFile.write( - 'Sorry, but this building cannot have Perim/Core option..it failed with perim below 0.75m\n') - except: - pass - return print('Sorry, but this building cannot have Perim/Core option..it failed with perim below 0.75m') + msg = '[Core Perimeter] This building cannot have Perim/Core option..it failed with perimeter depth below 0.5m\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + return # the shading walls around the building are created with the function below # these are used in the function that defines the boundary conditions @@ -73,11 +68,12 @@ def createBuilding(LogFile,idf,building,perim,FloorZoning,ForPlots =False): # end = time.time() # print('[Time Report] : The intersect_match function took : ', round(end - start, 2), ' sec') if MatchedShade: - LogFile.write('[Nb Adjacent_Surfaces] This building has ' + str(len(MatchedShade)) + ' adiabatic surfaces\n') + msg = '[Nb Adjacent_Surfaces] This building has ' + str(len(MatchedShade)) + ' adiabatic surfaces\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) except: - LogFile.write('[Error - Boundary] function intersect_match() failed....\n') - print('[Error - Boundary] function intersect_match() failed....') - #this is to generate an erro as it'shandles elswhere + msg ='[Error - Boundary] function intersect_match() failed....\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) + #this is to generate an error as it's handles else where int('process failded') # this last function on the Geometry is here to split the non convex surfaces @@ -87,11 +83,8 @@ def createBuilding(LogFile,idf,building,perim,FloorZoning,ForPlots =False): if not ForPlots: split2convex(idf) except: - try: - LogFile.write('[Error] The Split2convex function failed for this building....\n') - except: - pass - print('The Split2convex function failed for this building....') + msg ='[Error - Convex] The Split2convex function failed for this building....\n' + if DebugMode: GrlFct.Write2LogFile(msg, LogFile) pass def createRapidGeomElem(idf,building): @@ -108,7 +101,7 @@ def createRapidGeomElem(idf,building): def createShadings(building,idf): for ii,sh in enumerate(building.shades): - if building.shades[sh]['distance'] <= building.MaxShadingDist: + if building.shades[sh]['distance'] <= building.GE['MaxShadingDist']: idf.add_shading_block( name='Shading_'+sh, coordinates=building.shades[sh]['Vertex'], #[GeomElement['VertexKey']], diff --git a/CoreFiles/LaunchSim.py b/CoreFiles/LaunchSim.py index ab761ab..53ce40e 100644 --- a/CoreFiles/LaunchSim.py +++ b/CoreFiles/LaunchSim.py @@ -3,7 +3,7 @@ #this program laucnhes all the simulation -import os, sys, stat, platform +import os, sys, stat, platform, time import pickle import shutil import CoreFiles.Set_Outputs as Set_Outputs @@ -15,12 +15,13 @@ def initiateprocess(MainPath): listOfFiles = os.listdir(MainPath) file2run = [] for file in listOfFiles: - if '.idf' in file: + if '.idf' in file and 'template' not in file and not os.path.isfile(os.path.join(MainPath, 'Sim_Results', file[:-4] + '.pickle')): file2run.append(file) return file2run -def runcase(file,filepath, epluspath): +def runcase(file,filepath, epluspath, API = False,Verbose = False): #this function runs a case + ResSimpath = os.path.join(filepath,'Sim_Results') if not os.path.exists(ResSimpath): os.mkdir(ResSimpath) @@ -28,9 +29,10 @@ def runcase(file,filepath, epluspath): loadB = pickle.load(handle) building = loadB['BuildData'] #the building object is loaded in order to be saved afterward with the simulation results + if Verbose: print('Simulation of '+building.name+' is being launched') Runfile = os.path.join(filepath,file) RunDir = os.path.join(filepath,file[:-4]) - print('Launching :'+file) + #print('Launching :'+file) if not os.path.exists(RunDir): os.mkdir(RunDir) os.chdir(RunDir) @@ -53,41 +55,81 @@ def runcase(file,filepath, epluspath): weatherpath = os.path.join(epluspath,building.WeatherDataFile) cmd = [eplus_exe, '--weather',os.path.normcase(weatherpath),'--output-directory',RunDir, \ '--idd',os.path.join(epluspath,'Energy+.idd'),'--expandobjects','-r','--output-prefix',CaseName,Runfile] - check_call(cmd, stdout=open(os.devnull, "w")) - #once the simulation has ended, the results are saved - #savecase(CaseName, RunDir, building, ResSimpath,file,idf,filepath) - savecase(CaseName, RunDir, building, ResSimpath, file, filepath) - print(file[:-4] + ' is finished') - -def savecase(CaseName,RunDir,building,ResSimpath,file,filepath,withFMU = False): - #the resultst are read with html table and energyplus eso files. The html could be avoid, but then some information will have to computes in the building object (could be) - if withFMU: - Res = Set_Outputs.Read_Outputhtml(os.path.join(RunDir, CaseName + 'Table.htm')) - ResEso = Set_Outputs.Read_OutputsEso(os.path.join(RunDir, CaseName + '.eso'), Res['OutdoorSurfacesNames'], ZoneOutput=False) + start = time.time() + try: + if building.SaveLogFiles: + check_call(cmd, stdout=open(os.path.join(RunDir,'ConsolOutput.log'), "w"), stderr=open(os.devnull, "w")) + else: + check_call(cmd, stdout=open(os.devnull, "w"), stderr=open(os.devnull, "w")) + computeTime = time.time()-start + #once the simulation has ended, the results are saved + savecase(CaseName, RunDir, building, ResSimpath, file, filepath, API = API,CTime = computeTime) + return (file[:-4] + ' is finished') + except: return (file[:-4] + ' has failed') +def savecase(CaseName,RunDir,building,ResSimpath,file,filepath,API = False,CTime = [],withFMU = False): + start = time.time() + IdKy = building.BuildID['BldIDKey'] + if API: + res2export = [] + res2export.append( IdKy, building.BuildID[IdKy]) + import pandas as pd + df = pd.read_csv(os.path.join(RunDir, 'Runout.csv'), sep=',') + SpaceHeating = 0 + SpaceCooling = 0 + for key in df.keys(): + if 'Zone Ideal Loads Supply Air Total Heating Rate' in key: + SpaceHeating += sum(df[key]) + if 'Zone Ideal Loads Supply Air Total Cooling Rate' in key: + SpaceCooling += sum(df[key]) + res2export.append(['Total Space Heating Energy Needs (MWh) : ',round(SpaceHeating/1e6,3)]) + res2export.append(['Total Space Cooling Energy Needs (MWh) : ', round(SpaceCooling / 1e6, 3)]) + res2export.append(['Total Space Heating Energy Needs (MWh) : ',round(SpaceHeating/1e3/building.EPHeatedArea,3)]) + res2export.append(['Total Space Cooling Energy Needs (MWh) : ', round(SpaceCooling / 1e3/building.EPHeatedArea, 3)]) + res2export.append( + ['ATemp (m2),EP_Heated_Area (m2) : ', building.ATemp,round(building.EPHeatedArea,1)]) + resTime = time.time()-start + res2export.append( + ['Total computational time : ', round(CTime, 1), ' seconds']) + res2export.append( + ['Total results reporting : ', round(resTime, 1), ' seconds']) + Write2file(res2export, os.path.join(ResSimpath, IdKy+'_'+building.BuildID[IdKy] + '.txt')) else: - Res = Set_Outputs.Read_Outputhtml(os.path.join(RunDir,CaseName+'tbl.htm')) - ResEso = Set_Outputs.Read_OutputsEso(os.path.join(RunDir,CaseName+'out.eso'), Res['OutdoorSurfacesNames'], ZoneOutput=building.ZoneOutput) - - Res['BuildDB'] = building - for key1 in ResEso: - # if not 'Environ' in key1: - Res[key1] = {} - for key2 in ResEso[key1]: - Res[key1]['Data_' + key2] = ResEso[key1][key2]['GlobData'] - Res[key1]['TimeStep_' + key2] = ResEso[key1][key2]['TimeStep'] - Res[key1]['Unit_' + key2] = ResEso[key1][key2]['Unit'] - if withFMU: - shutil.copyfile(os.path.join(RunDir,CaseName+'.err'), os.path.join(ResSimpath,file[:-4] + '.err')) - shutil.copyfile(os.path.join(RunDir,CaseName+'Table.htm'), os.path.join(ResSimpath,file[:-4] + '.html')) - else: - shutil.copyfile(os.path.join(RunDir, 'Runout.err'), os.path.join(ResSimpath, file[:-4] + '.err')) - shutil.copyfile(os.path.join(RunDir, 'Runtbl.htm'), os.path.join(ResSimpath, file[:-4] + '.html')) - #shutil.copyfile(RunDir + '\\' + 'Runout.csv', ResSimpath + file[:-4] + '.csv') - with open(os.path.join(ResSimpath, file[:-4]+'.pickle'), 'wb') as handle: - pickle.dump(Res, handle, protocol=pickle.HIGHEST_PROTOCOL) - #csv2tabdelim.convert(ResSimpath + file[:-4] + '.csv') - #csv2tabdelim.WriteCSVFile(ResSimpath+'\\'+file[:-4] + '.csv', ResEso) + #the resultst are read with html table and energyplus eso files. The html could be avoid, but then some information will have to computes in the building object (could be) + if withFMU: + Res = Set_Outputs.Read_Outputhtml(os.path.join(RunDir, CaseName + 'Table.htm')) + ResEso = Set_Outputs.Read_OutputsEso(os.path.join(RunDir, CaseName + '.eso'), Res['OutdoorSurfacesNames'], ZoneOutput=False) + else: + Res = Set_Outputs.Read_Outputhtml(os.path.join(RunDir, CaseName + 'tbl.htm')) + ResEso = Set_Outputs.Read_OutputsEso(os.path.join(RunDir, CaseName + 'out.eso'), Res['OutdoorSurfacesNames'], + ZoneOutput=False) + Res['BuildDB'] = building + for key1 in ResEso: + # if not 'Environ' in key1: + Res[key1] = {} + for key2 in ResEso[key1]: + Res[key1]['Data_' + key2] = ResEso[key1][key2]['GlobData'] + Res[key1]['TimeStep_' + key2] = ResEso[key1][key2]['TimeStep'] + Res[key1]['Unit_' + key2] = ResEso[key1][key2]['Unit'] + resTime = time.time() - start + if withFMU: + shutil.copyfile(os.path.join(RunDir, CaseName + '.err'), os.path.join(ResSimpath, file[:-4] + '.err')) + #shutil.copyfile(os.path.join(RunDir, CaseName + 'Table.htm'), os.path.join(ResSimpath, file[:-4] + '.html')) + else: + shutil.copyfile(os.path.join(RunDir, 'Runout.err'), os.path.join(ResSimpath, file[:-4] + '.err')) + #shutil.copyfile(os.path.join(RunDir, 'Runtbl.htm'), os.path.join(ResSimpath, file[:-4] + '.html')) + # shutil.copyfile(RunDir + '\\' + 'Runout.csv', ResSimpath + file[:-4] + '.csv') + with open(os.path.join(ResSimpath, file[:-4] + '.pickle'), 'wb') as handle: + pickle.dump(Res, handle, protocol=pickle.HIGHEST_PROTOCOL) + # csv2tabdelim.convert(ResSimpath + file[:-4] + '.csv') + #csv2tabdelim.WriteCSVFile(ResSimpath+'\\'+file[:-4] + '.csv', ResEso) + if 'v0' in file[-6:-4] and not withFMU: + res2export = [] + res2export.append( + '[Reported Time] Full Computation : '+ str(round(CTime, 2)) + ' seconds') + res2export.append( + '[Reported Time] Read and Save Results : '+ str(round(resTime, 2))+ ' seconds') + Write2file(res2export, os.path.join(ResSimpath, IdKy+'_'+str(building.BuildID[IdKy])+ '.txt')) os.chdir(filepath) if not building.SaveLogFiles: @@ -95,6 +137,12 @@ def savecase(CaseName,RunDir,building,ResSimpath,file,filepath,withFMU = False): os.remove(os.path.join(RunDir,i)) os.rmdir(RunDir) # Now the directory is empty of files +def Write2file(val,name): + with open(name, 'w') as f: + for item in val: + f.write("%s\n" % item) + + if __name__ == '__main__' : print('Launch_Sim.py') diff --git a/CoreFiles/Load_and_occupancy.py b/CoreFiles/Load_and_occupancy.py index 491dca0..2949cf6 100644 --- a/CoreFiles/Load_and_occupancy.py +++ b/CoreFiles/Load_and_occupancy.py @@ -174,7 +174,7 @@ def CreateInternalMass(idf,zone,FloorArea,name,Material): #buffering effect of internal mass, taking into account version issues from 9.1.0 and 9.4.0 surf = 2*Material['WeightperZoneArea']*FloorArea/Material['Density']/Material['Thickness'] surf = 2 * FloorArea #as used in https://www.sciencedirect.com/science/article/pii/S036013231930160X?via%3Dihub - if idf.idd_version[1] == 1: + if idf.idd_version == (9,1,0): idf.newidfobject( "INTERNALMASS", Name=zone.Name + 'IntMass', diff --git a/CoreFiles/Set_Outputs.py b/CoreFiles/Set_Outputs.py index 99fbdc7..89525cd 100644 --- a/CoreFiles/Set_Outputs.py +++ b/CoreFiles/Set_Outputs.py @@ -51,6 +51,11 @@ def AddOutputs(idf,building,path,EMSOutputs,OutputsFile): setEMS4TotHeatPow(idf, building,zonelist, OutputsVar['Reportedfrequency'], EMSOutputs[1]) if len(EMSOutputs)>2: setEMS4TotDHWPow(idf, building, zonelist, OutputsVar['Reportedfrequency'], EMSOutputs[2]) + + # idf.newidfobject("OUTPUT:SQLITE", + # Option_Type = 'SimpleAndTabular') # could be 'Simple' as well + + return idf def getHeatedZones(idf): @@ -310,26 +315,8 @@ def Read_OutputsEso(CaseName,ExtSurfNames, ZoneOutput): BuildAgregRes[KeyArea][i]['GlobData'] = [sum(x)/2 for x in zip(BuildAgregRes[KeyArea][i]['GlobData'], ZoneAgregRes[key][i]['GlobData'])] else: BuildAgregRes[KeyArea][i]['GlobData'] = [sum(x) for x in zip(BuildAgregRes[KeyArea][i]['GlobData'], ZoneAgregRes[key][i]['GlobData'])] - if ZoneOutput: - #we need to make it comply with the keys of the Building Level (as these are used further in the ReadMe mostly) - #so all levels are stored in a three keys dictionary - NewZoneAgregRes = {} - NewZoneAgregRes['HeatedArea'] = {} - NewZoneAgregRes['NonHeatedArea'] = {} - NewZoneAgregRes['Other'] = {} - for key in ZoneAgregRes.keys(): - if 'STOREY' in key: - if int(key[6:]) <0: - for name in ZoneAgregRes[key].keys(): - NewZoneAgregRes['NonHeatedArea'][key+' '+name] = ZoneAgregRes[key][name] - else: - for name in ZoneAgregRes[key].keys(): - NewZoneAgregRes['HeatedArea'][key+' '+name] = ZoneAgregRes[key][name] - else: - for name in ZoneAgregRes[key].keys(): - NewZoneAgregRes['Other'][key + ' ' + name] = ZoneAgregRes[key][name] - return NewZoneAgregRes if ZoneOutput else BuildAgregRes + return ZoneAgregRes if ZoneOutput else BuildAgregRes def Plot_Outputs(res,idf): # visualization of the results @@ -375,4 +362,5 @@ def Read_OutputError(CaseName): Endsinfo if __name__ == '__main__' : - print('Set_Outputs Main') \ No newline at end of file + print('Set_Outputs Main') + diff --git a/CoreFiles/Sim_param.py b/CoreFiles/Sim_param.py index f5188f2..1af7749 100644 --- a/CoreFiles/Sim_param.py +++ b/CoreFiles/Sim_param.py @@ -7,7 +7,7 @@ def setSimparam(idf,building): simctrl = idf.idfobjects['SIMULATIONCONTROL'][0] simctrl.Run_Simulation_for_Sizing_Periods= 'No' simctrl.Run_Simulation_for_Weather_File_Run_Periods = 'Yes' - #chqnging the Solar calculation because of complex surface (non convexe) + #changing the Solar calculation because of complex surface (non convexe for inside floors) build_param = idf.idfobjects['BUILDING'][0] build_param.Solar_Distribution = 'FullExterior' #'FullInteriorAndExterior' #'FullExteriorWithReflections' #'FullExterior' #'MinimalShadowing' # FullExterior is the most detailed option possible in our case. #it computes exteriori shading bu not internal. all the radiation that enters the zones is allocated to the floor @@ -15,6 +15,26 @@ def setSimparam(idf,building): #the one with reflection might not be needed and takes more cumputational time (it worth it for specific radiation propreties of the surroundings surfaces #but these are tekan from default value from now + shadow_param = idf.newidfobject('SHADOWCALCULATION') + #ShadowCalculation options for energyPlus v9.1.0 + if idf.idd_version == (9, 1, 0): + shadow_param.Calculation_Frequency = 20 #number of days acounted for the method. not used if TimestepFrequency is choosen in Calculation_Method + shadow_param.Calculation_Method = 'AverageOverDaysInFrequency' #or TimestepFrequency + shadow_param.Sky_Diffuse_Modeling_Algorithm = 'SimpleSkyDiffuseModeling' #unless there is changes in the transmittance of shadings along the year (DetailedSkyDiffuseModeling) + shadow_param.External_Shading_Calculation_Method = 'InternalCalculation' #can be ImportedShading is it has been computed before (with yes on the option below) + else: + # ShadowCalculation options for energyPlus v9.4.0 and above(maybe) + shadow_param.Shading_Calculation_Method = 'PolygonClipping' #can be PixelCounting for the use of GPU or Imported if earlier saved in a file + shadow_param.Shading_Calculation_Update_Frequency_Method = 'Periodic' # Can be Timestep + shadow_param.Shading_Calculation_Update_Frequency = 20 #number of days acounted for the method. not used if TimestepFrequency is choosen in Calculation_Method + shadow_param.Pixel_Counting_Resolution = 512 #default value, increasing it can incerease the time of computation, but I don't know if used if not pixelcounting method used" + #the following should be commun to all version (above 9.1.0) + shadow_param.Maximum_Figures_in_Shadow_Overlap_Calculations = 25000 # it's 15000 by default but raised here because of UBEM and large surrounding + shadow_param.Polygon_Clipping_Algorithm = 'SutherlandHodgman' + shadow_param.Sky_Diffuse_Modeling_Algorithm = 'SimpleSkyDiffuseModeling' # unless there is changes in the transmittance of shadings along the year (DetailedSkyDiffuseModeling) + shadow_param.Output_External_Shading_Calculation_Results = 'No' # if Yes,the calculated external shading fraction results will be saved to an external CSV file with surface names as the column headers (for further used in parametric simulation and gain time + + runperiod = idf.idfobjects['RUNPERIOD'][0] runperiod.Begin_Day_of_Month = building.Begin_Day_of_Month runperiod.Begin_Month = building.Begin_Month @@ -40,26 +60,26 @@ def setSimparam(idf,building): def Location_and_weather(idf,building): #Weather_file = "USA_CO_Golden-NREL.724666_TMY3.epw" Weather_file = building.WeatherDataFile - idf.epw = Weather_file+'.epw' + idf.epw = Weather_file if '.epw' in Weather_file[:-4] else Weather_file+'.epw' location = idf.idfobjects['SITE:LOCATION'][0] #there might be some way of taking the information from the weather file directly in the idf object... location.Name = Weather_file - location.Latitude = 59.65 - location.Longitude = 17.95 - location.Time_Zone = +1 - location.Elevation = 61 + location.Latitude = building.Latitude + location.Longitude = building.Longitude + location.Time_Zone = building.Time_Zone + location.Elevation = building.Elevation ground_Temp = idf.newidfobject('SITE:GROUNDTEMPERATURE:BUILDINGSURFACE') - ground_Temp.January_Ground_Temperature = 15 - ground_Temp.February_Ground_Temperature = 15 - ground_Temp.March_Ground_Temperature = 15 - ground_Temp.April_Ground_Temperature = 15 - ground_Temp.May_Ground_Temperature = 15 - ground_Temp.June_Ground_Temperature = 15 - ground_Temp.July_Ground_Temperature = 15 - ground_Temp.August_Ground_Temperature = 15 - ground_Temp.September_Ground_Temperature = 15 - ground_Temp.October_Ground_Temperature = 15 - ground_Temp.November_Ground_Temperature = 15 - ground_Temp.December_Ground_Temperature = 15 + ground_Temp.January_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.February_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.March_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.April_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.May_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.June_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.July_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.August_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.September_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.October_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.November_Ground_Temperature = building.YearRoundGroundTemp + ground_Temp.December_Ground_Temperature = building.YearRoundGroundTemp DesignDay= idf.idfobjects['SIZINGPERIOD:DESIGNDAY'] for Obj in DesignDay: diff --git a/CoreFiles/setConfig.py b/CoreFiles/setConfig.py new file mode 100644 index 0000000..eb641ca --- /dev/null +++ b/CoreFiles/setConfig.py @@ -0,0 +1,184 @@ +# @Author : Xavier Faure +# @Email : xavierf@kth.se + +import yaml, os +import distutils.spawn + + +def is_tool(name): +#it will return the path of the executable or None if not installed + return distutils.spawn.find_executable(name) is not None + +def read_yaml(file_path): + with open(file_path, "r") as f: + config = yaml.safe_load(f) + return config + +def check4localConfig(config,path): + Liste = os.listdir(path) + new_config = config + filefound = False + msg = False + for file in Liste: + if '.yml' in file: + if file != 'LocalConfig_Template.yml': + if filefound: + msg = '/!\ More than one *.yml file other than the template was found' + else: + localConfig = read_yaml(os.path.join(path,file)) + filefound = os.path.join(path,file) + if not filefound: + localConfig = read_yaml(os.path.join(path, 'LocalConfig_Template.yml')) + filefound = os.path.join(path,'LocalConfig_Template.yml') + new_config, msg1 = ChangeConfigOption(config, localConfig) + return new_config, filefound, msg1 if not msg else msg + +def ChangeConfigOption(config,localConfig): + msg = False + for Mainkey in localConfig.keys(): + if type(localConfig[Mainkey]) == dict: + for subkey1 in localConfig[Mainkey].keys(): + if type(localConfig[Mainkey][subkey1]) == dict: + for subkey2 in localConfig[Mainkey][subkey1].keys(): + if type(localConfig[Mainkey][subkey1][subkey2]) == dict: + for subkey3 in localConfig[Mainkey][subkey1][subkey2].keys(): + if subkey3 not in config[Mainkey][subkey1][subkey2].keys(): + msg = '[Warning Config] '+Mainkey +' : '+ subkey1 +' : '+ subkey2 + ' : '+ subkey3+\ + ' is unknown from the DefaultConfig.yml.It will be ignored.' + else: + config[Mainkey][subkey1][subkey2][subkey3] = localConfig[Mainkey][subkey1][subkey2][subkey3] + else: + if subkey2 not in config[Mainkey][subkey1].keys(): + msg = '[Warning Config] '+Mainkey +' : '+ subkey1 +' : '+subkey2 + ' is unknown from the DefaultConfig.yml.It will be ignored.' + else: + config[Mainkey][subkey1][subkey2] = localConfig[Mainkey][subkey1][subkey2] + else: + if subkey1 not in config[Mainkey].keys(): + msg = '[Warning Config] '+Mainkey +' : '+subkey1 + ' is unknown from the DefaultConfig.yml.It will be ignored.' + else: + config[Mainkey][subkey1] = localConfig[Mainkey][subkey1] + else: + if Mainkey not in config.keys(): + msg = '[Warning Config] '+ Mainkey+ ' is unknown from the DefaultConfig.yml.It will be ignored.' + else: + config[Mainkey] = localConfig[Mainkey] + return config,msg + +def checkUnit(key): + if type(key) == list: + if len(key) ==0 : + return [] + elif type(key[0]) == list: + return [type(a) for b in key for a in b] + else: + return [type(a) for a in key] + else: + return [type(key)] + +def checkConfigUnit(config,Unit): + for Mainkey in Unit.keys(): + if type(Unit[Mainkey]) == dict: + for subkey1 in Unit[Mainkey].keys(): + if type(Unit[Mainkey][subkey1]) == dict: + for subkey2 in Unit[Mainkey][subkey1].keys(): + if type(Unit[Mainkey][subkey1][subkey2]) == dict: + for subkey3 in Unit[Mainkey][subkey1][subkey2].keys(): + test = checkUnit(config[Mainkey][subkey1][subkey2][subkey3]) + check = [eval(a) for a in Unit[Mainkey][subkey1][subkey2][subkey3]] + if False in [ch in check for ch in test] and test: + msg = (Mainkey+' : '+subkey1+' : '+subkey2+' : '+subkey3+' : ' + + str(config[Mainkey][subkey1][subkey2][subkey3])+ + 'is not conform with input type, Please check the config.yml file') + return msg + else: + test = checkUnit(config[Mainkey][subkey1][subkey2]) + check = [eval(a) for a in Unit[Mainkey][subkey1][subkey2]] + if False in [ch in check for ch in test] and test: + msg = (Mainkey + ' : ' + subkey1 + ' : ' + subkey2 +' : ' + + str(config[Mainkey][subkey1][subkey2]) + + ' is not conform with input type, Please check the config.yml file') + return msg + else: + test = checkUnit(config[Mainkey][subkey1]) + check = [eval(a) for a in Unit[Mainkey][subkey1]] + if False in [ch in check for ch in test] and test: + msg = (Mainkey + ' : ' + subkey1 +' : '+ + str(config[Mainkey][subkey1]) + ' is not conform with input type, Please check the config.yml file') + return msg + else: + test = checkUnit(config[Mainkey]) + check = [eval(a) for a in Unit[Mainkey]] + if [ch not in check for ch in test]: + msg = (Mainkey + ' : '+ str(config[Mainkey])+' is not conform with input type, Please check the config.yml file') + return msg + return config + +def checkGlobalConfig(config): + #lets check for the paths + if not is_tool(os.path.join(config['0_APP']['PATH_TO_ENERGYPLUS'],'energyplus')): + print(' /!\ ERROR /!\ ') + print('It seems that the path to EnergyPlus is missing, please specify it in your local.yml') + return 'EnergyPlus path',False + #lets check for the weather file needed for EnergyPlus + if not os.path.isfile(os.path.join(os.path.abspath(config['0_APP']['PATH_TO_ENERGYPLUS']),config['3_SIM']['1_WeatherData']['WeatherDataFile'])): + print(' /!\ ERROR /!\ ') + print('It seems that the given Weatherfile to EnergyPlus is missing') + print('Please check if : '+config['3_SIM']['1_WeatherFile']['Loc'] +' is present in : '+os.path.abspath(config['0_APP']['PATH_TO_ENERGYPLUS'])) + return 'EnergyPlus Weather path',False + #lets check for the geojsonfile: + ok = [] + if os.path.isdir(os.path.abspath(config['1_DATA']['PATH_TO_DATA'])): + liste = os.listdir(config['1_DATA']['PATH_TO_DATA']) + ok = [file for file in liste if '.geojson' in file] + else: + if '.geojson' in config['1_DATA']['PATH_TO_DATA']: + ok = True + if not ok: + return 'DATA path',False + config,SepThreads = checkChoicesCombinations(config) + return config,SepThreads + +def checkParamtricSimCases(config): + SepThreads = False + errormsg = False + if len(config['2_CASE']['1_SimChoices']['VarName2Change']) > 0: + if type(config['2_CASE']['1_SimChoices']['VarName2Change']) != list: + errormsg = '/!\ The VarName2Change must be a list either empty or a list of strings' + return errormsg, SepThreads + if len(config['2_CASE']['1_SimChoices']['VarName2Change']) > len(config['2_CASE']['1_SimChoices']['Bounds']) or \ + len(config['2_CASE']['1_SimChoices']['VarName2Change']) > len( + config['2_CASE']['1_SimChoices']['ParamMethods']): + errormsg = '/!\ VarName2Change [list of str], Bounds [list of lists of float or int] and ParamMethods [list of str] must have the same length' + return errormsg,SepThreads + else: + for idx, key in enumerate(config['2_CASE']['1_SimChoices']['VarName2Change']): + if type(config['2_CASE']['1_SimChoices']['Bounds'][idx]) != list: + errormsg = '/!\ Bounds must be a list of lists of 2 values for each VarName2Change' + return errormsg,SepThreads + elif config['2_CASE']['1_SimChoices']['Bounds'][idx][1] < \ + config['2_CASE']['1_SimChoices']['Bounds'][idx][0]: + errormsg = '/!\ Bounds must be [lower bound, upper bounds] in this order' + return errormsg,SepThreads + if config['2_CASE']['1_SimChoices']['NbRuns'] > 1: + SepThreads = True + if config['2_CASE']['2_AdvancedChoices']['CreateFMU']: + errormsg = '/!\ It is asked to ceate FMUs but more than one simulation per building is asked...' + return errormsg, SepThreads + if not config['2_CASE']['1_SimChoices']['VarName2Change'] or not config['2_CASE']['1_SimChoices']['Bounds']: + if not config['2_CASE']['2_AdvancedChoices']['FromPosteriors']: + errormsg = '/!\ It is asked to make several runs but no variable is specified with range of variation' + return errormsg, SepThreads + return errormsg, SepThreads + +def checkChoicesCombinations(config): + errormsg, SepThreads = checkParamtricSimCases(config) + if errormsg: + print('### INPUT ERROR ### ') + print(errormsg) + return 'Choices combination issue', SepThreads + return config,SepThreads + +# path2file = 'C:\\Users\\xav77\\Documents\\FAURE\\prgm_python\\UrbanT\\Eplus4Mubes\\MUBES_UBEM\\CoreFiles' +# path2read = os.path.join(path2file,'DefaultConfigTest.yml') +# +# tutu = read_yaml(path2read) \ No newline at end of file diff --git a/Minneberg.jpg b/Minneberg.jpg index 640e8f4..247d546 100644 Binary files a/Minneberg.jpg and b/Minneberg.jpg differ diff --git a/ModelerFolder/FMPySimPlayGroundEx1.py b/ModelerFolder/FMPySimPlayGroundEx1.py index c4c6ba9..8db88ef 100644 --- a/ModelerFolder/FMPySimPlayGroundEx1.py +++ b/ModelerFolder/FMPySimPlayGroundEx1.py @@ -4,7 +4,6 @@ on the number of FMU considered in total.""" import os,sys - from fmpy import * #from fmpy.fmi1 import FMU1Slave from fmpy.fmi1 import fmi1OK @@ -13,11 +12,13 @@ from fmpy.simulation import instantiate_fmu path2addgeom = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())),'geomeppy') sys.path.append(path2addgeom) -sys.path.append('..') +sys.path.append(os.path.dirname(os.getcwd())) import shutil import pickle import time as timedelay from CoreFiles import LaunchSim as LaunchSim +import CoreFiles.setConfig as setConfig +from BuildObject.BuildingObject import Building ##Callback function required to avoid having the prnted message when everything goes fine with 2.0 ! @@ -82,6 +83,7 @@ def InstAndInitiV2(filelist,VarNames,start_time,stop_time) : for variable in model_description.modelVariables: vrs[variable.name] = variable.valueReference FMUElement[FMUKeyName]['Exch_Var'] = vrs + FMUElement[FMUKeyName]['fmu'] = instantiate_fmu(FMUElement[FMUKeyName]['unzipdir'], model_description, fmi_type='CoSimulation', visible=False, debug_logging=False, logger=log_message2, @@ -165,11 +167,72 @@ def CleanUpSimRes(work_dir,keepLogFolder = False): #unable to erase the fmu extracted folder as the dll is still open at this stage of the code....why ? still weird to me #shutil.rmtree(buildName) -if __name__ == '__main__': - MainPath = os.getcwd() - SavedFolder = 'MUBES_SimResults/ForTest' - work_dir = os.path.normcase( - os.path.join(os.path.dirname(os.path.dirname(MainPath)), SavedFolder)) +def Read_Arguments(): + #these are defaults values: + Config2Launch = [] + CaseNameArg =[] + # Get command-line options. + lastIdx = len(sys.argv) - 1 + currIdx = 1 + while (currIdx < lastIdx): + currArg = sys.argv[currIdx] + if (currArg.startswith('-yml')): + currIdx += 1 + Config2Launch = sys.argv[currIdx] + if (currArg.startswith('-Case')): + currIdx += 1 + CaseNameArg = sys.argv[currIdx] + currIdx += 1 + return Config2Launch,CaseNameArg + +def getPathList(config): + CaseName = config['2_CASE']['0_GrlChoices']['CaseName'].split(',') + path = [] + Names4Plots = [] + congifPath = os.path.abspath(os.path.join(config['0_APP']['PATH_TO_RESULTS'],CaseName[0])) + if not os.path.exists(congifPath): + print('Sorry, the folder '+CaseName[0]+' does not exist...use -Case or -yml option or change your localConfig.yml') + sys.exit() + fmufound = False + liste = os.listdir(congifPath) + for file in liste: + if '.fmu' in file[-4:]: + fmufound = True + break + if not fmufound: + print('Sorry, but no .fmu were found in ' + str(CaseName[0])) + sys.exit() + else: + path.append(congifPath) + if len(CaseName)>1: + print('Sorry, but only one CaseName is allowed from now for fmu cosimulation. '+CaseName+' were given as inputs.') + sys.exit() + return path[0],CaseName[0] + +if __name__ == '__main__' : + + ConfigFromArg,CaseNameArg = Read_Arguments() + config = setConfig.read_yaml(os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfig.yml')) + configUnit = setConfig.read_yaml( + os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfigKeyUnit.yml')) + config, filefound, msg = setConfig.check4localConfig(config, os.getcwd()) + #config['2_CASE']['0_GrlChoices']['CaseName'] = 'Simple' + + if CaseNameArg: + config['2_CASE']['0_GrlChoices']['CaseName'] = CaseNameArg + work_dir,CaseName = getPathList(config) + elif type(ConfigFromArg) == str: + if ConfigFromArg[-4:] == '.yml': + localConfig = setConfig.read_yaml(ConfigFromArg) + config = setConfig.ChangeConfigOption(config, localConfig) + work_dir,CaseName = getPathList(config) + else: + print('[Unknown Argument] Please check the available options for arguments : -yml or -Case') + sys.exit() + else: + work_dir,CaseName = getPathList(config) + print('[Studied Results Folder] '+str(CaseName)) + os.chdir(work_dir) filelist = os.listdir(work_dir) start_time = 0*24*3600 diff --git a/ModelerFolder/FMPySimPlayGroundEx2.py b/ModelerFolder/FMPySimPlayGroundEx2.py index f02afeb..9f08e4e 100644 --- a/ModelerFolder/FMPySimPlayGroundEx2.py +++ b/ModelerFolder/FMPySimPlayGroundEx2.py @@ -18,6 +18,8 @@ import time as timedelay from CoreFiles import LaunchSim as LaunchSim from ReadResults import Utilities +from BuildObject.BuildingObject import Building +import CoreFiles.setConfig as setConfig @@ -99,7 +101,7 @@ def InstAndInitiV2(filelist,VarNames,start_time,stop_time) : FMUElement[FMUKeyName]['fmu'].exitInitializationMode() return FMUElement -def LaunchFMU_Sim(FMUElement,VarNames, start_time,stop_time,step_size): +def LaunchFMU_Sim(FMUElement,VarNames, start_time,stop_time,step_size,MainPath): time = start_time day = 0 SetPoints = {} @@ -120,8 +122,7 @@ def LaunchFMU_Sim(FMUElement,VarNames, start_time,stop_time,step_size): extraVar = ['nbAppartments'] Res = Utilities.GetData(work_dir, extraVar) - WatertapsFile = os.path.normcase(os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), - 'MUBES_UBEM/ExternalFiles/mDHW_Sum_over_40.txt')) + WatertapsFile = os.path.join(os.path.dirname(MainPath),'ExternalFiles/mDHW_Sum_over_40.txt') with open(WatertapsFile, 'r') as handle: FileLines = handle.readlines() Watertaps = [] @@ -153,7 +154,8 @@ def LaunchFMU_Sim(FMUElement,VarNames, start_time,stop_time,step_size): FMUElement[key]['fmu'].setReal([FMUElement[key]['Exch_Var']['TempSetPoint']], [SetPoints[key][-1]]) FMUElement[key]['fmu'].setReal([FMUElement[key]['Exch_Var']['IntLoadPow']], [IntLoad[key][-1]]) FMUElement[key]['fmu'].setReal([FMUElement[key]['Exch_Var']['WaterTap_m3_s']], - [watercurrenttaps * Res['nbAppartments'][Res['SimNum'].index(key)]]) + [watercurrenttaps * Res['nbAppartments'] + [Res['BldSimName'].index(FMUElement[key]['fmu'].instanceName)]]) FMUElement[key]['fmu'].doStep(currentCommunicationPoint=time, communicationStepSize=step_size) #lets catch the outputs (even if not used in this example, it could be used to control the next inputs) @@ -190,11 +192,72 @@ def CleanUpSimRes(work_dir,keepLogFolder = False): #unable to erase the fmu extracted folder as the dll is still open at this stage of the code....why ? still weird to me #shutil.rmtree(buildName) -if __name__ == '__main__': +def Read_Arguments(): + #these are defaults values: + Config2Launch = [] + CaseNameArg =[] + # Get command-line options. + lastIdx = len(sys.argv) - 1 + currIdx = 1 + while (currIdx < lastIdx): + currArg = sys.argv[currIdx] + if (currArg.startswith('-yml')): + currIdx += 1 + Config2Launch = sys.argv[currIdx] + if (currArg.startswith('-Case')): + currIdx += 1 + CaseNameArg = sys.argv[currIdx] + currIdx += 1 + return Config2Launch,CaseNameArg + +def getPathList(config): + CaseName = config['2_CASE']['0_GrlChoices']['CaseName'].split(',') + path = [] + Names4Plots = [] + congifPath = os.path.abspath(os.path.join(config['0_APP']['PATH_TO_RESULTS'],CaseName[0])) + if not os.path.exists(congifPath): + print('Sorry, the folder '+CaseName[0]+' does not exist...use -Case or -yml option or change your localConfig.yml') + sys.exit() + fmufound = False + liste = os.listdir(congifPath) + for file in liste: + if '.fmu' in file[-4:]: + fmufound = True + break + if not fmufound: + print('Sorry, but no .fmu were found in ' + str(CaseName[0])) + sys.exit() + else: + path.append(congifPath) + if len(CaseName)>1: + print('Sorry, but only one CaseName is allowed from now for fmu cosimulation. '+CaseName+' were given as inputs.') + sys.exit() + return path[0],CaseName[0] + +if __name__ == '__main__' : + MainPath = os.getcwd() - SavedFolder = 'MUBES_SimResults/ForTest' - work_dir = os.path.normcase( - os.path.join(os.path.dirname(os.path.dirname(MainPath)), SavedFolder)) + ConfigFromArg,CaseNameArg = Read_Arguments() + config = setConfig.read_yaml(os.path.join(os.path.dirname(MainPath), 'CoreFiles', 'DefaultConfig.yml')) + configUnit = setConfig.read_yaml( + os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfigKeyUnit.yml')) + config, filefound, msg = setConfig.check4localConfig(config, MainPath) + #config['2_CASE']['0_GrlChoices']['CaseName'] = 'Simple' + + if CaseNameArg: + config['2_CASE']['0_GrlChoices']['CaseName'] = CaseNameArg + work_dir,CaseName = getPathList(config) + elif type(ConfigFromArg) == str: + if ConfigFromArg[-4:] == '.yml': + localConfig = setConfig.read_yaml(ConfigFromArg) + config = setConfig.ChangeConfigOption(config, localConfig) + work_dir,CaseName = getPathList(config) + else: + print('[Unknown Argument] Please check the available options for arguments : -yml or -Case') + sys.exit() + else: + work_dir,CaseName = getPathList(config) + print('[Studied Results Folder] '+str(CaseName)) os.chdir(work_dir) filelist = os.listdir(work_dir) start_time = 0*24*3600 @@ -210,6 +273,6 @@ def CleanUpSimRes(work_dir,keepLogFolder = False): except: FMUElement = InstAndInitiV2(filelist,VarNames, start_time, stop_time) print('FMU 2.0 used') - LaunchFMU_Sim(FMUElement,VarNames, start_time, stop_time, step_size) + LaunchFMU_Sim(FMUElement,VarNames, start_time, stop_time, step_size,MainPath) CleanUpSimRes(work_dir, keepLogFolder=True) diff --git a/ModelerFolder/LocalConfig_Template.yml b/ModelerFolder/LocalConfig_Template.yml new file mode 100644 index 0000000..9c15d15 --- /dev/null +++ b/ModelerFolder/LocalConfig_Template.yml @@ -0,0 +1,26 @@ +0_APP: + PATH_TO_ENERGYPLUS: C:/EnergyPlusV9-1-0 #[Str] this is for energyplus.exe path + PATH_TO_RESULTS: ../../MUBES_SimResults/ #[Str] this is the main folder where the results will be stored. it will be created if not exists +1_DATA: + PATH_TO_DATA : ../ModelerFolder/Minneberg_Buildings_v1.geojson #[Str] Input file or folder in case of several files +2_CASE: + 0_GrlChoices : + CaseName : 'ForTest' #[Str] Name of the studied Case, a subfolder of this name will be created in the main folder defined in '0_APP:PATH_TO_RESULTS' + OutputsFile : 'Outputs_Template.txt' #[Str] Output file to identify the outputs the modeller would want and their frequency + RefreshFolder : True #[Bool] True : the folder CaseName, if exist, is emptied whatever could be in it + MakePlotsOnly : False #[Bool] True : a figure will be created with the buildings specified in BldID + MakePlotsPerBld : False #[Bool] True : a figure is created for each building including it's shadowing surfaces (if present) + Verbose : True #[Bool] True : Gives messages in the prompt window along the ongoing process + CPUusage : 0.8 #[Float] Factor of CPU allowed to be used during parallel computing + DebugMode : False #[Bool] The log file will be feeded by detailed information along the process + 1_SimChoices : + CorePerim : False #[Bool] True : core and perimeter zone thermal modelling + FloorZoning : False #[Bool] True : each floor is considered as a zone or for core zone construction +3_SIM : + #files are needed to be located in the eather folder of EnergyPlus asthe same path is used afterward to launch the simulation + 1_WeatherData : + WeatherDataFile : 'WeatherData/USA_CA_San.Francisco.Intl.AP.724940_TMY3.epw' #[Str] Need to be a epw file. This one is given because installed by default when installing EnergyPlus + Latitude: 37.62 #[Float] Latitude of the location (+ is North, - is South) [-90,+90] + Longitude: -122.40 #[Float] Longitude of the location (- is West, + is East [-180,180] + Time_Zone: -8.0 #[Float] Time relative to GMT [-12,14] + Elevation: 2.0 #[Float] Elevation diff --git a/ModelerFolder/MakeShadowingWallFile.py b/ModelerFolder/MakeShadowingWallFile.py new file mode 100644 index 0000000..8494ab8 --- /dev/null +++ b/ModelerFolder/MakeShadowingWallFile.py @@ -0,0 +1,496 @@ +# @Author : Xavier Faure +# @Email : xavierf@kth.se + +import os +import sys +import re +import yaml +import shutil + +# #add the required path for geomeppy special branch +path2addgeom = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())),'geomeppy') +sys.path.append(path2addgeom) +#add the reauired path for all the above folder +sys.path.append('..') +MUBES_Paths = os.path.normcase(os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'MUBES_UBEM')) +sys.path.append(MUBES_Paths) + +import CoreFiles.GeneralFunctions as GrlFct +from geomeppy.geom.polygons import Polygon2D +import CoreFiles.setConfig as setConfig +from BuildObject.BuildingObject import Building +import numpy as np +import json +from shapely.geometry import Polygon, LineString, Point + +import matplotlib.pyplot as plt + +def MakeIntermediateFig(bld, bld1, bldunit,StartingEdge, EndingEdge): + plt.figure() + x,y = zip(*bld) + plt.plot(x,y,'k-') + x, y = zip(*bld1) + plt.plot(x, y, 'b-') + x, y = zip(*bldunit) + plt.plot(x, y, 'y-') + x, y = zip(*StartingEdge) + plt.plot(x, y, 's-') + x, y = zip(*EndingEdge) + plt.plot(x, y, 's-') + plt.show() + +def Read_Arguments(): + #these are defaults values: + Config2Launch = [] + ShadeLim = [] + # Get command-line options. + lastIdx = len(sys.argv) - 1 + currIdx = 1 + while (currIdx < lastIdx): + currArg = sys.argv[currIdx] + if (currArg.startswith('-CONFIG')): + currIdx += 1 + Config2Launch = json.loads(sys.argv[currIdx]) + if (currArg.startswith('-yml')): + currIdx += 1 + Config2Launch = sys.argv[currIdx] + if (currArg.startswith('-geojson')): + currIdx += 1 + Config2Launch = sys.argv[currIdx] + if (currArg.startswith('-ShadeLimits')): + currIdx += 1 + ShadeLim = sys.argv[currIdx] + currIdx += 1 + return Config2Launch, ShadeLim + +def CreatePolygonEnviro(GlobKey,config,WithBackSide = True): + PolygonEnviro = {} + MainPath = os.getcwd() + TotalSimDir = [] + for nbfile,keyPath in enumerate(GlobKey): + #we need to create a temporary folder to stor the log file if needed + SimDir = os.path.join(os.path.dirname(keyPath['Buildingsfile']),'Temp') + if not os.path.isdir(SimDir): + os.mkdir(SimDir) + os.chdir(SimDir) + with open(os.path.join(SimDir, 'ConfigFile.yml'), 'w') as file: + documents = yaml.dump(config, file) + PolygonEnviro[nbfile] = {'Bld_ID': [], 'EdgesAndHeights': [], 'Bld_Height': [],'BlocNum':[], + 'AggregFootPrint':[],'FootPrint': [], 'BldNum': [], 'WindowSize': [], + 'Centroid': []} + PolygonEnviro[nbfile]['PathName'] = keyPath['Buildingsfile'] + Edges2Store = {} + NewEdges2Store = {} + DataBaseInput = GrlFct.ReadGeoJsonFile(keyPath) + Size = len(DataBaseInput['Build'])-1 + print('Studying buildings file : '+os.path.basename(keyPath['Buildingsfile'])) + print('Urban Area under construction with:') + for bldNum, Bld in enumerate(DataBaseInput['Build']): + print('\r', end='') + print('--building '+str(bldNum) +' / '+str(Size), end='', flush=True) + BldObj = Building('Bld'+str(bldNum), DataBaseInput, bldNum, SimDir,keyPath['Buildingsfile'],LogFile=[],PlotOnly=True, DebugMode=False) + BldObj = GrlFct.MakeAbsoluteCoord(BldObj,roundfactor=4) + BldID = BldObj.BuildID[BldObj.BuildID['BldIDKey']] + if WithBackSide: + Edges = getBlocEdgesAndHeights(BldObj, roundfactor=4) + for blocnum, bloc in enumerate(BldObj.footprint): + PolygonEnviro[nbfile]['FootPrint'].append(bloc) + PolygonEnviro[nbfile]['Bld_Height'].append(BldObj.BlocHeight[blocnum]) + PolygonEnviro[nbfile]['Bld_ID'].append(BldID) + PolygonEnviro[nbfile]['BldNum'].append(bldNum) + PolygonEnviro[nbfile]['BlocNum'].append(blocnum) + else: + Edges = getBldEdgesAndHeights(BldObj, roundfactor=4) + PolygonEnviro[nbfile]['AggregFootPrint'].append(BldObj.AggregFootprint) + PolygonEnviro[nbfile]['Bld_ID'].append(BldID) + PolygonEnviro[nbfile]['BldNum'].append(bldNum) + PolygonEnviro[nbfile]['BlocNum'].append(0) + PolygonEnviro[nbfile]['Bld_Height'].append(max(BldObj.BlocHeight)) + Edges2Store[bldNum] = Edges + print('\nUrban area constructed') + PolygonEnviro[nbfile]['EdgesAndHeights'] = Edges2Store + TotalSimDir.append(SimDir) + return PolygonEnviro,TotalSimDir + +def getBlocEdgesAndHeights(BldObj,roundfactor = 8): + EdgesAndHeights = {'Height':[],'Edge':[],'BlocNum':[]} + for idx,poly in enumerate(BldObj.footprint): + localBloc = Polygon2D(poly) + for edge in localBloc.edges: + EdgesAndHeights['Edge'].append([(round(x,roundfactor),round(y,roundfactor)) for x,y in edge.vertices]) + EdgesAndHeights['Height'].append(BldObj.BlocHeight[idx]) + EdgesAndHeights['BlocNum'].append(idx) + EdgesAndHeights['BldID']= BldObj.BuildID + return EdgesAndHeights + +def getBldEdgesAndHeights(BldObj,roundfactor = 8): + GlobalFootprint = Polygon2D(BldObj.AggregFootprint[:-1]) + EdgesHeights = {'Height': [], 'Edge': [], 'BlocNum': []} + for edge in GlobalFootprint.edges: + EdgesHeights['Edge'].append( + [(round(x, roundfactor), round(y, roundfactor)) for x, y in + edge.vertices]) + EdgesHeights['Height'].append(0) + EdgesHeights['BlocNum'].append(0) + for idx, poly in enumerate(BldObj.footprint): + localBloc = Polygon2D(poly) + for edge, edge_reversed in zip(localBloc.edges, localBloc.edges_reversed): + Heightidx1 = [idx for idx, val in enumerate(GlobalFootprint.edges) if edge == val] + Heightidx2 = [idx for idx, val in enumerate(GlobalFootprint.edges_reversed) if edge == val] + if Heightidx1 or Heightidx2: + Heigthidx = Heightidx1 if Heightidx1 else Heightidx2 + EdgesHeights['Height'][Heigthidx[0]] = BldObj.BlocHeight[idx] + EdgesHeights['BldID'] = BldObj.BuildID + return EdgesHeights + +def MakePointOutside(Edge,poly): + p1 = Edge[0] + p2 = Edge[1] + epsilon = 1e-2 + resolution = 3 + x = round((p2[0] + p1[0]) / 2,resolution) + epsilon + y = round((p2[1] + p1[1]) / 2,resolution) + epsilon + x1 = round((0.9*p2[0] + 0.1*p1[0]), resolution) + epsilon + y1 = round((0.9*p2[1] + 0.1*p1[1]), resolution) + epsilon + x2 = round((0.1*p2[0] + 0.9*p1[0]), resolution) + epsilon + y2 = round((0.1*p2[1] + 0.9*p1[1]), resolution) + epsilon + orientation = 1 + if Polygon(poly).contains(Point(x,y)): #helper_fcts.inside_polygon(x, y, np.array(poly), border_value=False): + x -= 2*epsilon + y -= 2*epsilon + x1 -= 2 * epsilon + y1 -= 2 * epsilon + x2 -= 2 * epsilon + y2 -= 2 * epsilon + orientation = -1 + return (x,y),(x1,y1),(x2,y2),orientation + +def getEdgeIdx(Matches,Edge,BldID,BldBloc): + p1 = Edge[0] + p2 = Edge[1] + edgeIdx = [idx for idx,Shade in enumerate(Matches.keys()) if \ + ([p1, p2] == Matches[Shade]['AbsCoord'] and BldID == Matches[Shade]['OwnerBld_ID'] and BldBloc == Matches[Shade]['OwnerBld_Bloc']) + or ([p2, p1] == Matches[Shade]['AbsCoord'] and BldID == Matches[Shade]['OwnerBld_ID'] and BldBloc == Matches[Shade]['OwnerBld_Bloc'])] + if len(edgeIdx)>1: + print('heu...error in the index caught forthe edges done or not...several locations') + return edgeIdx[0] if edgeIdx else -999 + +def isBldDone(Matches,EdgeIdx,BldID): + BldDone = BldID in Matches[EdgeIdx]['RecepientBld_ID'] + return BldDone + +def isBldBlocDone(Matches,EdgeIdx,BldID,Bloc): + BldDone = BldID in Matches[EdgeIdx]['RecepientBld_ID'] and Bloc in Matches[EdgeIdx]['RecepientBlocNum'] + return BldDone + + +def signed_area(pr2): + """Return the signed area enclosed by a ring using the linear time + algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0 + indicates a counter-clockwise oriented ring.""" + xs, ys = map(list, zip(*pr2)) + xs.append(xs[1]) + ys.append(ys[1]) + return sum(xs[i] * (ys[i + 1] - ys[i - 1]) for i in range(1, len(pr2))) / 2.0 + +def getLineCoef(Edge): + p1 = Edge[0] + p2 = Edge[1] + try : a = (p2[1] - p1[1]) / (p2[0] - p1[0]) + except ZeroDivisionError : a = 1e9 + b = p2[1] - a * p2[0] + return a,b + +def checkEdgeOrientation(Edge,Point): + a,b = getLineCoef(Edge) + #make test with x + if Point[1]-(a * Point[0] + b)>0: + Orientation = 1 + else: + Orientation = -1 + return a,b,Orientation + +def checkVisibility(Target,a,b,Orientation): + TargetVisibility = [node[1] - a * node[0] - b for node in Target] + if (len([val for val in TargetVisibility if val < 0]) == len(Target) and Orientation > 0) or \ + (len([val for val in TargetVisibility if val > 0]) == len(Target) and Orientation < 0): + return False + else: + return True + +def feedMatches(Matches,Data,EdgeIdx,BldIdx,Raylength): + if not isBldDone(Matches, EdgeIdx, Data['Bld_ID'][BldIdx]): + Matches[EdgeIdx]['RecepientBld_ID'].append(Data['Bld_ID'][BldIdx]) + Matches[EdgeIdx]['RecepientBlocNum'].append(Data['BlocNum'][BldIdx]) + Matches[EdgeIdx]['RecepientBld_Height'].append(Data['Bld_Height'][BldIdx]) + Matches[EdgeIdx]['RecepientBld_Dist'].append(Raylength) + return Matches + +def isIntersection(bld,Rays): + return [1 if Polygon(bld).intersection(Ray) else 0 for Ray in Rays] + +def getAngle(a,a1): + import math + angle = abs((a1 - a) / (1 + a * a1)) + return math.atan(angle) * 180 / 3.14159 + +def checkEdgesOrientation(s1,s2,g1,g2): + a, b = getLineCoef([s1,g1]) + a1, b1 = getLineCoef([s1, g2]) + a2, b2 = getLineCoef([s2, g1]) + a3, b3 = getLineCoef([s2, g2]) + Angles1 = getAngle(a, a1) + Angles2 = getAngle(a2, a3) + return Angles1,Angles2 + +def computMatchesForAllBuildings(Data,ThresholdDist = 200): + NewBld, Matches = prepareElements(Data,'FootPrint') + for bldidx,bld in enumerate(NewBld): + print('\r', end='') + print('---' + str(round(100*bldidx/(len(NewBld)-1),1)) + ' % has been treated', end='', flush=True) + for bldidx1, bld1 in enumerate(NewBld): + print('\r', end='') + if bldidx1 % 4 == 0: + print('-. ' + str(round(100 * bldidx / (len(NewBld) - 1), 1)) + ' % has been treated', end='', + flush=True) + else: + print('-.. ' + str(round(100 * bldidx / (len(NewBld) - 1), 1)) + ' % has been treated', end='', + flush=True) + if Data['Bld_ID'][bldidx] == Data['Bld_ID'][bldidx1]: + continue #this means that we are looking at the same building (but two different blocs) + Dist = Polygon(bld).centroid.distance(Polygon(bld1).centroid) + if Polygon(bld).centroid.distance(Polygon(bld1).centroid) < ThresholdDist: + for edge in Matches.keys(): + if Data['Bld_ID'][bldidx1] == Matches[edge]['OwnerBld_ID']: + Matches = feedMatches(Matches, Data, edge, bldidx,Dist) + + return Matches,NewBld + +def prepareElements(Data,FootKey): + NewBld = [] + ShadowingHeight = [] + for bld in Data[FootKey]: + ShadowingHeight.append(0) + if bld[0]==bld[-1]: + NewBld.append(bld[:-1]) + else: + NewBld.append(bld) + #lets compute distances now with starting points from all segments for all polygons and ending point all semgent of all polygons except the current one + print('Computing visible surfaces...') + offset = 0 + Matches = {} + for i in Data['EdgesAndHeights'].keys(): + for j,edge in enumerate(Data['EdgesAndHeights'][i]['Edge']): + Matches[i+j+offset] = {'AbsCoord': edge, + 'OwnerBld_ID': Data['EdgesAndHeights'][i]['BldID'][Data['EdgesAndHeights'][i]['BldID']['BldIDKey']], + 'OwnerBld_Bloc': Data['EdgesAndHeights'][i]['BlocNum'][j], + 'RecepientBld_ID': [], + 'RecepientBlocNum':[], + 'RecepientBld_Height': [], + 'RecepientBld_Dist': [], + 'Height': Data['EdgesAndHeights'][i]['Height'][j], + 'Rays': []} + offset += j + return NewBld,Matches + +def computMatchesWithbackSideSurfaces(Data,WithBackSide = True): + if WithBackSide: + NewBld,Matches = prepareElements(Data,'FootPrint') + else: + NewBld, Matches = prepareElements(Data, 'AggregFootPrint') + for bldidx,bld in enumerate(NewBld[:-1]): + print('\r', end='') + print('---' + str(round(100*bldidx/(len(NewBld)-1),1)) + ' % has been treated', end='', flush=True) + offsetidx = bldidx +1 + for bldidx1, bld1 in enumerate(NewBld[offsetidx:]): + if Data['BldNum'][bldidx1 + offsetidx] in [5] and Data['BldNum'][bldidx] in [0]: + tutu = 1 + if Data['Bld_ID'][bldidx]==Data['Bld_ID'][bldidx1+offsetidx]: + continue #this means that we are looking at the same building (but two different blocs) + #lets grab the coarse box around the building + box = Polygon(bld1).minimum_rotated_rectangle + bld1Box = [(x,y) for x,y in box.exterior.coords][:-1] #[(min(x),min(y)),(min(x),max(y)),(max(x),max(y)),(max(x),min(y))] + print('\r', end='') + if bldidx1%4==0: + print('-. ' + str(round(100 * bldidx / (len(NewBld) - 2), 1)) + ' % has been treated', end='', flush=True) + else: + print('-.. ' + str(round(100 * bldidx / (len(NewBld) - 2), 1)) + ' % has been treated', end='', + flush=True) + for seg,vertex in enumerate(bld): + StartingEdge = [vertex, bld[(seg + 1) % len(bld)]] + StartingEdgeIdx = getEdgeIdx(Matches, StartingEdge, Data['Bld_ID'][bldidx], Data['BlocNum'][bldidx]) + #lets make the sarting point + start_coordinates,s1,s2,StartOrientation = MakePointOutside(StartingEdge, np.array(bld)) + a, b, Orientation = checkEdgeOrientation(StartingEdge,start_coordinates) + #check if the building or a piece of it is within the 180deg view from the edge + if not checkVisibility(bld1Box, a, b, Orientation): + continue + Rays = [] + for node in bld1Box: + Rays.append(LineString([s1, node])) + Rays.append(LineString([s2, node])) + if sum(isIntersection(bld,Rays)) == len(Rays): + continue + for seg1, vertex1 in enumerate(bld1): + EndingEdge = [vertex1, bld1[(seg1 + 1) % len(bld1)]] + #define if there is a need to go into this edge + if not checkVisibility(EndingEdge, a, b, Orientation): + continue + EndingEdgeIdx = getEdgeIdx(Matches, EndingEdge, Data['Bld_ID'][bldidx1 + offsetidx],Data['BlocNum'][bldidx1 + offsetidx]) + if StartingEdge == EndingEdge or StartingEdge[::-1] == EndingEdge: + #in case of adjacent walls + Matches = feedMatches(Matches, Data, StartingEdgeIdx, bldidx1 + offsetidx, 0) + Matches = feedMatches(Matches, Data, EndingEdgeIdx, bldidx, 0) + continue + if isBldDone(Matches, StartingEdgeIdx, Data['Bld_ID'][bldidx1 + offsetidx]) and isBldDone(Matches, EndingEdgeIdx, Data['Bld_ID'][bldidx]): + continue + goal_coordinates,g1,g2,GoalOrientation = MakePointOutside(EndingEdge, np.array(bld1)) + #the five Ray below are to consider either the middle point of the edge point (being 5% form the edges (see MakePointOutside()) + Raym = LineString([start_coordinates, goal_coordinates]) + Rays = [LineString([s1, g1]), LineString([s1, g2]), LineString([s2, g1]), LineString([s2, g2])] + #lets first check for intersection with it's own building bloc (if it's the case, no need to go along all the others + InterBld = isIntersection(bld,Rays) + InterBld1 = isIntersection(bld1, Rays) + InterBldBld1 = [a+b for a,b in zip(InterBld,InterBld1)] + if sum(InterBld1) == len(Rays) or sum(InterBld) == len(Rays) or 0 not in InterBldBld1: + continue + angles1, angles2 = checkEdgesOrientation(s1, s2, g1, g2) + if angles1 < 0.01 and angles2 < 0.01: + #MakeIntermediateFig(bld, bld1, NewBld[47], StartingEdge, EndingEdge) + continue + DirectVisible = True + OneLineHiddenBldIdx = [] + HiddenRay = [] + #Rays from Vertex now to avoid parallel intermediate building being missed + #doesn't work because we need to check as well same buildings for overlapping + RayVers = [LineString([StartingEdge[0], EndingEdge[0]]), LineString([StartingEdge[0], EndingEdge[1]]), + LineString([StartingEdge[1], EndingEdge[0]]), LineString([StartingEdge[1], EndingEdge[1]])] + for idxbetween,bldbeween in enumerate(NewBld): + Inter = isIntersection(bldbeween,Rays) + if sum(Inter) == len(Rays) and not WithBackSide: + DirectVisible = False + OneLineHiddenBldIdx = [] + HiddenRay = [] + break + if sum(Inter) > 0 : + #this means that at least one oint as a direct view + OneLineHiddenBldIdx.append(idxbetween) + HiddenRay.append(Inter) + DirectVisible = False + if DirectVisible: + Matches = feedMatches(Matches,Data,StartingEdgeIdx,bldidx1 + offsetidx,Raym.length) + Matches = feedMatches(Matches, Data, EndingEdgeIdx, bldidx, Raym.length) + else: + FullCoverBld = [OneLineHiddenBldIdx[idx] for idx, spot in enumerate(HiddenRay) if sum(spot) == len(Rays)] + NotFullIndex = [spot for spot in HiddenRay if sum(spot) < len(Rays)] + HeightFromFullCover = [0] + GlobalPartialHeight = [0] + if FullCoverBld: + Distance = [Polygon(NewBld[idxB]).distance(Point(start_coordinates)) for idxB in FullCoverBld] + HeightFromFullCover = [Data['Bld_Height'][idxB] for idxB in FullCoverBld] + ShadeOrder = sorted(range(len(Distance)), key=lambda k: Distance[k]) + if NotFullIndex: + if 0 not in sum(np.array(NotFullIndex)): #if one ray still goes to the target directly, no need to add a height filter on this one + PartialDist = [Polygon(NewBld[idxB]).distance(Point(start_coordinates)) for idxB in OneLineHiddenBldIdx if idxB not in FullCoverBld] + HeightFromPartialCover = [Data['Bld_Height'][idxB] for idxB in OneLineHiddenBldIdx if idxB not in FullCoverBld] + HeigthAdnRays = np.array([[HeightFromPartialCover[id]*j for j in ray] for id,ray in enumerate(NotFullIndex)]) + GlobalPartialHeight = np.max(HeigthAdnRays,axis = 0) + ShadeOrder = sorted(range(len(PartialDist)), key=lambda k: PartialDist[k]) + elif not WithBackSide: + Matches = feedMatches(Matches, Data, StartingEdgeIdx, bldidx1 + offsetidx, Raym.length) + Matches = feedMatches(Matches, Data, EndingEdgeIdx, bldidx, Raym.length) + if WithBackSide: + TotalShadowHeight = max(max(HeightFromFullCover),min(GlobalPartialHeight)) + if NotFullIndex or FullCoverBld: + if Data['Bld_Height'][bldidx1 + offsetidx] > TotalShadowHeight: + Matches = feedMatches(Matches, Data, EndingEdgeIdx, bldidx, Raym.length) + if Data['Bld_Height'][bldidx] > TotalShadowHeight: + Matches = feedMatches(Matches, Data, StartingEdgeIdx, bldidx1 + offsetidx,Raym.length) + else: + print('[Warnings] Some weird case is being encountered with bldidx, bldidx1, seg and seg1 = ',str([bldidx, bldidx1, seg, seg1])) + return Matches,NewBld + +def cleaningTempFolders(SimDir): + for Folder in SimDir: + #empyting the files being inside" + Liste = os.listdir(Folder) + for file in Liste: + os.remove(os.path.join(Folder,file)) + #remove the folder + os.rmdir(Folder) + +def SaveAndWrite(Matches,Data): + print('\nAll building treated') + j = json.dumps(Matches) + PathName = os.path.dirname(Data['PathName']) + FileName = os.path.basename(Data['PathName']) + with open(os.path.join(PathName,FileName[:FileName.index('.')]+'_Walls.json'), 'w') as f: + f.write(j) + # import pickle + # with open(os.path.join(PathName,FileName[:FileName.index('.')]+'_Walls.pickles'), 'wb') as handle: + # pickle.dump(Matches, handle, protocol=pickle.HIGHEST_PROTOCOL) + + +if __name__ == '__main__' : + MainPath = os.getcwd() + ConfigFromArg, ShadeLim = Read_Arguments() + WithBackSide = True + ComputAllSurf = False #default is compute with backside surfaces + if ShadeLim == 'AllSurf': + ComputAllSurf = True + elif ShadeLim == 'SimpleSurf': + WithBackSide = False + + config = setConfig.read_yaml(os.path.join(os.path.dirname(os.getcwd()),'CoreFiles','DefaultConfig.yml')) + configUnit = setConfig.read_yaml( + os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfigKeyUnit.yml')) + geojsonfile = False + if type(ConfigFromArg) == str and ConfigFromArg[-4:] == '.yml': + localConfig = setConfig.read_yaml(ConfigFromArg) + config = setConfig.ChangeConfigOption(config, localConfig) + elif type(ConfigFromArg) == str and ConfigFromArg[-8:] == '.geojson': + geojsonfile = True + elif ConfigFromArg: + config = setConfig.ChangeConfigOption(config, ConfigFromArg) + else: + config,filefound,msg = setConfig.check4localConfig(config, os.getcwd()) + if msg: print(msg) + print('[Config Info] Config completed by ' + filefound) + config = setConfig.checkConfigUnit(config, configUnit) + if type(config) != dict: + print('[Config Error] Something seems wrong : \n' + config) + sys.exit() + config, SepThreads = setConfig.checkGlobalConfig(config) + if type(config) != dict: + print('[Config Error] Something seems wrong in : ' + config) + sys.exit() + # the config file is now validated, lets vreate a smaller dict that will called along the process + + + Key2Aggregate = ['0_GrlChoices', '1_SimChoices', '2_AdvancedChoices'] + CaseChoices = {} + for key in Key2Aggregate: + for subkey in config['2_CASE'][key]: + CaseChoices[subkey] = config['2_CASE'][key][subkey] + if CaseChoices['Verbose']: print('[OK] Input config. info checked and valid.') + epluspath = config['0_APP']['PATH_TO_ENERGYPLUS'] + #a first keypath dict needs to be defined to comply with the current paradigme along the code + Buildingsfile = os.path.abspath(config['1_DATA']['PATH_TO_DATA']) + keyPath = {'epluspath': epluspath, 'Buildingsfile': Buildingsfile,'pythonpath': '','GeojsonProperties':''} + if geojsonfile: + keyPath['Buildingsfile'] = ConfigFromArg + #this function makes the list of dictionnary with single input files if several are present inthe sample folder + GlobKey, MultipleFiles = GrlFct.ListAvailableFiles(keyPath) + #this function creates the full pool to launch afterward, including the file name and which buildings to simulate + print('Urban Area is first build by aggregating all building in each geojson files') + PolygonEnviro,Folders2Clean = CreatePolygonEnviro(GlobKey,config,WithBackSide = WithBackSide) + print('Lets compute, for each building the shadowing surfaces from others') + for Enviro in PolygonEnviro: + if ComputAllSurf: + Matches, NewBld = computMatchesForAllBuildings(PolygonEnviro[Enviro]) + else: + Matches, NewBld = computMatchesWithbackSideSurfaces(PolygonEnviro[Enviro],WithBackSide=WithBackSide) + SaveAndWrite(Matches, PolygonEnviro[Enviro]) + os.chdir(MainPath) + cleaningTempFolders(Folders2Clean) + print('Wall file created in the same folder of the Building file. see ***.json file') \ No newline at end of file diff --git a/ModelerFolder/Minneberg_Wallsv1.geojson b/ModelerFolder/Minneberg_Walls_v1.geojson similarity index 100% rename from ModelerFolder/Minneberg_Wallsv1.geojson rename to ModelerFolder/Minneberg_Walls_v1.geojson diff --git a/ModelerFolder/Outputs_Template.txt b/ModelerFolder/Outputs_Template.txt index d426c53..8dc4844 100644 --- a/ModelerFolder/Outputs_Template.txt +++ b/ModelerFolder/Outputs_Template.txt @@ -23,7 +23,7 @@ Here is a list of the mains common ouputs Variable to output : add '## ' (double hashtag with space) in front of the variable you'd like to have in the outputs: Var Type (reported time step),Var Report Type,Variable Name [Units] -## Zone,Average,Site Outdoor Air Drybulb Temperature [C] +Zone,Average,Site Outdoor Air Drybulb Temperature [C] Zone,Average,Site Outdoor Air Dewpoint Temperature [C] Zone,Average,Site Outdoor Air Wetbulb Temperature [C] Zone,Average,Site Outdoor Air Humidity Ratio [kgWater/kgDryAir] @@ -119,7 +119,7 @@ Zone,Average,Zone Electric Equipment Latent Gain Rate [W] Zone,Sum,Zone Electric Equipment Lost Heat Energy [J] Zone,Average,Zone Electric Equipment Lost Heat Rate [W] Zone,Sum,Zone Electric Equipment Total Heating Energy [J] -## Zone,Average,Zone Electric Equipment Total Heating Rate [W] +Zone,Average,Zone Electric Equipment Total Heating Rate [W] Zone,Average,Mean Heated Zones Air Temperature [] Zone,Average,Total Building Heating Power [] Zone,Average,Zone Windows Total Transmitted Solar Radiation Rate [W] @@ -281,7 +281,7 @@ Zone,Average,Surface Storm Window On Off Status [] Zone,Average,Surface Window Blind Slat Angle [deg] Zone,Average,Zone Mean Radiant Temperature [C] Zone,Sum,Site Total Surface Heat Emission to Air [J] -## Zone,Average,Zone Mean Air Temperature [C] +Zone,Average,Zone Mean Air Temperature [C] Zone,Average,Zone Operative Temperature [C] Zone,Average,Zone Mean Air Dewpoint Temperature [C] Zone,Average,Zone Mean Air Humidity Ratio [kgWater/kgDryAir] @@ -343,7 +343,7 @@ HVAC,Average,Zone Predicted Moisture Load Moisture Transfer Rate [kgWater/s] HVAC,Average,Zone Predicted Moisture Load to Humidifying Setpoint Moisture Transfer Rate [kgWater/s] HVAC,Average,Zone Predicted Moisture Load to Dehumidifying Setpoint Moisture Transfer Rate [kgWater/s] Zone,Average,Zone Thermostat Control Type [] -## HVAC,Average,Zone Thermostat Heating Setpoint Temperature [C] +HVAC,Average,Zone Thermostat Heating Setpoint Temperature [C] HVAC,Average,Zone Thermostat Cooling Setpoint Temperature [C] Zone,Average,Zone Adaptive Comfort Operative Temperature Set Point [C] HVAC,Average,Zone Predicted Sensible Load Room Air Correction Factor [] @@ -378,7 +378,7 @@ HVAC,Average,Zone Ideal Loads Supply Air Latent Heating Rate [W] ## HVAC,Average,Zone Ideal Loads Supply Air Total Heating Rate [W] HVAC,Average,Zone Ideal Loads Supply Air Sensible Cooling Rate [W] HVAC,Average,Zone Ideal Loads Supply Air Latent Cooling Rate [W] -## HVAC,Average,Zone Ideal Loads Supply Air Total Cooling Rate [W] +HVAC,Average,Zone Ideal Loads Supply Air Total Cooling Rate [W] HVAC,Average,Zone Ideal Loads Zone Sensible Heating Rate [W] HVAC,Average,Zone Ideal Loads Zone Latent Heating Rate [W] HVAC,Average,Zone Ideal Loads Zone Total Heating Rate [W] @@ -407,8 +407,8 @@ HVAC,Average,Zone Ideal Loads Supply Air Standard Density Volume Flow Rate [m3/s HVAC,Sum,Air System Simulation Maximum Iteration Count [] HVAC,Sum,Air System Simulation Iteration Count [] HVAC,Sum,Air System Component Model Simulation Calls [] -HVAC,Average,Water Use Equipment Hot Water Mass Flow Rate [kg/s] -HVAC,Average,Water Use Equipment Cold Water Mass Flow Rate [kg/s] +## HVAC,Average,Water Use Equipment Hot Water Mass Flow Rate [kg/s] +## HVAC,Average,Water Use Equipment Cold Water Mass Flow Rate [kg/s] HVAC,Average,Water Use Equipment Total Mass Flow Rate [kg/s] HVAC,Average,Water Use Equipment Hot Water Volume Flow Rate [m3/s] HVAC,Average,Water Use Equipment Cold Water Volume Flow Rate [m3/s] @@ -417,12 +417,12 @@ HVAC,Sum,Water Use Equipment Hot Water Volume [m3] HVAC,Sum,Water Use Equipment Cold Water Volume [m3] HVAC,Sum,Water Use Equipment Total Volume [m3] HVAC,Sum,Water Use Equipment Mains Water Volume [m3] -HVAC,Average,Water Use Equipment Hot Water Temperature [C] -HVAC,Average,Water Use Equipment Cold Water Temperature [C] +## HVAC,Average,Water Use Equipment Hot Water Temperature [C] +## HVAC,Average,Water Use Equipment Cold Water Temperature [C] HVAC,Average,Water Use Equipment Target Water Temperature [C] HVAC,Average,Water Use Equipment Mixed Water Temperature [C] HVAC,Average,Water Use Equipment Drain Water Temperature [C] -HVAC,Average,Water Use Equipment Heating Rate [W] +## HVAC,Average,Water Use Equipment Heating Rate [W] HVAC,Sum,Water Use Equipment Heating Energy [J] HVAC,Average,Facility Total Purchased Electric Power [W] HVAC,Sum,Facility Total Purchased Electric Energy [J] diff --git a/ModelerFolder/Pathways_Template.txt b/ModelerFolder/Pathways_Template.txt deleted file mode 100644 index f29b85f..0000000 --- a/ModelerFolder/Pathways_Template.txt +++ /dev/null @@ -1,3 +0,0 @@ -Buildingsfile:C:/Users/xav77/Documents/FAURE/prgm_python/UrbanT/Eplus4Mubes/MUBES_UBEM/ModelerFolder/Minneberg_Buildings_v1.geojson -Shadingsfile:C:/Users/xav77/Documents/FAURE/prgm_python/UrbanT/Eplus4Mubes/MUBES_UBEM/ModelerFolder/Minneberg_Wallsv1.geojson -epluspath:C:/EnergyPlusV9-1-0/ diff --git a/ModelerFolder/PlotBuilder.py b/ModelerFolder/PlotBuilder.py deleted file mode 100644 index db784b0..0000000 --- a/ModelerFolder/PlotBuilder.py +++ /dev/null @@ -1,189 +0,0 @@ -# @Author : Xavier Faure -# @Email : xavierf@kth.se - -import os -import sys - -path2addgeom = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'geomeppy') -sys.path.append(path2addgeom) -sys.path.append("..") -import CoreFiles.GeneralFunctions as GrlFct -from BuildObject.DB_Building import BuildingList -import BuildObject.DB_Data as DB_Data -from BuildObject.DB_Filter4Simulations import checkBldFilter -import matplotlib.pyplot as plt - - -def LaunchProcess(SimDir, DataBaseInput, LogFile, bldidx, keyPath, nbcase, CorePerim=False, FloorZoning=False, - FigCenter=(0, 0), WindSize=50, PlotBuilding=False): - # process is launched for the considered building - msg = 'Building ' + str(nbBuild) + ' is starting\n' - print('#############################################') - print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - MainPath = os.getcwd() - epluspath = keyPath['epluspath'] - os.chdir(SimDir) - StudiedCase = BuildingList() - # lets build the two main object we'll be playing with in the following' - idf_ref, building_ref = GrlFct.appendBuildCase(StudiedCase, epluspath, nbcase, DataBaseInput, MainPath, LogFile, - PlotOnly=True) - refName = 'Building_' + str(nbcase) - for key in building_ref.BuildID: - print(key + ' : ' + str(building_ref.BuildID[key])) - refName += '\n ' + key + str(building_ref.BuildID[key]) - idf_ref.idfname = refName - # Rounds of check if we continue with this building or not, see DB_Filter4Simulation.py if other filter are to add - CaseOK = checkBldFilter(building_ref) - - if not CaseOK: - msg = '[Error] This Building/bloc has either no height, height below 1, surface below 50m2 or no floors, process abort for this one\n' - print(msg[:-1]) - os.chdir(MainPath) - GrlFct.Write2LogFile(msg, LogFile) - GrlFct.Write2LogFile('##############################################################\n', LogFile) - return FigCenter, WindSize - - FigCenter.append(building_ref.RefCoord) - refx = sum([center[0] for center in FigCenter]) / len(FigCenter) - refy = sum([center[1] for center in FigCenter]) / len(FigCenter) - - if not PlotBuilding: - a=1 - #building_ref.MaxShadingDist = 0 - # building_ref.shades = building_ref.getshade(DataBaseInput['Build'][nbcase], DataBaseInput['Shades'], - # DataBaseInput['Build'], DB_Data.GeomElement, [])#LogFile) - GrlFct.setBuildingLevel(idf_ref, building_ref, LogFile, CorePerim, FloorZoning, ForPlots=True) - GrlFct.setEnvelopeLevel(idf_ref, building_ref) - FigCentroid = building_ref.RefCoord if PlotBuilding else (refx, refy) - #we need to transform the prvious relatve coordinates into absolute one in order to make plot of several building keeping their location - idf_ref, building_ref = GrlFct.MakeAbsoluteCoord(idf_ref,building_ref) - - # compåuting the window size for visualization - for poly in building_ref.footprint: - for vertex in poly: - WindSize = max(GrlFct.ComputeDistance(FigCentroid, vertex), WindSize) - - surf = idf_ref.getsurfaces() - ok2plot = False - nbadiab = 0 - adiabsurf = [] - for s in surf: - if s.Outside_Boundary_Condition == 'adiabatic': - ok2plot = True - if s.Name[:s.Name.index('_')] not in adiabsurf: - adiabsurf.append(s.Name[:s.Name.index('_')]) - nbadiab += 1 - if ok2plot: - GrlFct.Write2LogFile('[Nb Adjacent_Walls] This building has '+str(nbadiab)+' walls with adiabatic surfaces\n', LogFile) - - idf_ref.view_model(test=PlotBuilding, FigCenter=FigCentroid, WindSize=2 * WindSize) - - GrlFct.Write2LogFile('##############################################################\n', LogFile) - - # lets get back to the Main Folder we were at the very beginning - os.chdir(MainPath) - return (refx, refy), WindSize - - -if __name__ == '__main__': - - ###################################################################################################################### - ######## MAIN INPUT PART ################################################################################## - ###################################################################################################################### - # This file is only to make graphs of the building geometry given in the GoeJsonF - - # BuildNum = [1,2,3,4] #list of numbers : number of the buildings to be simulated (order respecting the - # PathInputFile = 'String' #Name of the PathFile containing the paths to the data and to energyplus application (see ReadMe) - # CorePerim = False / True #True = create automatic core and perimeter zonning of each building. This options increases in a quite - # large amount both building process and simulation process. - # It can used with either one zone per floor or one zone per heated or none heated zone - # building will be generated first, all results will be saved in one single folder - # FloorZoning = False / True True = thermal zoning will be realized for each floor of the building, if false, there will be 1 zone - # for the heated volume and, if present, one zone for the basement (non heated volume - ## PlotBuilding = False / True #True = after each building the building will be plotted for visual check of geometry and thermal zoning. - # It include the shadings, if False, all the building will be plotted wihtout the shadings - # ZoneOfInterest = 'String' #Text file with Building's ID that are to be considered withoin the BuildNum list, if '' than all building in BuildNum will be considered - - - BuildNum = [] - PathInputFile = 'Pathways_Template.txt' - CorePerim = False - FloorZoning = False - PlotBuilding = False - ZoneOfInterest = '' - - ###################################################################################################################### - ######## LAUNCHING MULTIPROCESS PROCESS PART ################################################################# - ###################################################################################################################### - CaseName = 'ForTest' - # reading the pathfiles and the geojsonfile - GlobKey =[GrlFct.readPathfile(PathInputFile)] - # lets see if the input file is a dir with several geojson files - multipleFiles = False - BuildingFiles,WallFiles = GrlFct.ReadGeoJsonDir(GlobKey[0]) - if BuildingFiles: - multipleFiles = True - MainRootPath = GlobKey[0]['Buildingsfile'] - GlobKey[0]['Buildingsfile'] = os.path.join(MainRootPath,BuildingFiles[0]) - GlobKey[0]['Shadingsfile'] = os.path.join(MainRootPath, WallFiles[0]) - for nb,file in enumerate(BuildingFiles[1:]): - GlobKey.append(GlobKey[-1].copy()) - GlobKey[-1]['Buildingsfile'] = os.path.join(MainRootPath, file) - GlobKey[-1]['Shadingsfile'] = os.path.join(MainRootPath, WallFiles[nb+1]) - - for nbfile, keyPath in enumerate(GlobKey): - # if nbfile not in [0]: - # continue - if multipleFiles: - nb = len(GlobKey) - print('File number : '+str(nbfile) + ' which correspond to Area Ref : '+BuildingFiles[nbfile][:-18]) - DataBaseInput = GrlFct.ReadGeoJsonFile(keyPath) - BuildNum2Launch = [i for i in range(len(DataBaseInput['Build']))] - if BuildNum: - BuildNum2Launch = BuildNum - if os.path.isfile(os.path.join(os.getcwd(), ZoneOfInterest)): - NewBuildNum2Launch = [] - Bld2Keep = GrlFct.ReadZoneOfInterest(os.path.join(os.getcwd(), ZoneOfInterest), keyWord='50A Uuid') - for bldNum, Bld in enumerate(DataBaseInput['Build']): - if Bld.properties['50A_UUID'] in Bld2Keep and bldNum in BuildNum2Launch: - NewBuildNum2Launch.append(bldNum) - BuildNum2Launch = NewBuildNum2Launch - if not BuildNum2Launch: - print('Sorry, but no building matches with the requirements....Please, check your ZoneOfInterest') - else: - if not plt.fignum_exists(0): - FigCenter = [] - LogFile = [] - CurrentPath = os.getcwd() - WindSize = 50 - SimDir = CurrentPath - LogFile = open(os.path.join(SimDir, CaseName+'_Logs.log'), 'w') - if multipleFiles: - msg = '[New AREA] A new goejson file is open (num '+str(nbfile)+'), Area Id : '+BuildingFiles[nbfile][:-18]+'\n' - print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - for idx, nbBuild in enumerate(BuildNum2Launch): - if idx < len(DataBaseInput['Build']): - # getting through the mainfunction above :LaunchProcess() each building sees its idf done in a row within this function - try: - NewCentroid, WindSize = LaunchProcess(SimDir, DataBaseInput, LogFile, idx, keyPath, nbBuild, - CorePerim, FloorZoning, - FigCenter, WindSize, PlotBuilding) - except: - msg = '[Error] There was an error on this building, process aborted\n' - print(msg[:-1]) - GrlFct.Write2LogFile(msg, LogFile) - GrlFct.Write2LogFile('##############################################################\n', LogFile) - os.chdir(CurrentPath) - # if choicies is done, once the building is finished parallel computing is launched for this one - else: - print('All buildings in the input file have been treated.') - print('###################################################') - break - if not multipleFiles: - LogFile.close() - plt.show() - if multipleFiles: - LogFile.close() - sys.path.remove(path2addgeom) \ No newline at end of file diff --git a/ModelerFolder/SimLauncher.py b/ModelerFolder/SimLauncher.py deleted file mode 100644 index e6b4c99..0000000 --- a/ModelerFolder/SimLauncher.py +++ /dev/null @@ -1,194 +0,0 @@ -# @Author : Xavier Faure -# @Email : xavierf@kth.se - -import os -import sys -# #add the required path for geomeppy special branch -path2addgeom = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())),'geomeppy') -sys.path.append(path2addgeom) -#add the reauired path for all the above folder -sys.path.append('..') - -import CoreFiles.GeneralFunctions as GrlFct -import CoreFiles.LaunchSim as LaunchSim -import CoreFiles.CaseBuilder_OAT as CB_OAT -import multiprocessing as mp - -if __name__ == '__main__' : - -###################################################################################################################### -######## MAIN INPUT PART (choices from the modeler) ######################################################## -###################################################################################################################### -#The Modeler have to fill in the following parameter to define his choices - -# CaseName = 'String' #name of the current study (the ouput folder will be renamed using this entry) -# BuildNum = [1,2,3,4] #list of numbers : number of the buildings to be simulated (order respecting the -# geojsonfile), if empty, all building in the geojson file will be considered -# VarName2Change = ['String','String'] #list of strings: Variable names (same as Class Building attribute, if different -# see LaunchProcess 'for' loop for examples) -# Bounds = [[x1,y1],[x2,y2]] #list of list of 2 values :bounds in which the above variable will be allowed to change -# NbRuns = 1000 #number of run to launch for each building (all VarName2Change will have automotaic -# allocated value (see sampling in LaunchProcess) -# CPUusage = 0.7 #factor of possible use of total CPU for multiprocessing. If only one core is available, -# this value should be 1 -# CreateFMU = False / True #True = FMU are created for each building selected to be computed in BuildNum -# #no simulation will be run but the folder CaseName will be available for the FMUSimPlayground.py -# CorePerim = False / True #True = create automatic core and perimeter zonning of each building. This options increases in a quite -# large amount both building process and simulation process. -# It can used with either one zone per floor or one zone per heated or none heated zone -# building will be generated first, all results will be saved in one single folder -# FloorZoning = False / True True = thermal zoning will be realized for each floor of the building, if false, there will be 1 zone -# for the heated volume and, if present, one zone for the basement (non heated volume -# PathInputFile = 'String' #Name of the PathFile containing the paths to the data and to energyplus application (see ReadMe) -# OutputsFile = 'String' #Name of the Outfile with the selected outputs wanted and the associated frequency (see file's template) -# ZoneOfInterest = 'String' #Text file with Building's ID that are to be considered withoin the BuildNum list, if '' than all building in BuildNum will be considered - - CaseName = 'ForTest' - BuildNum = [0,1,2] - VarName2Change = [] - Bounds = [] - NbRuns = 1 - CPUusage = 0.8 - CreateFMU = False - CorePerim = False - FloorZoning = True - RefreshFolder = True - PathInputFile = 'Pathways_Template.txt' - OutputsFile = 'Outputs_Template.txt' - ZoneOfInterest = '' - -###################################################################################################################### -######## LAUNCHING MULTIPROCESS PROCESS PART (nothing should be changed hereafter) ############################ -###################################################################################################################### - if NbRuns>1: - SepThreads = True - if CreateFMU: - print('### INPUT ERROR ### ' ) - print('/!\ It is asked to ceate FMUs but the number of runs for each building is above 1...') - print('/!\ Please, check you inputs as this case is not allowed yet') - sys.exit() - else: - SepThreads = False - nbcpu = max(mp.cpu_count() * CPUusage, 1) - # reading the pathfiles and the geojsonfile - GlobKey = [GrlFct.readPathfile(PathInputFile)] - # lets see if the input file is a dir with several geojson files - multipleFiles = False - BuildingFiles, WallFiles = GrlFct.ReadGeoJsonDir(GlobKey[0]) - if BuildingFiles: - multipleFiles = True - MainRootPath = GlobKey[0]['Buildingsfile'] - GlobKey[0]['Buildingsfile'] = os.path.join(MainRootPath, BuildingFiles[0]) - GlobKey[0]['Shadingsfile'] = os.path.join(MainRootPath, WallFiles[0]) - for nb, file in enumerate(BuildingFiles[1:]): - GlobKey.append(GlobKey[-1].copy()) - GlobKey[-1]['Buildingsfile'] = os.path.join(MainRootPath, file) - GlobKey[-1]['Shadingsfile'] = os.path.join(MainRootPath, WallFiles[nb + 1]) - nbBuild = 0 - idx = 0 - for nbfile,keyPath in enumerate(GlobKey): - # if nbfile not in [70]: - # continue - print('Process is started with file nb : '+str(nbfile)+' over a total of : '+str(len(GlobKey))+' files') - epluspath = keyPath['epluspath'] - pythonpath = keyPath['pythonpath'] #this is needed only if processes are launch in terminal as it could be an options instead of staying in python environnement - DataBaseInput = GrlFct.ReadGeoJsonFile(keyPath) - - #check of the building to run - BuildNum2Launch = [i for i in range(len(DataBaseInput['Build']))] - if BuildNum: - BuildNum2Launch = BuildNum - if os.path.isfile(os.path.join(os.getcwd(), ZoneOfInterest)): - NewBuildNum2Launch = [] - Bld2Keep = GrlFct.ReadZoneOfInterest(os.path.join(os.getcwd(), ZoneOfInterest), keyWord='50A Uuid') - for bldNum, Bld in enumerate(DataBaseInput['Build']): - if Bld.properties['50A_UUID'] in Bld2Keep and bldNum in BuildNum2Launch: - NewBuildNum2Launch.append(bldNum) - BuildNum2Launch = NewBuildNum2Launch - if not BuildNum2Launch: - print('Sorry, but no building matches with the requirements....Please, check your ZoneOfInterest') - else: - #all argument are packed in a dictionnarie, as parallel process is used, the arguments shall be strictly kept for each - #no moving object of dictionnary values that should change between two processes. - FigCenter = [] - CurrentPath = os.getcwd() - MainInputs = {} - MainInputs['CorePerim'] = CorePerim - MainInputs['FloorZoning'] = FloorZoning - MainInputs['CreateFMU'] = CreateFMU - MainInputs['TotNbRun'] = NbRuns - MainInputs['OutputsFile'] = OutputsFile - MainInputs['VarName2Change'] = VarName2Change - MainInputs['PathInputFiles'] = PathInputFile - MainInputs['DataBaseInput'] = DataBaseInput - File2Launch = {'nbBuild' : []} - - for idx,nbBuild in enumerate(BuildNum2Launch): - MainInputs['FirstRun'] = True - #First, lets create the folder for the building and simulation processes - if multipleFiles: - SimDir = GrlFct.CreateSimDir(CurrentPath,CaseName,SepThreads,nbBuild,idx,MultipleFile = BuildingFiles[nbfile][:-18], Refresh=RefreshFolder) - else: - SimDir = GrlFct.CreateSimDir(CurrentPath, CaseName, SepThreads, nbBuild, idx, Refresh=RefreshFolder) - #a sample of parameter is generated is needed - ParamSample = GrlFct.SetParamSample(SimDir, NbRuns, VarName2Change, Bounds,SepThreads) - if idx 1: - # lets check if this building is already present in the folder (means Refresh = False in CreateSimDir() above) - if not os.path.isfile(os.path.join(SimDir, ('Building_' + str(nbBuild) + '_template.idf'))): - #there is a need to launch the first one that will also create the template for all the others - CB_OAT.LaunchOAT(MainInputs,SimDir,nbBuild,ParamSample[0, :],0,pythonpath) - # lets check whether all the files are to be run or if there's only some to run again - NewRuns = [] - for i in range(NbRuns): - if not os.path.isfile(os.path.join(SimDir, ('Building_' + str(nbBuild) + 'v'+str(i)+'.idf'))): - NewRuns.append(i) - #now the pool can be created changing the FirstRun key to False for all other runs - MainInputs['FirstRun'] = False - pool = mp.Pool(processes=int(nbcpu)) # let us allow 80% of CPU usage - for i in NewRuns: - pool.apply_async(CB_OAT.LaunchOAT, args=(MainInputs,SimDir,nbBuild,ParamSample[i, :],i,pythonpath)) - pool.close() - pool.join() - # lets check if this building is already present in the folder (means Refresh = False in CreateSimDir() above) - elif not os.path.isfile(os.path.join(SimDir, ('Building_' + str(nbBuild) + 'v0.idf'))): - #if not, then the building number will be appended to alist that will be used afterward - File2Launch['nbBuild'].append(nbBuild) - #the simulation are launched below using a pool of the earlier created idf files - if SepThreads and not CreateFMU: - file2run = LaunchSim.initiateprocess(SimDir) - nbcpu = max(mp.cpu_count()*CPUusage,1) - pool = mp.Pool(processes=int(nbcpu)) # let us allow 80% of CPU usage - for i in range(len(file2run)): - pool.apply_async(LaunchSim.runcase, args=(file2run[i], SimDir, epluspath)) - pool.close() - pool.join() - - else: - print('All buildings in the input file have been treated.') - print('###################################################') - break - if not SepThreads and not CreateFMU: - #lets launch the idf file creation process using the listed created above - pool = mp.Pool(processes=int(nbcpu)) - for nbBuild in File2Launch['nbBuild']: - pool.apply_async(CB_OAT.LaunchOAT, args=(MainInputs,SimDir,nbBuild,[1],0,pythonpath)) - pool.close() - pool.join() - # now that all the files are created, we can aggregate all the log files into a single one. - GrlFct.CleanUpLogFiles(SimDir) - # lest create the pool and launch the simulations - file2run = LaunchSim.initiateprocess(SimDir) - pool = mp.Pool(processes=int(nbcpu)) - for i in range(len(file2run)): - pool.apply_async(LaunchSim.runcase, args=(file2run[i], SimDir, epluspath)) - pool.close() - pool.join() - elif CreateFMU: - # now that all the files are created, we can aggregate all the log files into a single one. - GrlFct.CleanUpLogFiles(SimDir) - #the FMU are not taking advantage of the parallel computing option yet - for nbBuild in File2Launch['nbBuild']: - CB_OAT.LaunchOAT(MainInputs,SimDir,nbBuild,[1],0,pythonpath) - diff --git a/ModelerFolder/runMUBES.py b/ModelerFolder/runMUBES.py new file mode 100644 index 0000000..adb9a51 --- /dev/null +++ b/ModelerFolder/runMUBES.py @@ -0,0 +1,335 @@ +# @Author : Xavier Faure +# @Email : xavierf@kth.se + +import os +import sys +import re + +# #add the required path for geomeppy special branch +path2addgeom = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())),'geomeppy') +sys.path.append(path2addgeom) +#add the reauired path for all the above folder +sys.path.append('..') +MUBES_Paths = os.path.normcase(os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'MUBES_UBEM')) +sys.path.append(MUBES_Paths) + +import CoreFiles.GeneralFunctions as GrlFct +import CoreFiles.LaunchSim as LaunchSim +import CoreFiles.CaseBuilder_OAT as CB_OAT +import CoreFiles.setConfig as setConfig +import CoreFiles.CalibUtilities as CalibUtil +import shutil +import multiprocessing as mp +import platform +import json +import yaml +import copy + +def giveReturnFromPool(results): + donothing = 0 + print(results) + +def Read_Arguments(): + #these are defaults values: + Config2Launch = [] + Case2Launch = [] + # Get command-line options. + lastIdx = len(sys.argv) - 1 + currIdx = 1 + while (currIdx < lastIdx): + currArg = sys.argv[currIdx] + if (currArg.startswith('-CONFIG')): + currIdx += 1 + Config2Launch = json.loads(sys.argv[currIdx]) + return Config2Launch,Case2Launch + if (currArg.startswith('-yml')): + currIdx += 1 + Config2Launch = sys.argv[currIdx] + return Config2Launch,Case2Launch + if (currArg.startswith('-Case')): + currIdx += 1 + Case2Launch = sys.argv[currIdx] + return Config2Launch,Case2Launch + currIdx += 1 + return Config2Launch,Case2Launch + +def CreatePool2Launch(BldIDs,GlobKey,IDKeys,PassBldObject): + Pool2Launch = [] + NewUUIDList = [] + for nbfile,keyPath in enumerate(GlobKey): + DataBaseInput = GrlFct.ReadGeoJsonFile(keyPath,toBuildPool = True if not PassBldObject else False) + #check of the building to run + idx = len(Pool2Launch) + IdKey = 'NoBldID' + for key in IDKeys: + if key in DataBaseInput['Build'][0].properties.keys(): + IdKey = key + print('[Prep. Info] Buildings will be considered with ID key : '+IdKey ) + for bldNum, Bld in enumerate(DataBaseInput['Build']): + if not BldIDs: + try: BldID = Bld.properties[IdKey] + except: BldID = 'NoBldID' + Pool2Launch.append({'keypath': keyPath, 'BuildNum2Launch': bldNum,'BuildID':BldID ,'TotBld_and_Origin':'' }) + try: NewUUIDList.append(Bld.properties[IdKey]) + except: pass + else: + try: + if Bld.properties[IdKey] in BldIDs: + Pool2Launch.append({'keypath': keyPath, 'BuildNum2Launch': bldNum,'BuildID':Bld.properties[IdKey], 'TotBld_and_Origin':'' }) + NewUUIDList.append(Bld.properties[IdKey]) + except: pass + if not Pool2Launch: + print('### INPUT ERROR ### ') + print('/!\ None of the building BldID were found in the input GeoJson file...') + print('/!\ Please, check you inputs.') + sys.exit() + Pool2Launch[idx]['TotBld_and_Origin'] = str(len(Pool2Launch)-idx) +' buildings will be considered from '+os.path.basename(keyPath['Buildingsfile']) + return Pool2Launch,NewUUIDList,DataBaseInput if PassBldObject else [],IdKey + +if __name__ == '__main__' : + #Main script to launch either simulation or plot of the urban area represened in the main geojson file + #all inputs can be given inside a yml file. If not specified in a specific yml file, value from the defaultConfig.yml file will be considered + #It can be launched by : + #python runMUBES.py it will load the default yml file and check if a local one is present in the same folder to adapt the default one + #python runMUBES.py -yml MyConfig.yml it will consider the specified yml file to adapt the default one + #python runMUBES.py -CONFIG {xxxxxxx} json format of the yml file to adapt the default one for API application + + ConfigFromArg,Case2Launch = Read_Arguments() + config = setConfig.read_yaml(os.path.join(os.path.dirname(os.getcwd()),'CoreFiles','DefaultConfig.yml')) + configUnit = setConfig.read_yaml(os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfigKeyUnit.yml')) + if Case2Launch: + config, filefound, msg = setConfig.check4localConfig(config, os.getcwd()) + config['2_CASE']['0_GrlChoices']['CaseName'] = Case2Launch + print(os.path.join(os.path.abspath(config['0_APP']['PATH_TO_RESULTS']), Case2Launch, 'ConfigFile.yml')) + if os.path.isfile( + os.path.join(os.path.abspath(config['0_APP']['PATH_TO_RESULTS']), Case2Launch, 'ConfigFile.yml')): + localConfig = setConfig.read_yaml( + os.path.join(os.path.abspath(config['0_APP']['PATH_TO_RESULTS']), Case2Launch, 'ConfigFile.yml')) + config, msg = setConfig.ChangeConfigOption(config, localConfig) + if msg: print(msg) + else: + print('[Unknown Case] the following folder was not found : ' + os.path.join( + os.path.abspath(config['0_APP']['PATH_TO_RESULTS']), Case2Launch)) + sys.exit() + elif type(ConfigFromArg) == str: + if ConfigFromArg[-4:] == '.yml': + localConfig = setConfig.read_yaml(ConfigFromArg) + config, msg = setConfig.ChangeConfigOption(config, localConfig) + if msg: print(msg) + else: + print('[Unknown Argument] Please check the available options for arguments : -yml or -CONFIG') + sys.exit() + elif ConfigFromArg: + config, msg = setConfig.ChangeConfigOption(config, ConfigFromArg) + if msg: print(msg) + config['2_CASE']['0_GrlChoices']['OutputFile'] = 'Outputs4API.txt' + else: + config,filefound,msg = setConfig.check4localConfig(config, os.getcwd()) + if msg: print(msg) + print('[Config Info] Config completed by ' + filefound) + config = setConfig.checkConfigUnit(config,configUnit) + if type(config) != dict: + print('[Config Error] Something seems wrong : \n' + config) + sys.exit() + config, SepThreads = setConfig.checkGlobalConfig(config) + if type(config) != dict: + print('[Config Error] Something seems wrong in : ' + config) + sys.exit() + #the config file is now validated, lets vreate a smaller dict that will called along the process + Key2Aggregate = ['0_GrlChoices', '1_SimChoices', '2_AdvancedChoices'] + CaseChoices = {} + for key in Key2Aggregate: + for subkey in config['2_CASE'][key]: + CaseChoices[subkey] = config['2_CASE'][key][subkey] + if CaseChoices['Verbose']: print('[OK] Input config. info checked and valid.') + if 'See ListOfBuiling_Ids.txt for list of IDs' in CaseChoices['BldID']: + CaseChoices['BldID'] = [] + epluspath = config['0_APP']['PATH_TO_ENERGYPLUS'] + #a first keypath dict needs to be defined to comply with the current paradigme along the code + Buildingsfile = os.path.abspath(config['1_DATA']['PATH_TO_DATA']) + keyPath = {'epluspath': epluspath, 'Buildingsfile': Buildingsfile, 'pythonpath': '','GeojsonProperties': ''} + #this function makes the list of dictionnary with single input files if several are present inthe sample folder + GlobKey, MultipleFiles = GrlFct.ListAvailableFiles(keyPath) + #this function creates the full pool to launch afterward, including the file name and which buildings to simulate + IDKeys = config['3_SIM']['GeomElement']['BuildIDKey'] + Pool2Launch,CaseChoices['BldID'],CaseChoices['DataBaseInput'],BldIDKey = CreatePool2Launch(CaseChoices['BldID'],GlobKey,IDKeys,CaseChoices['PassBldObject']) + ###################################################################################################################### + ######## LAUNCHING MULTIPROCESS PROCESS PART (nothing should be changed hereafter) ############################ + ###################################################################################################################### + nbcpu = max(mp.cpu_count() * CaseChoices['CPUusage'], 1) + nbBuild = 0 + FigCenter = [] + CurrentPath = os.getcwd() + pythonpath = keyPath['pythonpath'] + MultipleFileidx = 0 + MultipleFileName = '' + if MultipleFiles: + File2Launch = {key:[] for key in range(len(MultipleFiles))} + else: + File2Launch = {0:[]} + for idx,Case in enumerate(Pool2Launch): + if len(Case['TotBld_and_Origin'])>0: + if MultipleFiles: + MultipleFileName = MultipleFiles[MultipleFileidx] + MultipleFileidx += 1 + if CaseChoices['Verbose']: print('[Prep. phase] '+Case['TotBld_and_Origin']) + keypath = Case['keypath'] + nbBuild = Case['BuildNum2Launch'] #this will be used in case the file has to be read again (launched through prompt cmd) + CaseChoices['FirstRun'] = True + #First, lets create the folder for the building and simulation processes + SimDir = GrlFct.CreateSimDir(CurrentPath, config['0_APP']['PATH_TO_RESULTS'],CaseChoices['CaseName'], + SepThreads, nbBuild, idx, MultipleFile = MultipleFileName, Refresh=CaseChoices['RefreshFolder'],Verbose = CaseChoices['Verbose']) + #a sample of parameter is generated if needed + ParamSample,CaseChoices = GrlFct.SetParamSample(SimDir, CaseChoices, SepThreads) + #if a simulation is asked to be done from posterriors that does not exist, the process will skip this building + if len(ParamSample) == 0 : + shutil.rmtree(SimDir) + continue + #lest create the local yml file that will be used afterward + if not os.path.isfile((os.path.join(SimDir,'ConfigFile.yml'))) or idx ==0: + LocalConfigFile = copy.deepcopy(config) + writeIds = False + if CaseChoices['NbRuns'] >1: + LocalConfigFile['2_CASE']['1_SimChoices']['BldID'] = CaseChoices['BldID'][idx] + else: + if len(CaseChoices['BldID'])>10: + LocalConfigFile['2_CASE']['1_SimChoices']['BldID'] = '# See ListOfBuiling_Ids.txt for list of IDs ' + writeIds = True + else: + LocalConfigFile['2_CASE']['1_SimChoices']['BldID'] = CaseChoices['BldID'] + if CaseChoices['VarName2Change']: + if CaseChoices['Verbose']: print('[Info] It seems that at least one parameter is to be changed but only one simulation is asked. Parameter default values will be used. ') + CaseChoices['VarName2Change'] = [] + with open(os.path.join(SimDir,'ConfigFile.yml'), 'w') as file: + documents = yaml.dump(LocalConfigFile, file) + + #lets check if there are several simulation for one building or not + if CaseChoices['NbRuns'] > 1 and not CaseChoices['MakePlotsOnly']: + if idx == 0 and CaseChoices['Verbose']: print('Idf input files under process...') + Finished = False + idx_offset = 0 #this offset is used forcalibration ppurposes to extend the amount of needed file to laucnh + NbRun = CaseChoices['NbRuns'] + while not Finished: + #if CaseChoices['Verbose']: print('Initial input file is being created...') + # lets check if this building is already present in the folder (means Refresh = False in CreateSimDir() above) + if not os.path.isfile(os.path.join(SimDir, ('Building_' + str(nbBuild) + '_template.idf'))): + #there is a need to launch the first one that will also create the template for all the others + CB_OAT.LaunchOAT(CaseChoices,SimDir,keypath,nbBuild,ParamSample[0, :],0,pythonpath) + # lets check whether all the files are to be run or if there's only some to run again + NewRuns = [] + for i in range(NbRun): + if not os.path.isfile(os.path.join(SimDir, ('Building_' + str(nbBuild) + 'v'+str(i+idx_offset)+'.idf'))): + NewRuns.append(i) + #now the pool can be created changing the FirstRun key to False for all other runs + CaseChoices['FirstRun'] = False + pool = mp.Pool(processes=int(nbcpu)) # let us allow 80% of CPU usage + for i in NewRuns: + pool.apply_async(CB_OAT.LaunchOAT, args=(CaseChoices,SimDir,keypath,nbBuild,ParamSample[i+idx_offset, :],i+idx_offset,pythonpath)) + pool.close() + pool.join() + #the simulation are launched below using a pool of the earlier created idf files + if CaseChoices['Verbose']: print('Simulation runs have begun...') + file2run = LaunchSim.initiateprocess(SimDir) + if not file2run and CaseChoices['Verbose'] : print( + '[Info] All asked simulations are already done and results available...refreshfolder to remove those') + nbcpu = max(mp.cpu_count()*CaseChoices['CPUusage'],1) + pool = mp.Pool(processes=int(nbcpu)) # let us allow 80% of CPU usage + for i in range(len(file2run)): + pool.apply_async(LaunchSim.runcase, args=(file2run[i], SimDir, epluspath, CaseChoices['API']), callback=giveReturnFromPool) + pool.close() + pool.join() + GrlFct.AppendLogFiles(SimDir,BldIDKey) + if not CaseChoices['Calibration']: + Finished = True + else: + Finished,idx_offset,ParamSample = CalibUtil.CompareSample(Finished,idx_offset, SimDir, CurrentPath, nbBuild, CaseChoices['VarName2Change'], + CaseChoices['CalibTimeBasis'], CaseChoices['MeasurePath4Calibration'],ParamSample, + CaseChoices['Bounds'], CaseChoices['BoundsLim'], CaseChoices['ParamMethods'],NbRun) + if CaseChoices['Verbose'] and not Finished: print('Calibration under process, new files are needed : Offset is :'+ str(idx_offset)) + if CaseChoices['Verbose'] and Finished: print('Calibration has reach its end, congrats !!') + + # lets check if this building is already present in the folder (means Refresh = False in CreateSimDir() above) + elif not os.path.isfile(os.path.join(SimDir, ('Building_' + str(nbBuild) + 'v0.idf'))) or CaseChoices['MakePlotsOnly']: + # if not, then the building number will be appended to alist that will be used afterward + File2Launch[max(MultipleFileidx-1,0)].append({'nbBuild': nbBuild, 'keypath': keypath, 'SimDir': SimDir, 'BuildID': Case['BuildID']}) + # #lets write a file for the building IDs as it can be very long. + if writeIds: + if CaseChoices['Verbose']: print('[Prep.Info] Writing List of Building''s ID file') + for nbfile,ListKey in enumerate(File2Launch): + with open(os.path.join(File2Launch[ListKey][0]['SimDir'],'ListOfBuiling_Ids.txt'),'w') as f: + msg = 'SimNum' + '\t' + 'BldID_'+str(BldIDKey) + f.write(msg + '\n') + for file in File2Launch[ListKey]: + msg = str(file['nbBuild']) + '\t' + str(file['BuildID']) + f.write(msg+'\n') + + if CaseChoices['MakePlotsOnly']: + FigCenter = [] + WindSize = 50 + totalsize = 0 + offset = 0 + cpt = '--------------------' + cpt1 = ' ' + for ListKey in File2Launch: + totalsize += len(File2Launch[ListKey]) + for nbfile,ListKey in enumerate(File2Launch): + for file_idx,file in enumerate(File2Launch[ListKey]): + done = (file_idx+nbfile+1+offset)/totalsize + lastBld = True if done==1 and nbfile+1 == len(File2Launch) else False + BldObj,IDFObj,Check = CB_OAT.LaunchOAT(CaseChoices, file['SimDir'], file['keypath'], file['nbBuild'], [1], 0, + pythonpath,MakePlotOnly = CaseChoices['MakePlotsOnly']) + if CaseChoices['Verbose']: + print('Figure being completed by ' + str(round(100 * done, 1)) + ' %') + else: + print('\r',end='') + print('Figure being completed by ' + cpt[:int(20 * done)]+cpt1[int(20 * done):]+str(round(100 * done, 1)) + ' %',end = '',flush = True) + if Check == 'OK': + LastBldObj = copy.deepcopy(BldObj) + LastIDFObj = copy.deepcopy(IDFObj) + FigCenter, WindSize = GrlFct.ManageGlobalPlots(BldObj, IDFObj, FigCenter, WindSize, + CaseChoices['MakePlotsPerBld'],nbcase=[], LastBld=lastBld) + elif lastBld: + FigCenter, WindSize = GrlFct.ManageGlobalPlots(LastBldObj, LastIDFObj, FigCenter, WindSize, + CaseChoices['MakePlotsPerBld'], nbcase=[], + LastBld=lastBld) + offset += file_idx + os.chdir(CurrentPath) + GrlFct.CleanUpLogFiles(file['SimDir']) + elif not SepThreads and not CaseChoices['CreateFMU'] and File2Launch[0]: + CurrentSimDir = '' + for ListKey in File2Launch: + #lets launch the idf file creation process using the listed created above + if MultipleFiles: + if CurrentSimDir != File2Launch[ListKey][0]['SimDir']: + if CaseChoices['Verbose']: print('Idf input files under process using '+ + os.path.basename(File2Launch[ListKey][0]['SimDir'])) + if not MultipleFiles and CaseChoices['Verbose']: print('Idf input files under process...') + CurrentSimDir = File2Launch[ListKey][0]['SimDir'] + pool = mp.Pool(processes=int(nbcpu)) + for nbBuild in File2Launch[ListKey]: + pool.apply_async(CB_OAT.LaunchOAT, args=(CaseChoices,CurrentSimDir,nbBuild['keypath'],nbBuild['nbBuild'],[1],0,pythonpath)) + pool.close() + pool.join() + # now that all the files are created, we can aggregate all the log files into a single one. + os.chdir(CurrentPath) + GrlFct.CleanUpLogFiles(CurrentSimDir) + # lest create the pool and launch the simulations + if CaseChoices['Verbose']: print( + 'Simulations under process for ' + os.path.basename(CurrentSimDir)) + file2run = LaunchSim.initiateprocess(CurrentSimDir) + pool = mp.Pool(processes=int(nbcpu)) + for i in range(len(file2run)): + pool.apply_async(LaunchSim.runcase, args=(file2run[i], CurrentSimDir, epluspath,CaseChoices['API'],CaseChoices['Verbose']), callback=giveReturnFromPool) + pool.close() + pool.join() + GrlFct.AppendLogFiles(CurrentSimDir,BldIDKey) + elif CaseChoices['CreateFMU']: + # now that all the files are created, we can aggregate all the log files into a single one. + os.chdir(CurrentPath) + GrlFct.CleanUpLogFiles(SimDir) + #the FMU are not taking advantage of the parallel computing option yet + for ListKey in File2Launch: + for nbBuild in File2Launch[ListKey]: + CB_OAT.LaunchOAT(CaseChoices,SimDir,nbBuild['keypath'],nbBuild['nbBuild'],[1],0,pythonpath) + if not File2Launch[0] and CaseChoices['Verbose'] and CaseChoices['NbRuns']==1: print('[Info] All asked simulations are already done and results available...refreshfolder to remove those') + if CaseChoices['Verbose']: print('[Process Finished] runMUBES.py ended successfully') diff --git a/README.md b/README.md index 329d5aa..9da3492 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # MUBES_UBEM MUBES_UBEM propose a workflow that creates automatic building energy models for EnergyPlus software. +All inputs are dealt with an external *.yml file. Several thermal zoning options are proposed from single heated and non heated zones up to core and perimeter zones for each building floor. It can launch simulations using parallel computing or can automatically creates FMUs of each building in order to make co-simulation afterward (the co-simulation process, using FMPy, is validated in Windows, Mac and Linux (Ubuntu)). -The main input file is in a geojson format. It contains the footprint including height (3D vertexes) of each building's surface as well as some propreties taken from several databases (EPCs, and others). -__Rq__: Some new methods, more user-oriented are currently being developed in the ForDev branch (that will be soon be the main one). The input file is dealt with .yml file and runMUBES.py can be launched using json format as argument or a yml file path or using the local yml file available. A script building shadowing walls form a geojson file is also proposed. Some updates will come soon (you can still be informed by following the project in git). - +The main input file is in a geojson format. It contains the footprint including height (3D vertexes, or 2D vertexes and height as an attribut) of each building's surface as well as some propreties taken from several databases (EPCs, and others). +__Rq__: The platform is continually under development, newcomers are welcome to join by forking and proposing changes. ![Minneberg](Minneberg.jpg) @@ -14,54 +14,60 @@ It has been developed in Python 3.7 with EP 9.1 on Windows and has been successf It is based on 2 main packages: [EPPY](https://github.com/santoshphilip/eppy) and [GeomEppy](https://github.com/jamiebull1/geomeppy). ## Installation process -The needed packages are given in the requirements.txt file. -__Note__ : GeomEppy packages uses a specific branch of the original package. +The needed packages are given in the requirements.txt file : *__pip__ __install__ __-r__ __requirements.txt__* +__Note__ : GeomEppy packages uses a specific branch made from the original package. The FMUs creation option uses the [EnergyPlusToFMU-v3.1.0](https://simulationresearch.lbl.gov/fmu/EnergyPlus/export/userGuide/download.html) toolkit developed by LNBL. This toolkit should be downloaded and installed at the same level as MUBES_UBEM under a folder named __FMUsKit__ (see BuildFMUs.buildEplusFMU() in the CoreFile folder). The portability of FMUs (used on another computer than the one used to generate them) is valid but currently only when no external files are used as error are encountered when relative paths are defined. /!\ On Windows 10, some time delay had to be introduced in the original FMU toolkit code to enable to remove the intermediate files and make the FMU reach its end properly (https://github.com/lbl-srg/EnergyPlusToFMU/issues/54). - ## Folder organization The MUBES_UBEM main folder contains several subfolders: __CoreFile__ : contains all the core python scripts for the several levels of the building design process. __ExternalFiles__ : contains commun external files that one would use for all the buildings considered in the process. It currently contains the times series of cold water input temperature for the Domestic Hot Water needs as well as the water taps in l/min. The latter is an output from an other packages ([StROBe](https://github.com/open-ideas/StROBe)) that enables to create stochastics outputs for residential occupancy. -__BuildObject__ : contains the building class object as well as the default choices for missing inputs. The latter might (or should) be modified by the modeler depending on its studied cases. -__ModelerFolder__ : contains severals templates to build the process, select the required ouputs, and paths for the process to be launched. +__BuildObject__ : contains the building class object, filters to skip building from the input file and some geometric utilities used. +__ModelerFolder__ : contains the *runMUBES.py* file as well as two examples of FMU simulations. An example of geojson is also provided. *MakeShadowingWallFile.py* enables to create a Json input file for further shadowing effect consideration. __ReadResults__ : contains one template to read the results and some functions for post-processing in the Utilities.py file. ## Run simulation case -The __ModelerFolder__ is the playground of the Modeler. Within this folder, several templates are proposed. These are to be copy/paste in order to enable new releases/updates from the templates without altering your local changes. -The templates are : -__Pathways_Template.txt__ : This file gives the paths to your local path of energyplus and to the needed geojson intput files (one file for the buildings and one file for the shading walls of the surrounding environement of each building). Its name is given as parameter in the builder file (see below). **This file is to be modified with local paths** . -__Outputs_Template.txt__ : This file proposes a list of available outputs from EP. It has been build from a .rdd file from EP. The required outputs should be indicated in this file. It also indicates at which frequency the modeler wants his ouputs. -__SimLauncher.py__ : this is the main builder file. This template gives an example for a full process to be launched. Read carefuly the comments below the *if __name__ == '__main__' :* as important choices are to be done here by the modeler before launching a simulation. -This scripts is the main one. It deals with the construction of the .idf file for each building and either launches the parallel computing option for all or creates the FMUs of all buildings. It will automatically create a folder (at the same level of main MUBES_UBEM folder and if not already created) that will be called __MUBES_SimResults__ and that will have subfolders for each case that is launched. The subfolder will be named as the CaseName in the SimLauncher scripts (see comments below the *if __name__ == '__main__' :*). +__First__ __thing__ : Change the path to EnergyPlus and (if needed) to the Data (geojson files). +Simply change the path in the *LocalConfig_Template.yml* file in __ModelerFolder__ and give it another name (for further updates). +(Note: see *CoreFile/DefaultConfig.yml* to see all possible changes in *xxx.yml* files). + +*__python__ __runMUBES.py__* will launch the simulation using the *DefaultConfig.yml* modified by *LocalConfig.yml* file is in __ModelerFolder__. +*__python__ __runMUBES.py__ __-yml__ __path_to_config.yml__* will launch the simulation using the information given in the path_to_config.yml. The latter can contain only the changes wanted from the DefaultConfig.yml. +*__python__ __runMUBES.py__ __-CONFIG__ __{JSON Format}__* will launch the simulation using the information given in the {JSON Format} as arguments. The latter can contain only the changes wanted from the DefaultConfig.yml. + +__Note__ : *ConfigFile.yml* are systematically saved in the result folder and can thus be used afterward with the *-yml* argument -Some few other files are present in this folder : -__PlotBuilder.py__ : enables to make 3D Matplotlib figures out of the idf files. It will plot all the buildings that are considered or each building appart depending on the available option. -__FMPySimPlayGroundEx1.py__ and __FMPySimPlayGroundEx2.py__: it uses FMPy package and as been successfully tested for controlling temperature's setpoints, internal loads, or watertaps at each time steps of the simulation. For one who'd like to make co-simulation, a deep understanding is still needed on the EP side as inputs and ouputs are to be defined. The SimLauncher, using *CreateFMU = True*, proposes by default the temperature's setpoints and the water taps as inputs and the averaged indoor temperature, the total power for heat needs and for domestic hot water as outputs. +__Outputs_Template.txt__ : This file proposes a list of available outputs from EP. It has been build from a .rdd file from EP. The required outputs should be indicated in this file. It also indicates at which frequency the modeler wants his outputs. + +## Creating a shadowing wall file +*__python__ __MakeShadowingWallFile.py__* will built a .json file out of the geojson files in the same location, given in the *LocalConfig.yml*. +*__python__ __MakeShadowingWallFile.py__ __-yml__ __path_to_config.yml__* will built a .json file out of the geojson files in the same location, given in the *path_to_config.yml*. +*__python__ __MakeShadowingWallFile.py__ __-geojson__ __path_to_geojson.geojson__* will built a .json file out of the geojson files in the same location. +Extra argument can be given to choose shadowing resolution with simple neighborhood, extended neighborhood (higher buildings are considered even if behind others), and all surfaces from all buildings. +Can be added to the above command line : *__-ShadeLimits__ __SimpleSurf__* or *__-ShadeLimits__ __AllSurf__* . The default option is extended with higher buildings considered. +The more shadowing walls are considered the more warnings can be raised by EnergyPlus. + +## FMU examples +__FMPySimPlayGroundEx1.py__ and __FMPySimPlayGroundEx2.py__: it uses FMPy package and as been successfully tested for controlling temperature's setpoints, internal loads, or watertaps at each time steps of the simulation. For one who'd like to make co-simulation, a deep understanding is still needed on the EP side as inputs and ouputs are to be defined. +FMU construction are realized if *CreateFMU* is set to True in *LocalConfig.yml*. The two examples (Ex1 and Ex2) : Ex1 : proposes a simple offset on the temperature setPoints. Every two hours a new building sees its setpoint decreases from 21degC to 18degC. the frequency of changes for each building thus depends on the size of the district that is considered. The internal Loads are also modified depending on working and nonworking hours Ex2 : proposes a couple temperature setpoints and water taps controls for each building, keeping the hourly based internal load inputs. It reads an external file to feed the water taps at each time step, and depending on a threshold of water taps' flow, the temperature's setpoints are changed. -Ex1 is usable by default, Ex2 needs to have Domestic Hot Water in external file, so DB_Data.ExtraEnergy dictionnary in __BuildObject__ folder needs to be uncommented. - -The __ReadResults__ folder contains also a template for post-processing the results : -__ReadOutputs_Template.py__ : this script proposes a basic post-processing stage including reading the data, ploting the areas of footprint and the energy needs as well as some times series for specific building number. Its works for simulation done with or without FMUs. -__Utilities.py__ : contains several useful functions for the post-processing stage. The _getData()_ is highly helpful. It gathers all the pickle files present in a directory into one dictionnary. It can deal with several CaseNames and overplots results for same Building's Ids by simply appending the path's list. - -The systems and building's caracteristics are taken from the available data in the goejson file and\or in the _DB_Data.py_ file in the __BuildObject__ folder that contains several dictionnaries needed as inputs. The modeler is highly invited to look along this file to understand all the different thermal elements that are needed for the simulation. +*__python__ __FMPySimPlayGroundEx1.py.py__* will load the fmu and launch simulation from CaseName given in the *LocalConfig.yml*. +*__python__ __FMPySimPlayGroundEx1.py__ __-yml__ __path_to_config.yml__* will load the fmu and launch simulation from CaseName given in the *path_to_config.yml*. +*__python__ __FMPySimPlayGroundEx1.py__ __-Case__ __CaseName__* will load the fmu and launch simulation from CaseName. -**Example of building geometry construction using MUBES_UBEM for Minneberg District, Stockholm, Sweden** -The corresponding goejson files are given in the ModelerFolder. This example can be launched after having changed the paths to local ones in the Pathways_Template.txt file without any other modification. -The weather conditions used by default are from San Francisco as the weather file is automaticaly provided while installing EnergyPlus (see _DB_Data.py_ in the __BuildObject__ folder). -_python3_ _SimLauncher.py_ will create the idf files and will launch all the simulations. -_python3_ _ReadOutputs_Template.py_ will read all the simulations' results and make some graphs as presented above. - -_python3_ _PlotBuilder.py_ will read all the buildings and make the figure below (full district present in the geojson file) on your environment. It will not alter the results as no simulations are launched here. A log file will be created though in order to have insights of potential geometry issues in the process. - -By changing the *createFMU* key in *True*, in SimLauncher, it will automatically create FMUs for each building. -*python3 FMPySimPlayGroundEx1.py* enable to launch a co-simulation with the above simple temperature's setpoint and internal loads modulation law for each building. +## Reading the Ouputs +The __ReadResults__ folder contains also a template for post-processing the results : +*ReadOutputs_Template.py* proposes a basic post-processing stage including reading the data, ploting the areas of footprint and the energy needs as well as some times series for specific building number. Its works for simulation done with or without FMUs. +*Utilities.py* contains several useful functions for the post-processing stage. The *getData()* is highly helpful. It gathers all the pickle files present in a directory into one dictionnary. It can deal with several CaseNames and overplots results for same Building's Ids by simply appending the path's list. +*__python__ __ReadOutputs_Template.py__* will load the results from CaseName given in the *LocalConfig.yml*. +*__python__ __ReadOutputs_Template.py__ __-yml__ __path_to_config.yml__* will load the results from CaseName given in the *path_to_config.yml*. +*__python__ __ReadOutputs_Template.py__ __-Case__ __[CaseName1,CaseName2,...]__* will load the results from CaseName1 and CaseName2. + ## Engine structure The paradigm of simulation engine is to automate simulation on several different levels : diff --git a/ReadResults/ReadOuputs_Template.py b/ReadResults/ReadOuputs_Template.py index 28ccdfe..03ed786 100644 --- a/ReadResults/ReadOuputs_Template.py +++ b/ReadResults/ReadOuputs_Template.py @@ -7,6 +7,7 @@ import numpy as np sys.path.append(os.path.dirname(os.getcwd())) import Utilities +import CoreFiles.setConfig as setConfig # the main idea of this file is to present some way for analyzing the data. @@ -26,15 +27,17 @@ def plotAreaVal(GlobRes,FigName,name): - refVar= '[''BuildID''][''50A_UUID'']' - reference = [GlobRes[0]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! + key = GlobRes[0]['BuildID'][0]['BldIDKey'] + refVar= '[''BuildID''][key]' + reference = [GlobRes[0]['BuildID'][i][key] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! #definition of the reference for comparison signe = ['.','s','>','<','d','o','.','s','>','<','d','o'] for nb in GlobRes: Res = GlobRes[nb] - locref = [GlobRes[nb]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[nb]['BuildID']))] + locref = [GlobRes[nb]['BuildID'][i][key] for i in range(len(GlobRes[nb]['BuildID']))] index_y,varx = Utilities.getSortedIdx(reference,locref) - varyref = [Res['ATemp'][idx] for idx in index_y] + varx = [int(Res['SimNum'][idx]) for idx in index_y] + varyref = [Res['DB_Surf'][idx] for idx in index_y] if nb==0: Utilities.plotBasicGraph(FigName['fig_name'].number, FigName['ax'][0],varx, [varyref], 'Building num', ['ATemp'], 'Areas (m2)', 'x') @@ -49,26 +52,31 @@ def plotAreaVal(GlobRes,FigName,name): def plotErrorFile(GlobRes,FigName,name,legend = True): - #reference = [GlobRes[0]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! + key = GlobRes[0]['BuildID'][0]['BldIDKey'] + refVar = '[''BuildID''][key]' + reference = [GlobRes[0]['BuildID'][i][key] for i in range( + len(GlobRes[0]['BuildID']))] # we need this reference because some building are missing is somme simulation !!! #definition of the reference for comparison signe = ['.','s','>','<','d','o','.','s','>','<','d','o'] offset = 0 tot = 0 for nb in GlobRes: Res = GlobRes[nb] - #locref = [GlobRes[nb]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[nb]['BuildID']))] - #index_y,varx = Utilities.getSortedIdx(reference,locref) - vary = Res['Warnings'] - varx = [int(x) for x in np.linspace(offset, offset + len(vary), len(vary))] + locref = [GlobRes[nb]['BuildID'][i][key] for i in range(len(GlobRes[nb]['BuildID']))] + index_y,varx = Utilities.getSortedIdx(reference,locref) + vary = [Res['Warnings'][idx] for idx in index_y] + varx = [int(Res['SimNum'][idx]) for idx in index_y] + #arx = [int(x) for x in np.linspace(offset, offset + len(vary), len(vary))] xtitle = 'Building' Warnings = [Res['SimNum'][idx] for idx, val in enumerate(vary) if val > 0] if Warnings: - print('File nb : ' + str(nb) + ' has simulations with Warnings on buildings : ' + str(Warnings)) + print('Case nb : ' + str(nb) + ' has simulations with Warnings on buildings : ' + str(Warnings)) Utilities.plotBasicGraph(FigName['fig_name'].number, FigName['ax'][0],varx, [vary], xtitle, [name[nb]], 'Nb Warnings', signe[np.random.randint(0,10)],legend = legend) - vary = Res['Errors'] - varx = [int(x) for x in np.linspace(offset, offset + len(vary), len(vary))] - offset += (len(vary) + 1) + vary = [Res['Errors'][idx] for idx in index_y] + varx = [int(Res['SimNum'][idx]) for idx in index_y] + #varx = [int(x) for x in np.linspace(offset, offset + len(vary), len(vary))] + #offset += (len(vary) + 1) if wanted to be added in x axis along the different cases xtitle = 'Building' Errors = [Res['SimNum'][idx] for idx,val in enumerate(vary) if val>0] if Errors: @@ -78,14 +86,16 @@ def plotErrorFile(GlobRes,FigName,name,legend = True): def plotDim(GlobRes,FigName,name): - refVar= '[''BuildID''][''50A_UUID'']' - reference = [GlobRes[0]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! + key = GlobRes[0]['BuildID'][0]['BldIDKey'] + refVar= '[''BuildID''][key]' + reference = [GlobRes[0]['BuildID'][i][key] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! #definition of the reference for comparison signe = ['.','s','>','<','d','o','.','s','>','<','d','o'] for nb in GlobRes: Res = GlobRes[nb] - locref = [Res['BuildID'][i]['50A_UUID'] for i in range(len(Res['BuildID']))] + locref = [Res['BuildID'][i][key] for i in range(len(Res['BuildID']))] index_y,varx = Utilities.getSortedIdx(reference,locref) + varx = [int(Res['SimNum'][idx]) for idx in index_y] footprint = Res['BlocFootprintArea'] try: max(Res['BlocHeight'][0]) @@ -120,15 +130,17 @@ def plotDim(GlobRes,FigName,name): def plotEnergy(GlobRes,FigName,name): - refVar = '[''BuildID''][''50A_UUID'']' - reference = [GlobRes[0]['BuildID'][i]['50A_UUID'] for i in range( + refVar = '[''BuildID''][key]' + key = GlobRes[0]['BuildID'][0]['BldIDKey'] + reference = [GlobRes[0]['BuildID'][i][key] for i in range( len(GlobRes[0]['BuildID']))] # we need this reference because some building are missing is somme simulation !!! # definition of the reference for comparison signe = ['.', 's', '>', '<', 'd', 'o', '.', 's', '>', '<', 'd', 'o'] for nb in GlobRes: Res = GlobRes[nb] - locref = [GlobRes[nb]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[nb]['BuildID']))] + locref = [GlobRes[nb]['BuildID'][i][key] for i in range(len(GlobRes[nb]['BuildID']))] index_y, varx = Utilities.getSortedIdx(reference, locref) + varx = [int(Res['SimNum'][idx]) for idx in index_y] varyref = [Res['EPC_Heat'][idx] for idx in index_y] if nb == 0: Utilities.plotBasicGraph(FigName['fig_name'].number, FigName['ax'][0], varx, [varyref], 'Building num', @@ -146,19 +158,19 @@ def plotEnergy(GlobRes,FigName,name): def plotTimeSeries(GlobRes,FigName,name,Location,TimeSerieList,Unit,SimNum=[]): - - refVar= '[''BuildID''][''50A_UUID'']' - reference = [GlobRes[0]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! + key = GlobRes[0]['BuildID'][0]['BldIDKey'] + refVar= '[''BuildID''][key]' + reference = [GlobRes[0]['BuildID'][i][key] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! signe = ['.','s','>','<','d','o','.','s','>','<','d','o'] for nb in GlobRes: Res = GlobRes[nb] if not SimNum: SimNum = Res['SimNum'] - locref = [GlobRes[nb]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[nb]['BuildID']))] + locref = [GlobRes[nb]['BuildID'][i][key] for i in range(len(GlobRes[nb]['BuildID']))] - for nbBld in SimNum: + for num,nbBld in enumerate(SimNum): index_y, varx = Utilities.getSortedIdx(reference, locref) - vary = Res[Location][index_y[varx.index(nbBld)]][TimeSerieList] + vary = Res[Location][index_y[varx.index(num)]][TimeSerieList] varx = np.linspace(1,len(vary),len(vary)) Utilities.plotBasicGraph(FigName['fig_name'].number, FigName['ax'][0],varx, [vary], 'Time', [name[nb]+'_Bld_'+str(nbBld)+' '+TimeSerieList], Unit, '--') @@ -174,44 +186,96 @@ def plotTimeSeries(GlobRes,FigName,name,Location,TimeSerieList,Unit,SimNum=[]): def plotIndex(GlobRes,FigName,name): - refVar= '[''BuildID''][''50A_UUID'']' - reference = [GlobRes[0]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! + key = GlobRes[0]['BuildID'][0]['BldIDKey'] + refVar= '[''BuildID''][key]' + reference = [GlobRes[0]['BuildID'][i][key] for i in range(len(GlobRes[0]['BuildID']))]#we need this reference because some building are missing is somme simulation !!! #definition of the reference for comparison signe = ['.','s','>','<','d','o','.','s','>','<','d','o'] for nb in GlobRes: Res = GlobRes[nb] - locref = [GlobRes[nb]['BuildID'][i]['50A_UUID'] for i in range(len(GlobRes[nb]['BuildID']))] + locref = [GlobRes[nb]['BuildID'][i][key] for i in range(len(GlobRes[nb]['BuildID']))] index_y,varx = Utilities.getSortedIdx(reference,locref) vary = [Res['SimNum'][idx] for idx in index_y] Utilities.plotBasicGraph(FigName['fig_name'].number, FigName['ax0'], varx, [vary], 'Building', [name[nb]], 'Building num in the GeojSon file', signe[nb]) +def Read_Arguments(): + #these are defaults values: + Config2Launch = [] + CaseNameArg =[] + # Get command-line options. + lastIdx = len(sys.argv) - 1 + currIdx = 1 + while (currIdx < lastIdx): + currArg = sys.argv[currIdx] + if (currArg.startswith('-yml')): + currIdx += 1 + Config2Launch = sys.argv[currIdx] + if (currArg.startswith('-Case')): + currIdx += 1 + CaseNameArg = sys.argv[currIdx] + currIdx += 1 + return Config2Launch,CaseNameArg +def getPathList(config): + CaseNames = config['2_CASE']['0_GrlChoices']['CaseName'].split(',') + path = [] + Names4Plots = [] + for CaseName in CaseNames: + congifPath = os.path.abspath(os.path.join(config['0_APP']['PATH_TO_RESULTS'], + CaseName)) + if not os.path.exists(congifPath): + print('Sorry, the folder '+CaseName+' does not exist...use -Case or -yml option or change your localConfig.yml') + sys.exit() + if os.path.exists(os.path.join(congifPath,'Sim_Results')): + path.append(os.path.join(congifPath,'Sim_Results')) + Names4Plots.append(CaseName) + else: + if len(CaseNames)>1: + print( + 'Sorry, but CaseNames '+str(CaseNames)+' cannot be aggregated because all or some contains several subcases') + sys.exit() + liste = os.listdir(congifPath) + for folder in liste: + path.append(os.path.join(congifPath,folder,'Sim_Results')) + Names4Plots.append(CaseName + '/' + folder) + return path,Names4Plots,CaseNames if __name__ == '__main__' : - CaseName= 'ForTest' #Name of the case study to post-process + ConfigFromArg,CaseNameArg = Read_Arguments() + config = setConfig.read_yaml(os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfig.yml')) + configUnit = setConfig.read_yaml( + os.path.join(os.path.dirname(os.getcwd()), 'CoreFiles', 'DefaultConfigKeyUnit.yml')) + LocalConfigPath = os.path.join(os.path.dirname(os.getcwd()),'ModelerFolder') + config, filefound, msg = setConfig.check4localConfig(config, LocalConfigPath) + #config['2_CASE']['0_GrlChoices']['CaseName'] = 'Simple' + if CaseNameArg: + config['2_CASE']['0_GrlChoices']['CaseName'] = CaseNameArg + path, Names4Plots,CaseNames = getPathList(config) + elif type(ConfigFromArg) == str: + if ConfigFromArg[-4:] == '.yml': + localConfig = setConfig.read_yaml(ConfigFromArg) + config = setConfig.ChangeConfigOption(config, localConfig) + path,Names4Plots,CaseNames = getPathList(config) + else: + print('[Unknown Argument] Please check the available options for arguments : -yml or -Case') + sys.exit() + else: + path, Names4Plots,CaseNames = getPathList(config) + print('[Studied Results Folder] '+str(Names4Plots)) #Names (attributes) wanted to be taken in the pickle files for post-processing. The time series are agrregated into HeatedArea, NonHeatedArea and OutdoorSite extraVar=['height','StoreyHeigth','nbfloor','BlocHeight','BlocFootprintArea','BlocNbFloor','HeatedArea','NonHeatedArea','OutdoorSite'] - Names4Plots = [CaseName] #because we can have several path for several studies we want to overplot. - mainpath = os.path.dirname(os.path.dirname(os.getcwd())) - if os.path.exists(mainpath + os.path.normcase('/MUBES_SimResults/'+CaseName+'/Sim_Results')): - path = [mainpath + os.path.normcase('/MUBES_SimResults/'+CaseName+'/Sim_Results')] - else: - path = [] - Names4Plots = [] - liste = os.listdir(mainpath + os.path.normcase('/MUBES_SimResults/'+CaseName)) - for folder in liste: - path.append(mainpath + os.path.normcase('/MUBES_SimResults/'+CaseName+'/'+folder+'/Sim_Results')) - Names4Plots.append(CaseName+'/'+folder) + #because we can have several path for several studies we want to overplot. Res = {} TimeSerieList=[] TimeSerieUnit = [] id =0 for idx, curPath in enumerate(path): + print('Considering results from : '+CaseNames[idx]) Res[idx] = Utilities.GetData(curPath,extraVar) #lets grab the time series name (the chossen ouputs from EP). # /!\ the data are taken from the building number 0, thus if for example not an office type, the will be no occupant. Choose another building if needed @@ -224,11 +288,11 @@ def plotIndex(GlobRes,FigName,name): #The opening order does not follows the building simulation number while opening the data. Thus, this first graphs provides the correspondance between the other plots, building number and their simulation number - IndexFig = Utilities.createSimpleFig() - plotIndex(Res, IndexFig, Names4Plots) + # IndexFig = Utilities.createSimpleFig() + # plotIndex(Res, IndexFig, Names4Plots) #this 2nd plot gives the size of the error file. It gives insights if some buildings causses particulary over whole issue in the simulation process ErrorFig = Utilities.createMultilFig('',2,linked=False) - plotErrorFile(Res, ErrorFig, Names4Plots,legend = False) + plotErrorFile(Res, ErrorFig, Names4Plots) #this 3rd graph gives the footprint area and the correspondance between EPCs value if available AreaFig = Utilities.createMultilFig('',2,linked=False) plotAreaVal(Res, AreaFig, Names4Plots) diff --git a/ReadResults/Utilities.py b/ReadResults/Utilities.py index 2e9b304..819043a 100644 --- a/ReadResults/Utilities.py +++ b/ReadResults/Utilities.py @@ -172,8 +172,10 @@ def createMultilFig(title,nbFig,linked=True): ax[i].grid() if i>0 and linked: ax[i].sharex(ax[0]) + + if i ==0: + plt.title(title) #plt.tight_layout() - plt.title(title) return {'fig_name' : fig_name, 'ax': ax} def createMultilDblFig(title,nbFigx,nbFigy,linked=True): @@ -188,13 +190,16 @@ def createMultilDblFig(title,nbFigx,nbFigy,linked=True): totfig+=1 if i>0 and j>0 and linked: ax[i].sharex(ax[0]) + if i==0 and j==0: + plt.title(title) #plt.tight_layout() - plt.title(title) return {'fig_name' : fig_name, 'ax': ax} #this function enable to create a single graph areas def createSimpleFig(): - fig_name = plt.figure(figsize=(10, 7)) + fig_name = plt.figure(figsize=(7, 5)) + plt.rc('font', size=15) + #plt.subplots_adjust(bottom=0.3) gs = gridspec.GridSpec(4, 1, left=0.1, bottom = 0.1) ax0 = plt.subplot(gs[:, 0]) ax0.grid() @@ -202,20 +207,29 @@ def createSimpleFig(): return {'fig_name' : fig_name, 'ax0': ax0} #basic plots -def plotBasicGraph(fig_name,ax0,varx,vary,varxname,varyname,title,sign,legend = True, markersize = 5): +def plotBasicGraph(fig_name,ax0,varx,vary,varxname,varyname,title,sign,color = 'black', legend = True, markersize = 5, xlim =[], ylim = [], mfc = 'none'): plt.figure(fig_name) + if len(varyname)>0: for nb,var in enumerate(vary): - ax0.plot(varx,var,sign,label= varyname[nb], mfc='none',markersize=markersize) + ax0.plot(varx,var,sign,label= varyname[nb], mfc=mfc,markersize=markersize,color = color) ax0.set_xlabel(varxname) ax0.set_ylabel(title) + if xlim: + ax0.set_xlim(xlim) + if ylim: + ax0.set_ylim(ylim) if legend: ax0.legend() else: for nb,var in enumerate(vary): - ax0.plot(varx,var,sign, mfc='none',markersize=markersize) + ax0.plot(varx,var,sign, mfc=mfc,markersize=markersize,color = color) ax0.set_xlabel(varxname) ax0.set_ylabel(title) + if xlim: + ax0.set_xlim(xlim) + if ylim: + ax0.set_ylim(ylim) #this plots variables realtively to their maximum value def plotRelative2Max(fig_name,ax0,varx,vary,varxname,varyname): @@ -261,7 +275,7 @@ def plotHist(fig_name,ax0,vary,varyname): ax0.hist(vary,normed=True,label = varyname) ax0.legend() -def GetData(path,extravariables = [], Timeseries = [],BuildNum=[]): +def GetData(path,extravariables = [], Timeseries = [],BuildNum=[],BldList = []): os.chdir(path) liste = os.listdir() ResBld = {} @@ -294,18 +308,23 @@ def GetData(path,extravariables = [], Timeseries = [],BuildNum=[]): else: idxF = ['_'+str(BuildNum[0])+'v','.'] #now that we found this index, lets go along alll the files + for file in liste: if '.pickle' in file: + NbRun = int(file[file.index(idxF[0]) + len(idxF[0]):file.index(idxF[1])]) + if BldList: + if NbRun not in BldList: + continue try: - print(file) - SimNumb.append(int(file[file.index(idxF[0]) + len(idxF[0]):file.index(idxF[1])])) + #print(file) + SimNumb.append(NbRun) try: with open(file, 'rb') as handle: ResBld[SimNumb[-1]] = pickle.load(handle) except: - pass - # with open(file, 'rb') as handle: - # ResBld[SimNumb[-1]] = pickle5.load(handle) + import pickle5 + with open(file, 'rb') as handle: + ResBld[SimNumb[-1]] = pickle5.load(handle) try: Res['ErrFiles'].append(os.path.getsize(file[:file.index('.pickle')]+'.err')) with open(file[:file.index('.pickle')]+'.err') as file: @@ -319,7 +338,7 @@ def GetData(path,extravariables = [], Timeseries = [],BuildNum=[]): #lets get the mandatory variables variables=['EP_Elec','EP_Heat','EP_Cool','EP_DHW','SimNum','EPC_Elec','EPC_Heat','EPC_Cool','EPC_Tot', - 'ATemp','EP_Area','BuildID'] + 'DB_Surf','EP_Area','BuildID','BldSimName'] # lest build the Res dictionnary for key in variables: Res[key] = [] @@ -354,24 +373,25 @@ def GetData(path,extravariables = [], Timeseries = [],BuildNum=[]): except: Res['BuildID'].append(None) Res['EP_Area'].append(BuildObj.EPHeatedArea) + Res['BldSimName'].append(BuildObj.name) try: - Res['ATemp'].append(BuildObj.ATemp) + Res['DB_Surf'].append(BuildObj.DB_Surf) except: - Res['ATemp'].append(BuildObj.surface) + Res['DB_Surf'].append(BuildObj.surface) eleval = 0 for x in BuildObj.EPCMeters['ElecLoad']: if BuildObj.EPCMeters['ElecLoad'][x]: eleval += BuildObj.EPCMeters['ElecLoad'][x] - Res['EPC_Elec'].append(eleval/BuildObj.ATemp if BuildObj.ATemp!=0 else 0) + Res['EPC_Elec'].append(eleval/BuildObj.DB_Surf if BuildObj.DB_Surf!=0 else 0) heatval = 0 for x in BuildObj.EPCMeters['Heating']: heatval += BuildObj.EPCMeters['Heating'][x] - Res['EPC_Heat'].append(heatval/BuildObj.ATemp if BuildObj.ATemp!=0 else 0) + Res['EPC_Heat'].append(heatval/BuildObj.DB_Surf if BuildObj.DB_Surf!=0 else 0) coolval = 0 for x in BuildObj.EPCMeters['Cooling']: coolval += BuildObj.EPCMeters['Cooling'][x] - Res['EPC_Cool'].append(coolval/BuildObj.ATemp if BuildObj.ATemp!=0 else 0) - Res['EPC_Tot'].append((eleval+heatval+coolval)/BuildObj.ATemp if BuildObj.ATemp!=0 else 0) + Res['EPC_Cool'].append(coolval/BuildObj.DB_Surf if BuildObj.DB_Surf!=0 else 0) + Res['EPC_Tot'].append((eleval+heatval+coolval)/BuildObj.DB_Surf if BuildObj.DB_Surf!=0 else 0) #forthe old way of doing things and the new paradigm for global results try: @@ -412,7 +432,11 @@ def GetData(path,extravariables = [], Timeseries = [],BuildNum=[]): Res[varName] = np.vstack((Res[varName] ,ResBld[key][Timeseries[key1]['Location']][Timeseries[key1]['Data']])) except: pass - + #Finaly lets reorder the results by the number of the SimNum : + sorted_idx = np.argsort(Res['SimNum']) + for key in Res.keys(): + if Res[key]: + Res[key] = [Res[key][idx] for idx in sorted_idx] return Res diff --git a/requirements.txt b/requirements.txt index a636f0d..e940f5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ git+https://github.com/xavfa/geomeppy.git@geomeppyForMUBES eppy==0.5.53 numpy==1.21 +pandas==1.2.0 shapely==1.7.1 pyclipper==1.2.0 transforms3d==0.3.1 @@ -8,9 +9,9 @@ pypoly2tri==0.0.3 esoreader==1.2.3 matplotlib==3.3.3 tripy==1.0.0 -salib==1.3.12 openpyxl==3.0.7 pyproj==3.1.0 scikit-learn==0.24.1 scikit-optimize==0.8.1 -fmpy==0.3.1 \ No newline at end of file +fmpy==0.3.1 +openturns==1.18 \ No newline at end of file