Set period length parameters as indexed parameters#76
Conversation
…mensionless (do not need to be multiplied by any time length)
These indexed parameters take now a list of values for each stage.
…called in the constraints
…and duration of periods while converting them to nested dictionaries for flexible period definitions In the model class, I included a function to expand scalars/lists to nested dictionaries for representative, commitment, and dispatch periods to support flexible period structures.
…tructure dictionary needed in the expansion model class
… duration from scalars or json file. Also, add test for this. In the GTEP class, remove function to expand scalars to a period structure dictionary and add this to a new function in utils file. Also, include new function that allows both, scalars (to be expanded to a period structure dictionary) or a .json file with customed period structure.
…exing-period-lengths
…er and test to reflect these changes
… tests to avoid touching the error
…to avoid touching that error
… file to use in period structure test
…e consistency check
…ss and calculate them based on the provided scalars. Also, reorganize functions and update tests to reflect these changes.
| <= m.rampUpRates[generator] | ||
| * b.dispatchPeriod[dispatchPeriod].periodLength | ||
| * m.thermalCapacity[generator] |
There was a problem hiding this comment.
This looks like either the old implementation or the new implementation shouldn't be unit-consistent. Any idea which one is correct (and why the tests didn't catch it?)
| <= max( | ||
| pyo.value(m.thermalMin[generator]), | ||
| # [ESR: Make sure the time units are consistent | ||
| # here since we are only taking the value] | ||
| pyo.value(m.rampUpRates[generator]) | ||
| * pyo.value( | ||
| b.dispatchPeriod[dispatchPeriod].periodLength | ||
| ) # in minutes | ||
| * pyo.value(m.thermalCapacity[generator]), | ||
| ) |
There was a problem hiding this comment.
Design question: I generally don't like putting value() in model definitions. We could replace it with
<= MaxExpression((
m.thermalMin[generator],
# [ESR: Make sure the time units are consistent
# here since we are only taking the value]
m.rampUpRates[generator]
* b.dispatchPeriod[dispatchPeriod].periodLength) # in minutes
* m.thermalCapacity[generator]
))(Or if we know these are all NVP expressions (expressions of Params), then use a NPV_MaxExpression)
| num_commit=24, | ||
| num_dispatch=1, | ||
| duration_dispatch=60, | ||
| duration_representative_period=24, |
| import pyomo.environ as pyo | ||
| from pyomo.environ import units as u | ||
|
|
||
| curr_dir = os.path.dirname(os.path.abspath(__file__)) |
There was a problem hiding this comment.
This can be a bit fragile. you might consider pyomo.common.fileutils.this_file_dir()
There was a problem hiding this comment.
I would suggest renaming the variable, too: it is not holding the current (working) directory: it is holding this file's directory. Maybe:
| curr_dir = os.path.dirname(os.path.abspath(__file__)) | |
| data_dir = os.path.join(pyomo.common.fileutils.this_file_dir(), 'data') |
| commitment_hr = duration_commitment[rep][com] | ||
| if abs(pyo.value(dispatch_sum_hr) - commitment_hr) > 1e-6: | ||
| dispatch_errors.append( | ||
| f" - Representative period {rep}, commitment period {com}: sum of dispatch durations ({pyo.value(dispatch_sum_hr)} hr) != commitment period duration ({commitment_hr} hr)" |
There was a problem hiding this comment.
Can you break this into multiple lines?
| rep_period_hr = duration_representative_period[rep] | ||
| if abs(commitment_sum_hr - rep_period_hr) > 1e-6: | ||
| commitment_errors.append( | ||
| f" - Representative period {rep}: sum of commitment durations ({commitment_sum_hr} hr) != representative period duration ({rep_period_hr} hr)" |
There was a problem hiding this comment.
Can you break this into multiple lines?
| json_path = os.path.abspath( | ||
| os.path.join(curr_dir, "data", period_structure_json_file) | ||
| ) |
There was a problem hiding this comment.
This implies that .json files are being distributed by IDAES-gtep. Can you confirm that they are being bundled in the wheels?
|
|
||
| # Helper function to recursively convert string keys to | ||
| # integers | ||
| def convert_keys_to_int(obj): |
There was a problem hiding this comment.
This seems like a broadly useful capability. Should this be promoted to a general utility for "recovering" loaded general JSON files?
| # under the data directory. | ||
| filename = ( | ||
| os.path.abspath( | ||
| os.path.join(curr_dir, "data", "period_structure_from_gtep.json") |
There was a problem hiding this comment.
Again, is this file distributed in the wheel?
| check_period_structure_consistency, | ||
| ) | ||
|
|
||
| curr_dir = os.path.dirname(os.path.abspath(__file__)) |
There was a problem hiding this comment.
This is fragile (and does not appear to be used). Remove?
| import pyomo.environ as pyo | ||
| from pyomo.environ import units as u | ||
|
|
||
| curr_dir = os.path.dirname(os.path.abspath(__file__)) |
There was a problem hiding this comment.
I would suggest renaming the variable, too: it is not holding the current (working) directory: it is holding this file's directory. Maybe:
| curr_dir = os.path.dirname(os.path.abspath(__file__)) | |
| data_dir = os.path.join(pyomo.common.fileutils.this_file_dir(), 'data') |
| json.dump(period_structure, f, indent=2) | ||
|
|
||
|
|
||
| def generate_period_structure_utils( |
There was a problem hiding this comment.
I would recommend rethinking the name of this function (it doesn't generate "utils" does it?)
| If a JSON file is specified, it loads the period structure from | ||
| that file. Otherwise, generates the period structure from the | ||
| provided scalar arguments. Optionally saves the generated | ||
| structure to a JSON file. |
There was a problem hiding this comment.
This seems like three separate things. I would recommend these actions be three separate functions instead of switching them based on the arguments.
The
periodLengthparameters for each stage (representative period, commitment, and dispatch) are indexed by the number of periods on each respective stage. This will allow to determine different period lengths within each stage.