Skip to content

Commit

Permalink
fix(geometry): Correct the calculation to align with the units
Browse files Browse the repository at this point in the history
It seems there was no good reason to multiply the results by the area of the study region. It was just making the results harder to understand. Now, we can understand the total of the results in terms of the net degree-days improved. That is, the cooling degree-days helped minus the heating degree-days harmed. And the total result falls within the limits of the maximum possible levels of helping/harming.
  • Loading branch information
chriswmackey committed Jan 11, 2024
1 parent 17ac153 commit 47d5910
Show file tree
Hide file tree
Showing 4 changed files with 6 additions and 6 deletions.
4 changes: 2 additions & 2 deletions ladybug_grasshopper/json/LB_Thermal_Shade_Benefit.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.7.3",
"version": "1.7.4",
"nickname": "ThermalShadeBenefit",
"outputs": [
[
Expand Down Expand Up @@ -155,7 +155,7 @@
}
],
"subcategory": "3 :: Analyze Geometry",
"code": "\nimport math\n\ntry:\n from ladybug.sunpath import Sunpath\n from ladybug.datacollection import HourlyDiscontinuousCollection\n from ladybug.color import Colorset\n from ladybug.graphic import GraphicContainer\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.config import units_abbreviation\n from ladybug_{{cad}}.togeometry import to_joined_gridded_mesh3d, to_vector2d\n from ladybug_{{cad}}.fromgeometry import from_mesh3d, from_point3d, from_vector3d\n from ladybug_{{cad}}.fromobjects import legend_objects\n from ladybug_{{cad}}.text import text_objects\n from ladybug_{{cad}}.intersect import join_geometry_to_mesh, generate_intersection_rays, \\\n intersect_rays_with_mesh_faces\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, hide_output, \\\n recommended_processor_count\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component) and _run:\n # set the defaults and process all of the inputs\n workers = _cpu_count_ if _cpu_count_ is not None else recommended_processor_count()\n if north_ is not None: # process the north_\n try:\n north_ = math.degrees(\n to_vector2d(north_).angle_clockwise(Vector2D(0, 1)))\n except AttributeError: # north angle instead of vector\n north_ = float(north_)\n else:\n north_ = 0\n assert isinstance(_temperature, HourlyDiscontinuousCollection), \\\n 'Connected _temperature is not a Hourly Data Collection. Got {}'.format(\n type(_temperature))\n assert _temperature.header.unit == 'C', \\\n 'Connected _temperature must be in Celsius. Got {}.'.format(\n _temperature.header.unit)\n if _timestep_ and _timestep_ != _temperature.header.analysis_period.timestep:\n if _timestep_ < _temperature.header.analysis_period.timestep:\n data = _temperature.cull_to_timestep(_timestep_)\n else:\n data = _temperature.interpolate_to_timestep(_timestep_)\n else:\n data = _temperature\n t_step_per_day = data.header.analysis_period.timestep * 24\n up_thresh = 26 if _up_threshold_ is None else _up_threshold_\n low_thresh = 9 if _low_threshold_ is None else _low_threshold_\n assert up_thresh > low_thresh, 'Input _up_threshold_ [{}] must be greater ' \\\n 'than input _low_threshold_ [{}].'.format(up_thresh, low_thresh)\n\n # initialize sunpath based on location and get all of the vectors\n sp = Sunpath.from_location(_location, north_)\n lb_vecs, relevant_temps = [], []\n for dt, temp in zip(data.datetimes, data.values):\n if temp > up_thresh or temp < low_thresh:\n sun = sp.calculate_sun_from_date_time(dt)\n if sun.is_during_day:\n lb_vecs.append(sun.sun_vector_reversed)\n relevant_temps.append(temp)\n vectors = [from_vector3d(lb_vec) for lb_vec in lb_vecs]\n\n # create the gridded mesh from the geometry\n analysis_mesh = to_joined_gridded_mesh3d(_shade_geo, _grid_size)\n mesh = from_mesh3d(analysis_mesh)\n study_mesh = to_joined_gridded_mesh3d(_study_region, _grid_size / 2)\n points = [from_point3d(pt) for pt in study_mesh.face_centroids]\n hide_output(ghenv.Component, 2)\n\n # create a series of rays that represent the sun projected through the shade\n int_rays = generate_intersection_rays(points, vectors)\n\n # if there is context, remove any rays that are blocked by the context\n shade_mesh = join_geometry_to_mesh(context_) \\\n if len(context_) != 0 and context_[0] is not None else None\n\n # intersect the sun rays with the shade mesh\n face_int = intersect_rays_with_mesh_faces(\n mesh, int_rays, shade_mesh, cpu_count=workers)\n\n # loop through the face intersection result and evaluate the benefit\n region_cell_area = study_mesh.area / len(points)\n shade_help, shade_harm, shade_net = [], [], []\n for face_res, face_area in zip(face_int, analysis_mesh.face_areas):\n f_help, f_harm = 0, 0\n for t_ind in face_res:\n t_val = relevant_temps[t_ind]\n if t_val > up_thresh:\n f_help += t_val - up_thresh\n elif t_val < low_thresh:\n f_harm += t_val - low_thresh\n # Normalize by the area of the cell so there's is a consistent metric\n # between cells of different areas.\n # Also, divide the value by t_step_per_day such that the final unit is in\n # degree-days/model unit instead of degree-timesteps/model unit.\n shd_help = ((f_help / face_area) / t_step_per_day) * region_cell_area\n shd_harm = ((f_harm / face_area) / t_step_per_day) * region_cell_area\n shade_help.append(shd_help)\n shade_harm.append(shd_harm)\n shade_net.append(shd_help + shd_harm)\n\n # create the mesh and legend outputs\n graphic = GraphicContainer(shade_net, analysis_mesh.min, analysis_mesh.max, legend_par_)\n graphic.legend_parameters.title = 'degC-days/{}2'.format(units_abbreviation())\n if legend_par_ is None or legend_par_.are_colors_default:\n graphic.legend_parameters.colors = reversed(Colorset.shade_benefit_harm())\n if legend_par_ is None or legend_par_.min is None or legend_par_.max is None:\n bnd_val = max(max(shade_net), abs(min(shade_net)))\n if legend_par_ is None or legend_par_.min is None:\n graphic.legend_parameters.min = -bnd_val\n if legend_par_ is None or legend_par_.max is None:\n graphic.legend_parameters.max = bnd_val\n title = text_objects('Thermal Shade Benefit', graphic.lower_title_location,\n graphic.legend_parameters.text_height * 1.5,\n graphic.legend_parameters.font)\n\n # create all of the visual outputs\n analysis_mesh.colors = graphic.value_colors\n mesh = from_mesh3d(analysis_mesh)\n legend = legend_objects(graphic.legend)\n",
"code": "\nimport math\n\ntry:\n from ladybug.sunpath import Sunpath\n from ladybug.datacollection import HourlyDiscontinuousCollection\n from ladybug.color import Colorset\n from ladybug.graphic import GraphicContainer\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.config import units_abbreviation\n from ladybug_{{cad}}.togeometry import to_joined_gridded_mesh3d, to_vector2d\n from ladybug_{{cad}}.fromgeometry import from_mesh3d, from_point3d, from_vector3d\n from ladybug_{{cad}}.fromobjects import legend_objects\n from ladybug_{{cad}}.text import text_objects\n from ladybug_{{cad}}.intersect import join_geometry_to_mesh, generate_intersection_rays, \\\n intersect_rays_with_mesh_faces\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, hide_output, \\\n recommended_processor_count\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component) and _run:\n # set the defaults and process all of the inputs\n workers = _cpu_count_ if _cpu_count_ is not None else recommended_processor_count()\n if north_ is not None: # process the north_\n try:\n north_ = math.degrees(\n to_vector2d(north_).angle_clockwise(Vector2D(0, 1)))\n except AttributeError: # north angle instead of vector\n north_ = float(north_)\n else:\n north_ = 0\n assert isinstance(_temperature, HourlyDiscontinuousCollection), \\\n 'Connected _temperature is not a Hourly Data Collection. Got {}'.format(\n type(_temperature))\n assert _temperature.header.unit == 'C', \\\n 'Connected _temperature must be in Celsius. Got {}.'.format(\n _temperature.header.unit)\n if _timestep_ and _timestep_ != _temperature.header.analysis_period.timestep:\n if _timestep_ < _temperature.header.analysis_period.timestep:\n data = _temperature.cull_to_timestep(_timestep_)\n else:\n data = _temperature.interpolate_to_timestep(_timestep_)\n else:\n data = _temperature\n t_step_per_day = data.header.analysis_period.timestep * 24\n up_thresh = 26 if _up_threshold_ is None else _up_threshold_\n low_thresh = 9 if _low_threshold_ is None else _low_threshold_\n assert up_thresh > low_thresh, 'Input _up_threshold_ [{}] must be greater ' \\\n 'than input _low_threshold_ [{}].'.format(up_thresh, low_thresh)\n\n # initialize sunpath based on location and get all of the vectors\n sp = Sunpath.from_location(_location, north_)\n lb_vecs, relevant_temps = [], []\n for dt, temp in zip(data.datetimes, data.values):\n if temp > up_thresh or temp < low_thresh:\n sun = sp.calculate_sun_from_date_time(dt)\n if sun.is_during_day:\n lb_vecs.append(sun.sun_vector_reversed)\n relevant_temps.append(temp)\n vectors = [from_vector3d(lb_vec) for lb_vec in lb_vecs]\n\n # create the gridded mesh from the geometry\n analysis_mesh = to_joined_gridded_mesh3d(_shade_geo, _grid_size)\n mesh = from_mesh3d(analysis_mesh)\n study_mesh = to_joined_gridded_mesh3d(_study_region, _grid_size / 2)\n points = [from_point3d(pt) for pt in study_mesh.face_centroids]\n hide_output(ghenv.Component, 2)\n\n # create a series of rays that represent the sun projected through the shade\n int_rays = generate_intersection_rays(points, vectors)\n\n # if there is context, remove any rays that are blocked by the context\n shade_mesh = join_geometry_to_mesh(context_) \\\n if len(context_) != 0 and context_[0] is not None else None\n\n # intersect the sun rays with the shade mesh\n face_int = intersect_rays_with_mesh_faces(\n mesh, int_rays, shade_mesh, cpu_count=workers)\n\n # loop through the face intersection result and evaluate the benefit\n pt_div = 1 / float(len(points))\n shade_help, shade_harm, shade_net = [], [], []\n for face_res, face_area in zip(face_int, analysis_mesh.face_areas):\n f_help, f_harm = 0, 0\n for t_ind in face_res:\n t_val = relevant_temps[t_ind]\n if t_val > up_thresh:\n f_help += t_val - up_thresh\n elif t_val < low_thresh:\n f_harm += t_val - low_thresh\n # Normalize by the area of the cell so there's is a consistent metric\n # between cells of different areas.\n # Also, divide the value by t_step_per_day such that the final unit is in\n # degree-days/model unit instead of degree-timesteps/model unit.\n shd_help = ((f_help / face_area) / t_step_per_day) * pt_div\n shd_harm = ((f_harm / face_area) / t_step_per_day) * pt_div\n shade_help.append(shd_help)\n shade_harm.append(shd_harm)\n shade_net.append(shd_help + shd_harm)\n\n # create the mesh and legend outputs\n graphic = GraphicContainer(shade_net, analysis_mesh.min, analysis_mesh.max, legend_par_)\n graphic.legend_parameters.title = 'degC-days/{}2'.format(units_abbreviation())\n if legend_par_ is None or legend_par_.are_colors_default:\n graphic.legend_parameters.colors = reversed(Colorset.shade_benefit_harm())\n if legend_par_ is None or legend_par_.min is None or legend_par_.max is None:\n bnd_val = max(max(shade_net), abs(min(shade_net)))\n if legend_par_ is None or legend_par_.min is None:\n graphic.legend_parameters.min = -bnd_val\n if legend_par_ is None or legend_par_.max is None:\n graphic.legend_parameters.max = bnd_val\n title = text_objects('Thermal Shade Benefit', graphic.lower_title_location,\n graphic.legend_parameters.text_height * 1.5,\n graphic.legend_parameters.font)\n\n # create all of the visual outputs\n analysis_mesh.colors = graphic.value_colors\n mesh = from_mesh3d(analysis_mesh)\n legend = legend_objects(graphic.legend)\n",
"category": "Ladybug",
"name": "LB Thermal Shade Benefit",
"description": "Visualize the desirability of shade in terms of proximity of conditions to\na favorable temerature range.\n_\nThe calculation runs by generating solar vectors for a data collection of input\ntemperature values. Solar vectors for hours when the temperature is above the\nupper temperature threshold contribute positively to shade desirability (shade_help)\nwhile solar vectors for hours when the temperature is below the lower temperature\nthreshold contribute negatively (shade_harm).\n_\nThe component outputs a colored mesh of the shade illustrating the net effect of\nshading each part of the _shade_geo. A higher saturation of blue indicates that\nshading the cell is desirable to avoid excessively hot temperatures. A higher\nsaturation of red indicates that shading the cell is harmful, blocking helpful\nsun in cold conditions that could bring conditions closer to the desired\ntemperature range. Desaturated cells indicate that shading the cell will have\nrelatively little effect on keeping the _study_region in the desired thermal range.\n_\nThe units for shade desirability are degree-days per unit area of shade, which are\nessentially the amount of time in days that sun is blocked by a given cell\nmultiplied by the degrees above (or below) the temperature thresholds during\nthat time. So, if a given square meter of input _shade_geo has a shade desirability\nof 10 degree-days per square meter, this means that a shade in this location\nprovides roughly 1 day of sun protection from conditions 10 degrees Celsius\nwarmer than the _up_threshold_ to the _study_region.\n_\nMore information on the methods used by this component can be found in the\nfollowing publication:\nMackey, Christopher; Sadeghipour Roudsari, Mostapha; Samaras, Panagiotis.\n\u201cComfortCover: A Novel Method for the Design of Outdoor Shades.\u201d In Proceedings\nof Symposium on Simulation for Architecture and Urban Design. Washington, DC,\nUnited States, Apr 12-15 2015.\nhttps://drive.google.com/file/d/0Bz2PwDvkjovJQVRTRHhMSXZWZjQ/view?usp=sharing\n-"
Expand Down
8 changes: 4 additions & 4 deletions ladybug_grasshopper/src/LB Thermal Shade Benefit.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@

ghenv.Component.Name = 'LB Thermal Shade Benefit'
ghenv.Component.NickName = 'ThermalShadeBenefit'
ghenv.Component.Message = '1.7.3'
ghenv.Component.Message = '1.7.4'
ghenv.Component.Category = 'Ladybug'
ghenv.Component.SubCategory = '3 :: Analyze Geometry'
ghenv.Component.AdditionalHelpFromDocStrings = '4'
Expand Down Expand Up @@ -227,7 +227,7 @@
mesh, int_rays, shade_mesh, cpu_count=workers)

# loop through the face intersection result and evaluate the benefit
region_cell_area = study_mesh.area / len(points)
pt_div = 1 / float(len(points))
shade_help, shade_harm, shade_net = [], [], []
for face_res, face_area in zip(face_int, analysis_mesh.face_areas):
f_help, f_harm = 0, 0
Expand All @@ -241,8 +241,8 @@
# between cells of different areas.
# Also, divide the value by t_step_per_day such that the final unit is in
# degree-days/model unit instead of degree-timesteps/model unit.
shd_help = ((f_help / face_area) / t_step_per_day) * region_cell_area
shd_harm = ((f_harm / face_area) / t_step_per_day) * region_cell_area
shd_help = ((f_help / face_area) / t_step_per_day) * pt_div
shd_harm = ((f_harm / face_area) / t_step_per_day) * pt_div
shade_help.append(shd_help)
shade_harm.append(shd_harm)
shade_net.append(shd_help + shd_harm)
Expand Down
Binary file modified ladybug_grasshopper/user_objects/LB Thermal Shade Benefit.ghuser
Binary file not shown.
Binary file modified samples/comfort_shade_benefit.gh
Binary file not shown.

0 comments on commit 47d5910

Please sign in to comment.