add solar calculation in weather_controller and define fenestration constants#165
add solar calculation in weather_controller and define fenestration constants#165ecosang wants to merge 4 commits intogoogle:copybara_pushfrom
Conversation
…onstants, and add relative convergence argument in simulator
|
@s2t2 I've put only weather controller part in this PR. |
|
OK, thanks! I see the build is failing due to CSV path issues (see below). Would you please fix and then ping me and I will review again when the build is passing.
|
|
@s2t2 the csv file is automatically ignored. I just added. Sorry for the iteration. Can I also check if the testing is passed here on my end? when I made PR, I didn't see the failure. Maybe I didn't wait enough. |
|
For some reason, I have to click the "Approve workflows and run" button to trigger the tests. Running now... |
s2t2
left a comment
There was a problem hiding this comment.
Hi @ecosang thanks for this PR.
I left some comments below.
One of the major ones is to see if we can use separate test classes to leverage shared set up methods (as applicable), and see if we can use test class inheritance to reduce the amount of test code (if possible).
Also it would be helpful to define some of the expected column names and sensor names as constants.
Also we can see if using some dataclasses would be helpful in specific situations mentioned.
There are some opportunities to refactor shared methods.
Also, let's discuss how to best handle the pvlib dependency, either making it non-optional, or implementing it as optional using the pyproject.toml / poetry mechanism used for this purpose.
There was a problem hiding this comment.
There is an existing test data file in "smart_control/simulator/local_weather_test_data.csv". I was wondering how these files differ, and if we could leverage the existing file. Or organize both of these files to be in the same place (perhaps putting them in a "smart_control/simulator/test_data" folder.)
There was a problem hiding this comment.
Oh I see you moved this file from that location, to be in the resources dir. That could be OK. However I would prefer to keep both test data files in the same location.
There was a problem hiding this comment.
I put this in "building_radiation_test_data" folder.
There was a problem hiding this comment.
Since the local weather test data is used for replay weather controller tests (without radiation), let's prefer to keep it where it is. Or perhaps we can move both CSV files to a generic "simulator/test_data" directory.
There was a problem hiding this comment.
It would be nice to know how this data was acquired, what the source is, and perhaps also to have access to the code that pulled this data (as applicable).
| solar_azimuth: Solar azimuth angle in degrees (compass direction of sun). | ||
| Returns: | ||
| POA global irradiance in W/m2. | ||
| Example: |
There was a problem hiding this comment.
Appreciate the example in the docstring.
| ... surface_azimuth=180.0, solar_zenith=30.0, solar_azimuth=180.0) | ||
| """ | ||
| # Import here to avoid circular imports and keep pvlib as optional dependency | ||
| from pvlib import irradiance as pvlib_irradiance # pylint: disable=import-outside-toplevel |
There was a problem hiding this comment.
Can you please further explain the circular dependency concerns if this import were placed at the top of the file? And also separately some reasons we would want to keep pvlib as an optional dependency?
FYI there is a way we can do optional dependencies using pyproject.toml, like for example to great a new group called "solar":
[tool.poetry.group.docs.solar]
pvlib = "0.13.1"
Then it can be installed like poetry install --with solar (see Setup docs related to notebook setup, for an example.
There was a problem hiding this comment.
i updated it.. 3bf35f5#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711R62
also the lock file.
3bf35f5#diff-f53a023eedfa3fbf2925ec7dc76eecdc954ea94b7e47065393dbad519613dc89R1
Not sure if it is what you suggested.
There was a problem hiding this comment.
Let's just do a normal import of the pvlib at the top of the file.
It is a small enough dependency, and we are already using scipy, pandas, numpy, etc, so size isn't a big concern.
I know we discussed possibly making it an optional dependency in the poetry / pyproject.toml, but I don't see a strong argument for that, unless there's something I'm missing.
| Converts horizontal irradiance components (GHI, DNI, DHI) to the irradiance | ||
| incident on a tilted surface. Uses the pvlib library's get_total_irradiance | ||
| function. | ||
| Args: |
There was a problem hiding this comment.
Nit: I believe we might need a new line before each of these sections in the docstring: Args, Returns, Example. And I believe Return should go last.
| Uses Clark & Allen formula with dry bulb and dew point temperatures. | ||
| """ | ||
| # Stefan-Boltzmann constant | ||
| sigma = 5.6697e-8 # W/(m^2*K^4) |
There was a problem hiding this comment.
I see this constant is used in building radiation utils file as well. Let's define it once in the simulation/constants.py file, and import / access from there.
There was a problem hiding this comment.
|
|
||
| # Get dry bulb temperature in Kelvin (use numpy operations for arrays) | ||
| if 'TempC' in self._weather_data: | ||
| temp_k = self._weather_data['TempC'].values + 273.15 |
There was a problem hiding this comment.
if possible, let's use / apply temp conversion functions from conversion utils (c to k, f to k)
applies for temp and dewpoint below
| # Stefan-Boltzmann constant | ||
| sigma = 5.6697e-8 # W/(m^2*K^4) | ||
|
|
||
| def get_value(observation_response, measurement_name): |
There was a problem hiding this comment.
I see these nested get_value methods are repeated. Let's prefer to move them to a single function at the top of the file (outside the class), and invoke from that location.
There was a problem hiding this comment.
| continue | ||
|
|
||
| # Try to get dew point temperature, otherwise estimate from depression | ||
| dp_k = get_value(r, 'dew_point_temperature_sensor') |
There was a problem hiding this comment.
Let's define these sensor names as constants at the top of the file.
There was a problem hiding this comment.
| ) | ||
| return float(cloud_cover) | ||
|
|
||
| def get_current_irradiance( |
There was a problem hiding this comment.
If the specific structure of a dictionary containing these specific keys is used in many places, let's consider using a dataclass to define those expected properties. That would also allow us to better document the meaning behind each of these properties.
@dataclasses.dataclass
class Irradiance
""" Docstring with ...
Args:
...
"""
ghi: float
dni: float
dhi: float
# etc. This function can then return an instance of that dataclass.
Later, when we need to reference the values, we can use dot notation (like irr.ghi) or convert the dataclass to a dict using dataclasses.asdict() and access the values using dictionary notation.
There was a problem hiding this comment.
…, also move testing csv files into raditaion util folder
There was a problem hiding this comment.
Since the local weather test data is used for replay weather controller tests (without radiation), let's prefer to keep it where it is. Or perhaps we can move both CSV files to a generic "simulator/test_data" directory.
| @@ -0,0 +1,12 @@ | |||
| { | |||
There was a problem hiding this comment.
Can we use the JSON file from the configs/resources directory, instead of creating a copy of it here? That way we have one source of truth.
There was a problem hiding this comment.
The main reason is station depends on the weather file. So, csv file should be with the station.json.
| from smart_control.simulator import constants | ||
|
|
||
| if TYPE_CHECKING: | ||
| from smart_control.simulator import weather_controller |
There was a problem hiding this comment.
Let's do a normal import here.
|
|
||
|
|
||
| def calculate_poa_irradiance( | ||
| irradiance_components: 'weather_controller.IrradianceComponents', |
There was a problem hiding this comment.
Let's use the actual class here for type hint
| ... surface_azimuth=180.0, solar_zenith=30.0, solar_azimuth=180.0) | ||
| """ | ||
| # Import here to avoid circular imports and keep pvlib as optional dependency | ||
| from pvlib import irradiance as pvlib_irradiance # pylint: disable=import-outside-toplevel |
There was a problem hiding this comment.
Let's just do a normal import of the pvlib at the top of the file.
It is a small enough dependency, and we are already using scipy, pandas, numpy, etc, so size isn't a big concern.
I know we discussed possibly making it an optional dependency in the poetry / pyproject.toml, but I don't see a strong argument for that, unless there's something I'm missing.
| iteration_warning: int, | ||
| start_timestamp: pd.Timestamp, | ||
| relative_convergence_threshold: float | None = 1e-6, | ||
| relative_convergence_streak: int = 20, |
There was a problem hiding this comment.
@gemini-code-assist how are these new attributes used in this PR?
There was a problem hiding this comment.
These new attributes, relative_convergence_threshold and relative_convergence_streak, are introduced to enhance the simulator's convergence criteria. They enable an early stopping mechanism for the finite difference method (FDM) iterations.
Specifically:
relative_convergence_threshold: If set (notNone), the simulator will consider itself converged and stop iterating if the absolute change inmax_delta(which likely represents the maximum temperature change between iterations) falls below this threshold for a certain number of consecutive iterations.relative_convergence_streak: This defines how many consecutive iterations themax_deltachange must remain below therelative_convergence_thresholdbefore the simulator stops early.
This allows for more efficient simulations by preventing unnecessary iterations once a stable state is reached, even if the absolute iteration_limit hasn't been met.
| # 2. Circular-import risk: building_radiation_utils imports weather_controller | ||
| # (guarded by TYPE_CHECKING) to type-hint IrradianceComponents. If pvlib | ||
| # were imported at module load time here, and pvlib itself (or a future | ||
| # transitive dependency) ever imported anything from smart_control, that |
There was a problem hiding this comment.
at this time let's assume pvlib won't be importing from smart_control
| # any user who has not installed the "solar" extras group, even if they never | ||
| # use the irradiance / solar-position features. | ||
| # | ||
| # 2. Circular-import risk: building_radiation_utils imports weather_controller |
There was a problem hiding this comment.
if we need to move the IrradianceComponents to avoid a circular import, let's consider moving it to its own file in the simulator directory, perhaps named "building_radiation_models.py". We can have this weather controller and the radiation utils import from there. I think that should avoid a circular import, right?
|
|
||
| def get_current_irradiance( | ||
| self, timestamp: pd.Timestamp | ||
| ) -> 'IrradianceComponents': |
There was a problem hiding this comment.
Let's use the real class name here as a type hint
There was a problem hiding this comment.
don't understand what it means.
| ) | ||
|
|
||
|
|
||
| class WeatherControllerIrradianceTest(IrradianceTestBase): |
There was a problem hiding this comment.
Nice inheritance of the test classes.
Since these three Irradiance test classes have so many lines of code, let's move them to their own test files, like "weather_controller_irradiance_test.py" or something like that. This will make them easier to review and maintain.
|
I am kind of lost and can't figure it out. Let's clarify where to put which codes and what to create for a new testing file. Then, I will make a new PR, and I will drop this one.
Anything I missed? |
add solar calculation in weather_controller and define fenestration constants