ladybug-tools / uwg

:city_sunrise: The Urban Weather Generator (uwg) is a Python application for modeling the urban heat island effect.
https://www.ladybug.tools/uwg/docs/
GNU General Public License v3.0
56 stars 26 forks source link

Tree coverage #107

Open frank984 opened 4 years ago

frank984 commented 4 years ago

The UWG considers the tree coverage to define the latent heat flux in the urban area and the percentage of shaded surface in the canyon. If I consider this study, The average UHI intensity decrease at increasing of the Tree coverage as expectable. I downloaded this file and simply tried to modify the parameter related to tree coverage. If I consider the default value of tree coverage equal to 0.2 the average monthly dry bulb temperature is 25.505 °C. If I consider the modified value equal to 0.8 the average monthly dry bulb temperature is 25.515 °C. I would have expected the exact opposite. Even if I report all the hourly data for the entire month in a graph, there are no differences in terms of dry bulb temperature: image

can You explain why?

chriswmackey commented 4 years ago

If this is a bug of some kind, it is one that is in the UWG and not the dragonfly components. So I have transferred it to the UWG repo.

There is a chance that this is also just a case of poor input, What latent fraction and albedo are you assuming for the vegetation as you change the coverage here, @frank984 ?

frank984 commented 4 years ago

There is a chance that this is also just a case of poor input, What latent fraction and albedo are you assuming for the vegetation as you change the coverage here, @frank984 ?

Hi @chriswmackey, I hope you are well! I used the default values, as reported in your example: image

saeranv commented 4 years ago

One possibility: tree coverage also blocks longwave radiation from being emitted to the sky. If you have tall canyons, there might be some edge case where the canyons already block solar (mitigating heat reduction impact of the tree coverage), and the trees block outward longwave from buildings to the sky, resulting in hotter UHI.

frank984 commented 4 years ago

In the attached .zip you can find the .gh file and an excel where I copied the hourly values considering 3 average heights (60, 10, 3 m) and the two tree coverage (0.2 and 0.8): there are no differences. It seems there is an issue. Downloads.zip

saeranv commented 4 years ago

Thanks @frank984. I spent some time digging into this, and I think this may be a bug that can be traced back to the UWG_Matlab implementation.

cc @hansukyang,

First, I can confirm that increasing the tree fraction is increasing the drybulb temperature. I tested a tree coverage of 0.1 versus 0.6 for our Singapore parameter file, for July, and found that there's a 0.01 increase in temperature when tree coverage increases: image

I was able to isolate the primary contributor of this increase to this code here, in UWG_Matlab: https://github.com/hansukyang/UWG_Matlab/blob/b96bc2a8458ce10990d9bdbea16c5cef17628c50/SolarCalcs.m#L53-L54

Where the sensible and latent heat fraction is calculated. Essentially, the UWG calculates the heat contribution from trees, and the larger fraction of tree coverage results in a larger gain to the UHI. If I override this to zero, there is no difference between the two uwg results.

By itself, this is not a problem. The problem is that seems to be that there's nowhere in the current solar calcs that offsets tree sensible heat gain by incorporating the tree coverage fraction in the calculation of the solar radiation received by the road: https://github.com/hansukyang/UWG_Matlab/blob/b96bc2a8458ce10990d9bdbea16c5cef17628c50/SolarCalcs.m#L6-L50

I've grepped through all the code, and as far as I can tell, tree coverage is not incorporated into the solRec component of the road. The 'unshaded' solar received by the road is instead incorporated into the surface flux calcs, where veg coverage is accounted for (grass, green roofs), but not trees, and eventually used as part of the canyon heat balance here: https://github.com/hansukyang/UWG_Matlab/blob/master/UCMDef.m#L141-L148 and here: https://github.com/hansukyang/UWG_Matlab/blob/master/UCMDef.m#L141-L148

In that second line, this error may be further compounded by an additional overcounting of the tree sensible heat, which is an issue I raised over here a while back: https://github.com/ladybug-tools/uwg/issues/94

Together, this might explain the increase of drybulb with tree coverage. If this assessment is correct, we should be able to correct it by multiplying the fraction of of the treeCoverage with the solar received by the road in two places in the code.

@hansukyang, let me know if you need more context, but I would appreciate your thoughts on this.

hansukyang commented 4 years ago

@saeranv, my memory is a bit foggy at this point and my apology as it seems I didn't get around to replying to Issue #94 a while ago.

A couple of notes fyi on these calculations.

Let me know your thoughts.

saeranv commented 4 years ago

@hansukyang, thank you for your prompt response, and sorry for my delayed response.

incorporating trees into the canyon geometry was quite tricky (e.g. shading of walls by tree) given the basic urban canyon model representation so I think there were some simplification made, which was more or less flattening the trees onto the road surface as vegetation and not considering how it may cast shadow onto wall or even some roof if it is tall enough. The solar radiation received by the road then takes the vegetation albedo into account as weighted average when calculating short-wave reflection. (https://github.com/hansukyang/UWG_Matlab/blob/b96bc2a8458ce10990d9bdbea16c5cef17628c50/SolarCalcs.m#L20) So in the solar calculation, the 'road' is actually 'road and vegetation' surface when calculating the amount of solar energy received.

Hmm.. well then maybe this is where the problem is occurring. I understood that road vegetation (or the vegCoverage property) to represents grass on the road, not trees. You can see here in your UWG.m ln 190 that vegCover and treeCoverage are separate. Are these parameters supposed to interact somehow?

        % Vegetatin parameters
        vegCover = num(22);     % urban area veg coverage ratio
        treeCoverage = num(23); % urban area tree coverage ratio
        vegStart = num(24);     % vegetation start month
        vegEnd = num(25);       % vegetation end month
        albVeg = num(26);       % Vegetation albedo
        latGrss = num(27);      % latent fraction of grass
        latTree = num(28);      % latent fraction of tree
        rurVegCover = num(28);  % rural vegetation cover

https://github.com/hansukyang/UWG_Matlab/blob/b96bc2a8458ce10990d9bdbea16c5cef17628c50/UWG.m#L190-L198

As it stands right now only the vegCover is being defined as road.vegCoverage: road.vegCoverage = min(vegCover/(1-bldDensity),1); https://github.com/hansukyang/UWG_Matlab/blob/b96bc2a8458ce10990d9bdbea16c5cef17628c50/UWG.m#L224

@chriswmackey, is the uwg vegCover parameter refer to grass in Dragonfly, meaning it's seperate from tree coverage? (Sorry I don't have access to Grasshopper at the moment to check myself).

I think there were also some errors in how vegetation & tree coverage was treated - given the flattening of the trees, I remember thinking that they shouldn't be double counted but wondering if this is adding to the confusion.

My best guess is that the vegCoverage seems to be accounted for correctly, as far as I can tell. The albedo of the road accounts for the vegetation albedo, and then again in the surface flux calculation in element.m, but I think this is to account for radiation bounces versus absorption. The absorbed portion informs the surface temperature of the element, which participates in the canyon heat balance in UCMDef.m. This seems correct. The tree coverage is where it seems like it might be undercounting (since it's treeCoverage is not incorporated into the solar calcs for the road) or overcounted (if vegCoverage == treeCoverage, since the sensible/latent portion of vegCoverage is accounted for in elements.m, and the treeCoverage is not factored into the treeSensHeat portion of the canyon sensHeat i.e #94).

Increasing dry-bulb for the urban canyon can also be due to the fact that vegetation is treated as having zero thermal mass (i.e. all solar energy absorbed by vegetation (the non-latent portion) is manifested as sensible heat into the canyon) as opposed to roads that have thermal mass where its temperature increases first and also thermally conducts to lower layers in the ground that act as a damper. So in this case, it is possible that increasing tree coverage increases the urban temperature as the net energy influx into the canyon is increased due to this simplified modelling assumptions.

Yes, that seems to be exactly what is happening. Specifically the solar energy received by the canyon (to the roads, roof, and walls) is calculated, and then the portion received by the road is further multiplied by the treeCoverage to get the sensible/latent portions of tree heat. Is there a reason why we can't subtract the treeCoverage from the solar received by the road, so that it can be accounted for by the surface flux calculations by the element.py, so that the trees can shade the roads, even if they do provide some fraction of sensible heat?

S

frank984 commented 4 years ago

For a better comprehension of the issue, I add more details: I have used the same .gh file available in the shared .zip and unified the heat latent due to trees and grass (average value between the default 0.7 for tree and 0.5 for grass): image. Then I made a sensitivity analysis: As you can see, by increasing the fraction of grass coverage, the Dry Bulb Temperature (DBT) decreases. It can be also noted that the model considers correctly the starting site coverage because, for example, with a site coverage equal to 0.5 there is no difference between DBT values with grass coverage equal to 0.5 or 1: image Instead, if I modify the tree coverage, in addition to the issue related to the increase in DBT values with the increase of the tree coverage, it is possible to highlight that the starting site coverage is not taken into account because with a site coverage equal to 0.5, if I increase the tree coverage from 0.5 to 1, the DBT increases: image

saeranv commented 4 years ago

Thanks @frank984. This seems to confirm my interpretation of the code: that the heat balance for the surface vegetation parameter is accounting for it's shading effect, and that there is an absence of a similar shade budgeting for the tree parameter.

It can be also noted that the model considers correctly the starting site coverage because, for example, with a site coverage equal to 0.5 there is no difference between DBT values with grass coverage equal to 0.5 or 1:

Can you clarify this part. Isn't the site coverage = building_footprint / total_site_area ? Why should the DBT values not change when grass coverage is increased?

frank984 commented 4 years ago

Can you clarify this part. Isn't the site coverage = building_footprint / total_site_area ? Why should the DBT values not change when grass coverage is increased?

Thanks @saeranv, I clarify/rewrite considering the following screenshot: image

As you can see, the site coverage (that, as you reported is equal to building_footprint / total_site_area) is set to 0.5. If I increase the grass coverage from 0 to 0.5 the DBT value decreases (1 and 2 values in red). If I increase the grass coverage from 0.5 to 1, the DBT doesn't decrease.

I thought the green coverage was only on the building-free surface, so I justified the non-variation of the DBT because I thought there was no more space available. But actually I went to reread the description of the grasshopper component and the grass coverage is defined as "a number from 0 to 1 that defines the fraction of the entire urban area (including both pavement and roofs) that is covered by grass/vegetation".

So if you also consider the roof surface, why if I increase the grass cover from 0.5 to 1, the DBT does not decrease (2 and 3 red values in the screenshot)?

saeranv commented 4 years ago

@frank984

So if you also consider the roof surface, why if I increase the grass cover from 0.5 to 1, the DBT does not decrease (2 and 3 red values in the screenshot)?

I think it's not decreasing because you have a larger building density in that scenario, and so the increase in grass fraction results in a smaller amount of grass increase. Which just doesn't have that much of an impact on DBT. The UWG code truncates the DBT values to certain amount of decimal places, so we won't neccessarily see the actual impact of changing small values.

frank984 commented 4 years ago

@chriswmackey, is the uwg vegCover parameter refer to grass in Dragonfly, meaning it's seperate from tree coverage? (Sorry I don't have access to Grasshopper at the moment to check myself).

I think there were also some errors in how vegetation & tree coverage was treated - given the flattening of the trees, I remember thinking that they shouldn't be double counted but wondering if this is adding to the confusion.

My best guess is that the vegCoverage seems to be accounted for correctly, as far as I can tell. The albedo of the road accounts for the vegetation albedo, and then again in the surface flux calculation in element.m, but I think this is to account for radiation bounces versus absorption. The absorbed portion informs the surface temperature of the element, which participates in the canyon heat balance in UCMDef.m. This seems correct. The tree coverage is where it seems like it might be undercounting (since it's treeCoverage is not incorporated into the solar calcs for the road) or overcounted (if vegCoverage == treeCoverage, since the sensible/latent portion of vegCoverage is accounted for in elements.m, and the treeCoverage is not factored into the treeSensHeat portion of the canyon sensHeat i.e #94).

Thanks @frank984. This seems to confirm my interpretation of the code: that the heat balance for the surface vegetation parameter is accounting for it's shading effect, and that there is an absence of a similar shade budgeting for the tree parameter.

@chriswmackey, @saeranv, @hansukyang sorry if I still disturb you but there is a problem about the model's ability to consider an increase in tree coverage that has not yet been solved. Is there any news on these aspects?

hansukyang commented 4 years ago

Hi @frank984, sorry for the delayed response. I think the way tree coverage is considered may require updating. The geometry of the model is not able to properly consider the effects of shading by trees, so what I would suggest is to merge the vegetation parameters into one (grass + tree). I think this was what I had in mind when I was working on it but it looks like there are some bugs in the logic.

I don't have access to Matlab to test this out so perhaps if you or @saeranv can experiment with merging the two parameters?

chriswmackey commented 4 years ago

I agree that merging the two types of vegetation into one makes sense if tree shading isn't considered. I guess this would mean that users would have to use an latent fraction that is weighted for all vegetation in the canyon, which I think is fine.

saeranv commented 4 years ago

@frank984, @hansukyang, @chriswmackey

I also apologize for my late response here.

I can implement @hansukyang's suggestion of merging the vegetation and trees fraction. If no one can find an error in my thinking documented above, then I can also unify the various calculations currently being done separately for solar, and sensible heat.

S

frank984 commented 4 years ago

I agree that merging the two types of vegetation into one makes sense if tree shading isn't considered. I guess this would mean that users would have to use an latent fraction that is weighted for all vegetation in the canyon, which I think is fine.

At this point it could be also useful to merge the tree coverage and the grass coverage in a unique "vegetation coverage" input of the DF city component.

frank984 commented 4 years ago

@frank984, @hansukyang, @chriswmackey

I also apologize for my late response here.

I can implement @hansukyang's suggestion of merging the vegetation and trees fraction. If no one can find an error in my thinking documented above, then I can also unify the various calculations currently being done separately for solar, and sensible heat.

S

Dear @saeranv, as a consequence of the previous posts, I think it could be useful to merge the trees and grass coverage in a unique vegetation coverage. Consequently I think also it could be useful to unify the various calculations currently being done for solar and sensible heat.

Thank you, @saeranv , @hansukyang and @chriswmackey.

saeranv commented 4 years ago

Awesome, just a heads up that I'm in the middle of moving countries (+ other projects) so won't be able to tackle this until at minimum a week or two from now.

frank984 commented 4 years ago

Dear @saeranv, is there any upgrade about your last post?

saeranv commented 4 years ago

@frank984

Chris and I were just discussing it yesterday, and have started planning the update. It's coming along. It's part of a whole bunch of other updates that the UWG is going to get (some of which you've already asked for), but I'll make sure to tackle the trees ASAP.

frank984 commented 4 years ago

I'll make sure to tackle the trees ASAP

@saeranv, I'm sorry if I'm boring you again with this open question. As I would like to use the .epw resulting from dragonfly/UWG in another, more complex model, I would like to understand if you need to wait a few more days or much longer to "adjust" the tree contribution. Thank you for your patience.

hansukyang commented 4 years ago

@frank984 - if you're in a rush, one thing you can do is to zero-out the tree parameter. Also, I'm not sure if you're using TMY EPW file, but if you are, note that many 'typical' climate files will have slight cold-bias as substantial increase in cooling degree days are already being observed in the last several decades for many cities (check https://warmingcities.herokuapp.com/) so use AMY file if you can.

frank984 commented 4 years ago

Thank you @hansukyang for your useful informations!

saeranv commented 4 years ago

@hansukyang, @chriswmackey @frank984

Thanks for your patience guys, I’ve just finished revisiting (and hopefully fixing) this tree/vegetation error. It turned out to be a little bit more complicated then I originally thought to fix, as the vegetation heat is spread across multiple modules, but I believe the latest PR I just pushed should fix this bug.

I apologize in advance for this massive post. I documented my changes within the code, but I wanted to ensure there was a place on github where the logic behind the changes were documented in detail. I feel pretty confident about my fix, but there are places where it has deviated from what we discussed previously, so let me know if my reasoning is wrong somewhere.

The edits I made encompass three errors related to how the tree and vegetation heat balance is modeled in the current UWG. Two of these errors were inaccurately increasing the sensible heat contribution of trees in the UWG, and one error inaccurately decreasing the sensible heat contribution of grass. I’ve found that by fixing these three errors, the net contribution of increasing trees is a reduction in dry bulb.

The three errors are:

  1. The vegetation fraction in UWG (and Dragonfly) was meant to only represent grass. Only the shading impact of vegetation is accounted for in calculating the heat absorbed by the road, which means the tree shading is neglected.
  2. The UWG model only calculates the sensible heat from trees but not the sensible heat from grass, which inaccurately decreases the vegetation sensible heat contribution.
  3. The tree sensible heat property in the Urban Canopy Model (UCMDef) wasn’t completely accounting for the tree coverage fraction, which results in inaccurately overcounting the sensible heat contribution from trees.

1. The vegetation fraction in UWG (and Dragonfly) was meant to only represent grass, but there are parts of the code where it seems to represents the sum of the grass and tree fraction. This results in inaccurately increasing the solar radiation absorbed by the urban ground.

This is the fix suggested by Joseph (in comments above) to “merge vegetation parameters into one: grass + tree”, in contrast to the previous UWG/Dragonfly method which defined vegetation strictly as grass. Once the vegetation property is reframed as the combination of grass and tree, the heat balance for the urban canopy correctly accounts for the shading effect of the tree, without any major changes to the code.

Specifically: the problem we had before was that the tree property in UWG only modeled the obstruction of ground infrared radiation (in InfraCalcs.m), and the sensible heat fraction from the tree (in the UCMDef.m UCModel method) – but never subtracted the portion of solar radiation that fell on the tree fraction from the urban road Element object. Since it does subtract the solar contribution of vegetation, incorporating the tree fraction into the vegetation fraction solves the overcounting of solar. Furthermore, assuming trees are merged with the vegetation seems consistent with what Bueno wrote in his 2012 thesis (Bueno, PhD Thesis, 2012, pg 95):

image

I’ll go into some detail here about the actual computation of the vegetation heat balance since there are certain subtleties that provide important context for the next error I corrected. Here is the UWG_Matlab code computing the solar absorbed portion while accounting for vegetation in Element.m:

               else    % Summer, veg
                    obj.solAbs = ((1-obj.vegCoverage)*(1-obj.albedo)+...
                        obj.vegCoverage*(1-parameter.vegAlbedo))*obj.solRec;
                    vegLat = obj.vegCoverage*parameter.grassFLat*(1-parameter.vegAlbedo)*obj.solRec;
                    vegSens = obj.vegCoverage*(1.-parameter.grassFLat)*(1-parameter.vegAlbedo)*obj.solRec;
                end
                obj.lat = soilLat + vegLat;

                % Sensible & net heat flux 
                obj.sens = vegSens + obj.aeroCond*(obj.layerTemp(1)-tempRef);
                obj.flux = -obj.sens + obj.solAbs + obj.infra - obj.lat;

Note that the final heat flux transferred to the object consists of just the solar absorbed by the portion of the Element object without vegetation, and surface convection. Technically there is some latent impact of wet soil also in there, but that value has been hardcoded to zero so it has no impact. This makes sense for the grass coverage, but with our reframing of vegetation as trees + grass, it now also account for the solar blocked by tree coverage.

One potential concern I had was if the surface convection calculation is appropriate to model the tree canopy since it’s a function of the road surface temperature. However I found that Bueno explicitly assumes the temperature of tree canopy and urban surfaces are the same, so the tree canopy convection should be appropriate even though the surface temperature is taken from the road element (PhD Thesis, 2012, p. 95):

image

The correction for this error was therefore to simply change the way users input vegetation into the UWG. I added a new property called grasscover, and made a vegcover an internally calculated value that represents the sum of the grasscover and vegcover. I also modified the Element Surfflux code so that the latent fractions for trees and grass were applied for each coverage, since previously all vegetation was assumed to have the latent fraction of grass. Since the quantity of solar blocked by the vegetation will always be greater then the fraction of solar absorbed and converted into tree sensible heat, this reduces the radiation heat transferred to the urban road (relative to the original code).

2. The UWG model only calculates the sensible heat from trees but not the sensible heat from grass, which inaccurately decreases the vegetation sensible heat contribution.

This error is something I caught when checking the heat balance of the code snippet introduced above in Element.m:

               else    % Summer, veg
                    obj.solAbs = ((1-obj.vegCoverage)*(1-obj.albedo)+...
                        obj.vegCoverage*(1-parameter.vegAlbedo))*obj.solRec;
                    vegLat = obj.vegCoverage*parameter.grassFLat*(1-parameter.vegAlbedo)*obj.solRec;
                    vegSens = obj.vegCoverage*(1.-parameter.grassFLat)*(1-parameter.vegAlbedo)*obj.solRec;
                end
                obj.lat = soilLat + vegLat;

                % Sensible & net heat flux 
                obj.sens = vegSens + obj.aeroCond*(obj.layerTemp(1)-tempRef);
                obj.flux = -obj.sens + obj.solAbs + obj.infra - obj.lat;

The basic calculation here is to calculate the solar absorbed by the vegetated and unvegetated areas of the Element, and then subtract the sensible and latent heat associated with the vegetation, and surface convection to compute Element flux. The flux is then used directly in the calculation of Element conduction. To preserve the conservation of energy, the sensible, latent and convective components which have just been subtracted, need to be added as heat to the urban canopy model.

The UWG_Matlab does this for various construction types. It adds the obj.sens heat back to the urban canopy model if the Element represents a rural road (in UBLDef.m and RSMDef.m), or a green roof[1] (in UCMDef.m). For vertical Element objects (walls), the sensible heat contribution from elements only consists of surface convection, which is correctly factored into the canopy model in UCMDef.m. However, there is one omission: if the Element represents an urban road, the sensible vegetation heat is never factored into the canopy. I was able to confirm this by commenting out the rural road sensible vegetation fraction, and confirming that the road heat flux and UWG drybulb didn’t change when I modified the latent fraction of grass. So, unless anyone thinks otherwise, this seems like a mistake or omission in the code.

There are multiple ways I could have fixed this issue, although I couldn’t just simply add the urban road obj.sens property to the canopy model, since it would have lead to double-counting of the surface convection, since that component is separately accounted for in UCMDef.m:

obj.Q_road = h_conv * (T_road - obj.canTemp) * (1. - obj.bldDensity)

Thus the fix involves just adding the vegetation sensible heat fraction to the canopy. The UWG_Matlab already calculates the sensible heat fraction of tree coverage, in SolarCalcs.m like this, which gets added to the urban canopy in UCMDef.m:

 % Vegetation heat (per m^2 of veg)
 UCM.treeSensHeat = (1-parameter.vegAlbedo)*(1-parameter.treeFLat)*UCM.SolRecRoad;
 UCM.treeLatHeat = (1-parameter.vegAlbedo)*parameter.treeFLat*UCM.SolRecRoad;

My solution was to add the grass sensible/latent components in the same way to that parameter:

            # Modification from UWG_Matlab on 09/20:
            # Add the sensible and latent heat fraction of grass (vegetation not
            # accounted for by tree fraction). Note that the self.UCM.road.sens property
            # also contains a calculation of sensible heat from vegetation and surface
            # convection, but was not used here because the UCModel function in the
            # UCMDef module already accounts for the entire road convective heat transfer
            # so using the UCM.road.sens property would double-count convection. This
            # sensible heat therefore just accounts for the absorbed solar radiation
            # split into it's sensible heat fraction.

            # Vegetation heat (grass) (per m^2 of veg)
            grasscover = self.UCM.vegcover - self.UCM.treeCoverage
            self.UCM.treeSensHeat += (
                (1 - self.parameter.vegAlbedo) * (1 - self.parameter.grassFLat) *
                self.UCM.SolRecRoad * grasscover)
            self.UCM.treeLatHeat += (
                (1 - self.parameter.vegAlbedo) * self.parameter.grassFLat *
                self.UCM.SolRecRoad * grasscover)

And with that, the grass coverage is accounted for the canopy heat balance, and I was able to confirm changing the latent fraction of grass had an impact on the canopy sensible heat, and overall dry bulbs (unlike in the original code).

Also, you may notice that my code addition multiplies the grass sensible/latent fractions with the fraction of grass coverage, whereas the UWG_Matlab code I referenced doesn’t include the tree coverage fraction. This leads to the third error I found.

3. The tree sensible heat property in the Urban Canopy Model (UCMDef) wasn’t completely accounting for the tree coverage fraction, which results in overcounting the sensible heat contribution from trees.

This is a bug that I brought up originally in this git issue a while back: https://github.com/ladybug-tools/uwg/issues/94. Essentially I noted that the following calculation of the urban canopy sensible heat didn’t seem to account for the fraction of the tree in the urban area:

 % Sensible Heat
obj.sensHeat = obj.Q_wall + obj.Q_road + obj.Q_vent + obj.Q_window + obj.Q_hvac + obj.Q_traffic + obj.treeSensHeat + obj.Q_roof;

As I pointed out above, the obj.treeSensHeat just represents the solar absorbed on the full area of the road Element, split into sensible and latent components:

% Vegetation heat (per m^2 of veg)
UCM.treeSensHeat = (1-parameter.vegAlbedo)*(1-parameter.treeFLat)*UCM.SolRecRoad;
UCM.treeLatHeat = (1-parameter.vegAlbedo)*parameter.treeFLat*UCM.SolRecRoad;

Which doesn’t include the treeCoverage fraction.

At the time I posted this, Joseph and I weren’t sure if this was an error, so we left it as is. However, while revisiting the issue this time, I realize there’s a very obvious sign that this is a bug: in the case where there is 0% tree coverage, this property will still provide a value for tree sensible heat, when in fact that value should be zero. So I’m fairly confident this is just another bug, which is partially responsible for the bizarre behavior of increasing UWG drybulb when more trees are added.

The correction for this is simple, I added the tree coverage fraction into the treeSensHeat when it’s calculated in the solarcalcs module:

            # Modification from UWG_Matlab on 09/20:
            # Consolidate and incorporate the treeCoverage fraction into the tree
            # sensible and latent heat (W-m2) calculation. Previously this was
            # factored (inconsistently) at the urbflux and UCMDef.UCModel functions.

            # Vegetation heat (tree) (per m^2 of veg)
            self.UCM.treeSensHeat = (
                (1 - self.parameter.vegAlbedo) * (1 - self.parameter.treeFLat) *
                self.UCM.SolRecRoad * self.UCM.treeCoverage)
            self.UCM.treeLatHeat = (
                (1 - self.parameter.vegAlbedo) * self.parameter.treeFLat *
                self.UCM.SolRecRoad * self.UCM.treeCoverage)

In summary, it looks like these three errors were contributing to the tree coverage error Frank found. I’ll conclude by noting that I wrote a series of unit tests while investigating these issues, and have tested the results of these fixes on different combinations of tree coverage, grass coverage, tree latent fraction, and grass latent fraction. So I can confirm that while previously increasing tree coverage did increase the canopy sensible heat/dry bulb, the UWG is now showing much more reasonable behavior of reducing these components.

Specifically:

Let me know if there are any further suggestions, or mistakes in my reasoning.

[1] This is only tangentially related to this tree issue, but is worth mentioning briefly. While looking at the roof vegetation code, I realized there was in fact yet another vegetation-related error: the readDOE.m module accidently set all the roof Elements as vertical elements, not horizontal: https://github.com/hansukyang/UWG_Matlab/blob/b96bc2a8458ce10990d9bdbea16c5cef17628c50/readDOE.m#L233-L259. Which meant that adding roof vegetation had no impact on the UWG (vertical Elements don’t check vegCoverage). I have to admit, the curious lack of impact of roof vegetation was something that @simonmarti1992 brought up almost a year ago here https://github.com/ladybug-tools/uwg/issues/105, but I didn’t catch the error at the time. Anyway, I reserialzed the UWG refDOE typologies, and this error is now fixed, and I can confirm the roof vegetation has an impact on the UWG.

chriswmackey commented 4 years ago

This is excellent, @saeranv . Thank you for fixing these long-standing bugs in the UWG. After you merge your PR, we should probably also let Les know in case he wants to implement these same issues fixed in the Matlab version.

Also, the effect of increasing tree coverage on slower nighttime radiative heat loss is something that's been documented in the real world and with analog models going back to the time of TR Oke's early work. So I think your solution is correct. However, I would also assume that, if the tree latent fraction becomes significantly greater than the grass latent fraction, you could get a net cooling effect from replacing grass cover with tree cover. I have a sense that this is usually what happens in the real world given that trees tend to have much more surface area over which to transpire compared to grass (though this can obviously vary greatly with tree and grass species and with things like irrigation).

Again, great job!

hansukyang commented 4 years ago

Thanks for this @saeranv . It sounds like the fixes make sense and give expected results. I didn't realize you were continuing to work on fixing this so great job also! :+1:

Since my memory is quite foggy now, some comments for fyi and posterity if it comes up, and perhaps a call might be a quicker way to explain some of the points if you'd like.

uwb

Without doing a proper CFD, this obviously is a gross simplification and it took me a while to think it through and it doesn't seem like I did a very good job of explaining it in my thesis either so happy to elaborate more if you have questions.

saeranv commented 4 years ago

@hansukyang thank you! Good to see that my changes didn't raise any immediate red flags on your end.

Regarding your two notes:

  1. Road, wall and tree solar interaction.

The radiation exchange between the wall and road is based on two perpendicular surfaces (in SolarCalcs.m, with incident radiation and subsequent bounces of radiative exchanges. You can imagine how having 3D objects such as trees casting shades between the surfaces would make this quite complicated and likely incorrect.

That makes sense, and my revised tree model continues to act as a 2D surface "flattened" to the road surface. Thus the solar radiation that is absorbed by the road surface in the urban canyon road is blocked by the tree canopy defined by the horizontal vegetation density. It doesn't have any impact on the solar bounces on the wall (other then it's influence of the road reflection), so it's sort of like the tree canopy is always assumed to be below the canyon wall.

Here's Bruno's description of this process, which I believe you're alluding to: image

And here's the code that replicates this. The solAbs parameter represents the amount of solar radiation absorbed by the element surface. In the case of roads, the vegcoverage includes trees and grass, so the solar radiation is on the 2D road is obstructed by the fraction of tree and grass.

# Summer, veg
self.solAbs = (
    ((1.0 - self.vegcoverage) * (1. - self.albedo) + self.vegcoverage *
     (1.0 - parameter.vegAlbedo)) * self.solRec)
  1. Roof exchange with UBL.

So while I investigated it carefully, I didn't end up changing anything with the way the roof heat exchange interacts with the urban canopy. I changed the way the road sensible heat was calculated (to account for trees) but not the roof. I was mainly just concerned with investigating how the element sensible heat was added back into the UWG model after being discounted in solarcalcs (since it was omitting this for trees). Right now sensible heat from the roof is added to two parameters, one is the UCM.Q_ubl property and then to the UCM.sensHeat property, which gets used in the UBL and urbflux modules. So if the matlab version is handling this correctly, it should be correct in our version as well.

saeranv commented 4 years ago

Thanks, @chriswmackey

Also, the effect of increasing tree coverage on slower nighttime radiative heat loss is something that's been documented in the real world and with analog models going back to the time of TR Oke's early work. So I think your solution is correct. However, I would also assume that, if the tree latent fraction becomes significantly greater than the grass latent fraction, you could get a net cooling effect from replacing grass cover with tree cover. I have a sense that this is usually what happens in the real world given that trees tend to have much more surface area over which to transpire compared to grass (though this can obviously vary greatly with tree and grass species and with things like irrigation).

Re: feasibility of tree latent fraction being more then the grass latent fraction. That makes sense, and I'm glad I have an intuition now of why the tree latent fraction is higher then the grass latent fraction in the UWG defaults. Bruno in his 2012 thesis also cites research that transpiration is a function of solar radiation, "In the absence of light, plants' stomata are usually closed," so self-shading also explains why the increase in tree latent fraction isn't equivalent to it's geometric increase.

After you merge your PR, we should probably also let Les know in case he wants to implement these same issues fixed in the Matlab version.

Agreed. And I think it would be good to have more people be aware and look at my changes, so if there is a mistake someone can catch it.