Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
with:
repository: NatLabRockies/resstock
path: resstock
ref: develop
ref: sampling_regions
- name: Remove AWS from resstock yaml
run: |
cd resstock
Expand Down
8 changes: 8 additions & 0 deletions buildstockbatch/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ def run_building(
(resources_path / "hpxml-measures").symlink_to(hpxml_measures_path, target_is_directory=True)
else:
resources_path = None
# samplers_path = buildstock_path / "samplers"
# if samplers_path.exists():
# sampler_path = sim_path / "samplers"
# (sampler_path).symlink_to(samplers_path, target_is_directory=True)
# else:
# sampler_path = None
Comment on lines +117 to +122

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not totally sure if I need this yet.

custom_gems_path = buildstock_path / ".custom_gems"
if custom_gems_path.exists():
gems_path = sim_path / ".custom_gems"
Expand Down Expand Up @@ -205,6 +211,8 @@ def run_building(
if resources_path:
(resources_path / "hpxml-measures").unlink()
resources_path.rmdir()
# if sampler_path:
# sampler_path.unlink()
if gems_path:
gems_path.unlink()

Expand Down
1 change: 1 addition & 0 deletions buildstockbatch/sampler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
ResidentialQuotaSampler,
ResidentialQuotaDownselectSampler,
) # noqa F041
from .residential_stratified import ResidentialStratifiedSampler # noqa F041
from .commercial_sobol import CommercialSobolSampler # noqa F041
from .precomputed import PrecomputedSampler # noqa F041
6 changes: 3 additions & 3 deletions buildstockbatch/sampler/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def run_sampling(self):
return self._run_sampling_apptainer()
else:
assert self.container_runtime == ContainerRuntime.LOCAL_OPENSTUDIO
return self._run_sampling_local_openstudio()
return self._run_sampling_local()

def _run_sampling_docker(self):
"""
Expand All @@ -103,9 +103,9 @@ def _run_sampling_apptainer(self):
"""
raise NotImplementedError

def _run_sampling_local_openstudio(self):
def _run_sampling_local(self):
"""
Execute the sampling on the local openstudio instance
Execute the sampling on the local instance

Replace this in a subclass as necessary
"""
Expand Down
10 changes: 5 additions & 5 deletions buildstockbatch/sampler/residential_quota.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _run_sampling_docker(self):
self.parent().docker_image,
[
"ruby",
"resources/run_sampling.rb",
"samplers/quota/run_sampling.rb",
"-p",
self.cfg["project_directory"],
"-n",
Expand Down Expand Up @@ -100,7 +100,7 @@ def _run_sampling_apptainer(self):
"{}:/outbind".format(os.path.dirname(self.csv_path)),
self.parent().apptainer_image,
"ruby",
"resources/run_sampling.rb",
"samplers/quota/run_sampling.rb",
"-p",
self.cfg["project_directory"],
"-n",
Expand All @@ -113,11 +113,11 @@ def _run_sampling_apptainer(self):
logger.debug("Apptainer sampling completed.")
return self.csv_path

def _run_sampling_local_openstudio(self):
def _run_sampling_local(self):
subprocess.run(
[
self.parent().openstudio_exe(),

@joseph-robertson joseph-robertson Jun 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not totally sure why we needed the openstudio cli to run the quota sampler script (possibly pre-dates the run_sampling / run_sampling_lib split?), but it seems to work fine using system ruby.

str(pathlib.Path("resources", "run_sampling.rb")),
"ruby",
str(pathlib.Path("samplers", "quota", "run_sampling.rb")),
"-p",
self.cfg["project_directory"],
"-n",
Expand Down
181 changes: 181 additions & 0 deletions buildstockbatch/sampler/residential_stratified.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"""
buildstockbatch.sampler.residential_stratified
~~~~~~~~~~~~~~~
This object contains the code required for generating the set of simulations to execute

:author: Noel Merket, Ry Horsey
:copyright: (c) 2020 by The Alliance for Sustainable Energy
:license: BSD-3
"""

import docker
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import time
import yaml

from .base import BuildStockSampler
from buildstockbatch.exc import ValidationError

logger = logging.getLogger(__name__)


class ResidentialStratifiedSampler(BuildStockSampler):
def __init__(
self,
parent,
n_datapoints,
segment_vars=[
"Federal Poverty Level",
"Geometry Floor Area Bin",
"Geometry Building Type RECS",
"Vintage",
"Heating Fuel",
"Sampling Region",
],
segment_selection_sample_size=10000000,
num_samples_per_segment=8,
):
"""Residential Stratified Sampler

:param parent: BuildStockBatchBase object
:type parent: BuildStockBatchBase (or subclass)
:param n_datapoints: number of datapoints to sample
:type n_datapoints: int
"""
super().__init__(parent)
self.validate_args(self.parent().project_filename, n_datapoints=n_datapoints)
self.n_datapoints = n_datapoints
self.sampler_config = self.create_sampler_config(
os.path.dirname(self.parent().project_filename),
segment_vars,
segment_selection_sample_size,
num_samples_per_segment,
)

@classmethod
def validate_args(cls, project_filename, **kw):
expected_args = set(["n_datapoints"])
for k, v in kw.items():
expected_args.discard(k)
if k == "n_datapoints":
if not isinstance(v, int):
raise ValidationError("n_datapoints needs to be an integer")
if v <= 0:
raise ValidationError("n_datapoints need to be >= 1")
elif k == "segment_vars":
pass
elif k == "segment_selection_sample_size":
pass
elif k == "num_samples_per_segment":
pass
else:
raise ValidationError(f"Unknown argument for sampler: {k}")
if len(expected_args) > 0:
raise ValidationError("The following sampler arguments are required: " + ", ".join(expected_args))
return True

@classmethod
def create_sampler_config(self, folderpath, segment_vars, segment_selection_sample_size, num_samples_per_segment):
data = {}
data["segment_vars"] = segment_vars
data["segment_selection_sample_size"] = segment_selection_sample_size
data["num_samples_per_segment"] = num_samples_per_segment
filename = pathlib.Path(folderpath) / "sampler_config.yaml"
with open(filename, "w") as file:
yaml.dump(data, file)
return filename

def _run_sampling_docker(self):
docker_client = docker.DockerClient.from_env()
tick = time.time()
extra_kws = {}
if sys.platform.startswith("linux"):
extra_kws["user"] = f"{os.getuid()}:{os.getgid()}"
container_output = docker_client.containers.run(
self.parent().docker_image,
[
"python",
"samplers/stratified/sampler/run_sampler.py",
"-p",
self.cfg["project_directory"],
"-n",
str(self.n_datapoints),
"-o",
"buildstock.csv",
],
remove=True,
volumes={self.buildstock_dir: {"bind": "/var/simdata/openstudio", "mode": "rw"}},
name="buildstock_sampling",
**extra_kws,
)
tick = time.time() - tick
for line in container_output.decode("utf-8").split("\n"):
logger.debug(line)
logger.debug("Sampling took {:.1f} seconds".format(tick))
destination_filename = self.csv_path
if os.path.exists(destination_filename):
os.remove(destination_filename)
shutil.move(
os.path.join(self.buildstock_dir, "resources", "buildstock.csv"),
destination_filename,
)
return destination_filename

def _run_sampling_apptainer(self):
args = [
"apptainer",
"exec",
"--contain",
"--home",
"{}:/buildstock".format(self.buildstock_dir),
"--bind",
"{}:/outbind".format(os.path.dirname(self.csv_path)),
self.parent().apptainer_image,
"python",
"samplers/stratified/sampler/run_sampler.py",
"-p",
self.cfg["project_directory"],
"-n",
str(self.n_datapoints),
"-o",
"../../outbind/{}".format(os.path.basename(self.csv_path)),
]
logger.debug(f"Starting apptainer sampling with command: {' '.join(args)}")
subprocess.run(args, check=True, env=os.environ, cwd=self.parent().output_dir)
logger.debug("Apptainer sampling completed.")
return self.csv_path

def _run_sampling_local(self):
subprocess.run(
[
"python",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to https://github.com/NatLabRockies/buildstockbatch/pull/525/changes#r3390798176, another reason to not use the openstudio cli is that I tried it with this stratified sampler script (python), and ran into a bunch of issues related to missing packages (and needing to use --python_path), joblib/multiprocessing and using the wrong executable, etc. It's probably possible to use the openstudio cli, but it seems to work fine using system python.

str(pathlib.Path("samplers", "stratified", "sampler", "run_sampler.py")),
"sample",
"-p",
self.cfg["project_directory"],
"-n",
str(self.n_datapoints),
"-c",
self.sampler_config,
"-o",
"buildstock.csv",
],
cwd=self.buildstock_dir,
check=True,
)
destination_filename = pathlib.Path(self.csv_path)
if destination_filename.exists():
os.remove(destination_filename)
shutil.move(
pathlib.Path(self.buildstock_dir, "resources", "buildstock.csv"),
destination_filename,
)
config_filename = pathlib.Path(self.sampler_config)
if config_filename.exists():
os.remove(config_filename)
return destination_filename
Loading
Loading