-
Notifications
You must be signed in to change notification settings - Fork 43
Reliability Prototype #833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 5 commits
f6b1c18
19e3e1c
6bcd000
fccb5bd
6340023
e02b34d
58bdff9
4283ab5
f5e61da
dce308e
2463e78
efaf665
e4bfcb8
6388656
081386b
e5cbdd9
4e326bb
b3544be
c809904
2bde5e3
1d9112f
33f80a3
8568a2c
7e9d473
b4a0ff5
40ac1c9
823ea19
ff76626
6850587
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
|
|
||
| from h2integrate.core.utilities import BaseConfig, merge_shared_inputs | ||
| from h2integrate.core.validators import gt_zero, gte_zero | ||
| from h2integrate.reliability.models import WeibullReliabilityModel | ||
| from h2integrate.core.model_baseclasses import ( | ||
| CostModelBaseClass, | ||
| CostModelBaseConfig, | ||
|
|
@@ -67,6 +68,8 @@ def initialize(self): | |
| self.commodity = "electricity" | ||
| self.commodity_rate_units = "MW" | ||
| self.commodity_amount_units = "MW*h" | ||
| self.reliability_model = None | ||
| self.use_reliability = False | ||
|
|
||
| def setup(self): | ||
| super().setup() | ||
|
|
@@ -75,6 +78,12 @@ def setup(self): | |
| merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), | ||
| additional_cls_name=self.__class__.__name__, | ||
| ) | ||
| if self.options["tech_config"]["model_inputs"]["reliability"]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to use
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not exactly, but that did get me thinking on a better way to use |
||
| self.reliability_model = WeibullReliabilityModel.from_dict( | ||
| merge_shared_inputs(self.options["tech_config"]["model_inputs"], "reliability"), | ||
| additional_cls_name=self.__class__.__name__, | ||
| ) | ||
| self.use_reliability = True | ||
|
|
||
| # Add natural gas consumed output | ||
| self.add_output( | ||
|
|
@@ -154,6 +163,8 @@ def compute(self, inputs, outputs): | |
| inputs["electricity_command_value"], | ||
| ) | ||
| natural_gas_demand = electricity_command_value * heat_rate_mmbtu_per_mwh | ||
| if self.use_reliability: | ||
| natural_gas_demand * self.reliability_model.availability | ||
|
|
||
| # available feedstock, saturated at maximum system feedstock consumption | ||
| natural_gas_available = np.where( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import numpy as np | ||
| from attrs import field, define, validators | ||
|
|
||
| from h2integrate.core.utilities import BaseConfig | ||
|
|
||
|
|
||
| N_TIMESTEPS = 8760 | ||
|
|
||
|
|
||
| def create_reliability(config: dict): | ||
| """Retrieves and initializes a matching reliability model.""" | ||
| name = config.pop("reliability") | ||
| match name: | ||
| case "WeibullReliabilityModel": | ||
| return WeibullReliabilityModel.from_dict(config) | ||
| case _: | ||
| raise NotImplementedError(f"{name} is not a valid model name") | ||
|
|
||
|
|
||
| @define(kw_only=True) | ||
| class WeibullReliabilityModel(BaseConfig): | ||
| r"""Basic reliability model for operating/not operating statuses. | ||
|
|
||
| Assumes a full operational shutdown with zero ramping of production for an hourly, 1 year | ||
| simulation. | ||
|
|
||
| Args: | ||
| scale (float): Also referred to as :math:`\lambda` or :math:`\alpha`. Determines | ||
| the scale of distribution, and is equivalent to the mean time | ||
| between failure in years (MTBF), or 1 / annual failure rate. | ||
| shape (float): Also referred to as ``k`` or :math:`\beta`. A value less than 1 | ||
| corresponds to a decreasing hazard rate over time (break-in period failures); | ||
| a value greater than 1 corresponds to an increasing hazard rate over time ( | ||
| aging/wear-out failures); and a value of 1 corresponds to a constant hazard | ||
| rate over time (exponential distribution). | ||
| downtime (float): Average amount of downtime per failure. | ||
|
|
||
| Attributes: | ||
| rng (np.random._generator.Generator): NumPy random generator object. | ||
|
|
||
| TODO: | ||
| - how to pass n_timesteps through from plant? | ||
| - stabilize random generator/determine how to manage random seeding across library | ||
| """ | ||
|
|
||
| scale: float = field(validator=validators.instance_of(float)) | ||
| shape: float = field(validator=validators.instance_of(float)) | ||
| downtime: float = field(validator=validators.gt(1)) | ||
| rng: np.random._generator.Generator = field( | ||
| default=np.random.default_rng(), | ||
| init=False, | ||
| validator=validators.instance_of(np.random._generator.Generator), | ||
| ) | ||
| availability: np.ndarray = field( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we make
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The generation of availability is now done in |
||
| default=np.ones(N_TIMESTEPS), init=False, validator=validators.instance_of(np.ndarray) | ||
| ) | ||
| downtime_per_event: np.ndarray = field( | ||
| default=np.zeros(N_TIMESTEPS), init=False, validator=validators.instance_of(np.ndarray) | ||
| ) | ||
|
|
||
| def __attrs_post_init__(self): | ||
| self.create_downtime_events() | ||
| self.calculate_availability() | ||
|
|
||
| def create_downtime_events(self): | ||
| """Creates a ``time_to_failure`` and ``downtime_per_event`` array based on the distributions | ||
| described in ``WeibullReliabilityConfig``. | ||
| """ | ||
| # NOTE: Arrays are default length 30 to ensure enough events are created for a 1-year | ||
| # simulation without burdening the memory usage. | ||
| self.time_to_failures = np.ceil( | ||
| self.config.rng.weibul(self.config.shape, size=30) * self.config.scale * N_TIMESTEPS | ||
| ).astype(int) | ||
| downtime_per_event = np.ceil(np.rng.normal(loc=self.config.downtime, size=30)).astype(int) | ||
| self.downtime_per_event = np.where(downtime_per_event >= 1, downtime_per_event, 1) | ||
|
|
||
| def calculate_availability(self): | ||
| """Determine the timing and duration of outages for a single year of simulation time.""" | ||
| accumulated = 0 | ||
|
RHammond2 marked this conversation as resolved.
Outdated
|
||
| while accumulated < N_TIMESTEPS: | ||
| event, self.time_to_failures = self.time_to_failures[0], self.time_to_failures[1:] | ||
| duration, self.downtime_per_event = ( | ||
| self.downtime_per_event[0], | ||
| self.downtime_per_event[1:], | ||
| ) | ||
| if event + accumulated > N_TIMESTEPS: | ||
| break | ||
|
|
||
| start = accumulated + event | ||
| end = start + duration | ||
| self.availability[start:end] = 0 | ||
| accumulated = start | ||
| if not self.time_to_failures: | ||
| self.create_downtime_events() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could these be parameters in the
NaturalGasPerformanceConfig? So a user can inputreliability_modelanduse_reliability? Where the__attrs_post_init__checks thatreliability_modelis provided ifuse_reliabilityis True?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good question, it didn't dawn on me that a user could want to provide a definition, but not use it. Though it could be easier to iterate on a problem by simply turning it on/off instead of commenting out a whole section of the inputs. I'll also add this to the to do section.