From 4b7a7e900b7a0611360c93ab525f0ae877cdd2f0 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sat, 18 Mar 2017 20:53:59 -0400 Subject: [PATCH] Moved the ModelContainer to models.Model, and allowed for brackets and choices on the solver methods --- openmc/__init__.py | 3 +- openmc/model/__init__.py | 1 + openmc/{modelcontainer.py => model/model.py} | 2 +- openmc/search.py | 151 ++++++++++++++++--- 4 files changed, 129 insertions(+), 28 deletions(-) rename openmc/{modelcontainer.py => model/model.py} (99%) diff --git a/openmc/__init__.py b/openmc/__init__.py index 4030063d9f..2f65d0317e 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -28,5 +28,4 @@ from openmc.summary import * from openmc.particle_restart import * from openmc.mixin import * from openmc.plotter import * -from openmc.search import * -from openmc.modelcontainer import * +from openmc.search import * \ No newline at end of file diff --git a/openmc/model/__init__.py b/openmc/model/__init__.py index ffb1f42820..557effcefa 100644 --- a/openmc/model/__init__.py +++ b/openmc/model/__init__.py @@ -1 +1,2 @@ from .triso import * +from .model import * diff --git a/openmc/modelcontainer.py b/openmc/model/model.py similarity index 99% rename from openmc/modelcontainer.py rename to openmc/model/model.py index 384f3f84bf..13868f339c 100644 --- a/openmc/modelcontainer.py +++ b/openmc/model/model.py @@ -2,7 +2,7 @@ import openmc from openmc.checkvalue import check_type -class ModelContainer(object): +class Model(object): """OpenMC model container for the openmc.Geometry, openmc.Materials, openmc.Settings, openmc.Tallies, and openmc.CMFD objects diff --git a/openmc/search.py b/openmc/search.py index d99b427b00..c2c8d2fa9e 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -1,34 +1,58 @@ from numbers import Real from types import FunctionType -from inspect import getfullargspec import openmc -from openmc.checkvalue import check_type, check_length +import openmc.model +from openmc.checkvalue import check_type, check_iterable_type, check_length, \ + check_value + + +_SCALAR_BRACKETED_METHODS = ['brentq', 'brentq', 'ridder', 'bisect'] class KeffSearch(object): - """Class to perform a search for a certain keff value given changes on an - arbitrary scalar input. + """Class to perform a keff search by modifying an arbitrarily parametrized + model. Parameters ---------- model_builder : FunctionType - Callable function which builds a model according to a single, passed - parameter. This function must return an openmc.ModelContainer object. - guess : Real - Initial guess for the parameter modified in :param:`model_builder`. - target_keff : Real + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. + guess : Real, optional + Initial guess for the parameter to be searched in + :param:`model_builder`. One of :param:`guess` or :param`bracket` must + be provided. + target_keff : Real, optional keff value to search for, defaults to 1.0. + bracket : None or Iterable of Real, optional + Bracketing interval to search for the solution; if not provided, + a generic non-bracketing method is used. If provided, the brackets + are used. Defaults to no brackets provided. One of :param:`guess` or + :param`bracket` must be provided. If both are provided, the bracket + will be preferentially used. Attributes ---------- model_builder : FunctionType - Callable function which builds a model according to a single, passed - parameter. This function must return an openmc.ModelContainer object. - guess : Real - Initial guess for the parameter modified in :param:`model_builder`. + Callable function which builds a model according to parameters passed. + This function must return an openmc.model.Model + object. + initial_guess : Iterable of Real + Initial guess for the parameter to be searched in + :param:`model_builder`. target_keff : Real keff value to search for. + bracket : None or Iterable of Real + Bracketing interval to search for the solution; if not provided, + a generic non-bracketing method is used. If provided, the brackets + are used. + print_iterations : bool + Whether or not to print the guess and the resultant keff during the + iteration process. Defaults to False. + print_output : bool + Whether or not to print the OpenMC output during the iterations. + Defaults to False. guesses : List of Real List of guesses attempted by the search keffs : List of Real @@ -39,13 +63,17 @@ class KeffSearch(object): """ - def __init__(self, model_builder, guess, target_keff=1.0): + def __init__(self, model_builder, guess=None, bracket=None, + target_keff=1.0): self.initial_guess = guess + self.bracket = bracket self.model_builder = model_builder self.target_keff = target_keff self.guesses = [] self.keffs = [] self.keff_uncs = [] + self.print_iterations = False + self.print_output = False @property def model_builder(self): @@ -56,14 +84,13 @@ class KeffSearch(object): # Make sure model_builder is a function check_type('model_builder', model_builder, FunctionType) - # Make sure model_builder has only one parameter - argspec = getfullargspec(model_builder) - check_length('model_builder arguments', argspec.args, 1) - # Run the model builder function once to make sure it provides the - # correct output types - model = model_builder(self.initial_guess) - check_type('model_builder return', model, openmc.ModelContainer) + # correct output type + if self.bracket is not None: + model = model_builder(self.bracket[0]) + elif self.initial_guess is not None: + model = model_builder(self.initial_guess) + check_type('model_builder return', model, openmc.model.Model) self._model_builder = model_builder @property @@ -72,8 +99,21 @@ class KeffSearch(object): @initial_guess.setter def initial_guess(self, initial_guess): + if initial_guess is not None: + check_type('initial_guess', initial_guess, Real) self._initial_guess = initial_guess + @property + def bracket(self): + return self._bracket + + @bracket.setter + def bracket(self, bracket): + if bracket is not None: + check_iterable_type('bracket', bracket, Real) + check_length('bracket', bracket, 2) + self._bracket = bracket + @property def target_keff(self): return self._target_keff @@ -83,12 +123,30 @@ class KeffSearch(object): check_type('target_keff', target_keff, Real) self._target_keff = target_keff + @property + def print_iterations(self): + return self._print_iterations + + @print_iterations.setter + def print_iterations(self, print_iterations): + check_type('print_iterations', print_iterations, bool) + self._print_iterations = print_iterations + + @property + def print_output(self): + return self._print_output + + @print_output.setter + def print_output(self, print_output): + check_type('print_output', print_output, bool) + self._print_output = print_output + def _search_function(self, guess): # Build the model model = self.model_builder(guess) # Run the model - keff = model.execute(output=False) + keff = model.execute(output=self._print_output) # Close the model to ensure HDF5 will allow access during the next # OpenMC execution @@ -99,13 +157,22 @@ class KeffSearch(object): self.keffs.append(keff[0]) self.keff_uncs.append(keff[1]) + if self._print_iterations: + print(guess, '{:1.5f} +/- {:1.5f}'.format(keff[0], keff[1])) + return (keff[0] - self.target_keff) - def search(self, **kwargs): + def search(self, tol=None, bracketed_method='brentq', **kwargs): """Searches for the target eigenvalue with the Newton-Raphson method Parameters ---------- + tol : Real + Tolerance to pass to the search method + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use; only applies if + :param:`bracket` is set, otherwise the Newton method is used. + Defaults to 'brentq'. **kwargs Keyword arguments passed to :func:`scipy.optimize.newton`. @@ -118,9 +185,43 @@ class KeffSearch(object): """ + check_value('bracketed_method', bracketed_method, + _SCALAR_BRACKETED_METHODS) + import scipy.optimize as sopt - zero_value = sopt.newton(self._search_function, self.initial_guess, - **kwargs) + if self.bracket is not None: + # Generate our arguments + args = {'f': self._search_function, 'a': self.bracket[0], + 'b': self.bracket[1]} + if tol is not None: + args['rtol'] = tol + + # Set the root finding method + if bracketed_method == 'brentq': + root_finder = sopt.brentq + elif bracketed_method == 'brenth': + root_finder = sopt.brenth + elif bracketed_method == 'ridder': + root_finder = sopt.ridder + elif bracketed_method == 'bisect': + root_finder = sopt.bisect + + elif self.initial_guess is not None: + + # Generate our arguments + args = {'func': self._search_function, 'x0': self.initial_guess} + if tol is not None: + args['tol'] = tol + + # Set the root finding method + root_finder = sopt.newton + + else: + raise ValueError("One of the 'bracket' or 'initial_guess' " + "parameters must be set") + + # Perform the search + zero_value = root_finder(**args, **kwargs) return zero_value