From f6f6065d3cb5ec82cd1da70d7aeb47b106d9e1ed Mon Sep 17 00:00:00 2001 From: "Paul W. Talbot" Date: Wed, 2 Sep 2020 16:03:36 -0600 Subject: [PATCH 1/2] work on optimization option --- src/Cases.py | 18 +++-- templates/outer.xml | 48 ++++++++---- templates/template_driver.py | 73 ++++++++++++++++--- .../multimarket_fix_price/heron_input.xml | 2 +- .../production_flex/heron_input.xml | 1 + .../production_steady/heron_input.xml | 1 + .../var_demand_fix_price/heron_input.xml | 2 +- .../var_demand_var_price/heron_input.xml | 2 +- 8 files changed, 111 insertions(+), 36 deletions(-) diff --git a/src/Cases.py b/src/Cases.py index 2c5caf36..08fe5897 100644 --- a/src/Cases.py +++ b/src/Cases.py @@ -43,11 +43,11 @@ def get_input_specs(cls): input_specs.addParam('name', param_type=InputTypes.StringType, required=True, descr=r"""the name by which this analysis should be referred within HERON.""") - #mode_options = InputTypes.makeEnumType('ModeOptions', 'ModeOptionsType', ['opt', 'sweep']) - #desc_mode_options = r"""determines whether the outer RAVEN should perform optimization, - # or a parametric (``sweep'') study. \default{sweep}""" - #input_specs.addSub(InputData.parameterInputFactory('mode', contentType=mode_options, - # strictMode=True, descr=desc_mode_options)) + mode_options = InputTypes.makeEnumType('ModeOptions', 'ModeOptionsType', ['opt', 'sweep']) + desc_mode_options = r"""determines whether the outer RAVEN should perform optimization, + or a parametric (``sweep'') study. \default{sweep}""" + input_specs.addSub(InputData.parameterInputFactory('mode', contentType=mode_options, + strictMode=True, descr=desc_mode_options)) # not yet implemented TODO #econ_metrics = InputTypes.makeEnumType('EconMetrics', 'EconMetricsTypes', ['NPV', 'lcoe']) @@ -130,11 +130,11 @@ def __init__(self, **kwargs): """ Base.__init__(self, **kwargs) self.name = None # case name - self._mode = 'sweep' # extrema to find: min, max, sweep - self._metric = 'NPV' # economic metric to focus on: lcoe, profit, cost + self._mode = None # extrema to find: min, max, sweep + self._metric = 'NPV' # UNUSED (future work); economic metric to focus on: lcoe, profit, cost self.dispatch_name = None # type of dispatcher to use - self.dispatcher = None # type of dispatcher to use + self.dispatcher = None # type of dispatcher to use self._diff_study = None # is this only a differential study? self._num_samples = 1 # number of ARMA stochastic samples to use ("denoises") @@ -186,6 +186,8 @@ def read_input(self, xml): self._increments[item.parameterValues['resource']] = item.value # checks + if self._mode is None: + self.raiseAnError('No node was provided in the node!') if self.dispatcher is None: self.raiseAnError('No node was provided in the node!') if self._time_discretization is None: diff --git a/templates/outer.xml b/templates/outer.xml index 7b04226a..5d5f9b72 100644 --- a/templates/outer.xml +++ b/templates/outer.xml @@ -2,7 +2,8 @@ EX19_Outer . - sweep + + 3 raven grid grid - sweep + sweep + + + inner_workflow + heron_lib + transfers + raven + cap_opt + opt_eval + opt_soln + opt_soln @@ -42,6 +52,14 @@ GRO_capacities GRO_outer_results + + GRO_capacities + mean_NPV + + + trajID + iteration, accepted, GRO_capacities, mean_NPV + @@ -68,25 +86,20 @@ mean_NPV - - beale_dist - -2 - - - beale_dist - -2 - - cap_opt_eval + 1 + opt_eval + 2000 every + max - 1.25 - 1.5 + 2 + 2 @@ -94,6 +107,8 @@ 1e-2 + 1e-2 + 3 @@ -109,5 +124,10 @@ csv grid + + csv + opt_soln + trajID + diff --git a/templates/template_driver.py b/templates/template_driver.py index 7da3d504..45c3786b 100644 --- a/templates/template_driver.py +++ b/templates/template_driver.py @@ -181,6 +181,7 @@ def _modify_outer(self, template, case, components, sources): @ In, sources, list, list of HERON Placeholder instances for this run @ Out, template, xml.etree.ElementTree.Element, modified template """ + self._modify_outer_mode(template, case) self._modify_outer_runinfo(template, case) self._modify_outer_vargroups(template, components) self._modify_outer_files(template, sources) @@ -190,6 +191,44 @@ def _modify_outer(self, template, case, components, sources): # TODO including the heron library file return template + def _modify_outer_mode(self, template, case): + """ + Defines modifications throughout outer.xml RAVEN input file due to "sweep" or "opt" mode. + @ In, template, xml.etree.ElementTree.Element, root of XML to modify + @ In, case, HERON Case, defining Case instance + @ Out, None + """ + if case._mode == 'opt': + # RunInfo + template.find('RunInfo').find('Sequence').text = 'optimize' + # Steps + sweep = template.find('Steps').findall('MultiRun')[0] + template.find('Steps').remove(sweep) + # DataObjects + grid = template.find('DataObjects').findall('PointSet')[0] + template.find('DataObjects').remove(grid) + # Samplers + template.remove(template.find('Samplers')) + # OutStreams + sweep = template.find('OutStreams').findall('Print')[0] + template.find('OutStreams').remove(sweep) + else: # mode is 'sweep' + # RunInfo + template.find('RunInfo').find('Sequence').text = 'sweep' + # Steps + opt = template.find('Steps').findall('MultiRun')[1] + template.find('Steps').remove(opt) + # DataObjects + opt_eval = template.find('DataObjects').findall('PointSet')[1] + opt_soln = template.find('DataObjects').findall('PointSet')[2] + template.find('DataObjects').remove(opt_eval) + template.find('DataObjects').remove(opt_soln) + # Optimizers + template.remove(template.find('Optimizers')) + # OutStreams + opt_soln = template.find('OutStreams').findall('Print')[1] + template.find('OutStreams').remove(opt_soln) + def _modify_outer_runinfo(self, template, case): """ Defines modifications to the RunInfo of outer.xml RAVEN input file. @@ -265,7 +304,10 @@ def _modify_outer_samplers(self, template, case, components): """ """ TODO """ dists_node = template.find('Distributions') - samps_node = template.find('Samplers').find('Grid') + if case._mode == 'sweep': + samps_node = template.find('Samplers').find('Grid') + else: + samps_node = template.find('Optimizers').find('GradientDescent') # number of denoisings ## assumption: first node is the denoises node samps_node.find('constant').text = str(case._num_samples) @@ -285,9 +327,12 @@ def _modify_outer_samplers(self, template, case, components): # is the capacity variable being swept over? if isinstance(vals, list): # make new Distribution, Sampler.Grid.variable - dist, samp = self._create_new_sweep_capacity(name, var_name, vals) + dist, for_grid, for_opt = self._create_new_sweep_capacity(name, var_name, vals) dists_node.append(dist) - samps_node.append(samp) + if case._mode == 'sweep': + samps_node.append(for_grid) + else: + samps_node.append(for_opt) # NOTE assumption (input checked): only one interaction per component # if not being swept, then it's just a fixed value. else: @@ -303,7 +348,8 @@ def _create_new_sweep_capacity(self, comp_name, var_name, capacities): @ In, var_name, str, name of capacity variable @ In, capacities, list, float list of capacities to sweep/opt over @ Out, dist, xml.etree.ElementTree,Element, XML for distribution - @ Out, samp, xml.etree.ElementTree,Element, XML for sampler variable + @ Out, grid, xml.etree.ElementTree,Element, XML for grid sampler variable + @ Out, opt, xml.etree.ElementTree,Element, XML for optimizer variable """ # distribution dist_name = self.namingTemplates['distribution'].format(unit=comp_name, feature='capacity') @@ -311,12 +357,17 @@ def _create_new_sweep_capacity(self, comp_name, var_name, capacities): dist.attrib['name'] = dist_name dist.find('lowerBound').text = str(min(capacities)) dist.find('upperBound').text = str(max(capacities)) - # sampler variable - samp = copy.deepcopy(self.var_template) - samp.attrib['name'] = var_name - samp.find('distribution').text = dist_name - samp.find('grid').text = ' '.join(str(x) for x in sorted(capacities)) - return dist, samp + # sampler variable, for Grid case + grid = copy.deepcopy(self.var_template) + grid.attrib['name'] = var_name + grid.find('distribution').text = dist_name + grid.find('grid').text = ' '.join(str(x) for x in sorted(capacities)) + # optimizer variable, for opt case + opt = copy.deepcopy(grid) + opt.remove(opt.find('grid')) + initial = np.average(capacities) + opt.append(xmlUtils.newNode('initial', text=initial)) + return dist, grid, opt ##### INNER ##### def _modify_inner(self, template, case, components, sources): @@ -373,7 +424,7 @@ def _modify_inner_sources(self, template, case, components, sources): # add a step to load the model self._iostep_load_rom(template, case, components, source) # add a step to print the rom meta - self._iostep_rom_meta(template, case, components, source) + self._iostep_rom_meta(template, source) # add the source to the arma-and-dispatch ensemble self._add_arma_to_ensemble(template, source) # NOTE assuming input to all ARMAs is "scaling" constant = 1.0, already in MonteCarlo sampler diff --git a/tests/integration_tests/multimarket_fix_price/heron_input.xml b/tests/integration_tests/multimarket_fix_price/heron_input.xml index 0dcfccac..706193e6 100644 --- a/tests/integration_tests/multimarket_fix_price/heron_input.xml +++ b/tests/integration_tests/multimarket_fix_price/heron_input.xml @@ -12,9 +12,9 @@ + sweep 2 Time diff --git a/tests/integration_tests/production_flex/heron_input.xml b/tests/integration_tests/production_flex/heron_input.xml index ad8aecaf..a56fda2a 100644 --- a/tests/integration_tests/production_flex/heron_input.xml +++ b/tests/integration_tests/production_flex/heron_input.xml @@ -14,6 +14,7 @@ + sweep 10 Time diff --git a/tests/integration_tests/production_steady/heron_input.xml b/tests/integration_tests/production_steady/heron_input.xml index c271db38..f62f6678 100644 --- a/tests/integration_tests/production_steady/heron_input.xml +++ b/tests/integration_tests/production_steady/heron_input.xml @@ -14,6 +14,7 @@ + sweep 2 8760 diff --git a/tests/integration_tests/var_demand_fix_price/heron_input.xml b/tests/integration_tests/var_demand_fix_price/heron_input.xml index 697a1a78..bf8af65e 100644 --- a/tests/integration_tests/var_demand_fix_price/heron_input.xml +++ b/tests/integration_tests/var_demand_fix_price/heron_input.xml @@ -12,9 +12,9 @@ + sweep 2 Time diff --git a/tests/integration_tests/var_demand_var_price/heron_input.xml b/tests/integration_tests/var_demand_var_price/heron_input.xml index 6a8396a4..83b6d4fb 100644 --- a/tests/integration_tests/var_demand_var_price/heron_input.xml +++ b/tests/integration_tests/var_demand_var_price/heron_input.xml @@ -12,9 +12,9 @@ + sweep 3 Time From 90cad54d9c4204688871b2eb44aa62e213eb8422 Mon Sep 17 00:00:00 2001 From: "Paul W. Talbot" Date: Tue, 8 Sep 2020 09:35:01 -0600 Subject: [PATCH 2/2] opt option and heavy test --- templates/outer.xml | 1 - tests/integration_tests/production_flex/tests | 2 +- .../gold/Opt_Runs_o/opt_soln.csv | 2 + .../gold/Opt_Runs_o/opt_soln_0.csv | 17 +++ .../production_flex_opt/heron_input.xml | 142 ++++++++++++++++++ .../production_flex_opt/tests | 14 ++ .../production_flex_opt/transfers.py | 31 ++++ 7 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln.csv create mode 100644 tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln_0.csv create mode 100644 tests/integration_tests/production_flex_opt/heron_input.xml create mode 100644 tests/integration_tests/production_flex_opt/tests create mode 100644 tests/integration_tests/production_flex_opt/transfers.py diff --git a/templates/outer.xml b/templates/outer.xml index 5d5f9b72..89cff6b7 100644 --- a/templates/outer.xml +++ b/templates/outer.xml @@ -108,7 +108,6 @@ 1e-2 1e-2 - 3 diff --git a/tests/integration_tests/production_flex/tests b/tests/integration_tests/production_flex/tests index 9cfa81e1..14a3888b 100644 --- a/tests/integration_tests/production_flex/tests +++ b/tests/integration_tests/production_flex/tests @@ -1,5 +1,5 @@ [Tests] - [./Production_Flex] + [./ProductionFlex] type = HeronIntegration input = heron_input.xml # prereq = SineArma diff --git a/tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln.csv b/tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln.csv new file mode 100644 index 00000000..e5c97790 --- /dev/null +++ b/tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln.csv @@ -0,0 +1,2 @@ +trajID,filename +0,opt_soln_0.csv diff --git a/tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln_0.csv b/tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln_0.csv new file mode 100644 index 00000000..f4a6219d --- /dev/null +++ b/tests/integration_tests/production_flex_opt/gold/Opt_Runs_o/opt_soln_0.csv @@ -0,0 +1,17 @@ +iteration,accepted,steamer_capacity,generator_capacity,electr_market_capacity,electr_flex_capacity,mean_NPV +0.0,first,5.5,-100.0,-2.0,-2025.0,56.8233159522 +1.0,accepted,4.86360389693,-100.0,-2.0,-2025.0,58.4129319302 +2.0,accepted,4.22720779386,-100.0,-2.0,-2025.0,60.002548875 +3.0,rejected,2.95441558773,-100.0,-2.0,-2025.0,44.7372957613 +4.0,rerun,4.22720779386,-100.0,-2.0,-2025.0,60.002548875 +5.0,rejected,3.5908116908,-100.0,-2.0,-2025.0,54.3739345853 +6.0,rerun,4.22720779386,-100.0,-2.0,-2025.0,60.002548875 +7.0,rejected,3.90900974233,-100.0,-2.0,-2025.0,59.1922570259 +8.0,rerun,4.22720779386,-100.0,-2.0,-2025.0,60.002548875 +9.0,accepted,4.0681087681,-100.0,-2.0,-2025.0,60.3999531626 +10.0,rejected,3.74991071656,-100.0,-2.0,-2025.0,56.7830973199 +11.0,rerun,4.0681087681,-100.0,-2.0,-2025.0,60.3999531626 +12.0,rejected,3.90900974233,-100.0,-2.0,-2025.0,59.1922570259 +13.0,rerun,4.0681087681,-100.0,-2.0,-2025.0,60.3999531626 +14.0,rejected,3.98855925521,-100.0,-2.0,-2025.0,60.3968353646 +15.0,rerun,4.0681087681,-100.0,-2.0,-2025.0,60.3999531626 diff --git a/tests/integration_tests/production_flex_opt/heron_input.xml b/tests/integration_tests/production_flex_opt/heron_input.xml new file mode 100644 index 00000000..f5921690 --- /dev/null +++ b/tests/integration_tests/production_flex_opt/heron_input.xml @@ -0,0 +1,142 @@ + + + production + talbpaul + 2020-06-15 + + Demonstrates using an optimization strategy on a simple system, instead of a sweep strategy. + A steam source feeds a generator, which then sells electricity to a low-capacity high-value + market and a high-capacity negative-value market. The size of the steam source and high-capacity + market are optimized. The market size is irrelevant; the optimal steam source size is equal + to the high-value market size. + + HERON + + + + opt + 3 + + Time + 2 + 21 + + + 3 + 0.08 + 0.0 + 0.0 + 50 + + + pyomo + + + + + + + + 1, 10 + + + + 27 + + + + + + + steam + + -100 + + + + -1 + 0.5 + + + + + 27 + + + + + + + + + -2 + + + + 30 + + + transfers + + + + 0.5 + + + + + + + + + + + -2e3, -2.05e3 + + + + 30 + + + transfers + + + transfers + + + + + + + + + ../ARMA/Sine/arma.pk + transfers.py + + + diff --git a/tests/integration_tests/production_flex_opt/tests b/tests/integration_tests/production_flex_opt/tests new file mode 100644 index 00000000..7e56112a --- /dev/null +++ b/tests/integration_tests/production_flex_opt/tests @@ -0,0 +1,14 @@ +[Tests] + [./ProductionFlexOpt] + type = HeronIntegration + input = heron_input.xml + heavy = true + # prereq = SineArma + [./csv] + type = UnorderedCSV + output = 'Opt_Runs_o/opt_soln.csv Opt_Runs_o/opt_soln_0.csv' + zero_threshold = 1e-6 + [../] + [../] + +[] diff --git a/tests/integration_tests/production_flex_opt/transfers.py b/tests/integration_tests/production_flex_opt/transfers.py new file mode 100644 index 00000000..08720ed7 --- /dev/null +++ b/tests/integration_tests/production_flex_opt/transfers.py @@ -0,0 +1,31 @@ + +# Copyright 2020, Battelle Energy Alliance, LLC +# ALL RIGHTS RESERVED +""" + Implements transfer functions +""" + +def electric_consume(data, meta): + ## works with generic? + # activity = meta['raven_vars']['HERON_pyomo_model'] + # t = meta['t'] + # flip sign because we consume the electricity + # E = -1.0 * activity['electricity'][t] + + ## works with pyomo + # model = meta['HERON']['pyomo_model'] + # component = meta['HERON']['component'] + activity = meta['HERON']['activity'] + # TODO a get_activity method for the dispatcher -> returns object-safe activity (expression or value)? + amount = -1 * activity['electricity'] + data = {'driver': amount} + return data, meta + +def flex_price(data, meta): + sine = meta['HERON']['RAVEN_vars']['Signal'] + t = meta['HERON']['time_index'] + # DispatchManager + # scale electricity consumed to flex between -1 and 1 + amount = - 2 * (sine[t] - 0.5) + data = {'reference_price': amount} + return data, meta