diff --git a/.gitignore b/.gitignore index 83fc28d783..f6f454de46 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ suffix.h *.pyc rs.cred *.h5 +*.dat +tests/run_inputs.py +cyclus.sqlite \ No newline at end of file diff --git a/.travis-install.sh b/.travis-install.sh new file mode 100755 index 0000000000..cc8aff563f --- /dev/null +++ b/.travis-install.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -x # print cmds +set -e # exit as soon as an error occurs + +# log +msg=`git log --pretty=oneline -1` +echo "Building commit: $msg" + +# setup conda recipe to use develop cyclus +sed -i "s/- cyclus/- cyclus 0.0/g" conda-recipe/meta.yaml + +# build +conda build --no-test conda-recipe + +# install +conda install --use-local cycamore=0.0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..6a126fed6a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,44 @@ +language: python +python: + # We don't actually use the Travis Python, but this keeps it organized. + - "2.7" + - "3.4" +before_install: + - sudo apt-get update -qq +install: + # You may want to periodically update this, although the conda update + # conda line below will keep everything up-to-date. We do this + # conditionally because it saves us some downloading if the version is + # the same. + - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then + wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; + else + wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; + fi + - bash miniconda.sh -b -p $HOME/miniconda + - export PATH="$HOME/miniconda/bin:$PATH" + - hash -r + - conda config --set always_yes yes --set changeps1 no + - conda config --add channels pyne + - conda config --add channels cyclus + - conda update -q conda + - conda install conda-build jinja2 setuptools binstar patchelf nose + # Useful for debugging any issues with conda + - conda info -a + + # install cyclus + - git clone https://github.com/cyclus/cyclus ../cyclus + - cd ../cyclus + - ./.travis-install.sh + - cd ../cycamore + + # install cycamore + - ./.travis-install.sh + +script: + - export PATH="$HOME/miniconda/bin:$PATH" + - export LD_LIBRARY_PATH="$HOME/miniconda/lib:$LD_LIBRARY_PATH" + - cycamore_unit_tests + - conda install numpy pytables + - nosetests -w tests + diff --git a/CMakeLists.txt b/CMakeLists.txt index 336fb1cd1e..f2d1f2b451 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,11 +104,18 @@ IF(NOT CYCLUS_DOC_ONLY) FIND_PACKAGE(HDF5 REQUIRED) ADD_DEFINITIONS(${HDF5_DEFINITIONS}) set(LIBS ${LIBS} ${HDF5_LIBRARIES}) + MESSAGE("-- HDF5 Root: ${HDF5_ROOT}") + MESSAGE("-- HDF5 Include directory: ${HDF5_INCLUDE_DIR}") + MESSAGE("-- HDF5 Library directories: ${HDF5_LIBRARY_DIRS}") + MESSAGE("-- HDF5 Libraries: ${HDF5_LIBRARIES}") # find coin and link to it FIND_PACKAGE(COIN REQUIRED) set(LIBS ${LIBS} ${COIN_LIBRARIES}) + FIND_PACKAGE( Sqlite3 REQUIRED ) + SET(LIBS ${LIBS} ${SQLITE3_LIBRARIES}) + # include the agent directories SET(CYCAMORE_INCLUDE_DIR ${CYCAMORE_INCLUDE_DIR} tests ${CYCLUS_CORE_INCLUDE_DIR}/..) diff --git a/cmake/FindHDF5.cmake b/cmake/FindHDF5.cmake index 1722a7ea41..c7f7d73bee 100644 --- a/cmake/FindHDF5.cmake +++ b/cmake/FindHDF5.cmake @@ -20,13 +20,14 @@ # HDF5_USE_STATIC_LIBRARIES variable is set before the call to find_package. # # To provide the module with a hint about where to find your HDF5 installation, -# you can set the environment variable HDF5_ROOT. The Find module will then -# look in this path when searching for HDF5 executables, paths, and libraries. +# you can set the CMAKE variable -OR- environment variable HDF5_ROOT. The Find +# module will then look in this path when searching for HDF5 executables, paths, +# and libraries. # # In addition to finding the includes and libraries required to compile an HDF5 # client application, this module also makes an effort to find tools that come # with the HDF5 distribution that may be useful for regression testing. -# +# # This module will define the following variables: # HDF5_INCLUDE_DIRS - Location of the hdf5 includes # HDF5_INCLUDE_DIR - Location of the hdf5 includes (deprecated) @@ -60,268 +61,271 @@ include(SelectLibraryConfigurations) include(FindPackageHandleStandardArgs) # List of the valid HDF5 components -set(HDF5_VALID_COMPONENTS +set( HDF5_VALID_COMPONENTS C CXX - ) +) # try to find the HDF5 wrapper compilers -find_program(HDF5_C_COMPILER_EXECUTABLE +find_program( HDF5_C_COMPILER_EXECUTABLE NAMES h5cc h5pcc - HINTS ENV HDF5_ROOT + HINTS "${HDF5_ROOT}" ENV HDF5_ROOT PATH_SUFFIXES bin Bin - DOC "HDF5 Wrapper compiler. Used only to detect HDF5 compile flags.") -mark_as_advanced(HDF5_C_COMPILER_EXECUTABLE) + DOC "HDF5 Wrapper compiler. Used only to detect HDF5 compile flags." ) +mark_as_advanced( HDF5_C_COMPILER_EXECUTABLE ) -find_program(HDF5_CXX_COMPILER_EXECUTABLE +find_program( HDF5_CXX_COMPILER_EXECUTABLE NAMES h5c++ h5pc++ - HINTS ENV HDF5_ROOT + HINTS "${HDF5_ROOT}" ENV HDF5_ROOT PATH_SUFFIXES bin Bin - DOC "HDF5 C++ Wrapper compiler. Used only to detect HDF5 compile flags.") -mark_as_advanced(HDF5_CXX_COMPILER_EXECUTABLE) + DOC "HDF5 C++ Wrapper compiler. Used only to detect HDF5 compile flags." ) +mark_as_advanced( HDF5_CXX_COMPILER_EXECUTABLE ) -find_program(HDF5_DIFF_EXECUTABLE +find_program( HDF5_DIFF_EXECUTABLE NAMES h5diff - HINTS ENV HDF5_ROOT - PATH_SUFFIXES bin Bin - DOC "HDF5 file differencing tool.") -mark_as_advanced(HDF5_DIFF_EXECUTABLE) + HINTS "${HDF5_ROOT}" ENV HDF5_ROOT + PATH_SUFFIXES bin Bin + DOC "HDF5 file differencing tool." ) +mark_as_advanced( HDF5_DIFF_EXECUTABLE ) # Invoke the HDF5 wrapper compiler. The compiler return value is stored to the # return_value argument, the text output is stored to the output variable. -macro(_HDF5_invoke_compiler language output return_value) - if(HDF5_${language}_COMPILER_EXECUTABLE) - exec_program(${HDF5_${language}_COMPILER_EXECUTABLE} +macro( _HDF5_invoke_compiler language output return_value ) + if( HDF5_${language}_COMPILER_EXECUTABLE ) + exec_program( ${HDF5_${language}_COMPILER_EXECUTABLE} ARGS -show OUTPUT_VARIABLE ${output} RETURN_VALUE ${return_value} - ) - if(${${return_value}} EQUAL 0) + ) + if( ${${return_value}} EQUAL 0 ) # do nothing else() - message(STATUS - "Unable to determine HDF5 ${language} flags from HDF5 wrapper.") + message( STATUS + "Unable to determine HDF5 ${language} flags from HDF5 wrapper." ) endif() endif() endmacro() # Parse a compile line for definitions, includes, library paths, and libraries. -macro(_HDF5_parse_compile_line - compile_line_var - include_paths - definitions - library_paths - libraries) +macro( _HDF5_parse_compile_line + compile_line_var + include_paths + definitions + library_paths + libraries ) # Match the include paths - string(REGEX MATCHALL "-I([^\" ]+)" include_path_flags + string( REGEX MATCHALL "-I([^\" ]+)" include_path_flags "${${compile_line_var}}" - ) - foreach(IPATH ${include_path_flags}) - string(REGEX REPLACE "^-I" "" IPATH ${IPATH}) - string(REGEX REPLACE "//" "/" IPATH ${IPATH}) - list(APPEND ${include_paths} ${IPATH}) + ) + foreach( IPATH ${include_path_flags} ) + string( REGEX REPLACE "^-I" "" IPATH ${IPATH} ) + string( REGEX REPLACE "//" "/" IPATH ${IPATH} ) + list( APPEND ${include_paths} ${IPATH} ) endforeach() # Match the definitions - string(REGEX MATCHALL "-D[^ ]*" definition_flags "${${compile_line_var}}") - foreach(DEF ${definition_flags}) - list(APPEND ${definitions} ${DEF}) + string( REGEX MATCHALL "-D[^ ]*" definition_flags "${${compile_line_var}}" ) + foreach( DEF ${definition_flags} ) + list( APPEND ${definitions} ${DEF} ) endforeach() # Match the library paths - string(REGEX MATCHALL "-L([^\" ]+|\"[^\"]+\")" library_path_flags + string( REGEX MATCHALL "-L([^\" ]+|\"[^\"]+\")" library_path_flags "${${compile_line_var}}" - ) - - foreach(LPATH ${library_path_flags}) - string(REGEX REPLACE "^-L" "" LPATH ${LPATH}) - string(REGEX REPLACE "//" "/" LPATH ${LPATH}) - list(APPEND ${library_paths} ${LPATH}) + ) + + foreach( LPATH ${library_path_flags} ) + string( REGEX REPLACE "^-L" "" LPATH ${LPATH} ) + string( REGEX REPLACE "//" "/" LPATH ${LPATH} ) + list( APPEND ${library_paths} ${LPATH} ) endforeach() # now search for the library names specified in the compile line (match -l...) # match only -l's preceded by a space or comma # this is to exclude directory names like xxx-linux/ - string(REGEX MATCHALL "[, ]-l([^\", ]+)" library_name_flags - "${${compile_line_var}}") + string( REGEX MATCHALL "[, ]-l([^\", ]+)" library_name_flags + "${${compile_line_var}}" ) # strip the -l from all of the library flags and add to the search list - foreach(LIB ${library_name_flags}) - string(REGEX REPLACE "^[, ]-l" "" LIB ${LIB}) - list(APPEND ${libraries} ${LIB}) + foreach( LIB ${library_name_flags} ) + string( REGEX REPLACE "^[, ]-l" "" LIB ${LIB} ) + list( APPEND ${libraries} ${LIB} ) endforeach() endmacro() -if(HDF5_INCLUDE_DIRS AND HDF5_LIBRARIES) +if( HDF5_INCLUDE_DIRS AND HDF5_LIBRARIES ) # Do nothing: we already have HDF5_INCLUDE_PATH and HDF5_LIBRARIES in the # cache, it would be a shame to override them else() - _HDF5_invoke_compiler(C HDF5_C_COMPILE_LINE HDF5_C_RETURN_VALUE) - _HDF5_invoke_compiler(CXX HDF5_CXX_COMPILE_LINE HDF5_CXX_RETURN_VALUE) + _HDF5_invoke_compiler( C HDF5_C_COMPILE_LINE HDF5_C_RETURN_VALUE ) + _HDF5_invoke_compiler( CXX HDF5_CXX_COMPILE_LINE HDF5_CXX_RETURN_VALUE ) - if(NOT HDF5_FIND_COMPONENTS) - set(HDF5_LANGUAGE_BINDINGS "C") + if( NOT HDF5_FIND_COMPONENTS ) + set( HDF5_LANGUAGE_BINDINGS "C" ) else() # add the extra specified components, ensuring that they are valid. - foreach(component ${HDF5_FIND_COMPONENTS}) - list(FIND HDF5_VALID_COMPONENTS ${component} component_location) - if(${component_location} EQUAL -1) - message(FATAL_ERROR - "\"${component}\" is not a valid HDF5 component.") + foreach( component ${HDF5_FIND_COMPONENTS} ) + list( FIND HDF5_VALID_COMPONENTS ${component} component_location ) + if( ${component_location} EQUAL -1 ) + message( FATAL_ERROR + "\"${component}\" is not a valid HDF5 component." ) else() - list(APPEND HDF5_LANGUAGE_BINDINGS ${component}) + list( APPEND HDF5_LANGUAGE_BINDINGS ${component} ) endif() endforeach() endif() - + # seed the initial lists of libraries to find with items we know we need - set(HDF5_C_LIBRARY_NAMES_INIT hdf5_hl hdf5) - set(HDF5_CXX_LIBRARY_NAMES_INIT hdf5_cpp ${HDF5_C_LIBRARY_NAMES_INIT}) - - foreach(LANGUAGE ${HDF5_LANGUAGE_BINDINGS}) - if(HDF5_${LANGUAGE}_COMPILE_LINE) - _HDF5_parse_compile_line(HDF5_${LANGUAGE}_COMPILE_LINE - HDF5_${LANGUAGE}_INCLUDE_FLAGS - HDF5_${LANGUAGE}_DEFINITIONS - HDF5_${LANGUAGE}_LIBRARY_DIRS - HDF5_${LANGUAGE}_LIBRARY_NAMES + set( HDF5_C_LIBRARY_NAMES_INIT hdf5_hl hdf5 ) + set( HDF5_CXX_LIBRARY_NAMES_INIT hdf5_cpp ${HDF5_C_LIBRARY_NAMES_INIT} ) + + foreach( LANGUAGE ${HDF5_LANGUAGE_BINDINGS} ) + if( HDF5_${LANGUAGE}_COMPILE_LINE ) + _HDF5_parse_compile_line( HDF5_${LANGUAGE}_COMPILE_LINE + HDF5_${LANGUAGE}_INCLUDE_FLAGS + HDF5_${LANGUAGE}_DEFINITIONS + HDF5_${LANGUAGE}_LIBRARY_DIRS + HDF5_${LANGUAGE}_LIBRARY_NAMES ) + + # take a guess that the includes may be in the 'include' sibling directory + # of a library directory. + foreach( dir ${HDF5_${LANGUAGE}_LIBRARY_DIRS} ) + list( APPEND HDF5_${LANGUAGE}_INCLUDE_FLAGS ${dir}/../include ) + endforeach() + endif() - # take a guess that the includes may be in the 'include' sibling directory - # of a library directory. - foreach(dir ${HDF5_${LANGUAGE}_LIBRARY_DIRS}) - list(APPEND HDF5_${LANGUAGE}_INCLUDE_FLAGS ${dir}/../include) - endforeach() - endif() - - # set the definitions for the language bindings. - list(APPEND HDF5_DEFINITIONS ${HDF5_${LANGUAGE}_DEFINITIONS}) - - # find the HDF5 include directories - find_path(HDF5_${LANGUAGE}_INCLUDE_DIR hdf5.h - HINTS - ${HDF5_${LANGUAGE}_INCLUDE_FLAGS} - ENV - HDF5_ROOT - PATHS - $ENV{HOME}/.local/include - PATH_SUFFIXES - include - Include + # set the definitions for the language bindings. + list( APPEND HDF5_DEFINITIONS ${HDF5_${LANGUAGE}_DEFINITIONS} ) + + # find the HDF5 include directories + find_path( HDF5_${LANGUAGE}_INCLUDE_DIR hdf5.h + HINTS + "${HDF5_ROOT}" + ${HDF5_${LANGUAGE}_INCLUDE_FLAGS} + ENV + HDF5_ROOT + PATHS + $ENV{HOME}/.local/include + PATH_SUFFIXES + include + Include ) - mark_as_advanced(HDF5_${LANGUAGE}_INCLUDE_DIR) - list(APPEND HDF5_INCLUDE_DIRS ${HDF5_${LANGUAGE}_INCLUDE_DIR}) - - set(HDF5_${LANGUAGE}_LIBRARY_NAMES - ${HDF5_${LANGUAGE}_LIBRARY_NAMES_INIT} - ${HDF5_${LANGUAGE}_LIBRARY_NAMES}) - - # find the HDF5 libraries - foreach(LIB ${HDF5_${LANGUAGE}_LIBRARY_NAMES}) - if(UNIX AND HDF5_USE_STATIC_LIBRARIES) - # According to bug 1643 on the CMake bug tracker, this is the - # preferred method for searching for a static library. - # See http://www.cmake.org/Bug/view.php?id=1643. We search - # first for the full static library name, but fall back to a - # generic search on the name if the static search fails. - set(THIS_LIBRARY_SEARCH_DEBUG lib${LIB}d.a ${LIB}d) - set(THIS_LIBRARY_SEARCH_RELEASE lib${LIB}.a ${LIB}) - else() - set(THIS_LIBRARY_SEARCH_DEBUG ${LIB}d) - set(THIS_LIBRARY_SEARCH_RELEASE ${LIB}) - endif() - find_library(HDF5_${LIB}_LIBRARY_DEBUG - NAMES ${THIS_LIBRARY_SEARCH_DEBUG} - HINTS ${HDF5_${LANGUAGE}_LIBRARY_DIRS} - ENV HDF5_ROOT - PATH_SUFFIXES lib Lib) - find_library(HDF5_${LIB}_LIBRARY_RELEASE - NAMES ${THIS_LIBRARY_SEARCH_RELEASE} - HINTS ${HDF5_${LANGUAGE}_LIBRARY_DIRS} - ENV HDF5_ROOT - PATH_SUFFIXES lib Lib) - select_library_configurations(HDF5_${LIB}) - # even though we adjusted the individual library names in - # select_library_configurations, we still need to distinguish - # between debug and release variants because HDF5_LIBRARIES will - # need to specify different lists for debug and optimized builds. - # We can't just use the HDF5_${LIB}_LIBRARY variable (which was set - # up by the selection macro above) because it may specify debug and - # optimized variants for a particular library, but a list of - # libraries is allowed to specify debug and optimized only once. - list(APPEND HDF5_${LANGUAGE}_LIBRARIES_DEBUG - ${HDF5_${LIB}_LIBRARY_DEBUG}) - list(APPEND HDF5_${LANGUAGE}_LIBRARIES_RELEASE - ${HDF5_${LIB}_LIBRARY_RELEASE}) + mark_as_advanced( HDF5_${LANGUAGE}_INCLUDE_DIR ) + list( APPEND HDF5_INCLUDE_DIRS ${HDF5_${LANGUAGE}_INCLUDE_DIR} ) + + set( HDF5_${LANGUAGE}_LIBRARY_NAMES + ${HDF5_${LANGUAGE}_LIBRARY_NAMES_INIT} + ${HDF5_${LANGUAGE}_LIBRARY_NAMES} ) + + # find the HDF5 libraries + foreach( LIB ${HDF5_${LANGUAGE}_LIBRARY_NAMES} ) + if( UNIX AND HDF5_USE_STATIC_LIBRARIES ) + # According to bug 1643 on the CMake bug tracker, this is the + # preferred method for searching for a static library. + # See http://www.cmake.org/Bug/view.php?id=1643. We search + # first for the full static library name, but fall back to a + # generic search on the name if the static search fails. + set( THIS_LIBRARY_SEARCH_DEBUG lib${LIB}d.a ${LIB}d ) + set( THIS_LIBRARY_SEARCH_RELEASE lib${LIB}.a ${LIB} ) + else() + set( THIS_LIBRARY_SEARCH_DEBUG ${LIB}d ) + set( THIS_LIBRARY_SEARCH_RELEASE ${LIB} ) + endif() + find_library( HDF5_${LIB}_LIBRARY_DEBUG + NAMES ${THIS_LIBRARY_SEARCH_DEBUG} + HINTS "${HDF5_ROOT}" ${HDF5_${LANGUAGE}_LIBRARY_DIRS} + ENV HDF5_ROOT + PATH_SUFFIXES lib Lib ) + find_library( HDF5_${LIB}_LIBRARY_RELEASE + NAMES ${THIS_LIBRARY_SEARCH_RELEASE} + HINTS "${HDF5_ROOT}" ${HDF5_${LANGUAGE}_LIBRARY_DIRS} + ENV HDF5_ROOT + PATH_SUFFIXES lib Lib ) + select_library_configurations( HDF5_${LIB} ) + # even though we adjusted the individual library names in + # select_library_configurations, we still need to distinguish + # between debug and release variants because HDF5_LIBRARIES will + # need to specify different lists for debug and optimized builds. + # We can't just use the HDF5_${LIB}_LIBRARY variable (which was set + # up by the selection macro above) because it may specify debug and + # optimized variants for a particular library, but a list of + # libraries is allowed to specify debug and optimized only once. + list( APPEND HDF5_${LANGUAGE}_LIBRARIES_DEBUG + ${HDF5_${LIB}_LIBRARY_DEBUG} ) + list( APPEND HDF5_${LANGUAGE}_LIBRARIES_RELEASE + ${HDF5_${LIB}_LIBRARY_RELEASE} ) + endforeach() + list( APPEND HDF5_LIBRARY_DIRS ${HDF5_${LANGUAGE}_LIBRARY_DIRS} ) + + # Append the libraries for this language binding to the list of all + # required libraries. + list( APPEND HDF5_LIBRARIES_DEBUG + ${HDF5_${LANGUAGE}_LIBRARIES_DEBUG} ) + list( APPEND HDF5_LIBRARIES_RELEASE + ${HDF5_${LANGUAGE}_LIBRARIES_RELEASE} ) endforeach() - list(APPEND HDF5_LIBRARY_DIRS ${HDF5_${LANGUAGE}_LIBRARY_DIRS}) - - # Append the libraries for this language binding to the list of all - # required libraries. - list(APPEND HDF5_LIBRARIES_DEBUG - ${HDF5_${LANGUAGE}_LIBRARIES_DEBUG}) - list(APPEND HDF5_LIBRARIES_RELEASE - ${HDF5_${LANGUAGE}_LIBRARIES_RELEASE}) -endforeach() -# We may have picked up some duplicates in various lists during the above -# process for the language bindings (both the C and C++ bindings depend on -# libz for example). Remove the duplicates. -if(HDF5_INCLUDE_DIRS) - list(REMOVE_DUPLICATES HDF5_INCLUDE_DIRS) -endif() -if(HDF5_LIBRARIES_DEBUG) - list(REMOVE_DUPLICATES HDF5_LIBRARIES_DEBUG) -endif() -if(HDF5_LIBRARIES_RELEASE) - list(REMOVE_DUPLICATES HDF5_LIBRARIES_RELEASE) -endif() -if(HDF5_LIBRARY_DIRS) - list(REMOVE_DUPLICATES HDF5_LIBRARY_DIRS) -endif() + # We may have picked up some duplicates in various lists during the above + # process for the language bindings (both the C and C++ bindings depend on + # libz for example). Remove the duplicates. + if( HDF5_INCLUDE_DIRS ) + list( REMOVE_DUPLICATES HDF5_INCLUDE_DIRS ) + endif() + if( HDF5_LIBRARIES_DEBUG ) + list( REMOVE_DUPLICATES HDF5_LIBRARIES_DEBUG ) + endif() + if( HDF5_LIBRARIES_RELEASE ) + list( REMOVE_DUPLICATES HDF5_LIBRARIES_RELEASE ) + endif() + if( HDF5_LIBRARY_DIRS ) + list( REMOVE_DUPLICATES HDF5_LIBRARY_DIRS ) + endif() -# Construct the complete list of HDF5 libraries with debug and optimized -# variants when the generator supports them. -if(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) - set(HDF5_LIBRARIES - debug ${HDF5_LIBRARIES_DEBUG} - optimized ${HDF5_LIBRARIES_RELEASE}) -else() - set(HDF5_LIBRARIES ${HDF5_LIBRARIES_RELEASE}) -endif() + # Construct the complete list of HDF5 libraries with debug and optimized + # variants when the generator supports them. + if( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) + set( HDF5_LIBRARIES + debug ${HDF5_LIBRARIES_DEBUG} + optimized ${HDF5_LIBRARIES_RELEASE} ) + else() + set( HDF5_LIBRARIES ${HDF5_LIBRARIES_RELEASE} ) + endif() -# If the HDF5 include directory was found, open H5pubconf.h to determine if -# HDF5 was compiled with parallel IO support -set(HDF5_IS_PARALLEL FALSE) -foreach(_dir HDF5_INCLUDE_DIRS) - if(EXISTS "${_dir}/H5pubconf.h") - file(STRINGS "${_dir}/H5pubconf.h" - HDF5_HAVE_PARALLEL_DEFINE - REGEX "HAVE_PARALLEL 1") - if(HDF5_HAVE_PARALLEL_DEFINE) - set(HDF5_IS_PARALLEL TRUE) + # If the HDF5 include directory was found, open H5pubconf.h to determine if + # HDF5 was compiled with parallel IO support + set( HDF5_IS_PARALLEL FALSE ) + foreach( _dir HDF5_INCLUDE_DIRS ) + if( EXISTS "${_dir}/H5pubconf.h" ) + file( STRINGS "${_dir}/H5pubconf.h" + HDF5_HAVE_PARALLEL_DEFINE + REGEX "HAVE_PARALLEL 1" ) + if( HDF5_HAVE_PARALLEL_DEFINE ) + set( HDF5_IS_PARALLEL TRUE ) + endif() endif() - endif() -endforeach() -set(HDF5_IS_PARALLEL ${HDF5_IS_PARALLEL} CACHE BOOL - "HDF5 library compiled with parallel IO support") -mark_as_advanced(HDF5_IS_PARALLEL) + endforeach() + set( HDF5_IS_PARALLEL ${HDF5_IS_PARALLEL} CACHE BOOL + "HDF5 library compiled with parallel IO support" ) + mark_as_advanced( HDF5_IS_PARALLEL ) endif() -find_package_handle_standard_args(HDF5 DEFAULT_MSG - HDF5_LIBRARIES +find_package_handle_standard_args( HDF5 DEFAULT_MSG + HDF5_LIBRARIES HDF5_INCLUDE_DIRS - ) +) -mark_as_advanced( - HDF5_INCLUDE_DIRS - HDF5_LIBRARIES +mark_as_advanced( + HDF5_INCLUDE_DIRS + HDF5_LIBRARIES HDF5_DEFINTIONS HDF5_LIBRARY_DIRS HDF5_C_COMPILER_EXECUTABLE - HDF5_CXX_COMPILER_EXECUTABLE) + HDF5_CXX_COMPILER_EXECUTABLE ) # For backwards compatibility we set HDF5_INCLUDE_DIR to the value of # HDF5_INCLUDE_DIRS -set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIRS}") +set( HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIRS}" ) + + diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh new file mode 100644 index 0000000000..86da896a41 --- /dev/null +++ b/conda-recipe/build.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +mkdir build +cd build +export LD_LIBRARY_PATH=$PREFIX/lib/ +export CMAKE_LIBRARY_PATH=$PREFIX/lib/ +export PATH=$PREFIX/bin:$PATH +export MACOSX_DEPLOYMENT_TARGET= + +$PREFIX/bin/cyclus --version + +if [[ `uname` == 'Linux' ]]; then + cmake .. \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DCYCLUS_ROOT_DIR=$PREFIX \ + -DHDF5_ROOT=$PREFIX \ + -DBOOST_ROOT=$PREFIX \ + -DBOOST_LIBRARYDIR=$PREFIX/lib \ + -DBoost_NO_SYSTEM_PATHS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DLAPACK_LIBRARIES=$PREFIX/lib/liblapack.so \ + -DBLAS_LIBRARIES=$PREFIX/lib/libblas.so +else + export DYLD_FALLBACK_LIBRARY_PATH=$PREFIX/lib/cyclus:$PREFIX/lib + cmake .. \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DCYCLUS_ROOT_DIR=$PREFIX \ + -DHDF5_ROOT=$PREFIX \ + -DCOIN_ROOT_DIR=$PREFIX \ + -DBOOST_ROOT=$PREFIX \ + -DLAPACK_LIBRARIES=$PREFIX/lib/liblapack.dylib \ + -DBLAS_LIBRARIES=$PREFIX/lib/libblas.dylib +fi + +make +make install + +echo DONE diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml new file mode 100644 index 0000000000..95f525c964 --- /dev/null +++ b/conda-recipe/meta.yaml @@ -0,0 +1,29 @@ +package: + name: cycamore + version: 0.0 + +# Only use fn and url for polyphemus compatability +source: + fn: cycamore-src.tar.gz # ["TRAVIS" not in environ] + url: https://github.com/cyclus/cycamore/archive/develop.tar.gz # ["TRAVIS" not in environ] + path: .. # ["TRAVIS" in environ] + +requirements: + build: + - cyclus + - cmake + - python + run: + - cyclus + +test: + requires: + - nose + - pytables + +build: + string: nightly + +about: + home: Cyclus + license: BSD 3 Clause diff --git a/conda-recipe/post-link.sh b/conda-recipe/post-link.sh new file mode 100644 index 0000000000..ed30d81a2b --- /dev/null +++ b/conda-recipe/post-link.sh @@ -0,0 +1,17 @@ + +echo "post-link.sh, PREFIX: $PREFIX" + +mv $PREFIX/bin/cycamore_unit_tests $PREFIX/bin/cycamore_unit_tests_base +echo " +#!/bin/bash +export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib/cyclus +export DYLD_FALLBACK_LIBRARY_PATH=$PREFIX/lib/cyclus +export CYCLUS_NUC_DATA=$PREFIX/share/cyclus/cyclus_nuc_data.h5 +export CYCLUS_PATH=$PREFIX/lib/cyclus +export CYCLUS_RNG_SCHEMA=$PREFIX/share/cyclus/cyclus.rng.in +export DYLD_LIBRARY_PATH= +$PREFIX/bin/cycamore_unit_tests_base \$* + +" > $PREFIX/bin/cycamore_unit_tests +chmod 755 $PREFIX/bin/cycamore_unit_tests + diff --git a/conda-recipe/pre-link.sh b/conda-recipe/pre-link.sh new file mode 100644 index 0000000000..9a188008e3 --- /dev/null +++ b/conda-recipe/pre-link.sh @@ -0,0 +1,6 @@ + +echo "pre-link.sh, PREFIX: $PREFIX" + +export LD_LIBRARY_PATH=$PREFIX/lib/:$LD_LIBRARY_PATH +export CMAKE_LIBRARY_PATH=$PREFIX/lib/:$CMAKE_LIBRARY_PATH + diff --git a/input/batch_reactor/batch_rxtr_2_cycles.xml b/input/batch_reactor/batch_rxtr_2_cycles.xml deleted file mode 100644 index 5dacac3fe4..0000000000 --- a/input/batch_reactor/batch_rxtr_2_cycles.xml +++ /dev/null @@ -1,199 +0,0 @@ - - - - - 27 - 1 - 2000 - - - - - cycamore - Source - - - cycamore - BatchReactor - - - cycamore - Sink - - - agents - NullRegion - - - agents - NullInst - - - - - Source - - - enriched_u - 1.0e10 - lwr_fuel_recipe - - - - - - LWR_Reactor - 26 - - - - enriched_u - lwr_fuel_recipe - waste - lwr_used_fuel_recipe - - 10 - 2 - 4 - 7.8707064e4 - - lwr_power - 1000 - 1000 - - - - - - - Sink - - - - waste - - 1.0e10 - - - - - - SingleRegion - - - SingleInstitution - - - Source - 1 - - - LWR_Reactor - 1 - - - Sink - 1 - - - - - - - - natl_u - mass - - 922350000 - 0.711 - - - 922380000 - 99.289 - - - - - lwr_fuel_recipe - mass - - 922350000 - 4.0 - - - 922380000 - 96.0 - - - - - lwr_used_fuel_recipe - mass - - 922350000 - 156.729 - - - 922360000 - 102.103 - - - 922380000 - 18280.324 - - - 932370000 - 13.656 - - - 942380000 - 5.043 - - - 942390000 - 106.343 - - - 942400000 - 41.357 - - - 942410000 - 36.477 - - - 942420000 - 15.387 - - - 952410000 - 1.234 - - - - - - - 952430000 - 3.607 - - - 962440000 - 0.431 - - - 962450000 - 1.263 - - - - diff --git a/input/batch_reactor/batch_rxtr_lifetime.xml b/input/batch_reactor/batch_rxtr_lifetime.xml deleted file mode 100644 index 3fce5536bb..0000000000 --- a/input/batch_reactor/batch_rxtr_lifetime.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - - 482 - 1 - 2000 - - - - - cycamore - Source - - - cycamore - BatchReactor - - - agents - NullRegion - - - agents - NullInst - - - cycamore - Sink - - - - - Source - - - enriched_u - 1.0e10 - lwr_fuel_recipe - - - - - - LWR_Reactor - 480 - - - - enriched_u - lwr_fuel_recipe - waste - lwr_used_fuel_recipe - - 10 - 2 - 4 - 19676.766 - - lwr_power - 1000 - 1000 - - - - - - - Sink - - - - waste - - 1.0e10 - - - - - - SingleRegion - - - SingleInstitution - - - Source - 1 - - - LWR_Reactor - 1 - - - Sink - 1 - - - - - - - - natl_u - mass - - 922350000 - 0.711 - - - 922380000 - 99.289 - - - - - lwr_fuel_recipe - mass - - 922350000 - 4.0 - - - 922380000 - 96.0 - - - - - lwr_used_fuel_recipe - mass - - 922350000 - 156.729 - - - 922360000 - 102.103 - - - 922380000 - 18280.324 - - - 932370000 - 13.656 - - - 942380000 - 5.043 - - - 942390000 - 106.343 - - - 942400000 - 41.357 - - - 942410000 - 36.477 - - - 942420000 - 15.387 - - - 952410000 - 1.234 - - - - - - - 952430000 - 3.607 - - - 962440000 - 0.431 - - - 962450000 - 1.263 - - - - diff --git a/input/batch_reactor/hwr.xml b/input/batch_reactor/hwr.xml deleted file mode 100644 index 5438f256d4..0000000000 --- a/input/batch_reactor/hwr.xml +++ /dev/null @@ -1,249 +0,0 @@ - - - - - 482 - 11 - 2007 - - - - - cycamore - Source - - - cycamore - BatchReactor - - - cycamore - Sink - - - agents - NullRegion - - - agents - NullInst - - - - - Source - - - enriched_u - natl_u - - - - - - HW_Reactor - 480 - - - - enriched_u - natl_u - waste - hwr_used_fuel_recipe - - 10 - 2 - 1 - 1.39142873e5 - - hwr_power - 600 - 600 - - - - - - - Sink - - - - waste - - - - - - - SingleRegion - - - SingleInstitution - - - Source - 1 - - - HW_Reactor - 1 - - - Sink - 1 - - - - - - - - natl_u - mass - - 922350000 - 0.711 - - - 922380000 - 99.289 - - - - - lwr_fuel_recipe - mass - - 922350000 - 4.0 - - - 922380000 - 96.0 - - - - - lwr_used_fuel_recipe - mass - - 922350000 - 156.729 - - - 922360000 - 102.103 - - - 922380000 - 18280.324 - - - 932370000 - 13.656 - - - 942380000 - 5.043 - - - 942390000 - 106.343 - - - 942400000 - 41.357 - - - 942410000 - 36.477 - - - 942420000 - 15.387 - - - 952410000 - 1.234 - - - - - - - 952430000 - 3.607 - - - 962440000 - 0.431 - - - 962450000 - 1.263 - - - - - hwr_used_fuel_recipe - mass - - 922350000 - 330.478 - - - 922360000 - 98.944 - - - 922380000 - 137171.079 - - - 932370000 - 3.604 - - - 942380000 - 0.459 - - - 942390000 - 369.87 - - - 942400000 - 133.16 - - - 942410000 - 25.227 - - - 942420000 - 5.468 - - - 952410000 - 0.195 - - - - - - - 952430000 - 0.167 - - - 962440000 - 0.07 - - - 962450000 - 0.014 - - - - diff --git a/input/batch_reactor/lwr.xml b/input/batch_reactor/lwr.xml deleted file mode 100644 index 0936bd8fa2..0000000000 --- a/input/batch_reactor/lwr.xml +++ /dev/null @@ -1,249 +0,0 @@ - - - - - 482 - 11 - 2007 - - - - - cycamore - Source - - - cycamore - BatchReactor - - - cycamore - Sink - - - agents - NullRegion - - - agents - NullInst - - - - - Source - - - enriched_u - lwr_fuel_recipe - - - - - - LW_Reactor - 480 - - - - enriched_u - lwr_fuel_recipe - waste - hwr_used_fuel_recipe - - 10 - 2 - 4 - 7.8707064e4 - - lwr_power - 1000 - 1000 - - - - - - - Sink - - - - waste - - - - - - - SingleRegion - - - SingleInstitution - - - Source - 1 - - - LW_Reactor - 1 - - - Sink - 1 - - - - - - - - natl_u - mass - - 922350000 - 0.711 - - - 922380000 - 99.289 - - - - - lwr_fuel_recipe - mass - - 922350000 - 4.0 - - - 922380000 - 96.0 - - - - - lwr_used_fuel_recipe - mass - - 922350000 - 156.729 - - - 922360000 - 102.103 - - - 922380000 - 18280.324 - - - 932370000 - 13.656 - - - 942380000 - 5.043 - - - 942390000 - 106.343 - - - 942400000 - 41.357 - - - 942410000 - 36.477 - - - 942420000 - 15.387 - - - 952410000 - 1.234 - - - - - - - 952430000 - 3.607 - - - 962440000 - 0.431 - - - 962450000 - 1.263 - - - - - hwr_used_fuel_recipe - mass - - 922350000 - 330.478 - - - 922360000 - 98.944 - - - 922380000 - 137171.079 - - - 932370000 - 3.604 - - - 942380000 - 0.459 - - - 942390000 - 369.87 - - - 942400000 - 133.16 - - - 942410000 - 25.227 - - - 942420000 - 5.468 - - - 952410000 - 0.195 - - - - - - - 952430000 - 0.167 - - - 962440000 - 0.07 - - - 962450000 - 0.014 - - - - diff --git a/input/batch_reactor/preferences.xml b/input/batch_reactor/preferences.xml deleted file mode 100644 index ec27566b45..0000000000 --- a/input/batch_reactor/preferences.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - 10 - 1 - 2000 - - - - - cycamore - Source - - - cycamore - BatchReactor - - - cycamore - Sink - - - agents - NullRegion - - - agents - NullInst - - - - - Source - - - used_commodity - 1 - commod_recipe - - - - - - Reactor - - - - used_commodity - commod_recipe - processed_commodity - commod_recipe - - 1 - 1 - 1 - - power - 10 - 10 - - - used_commodity - 1.0 - - - - - - - Sink - - - - used_commodity - processed_commodity - - 2 - - - - - - SingleRegion - - - SingleInstitution - - - Source - 1 - - - Reactor - 1 - - - Sink - 1 - - - - - - - - commod_recipe - mass - - 010010000 - 1 - - - - diff --git a/input/enrichment/1_src_enr_rxtr_sink.xml b/input/enrichment/1_src_enr_rxtr_sink.xml index 7a7cc42008..ea8b519a41 100644 --- a/input/enrichment/1_src_enr_rxtr_sink.xml +++ b/input/enrichment/1_src_enr_rxtr_sink.xml @@ -10,8 +10,8 @@ cycamoreSink cycamoreSource - cycamoreEnrichmentFacility - cycamoreBatchReactor + cycamoreReactor + cycamoreEnrichment agentsNullRegion agentsNullInst @@ -20,9 +20,9 @@ Source - natl_u - 1000 - natl_u + natl_u + natl_u + 1000 @@ -30,35 +30,32 @@ Enrichment - - natl_u - enriched_u - natl_u + + natl_u + enriched_u + ef_tails + natl_u 0.003 - 500 - + 500 + Reactor - - - enriched_u - fuel_recipe - waste - used_fuel_recipe - - 1 - 1 - 2 - - power - 10 - 10 - - + + fuel_recipe + used_fuel_recipe + enriched_u + waste + + 1 + 0 + 2 + 1 + 1 + diff --git a/input/enrichment/linear_src_enr_rxtr_sink.xml b/input/enrichment/linear_src_enr_rxtr_sink.xml index bd09a23eab..1b9a720ce4 100644 --- a/input/enrichment/linear_src_enr_rxtr_sink.xml +++ b/input/enrichment/linear_src_enr_rxtr_sink.xml @@ -10,8 +10,8 @@ cycamoreSink cycamoreSource - cycamoreEnrichmentFacility - cycamoreBatchReactor + cycamoreReactor + cycamoreEnrichment cycamoreGrowthRegion cycamoreManagerInst @@ -20,9 +20,9 @@ Source - natl_u - 1000 - natl_u + natl_u + natl_u + 1000 @@ -30,35 +30,35 @@ Enrichment - - natl_u - natl_u - 1000 - enriched_u - 0.003 - + + natl_u + natl_u + 1000 + enriched_u + ef_tails + 0.003 + Reactor - - - enriched_u - fuel_recipe - waste - used_fuel_recipe - - 1 - 1 - 2 - - power - 10 - 10 - - + + fuel_recipe + used_fuel_recipe + enriched_u + waste + + 1 + 0 + 2 + 1 + 1 + + power + 10 + @@ -78,16 +78,20 @@ SingleRegion - power - - linear - - - 10 0 - - - 1 - + + + power + + + 1 + + linear + 10 0 + + + + + @@ -111,6 +115,7 @@ Source Enrichment + Reactor Sink diff --git a/input/enrichment/natu_capacitated.xml b/input/enrichment/natu_capacitated.xml index e363e5c392..3d94d88b20 100644 --- a/input/enrichment/natu_capacitated.xml +++ b/input/enrichment/natu_capacitated.xml @@ -10,8 +10,8 @@ cycamoreSink cycamoreSource - cycamoreEnrichmentFacility - cycamoreBatchReactor + cycamoreReactor + cycamoreEnrichment cycamoreGrowthRegion cycamoreManagerInst @@ -20,9 +20,9 @@ Source - natl_u - 1000 - natl_u + natl_u + natl_u + 1000 @@ -30,35 +30,35 @@ Enrichment - - natl_u - natl_u - 100 - enriched_u + + natl_u + natl_u + 100 + enriched_u + ef_tails 0.003 - + Reactor - - - enriched_u - fuel_recipe - waste - used_fuel_recipe - - 1 - 1 - 2 - - power - 10 - 10 - - + + fuel_recipe + used_fuel_recipe + enriched_u + waste + + 1 + 0 + 2 + 1 + 1 + + power + 10 + @@ -78,16 +78,20 @@ SingleRegion - power - - linear - - - 10 0 - - - 1 - + + + power + + + 1 + + linear + 10 0 + + + + + @@ -111,6 +115,7 @@ Source Enrichment + Reactor Sink diff --git a/input/enrichment/swu_capacitated.xml b/input/enrichment/swu_capacitated.xml index 35215a1d9f..06d961d877 100644 --- a/input/enrichment/swu_capacitated.xml +++ b/input/enrichment/swu_capacitated.xml @@ -10,8 +10,8 @@ cycamoreSink cycamoreSource - cycamoreEnrichmentFacility - cycamoreBatchReactor + cycamoreReactor + cycamoreEnrichment cycamoreGrowthRegion cycamoreManagerInst @@ -20,9 +20,9 @@ Source - natl_u - 1000 - natl_u + natl_u + natl_u + 1000 @@ -30,36 +30,36 @@ Enrichment - - natl_u - natl_u - 100 - enriched_u + + natl_u + natl_u + 100 + enriched_u + ef_tails 0.003 30.0 - + Reactor - - - enriched_u - fuel_recipe - waste - used_fuel_recipe - - 1 - 1 - 2 - - power - 10 - 10 - - + + fuel_recipe + used_fuel_recipe + enriched_u + waste + + 1 + 0 + 2 + 1 + 1 + + power + 10 + @@ -79,16 +79,20 @@ SingleRegion - power - - linear - - - 10 0 - - - 1 - + + + power + + + 1 + + linear + 10 0 + + + + + @@ -112,6 +116,7 @@ Source Enrichment + Reactor Sink diff --git a/input/growth/source_sink_linear.xml b/input/growth/source_sink_linear.xml index b9708be956..79752cc09f 100644 --- a/input/growth/source_sink_linear.xml +++ b/input/growth/source_sink_linear.xml @@ -18,9 +18,9 @@ Source1 - commodity - commod_recipe - 1.1 + commodity + commod_recipe + 1.1 @@ -29,9 +29,9 @@ Source2 - commodity - commod_recipe - 2 + commodity + commod_recipe + 2 @@ -51,16 +51,20 @@ SingleRegion - commodity - - linear - - - 1 2 - - - 0 - + + + commodity1 + + + 0 + + linear + 1 2 + + + + + diff --git a/input/minimal-input/source_1_lifetime_sink_1.xml b/input/minimal-input/source_1_lifetime_sink_1.xml index 0cbc316658..59b59ded62 100644 --- a/input/minimal-input/source_1_lifetime_sink_1.xml +++ b/input/minimal-input/source_1_lifetime_sink_1.xml @@ -31,9 +31,9 @@ 5 - commodity - commod_recipe - 1 + commodity + commod_recipe + 1 diff --git a/input/minimal-input/source_1_sink_1.xml b/input/minimal-input/source_1_sink_1.xml index 8b96e8d193..c21acbf9b4 100644 --- a/input/minimal-input/source_1_sink_1.xml +++ b/input/minimal-input/source_1_sink_1.xml @@ -30,9 +30,9 @@ SomeSource - commodity - 1 - commod_recipe + commodity + commod_recipe + 1 diff --git a/input/minimal-input/source_3_lifetime_sink_1.xml b/input/minimal-input/source_3_lifetime_sink_1.xml index 07a897342e..4f7197ad74 100644 --- a/input/minimal-input/source_3_lifetime_sink_1.xml +++ b/input/minimal-input/source_3_lifetime_sink_1.xml @@ -19,9 +19,9 @@ 5 - commodity - 1 - commod_recipe + commodity + commod_recipe + 1 diff --git a/input/minimal-input/source_3_sink_1.xml b/input/minimal-input/source_3_sink_1.xml index 78621f4a24..7efc34a570 100644 --- a/input/minimal-input/source_3_sink_1.xml +++ b/input/minimal-input/source_3_sink_1.xml @@ -18,9 +18,9 @@ Source - commodity - 1 - commod_recipe + commodity + commod_recipe + 1 diff --git a/input/physor/1_Enrichment_2_Reactor.xml b/input/physor/1_Enrichment_2_Reactor.xml index 1a41469cbf..ddb27a2b8e 100755 --- a/input/physor/1_Enrichment_2_Reactor.xml +++ b/input/physor/1_Enrichment_2_Reactor.xml @@ -8,113 +8,67 @@ - - cycamore - EnrichmentFacility - - - cycamore - BatchReactor - - - agents - NullRegion - - - agents - NullInst - + cycamore Enrichment + cycamore Reactor + agents NullRegion + agents NullInst Enrichment - - natl_u - natl_u - enriched_u + + natl_u + natl_u + enriched_u + ef_tails 0.003 - 10 - 1e5 - + 10.01 + 1e5 + Reactor1 - - - enriched_u - lwr_fuel_recipe - waste - lwr_used_fuel_recipe - - 1 - 1 - 1 - 0 - - - 1 - enriched_u - lwr_fuel_recipe - - - - enriched_u - lwr_fuel_recipe2 - - - - enriched_u - lwr_fuel_recipe - - - - lwr_power - .928 - 64 - - - enriched_u - 1.0 - - + + lwr_fuel_recipe + lwr_used_fuel_recipe + enriched_u + waste + 1.0 + + 1 + 0 + 0.1 + 10 + 10 + + 1 2 + enriched_u enriched_u + lwr_fuel_recipe2 lwr_fuel_recipe + lwr_used_fuel_recipe lwr_used_fuel_recipe + Reactor2 - - - enriched_u - lwr_fuel_recipe - waste - lwr_used_fuel_recipe - - 1 - 1 - 1 - 0 - - - 1 - enriched_u - lwr_fuel_recipe - - - - lwr_power - .928 - 64 - - - enriched_u - 0.5 - - + + lwr_fuel_recipe + lwr_used_fuel_recipe + enriched_u + waste + 0.5 + + 1 + 0 + 0.1 + 10 + 10 + diff --git a/input/physor/2_Sources_3_Reactors.xml b/input/physor/2_Sources_3_Reactors.xml index d9ee17ff8b..b7eb93131d 100755 --- a/input/physor/2_Sources_3_Reactors.xml +++ b/input/physor/2_Sources_3_Reactors.xml @@ -8,31 +8,19 @@ - - cycamore - Source - - - cycamore - BatchReactor - - - agents - NullRegion - - - cycamore - DeployInst - + cycamore Source + cycamore Reactor + agents NullRegion + cycamore DeployInst UOX_Source - uox - uox_fuel_recipe - 2.5 + uox + uox_fuel_recipe + 2.500000001 @@ -41,9 +29,9 @@ MOX_Source - mox - mox_fuel_recipe - 2.5 + mox + mox_fuel_recipe + 2.500000001 @@ -51,125 +39,63 @@ Reactor1 - - - uox - uox_fuel_recipe - waste - uox_used_fuel_recipe - - - mox - mox_fuel_recipe - waste - mox_used_fuel_recipe - - 1 - 1 - 1 - 0 - - - 1 - mox - mox_fuel_recipe - - - - lwr_power - .928 - 64 - - - mox - 1.0 - - - uox - 2.0 - - - + + + uox_fuel_recipe mox_fuel_recipe + uox_used_fuel_recipe mox_used_fuel_recipe + uox mox + waste waste + 0.0 1.0 + + 1 + 0 + 0.1 + 10 + 10 + + 4 + uox + 2.0 + + Reactor2 - - - uox - uox_fuel_recipe - waste - uox_used_fuel_recipe - - - mox - mox_fuel_recipe - waste - mox_used_fuel_recipe - - 1 - 1 - 1 - 0 - - - 1 - mox - mox_fuel_recipe - - - - lwr_power - .928 - 64 - - - mox - 1.0 - - + + uox_fuel_recipe mox_fuel_recipe + uox_used_fuel_recipe mox_used_fuel_recipe + uox mox + waste waste + 0.0 1.0 + + 1 + 0 + 0.1 + 10 + 10 + Reactor3 - - - uox - uox_fuel_recipe - waste - uox_used_fuel_recipe - - - mox - mox_fuel_recipe - waste - mox_used_fuel_recipe - - 1 - 1 - 1 - 0 - - - 1 - mox - mox_fuel_recipe - - - - lwr_power - .928 - 64 - - - mox - 0.5 - - + + uox_fuel_recipe mox_fuel_recipe + uox_used_fuel_recipe mox_used_fuel_recipe + uox mox + waste waste + 0.0 0.5 + + 1 + 0 + 0.1 + 10 + 10 + @@ -182,31 +108,29 @@ SingleInstitution - - UOX_Source - 1 - 1 - - - MOX_Source - 1 - 1 - - - Reactor1 - 1 - 1 - - - Reactor2 - 1 - 2 - - - Reactor3 - 1 - 3 - + + UOX_Source + MOX_Source + Reactor1 + Reactor2 + Reactor3 + + + + 1 + 1 + 1 + 2 + 3 + + + + 1 + 1 + 1 + 1 + 1 + diff --git a/input/recycle.xml b/input/recycle.xml new file mode 100644 index 0000000000..5917ef6d31 --- /dev/null +++ b/input/recycle.xml @@ -0,0 +1,214 @@ + + + 600 + 1 + 2000 + + + + agents NullInst + agents NullRegion + cycamore Source + cycamore Sink + cycamore Enrichment + cycamore Reactor + cycamore FuelFab + cycamore Separations + + + + enrichment + + + natl_u + natl_u + uox + 0.003 + waste + 1e100 + 1e100 + + + + + + separations + + + + + sep_stream + + 1e100 + + Pu .99 + + + + + + waste + 30001 + 30001 + spent_uox + 2.0 + + + + + + fuelfab + + + depleted_u + depleted_u + 30001 + + sep_stream + 15000 + + thermal + mox + 30001 + + + + + + reactor + + + fresh_uox fresh_mox + spent_uox spent_mox + uox mox + spent_uox waste + 1.0 2.0 + + 17 + 2 + 30000 + 3 + 1 + + + + + + repo + + + waste + 1e100 + + + + + + depleted_src + + + depleted_u + depleted_u + + + + + + SingleRegion + + + SingleInstitution + + + repo + 1 + + + reactor + 1 + + + depleted_src + 1 + + + fuelfab + 1 + + + separations + 1 + + + enrichment + 1 + + + + + + + + natl_u + mass + U235 0.711 + U238 99.289 + + + + fresh_uox + mass + U2350.04 + U2380.96 + + + + depleted_u + mass + U2350.003 + U2380.997 + + + + fresh_mox + mass + U235 0.0027381 + U238 0.9099619 + Pu238 0.001746 + Pu239 0.045396 + Pu240 0.020952 + Pu241 0.013095 + Pu242 0.005238 + + + + spent_mox + mass + U235 0.0017381 + U238 0.90 + Pu238 0.001746 + Pu239 0.0134 + Pu240 0.020952 + Pu241 0.013095 + Pu242 0.005238 + + + + spent_uox + mass + U235 156.729 + U236 102.103 + U238 18280.324 + Np237 13.656 + Pu238 5.043 + Pu239 106.343 + Pu240 41.357 + Pu241 36.477 + Pu242 15.387 + Am241 1.234 + Am243 3.607 + Cm244 0.431 + Cm245 1.263 + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c7ab466fb9..37dea6319c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,12 @@ # ------------------- Add all Concrete Agents ---------------------------- -USE_CYCLUS("cycamore" "batch_reactor") +USE_CYCLUS("cycamore" "reactor") -USE_CYCLUS("cycamore" "enrichment_facility") +USE_CYCLUS("cycamore" "fuel_fab") -#USE_CYCLUS("cycamore" "inpro_reactor") +USE_CYCLUS("cycamore" "enrichment") + +USE_CYCLUS("cycamore" "separations") USE_CYCLUS("cycamore" "sink") diff --git a/src/batch_reactor.cc b/src/batch_reactor.cc deleted file mode 100644 index ebde933459..0000000000 --- a/src/batch_reactor.cc +++ /dev/null @@ -1,1049 +0,0 @@ -// Implements the BatchReactor class -#include "batch_reactor.h" - -#include -#include - -namespace cycamore { - -// static members -std::map BatchReactor::phase_names_ = - std::map(); - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -BatchReactor::BatchReactor(cyclus::Context* ctx) - : cyclus::Facility(ctx), - process_time_(1), - preorder_time_(0), - refuel_time_(0), - start_time_(-1), - to_begin_time_(std::numeric_limits::max()), - n_batches_(1), - n_load_(1), - n_reserves_(0), - batch_size_(1), - phase_(INITIAL) { - cyclus::Warn("the BatchReactor agent " - "is considered experimental."); - if (phase_names_.empty()) { - SetUpPhaseNames_(); - } - spillover_ = cyclus::NewBlankMaterial(0); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -BatchReactor::~BatchReactor() {} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::string BatchReactor::schema() { - return - " \n" - + crctx_.schema() + - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n"; -} - -void BatchReactor::InitFrom(cyclus::QueryableBackend* b) { - cyclus::Facility::InitFrom(b); - - crctx_.InitFrom(b); - - // facility info - cyclus::QueryResult qr = b->Query("Info", NULL); - process_time_ = qr.GetVal("processtime"); - preorder_time_ = qr.GetVal("preorder_t"); - refuel_time_ = qr.GetVal("refueltime"); - start_time_ = qr.GetVal("starttime"); - to_begin_time_ = qr.GetVal("tobegintime"); - n_batches_ = qr.GetVal("nbatches"); - n_load_ = qr.GetVal("nreload"); - n_reserves_ = qr.GetVal("norder"); - batch_size_ = qr.GetVal("batchsize"); - phase_ = static_cast(qr.GetVal("phase")); - - std::string out_commod = qr.GetVal("out_commod"); - cyclus::toolkit::CommodityProducer::Add(out_commod); - cyclus::toolkit::CommodityProducer:: - SetCapacity(out_commod, qr.GetVal("out_commod_cap")); - cyclus::toolkit::CommodityProducer:: - SetCost(out_commod, qr.GetVal("out_commod_cap")); - - // initial condition inventories - std::vector conds; - conds.push_back(cyclus::Cond("inventory", "==", std::string("reserves"))); - qr = b->Query("InitialInv", &conds); - ics_.AddReserves( - qr.GetVal("nbatches"), - qr.GetVal("recipe"), - qr.GetVal("commod") - ); - conds[0] = cyclus::Cond("inventory", "==", std::string("core")); - qr = b->Query("InitialInv", &conds); - ics_.AddCore( - qr.GetVal("nbatches"), - qr.GetVal("recipe"), - qr.GetVal("commod") - ); - conds[0] = cyclus::Cond("inventory", "==", std::string("storage")); - qr = b->Query("InitialInv", &conds); - ics_.AddStorage( - qr.GetVal("nbatches"), - qr.GetVal("recipe"), - qr.GetVal("commod") - ); - - // trade preferences - try { - qr.Reset(); - qr = b->Query("CommodPrefs", NULL); - } catch(std::exception err) {} // table doesn't exist (okay) - for (int i = 0; i < qr.rows.size(); ++i) { - std::string c = qr.GetVal("incommodity", i); - commod_prefs_[c] = qr.GetVal("preference", i); - } - - // pref changes - try { - qr.Reset(); - qr = b->Query("PrefChanges", NULL); - } catch(std::exception err) {} // table doesn't exist (okay) - for (int i = 0; i < qr.rows.size(); ++i) { - std::string c = qr.GetVal("incommodity", i); - int t = qr.GetVal("time", i); - double new_pref = qr.GetVal("new_pref", i); - pref_changes_[t].push_back(std::make_pair(c, new_pref)); - } - - // pref changes - try { - qr.Reset(); - qr = b->Query("RecipeChanges", NULL); - } catch(std::exception err) {} // table doesn't exist (okay) - for (int i = 0; i < qr.rows.size(); ++i) { - std::string c = qr.GetVal("incommodity", i); - int t = qr.GetVal("time", i); - std::string new_recipe = qr.GetVal("new_recipe", i); - recipe_changes_[t].push_back(std::make_pair(c, new_recipe)); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::InfileToDb(cyclus::InfileTree* qe, cyclus::DbInit di) { - cyclus::Facility::InfileToDb(qe, di); - qe = qe->SubTree("config/*"); - - using cyclus::toolkit::Commodity; - using cyclus::toolkit::CommodityProducer; - using cyclus::OptionalQuery; - using cyclus::Query; - using cyclus::InfileTree; - using std::string; - - crctx_.InfileToDb(qe, di); - - // facility data - int processtime = Query(qe, "processtime"); - int nbatches = Query(qe, "nbatches"); - double batchsize = Query(qe, "batchsize"); - int refuel_t = OptionalQuery(qe, "refueltime", refuel_time()); - int preorder_t = OptionalQuery(qe, "orderlookahead", preorder_time()); - int nreload = OptionalQuery(qe, "nreload", n_load()); - int norder = OptionalQuery(qe, "norder", n_reserves()); - - InfileTree* commodity = qe->SubTree("commodity_production"); - std::string out_commod = commodity->GetString("commodity"); - double commod_cap = Query(commodity, "capacity"); - double commod_cost = Query(commodity, "cost"); - - di.NewDatum("Info") - ->AddVal("processtime", processtime) - ->AddVal("nbatches", nbatches) - ->AddVal("batchsize", batchsize) - ->AddVal("refueltime", refuel_t) - ->AddVal("preorder_t", preorder_t) - ->AddVal("nreload", nreload) - ->AddVal("norder", norder) - ->AddVal("starttime", -1) - ->AddVal("tobegintime", std::numeric_limits::max()) - ->AddVal("phase", static_cast(INITIAL)) - ->AddVal("out_commod", out_commod) - ->AddVal("out_commod_cap", commod_cap) - ->AddVal("out_commod_cost", commod_cost) - ->Record(); - - // initial condition inventories - std::vector inv_names; - inv_names.push_back("reserves"); - inv_names.push_back("core"); - inv_names.push_back("storage"); - for (int i = 0; i < inv_names.size(); ++i) { - int n = 0; - std::string recipe; - std::string commod; - if (qe->NMatches("initial_condition") > 0) { - InfileTree* ic = qe->SubTree("initial_condition"); - if (ic->NMatches(inv_names[i]) > 0) { - InfileTree* reserves = ic->SubTree(inv_names[i]); - n = Query(reserves, "nbatches"); - recipe = reserves->GetString("recipe"); - commod = reserves->GetString("commodity"); - } - } - di.NewDatum("InitialInv") - ->AddVal("inventory", inv_names[i]) - ->AddVal("nbatches", n) - ->AddVal("recipe", recipe) - ->AddVal("commod", commod) - ->Record(); - } - - // trade preferences - int nprefs = qe->NMatches("commod_pref"); - std::string c; - for (int i = 0; i < nprefs; i++) { - InfileTree* cp = qe->SubTree("commod_pref", i); - di.NewDatum("CommodPrefs") - ->AddVal("incommodity", cp->GetString("incommodity")) - ->AddVal("preference", Query(cp, "preference")) - ->Record(); - } - - // pref changes - int nchanges = qe->NMatches("pref_change"); - for (int i = 0; i < nchanges; i++) { - InfileTree* cp = qe->SubTree("pref_change", i); - di.NewDatum("PrefChanges") - ->AddVal("incommodity", cp->GetString("incommodity")) - ->AddVal("new_pref", Query(cp, "new_pref")) - ->AddVal("time", Query(cp, "time")) - ->Record(); - } - - // recipe changes - nchanges = qe->NMatches("recipe_change"); - for (int i = 0; i < nchanges; i++) { - InfileTree* cp = qe->SubTree("recipe_change", i); - di.NewDatum("RecipeChanges") - ->AddVal("incommodity", cp->GetString("incommodity")) - ->AddVal("new_recipe", cp->GetString("new_recipe")) - ->AddVal("time", Query(cp, "time")) - ->Record(); - } -} - -void BatchReactor::Snapshot(cyclus::DbInit di) { - cyclus::Facility::Snapshot(di); - crctx_.Snapshot(di); - - std::set:: - iterator it; - it = cyclus::toolkit::CommodityProducer::ProducedCommodities().begin(); - std::string out_commod = it->name(); - double cost = cyclus::toolkit::CommodityProducer::Cost(out_commod); - double cap = cyclus::toolkit::CommodityProducer::Capacity(out_commod); - di.NewDatum("Info") - ->AddVal("processtime", process_time_) - ->AddVal("nbatches", n_batches_) - ->AddVal("batchsize", batch_size_) - ->AddVal("refueltime", refuel_time_) - ->AddVal("preorder_t", preorder_time_) - ->AddVal("nreload", n_load_) - ->AddVal("norder", n_reserves_) - ->AddVal("starttime", start_time_) - ->AddVal("tobegintime", to_begin_time_) - ->AddVal("phase", static_cast(phase_)) - ->AddVal("out_commod", out_commod) - ->AddVal("out_commod_cap", cap) - ->AddVal("out_commod_cost", cost) - ->Record(); - - // initial condition inventories - di.NewDatum("InitialInv") - ->AddVal("inventory", std::string("reserves")) - ->AddVal("nbatches", ics_.n_reserves) - ->AddVal("recipe", ics_.reserves_rec) - ->AddVal("commod", ics_.reserves_commod) - ->Record(); - di.NewDatum("InitialInv") - ->AddVal("inventory", std::string("core")) - ->AddVal("nbatches", ics_.n_core) - ->AddVal("recipe", ics_.core_rec) - ->AddVal("commod", ics_.core_commod) - ->Record(); - di.NewDatum("InitialInv") - ->AddVal("inventory", std::string("storage")) - ->AddVal("nbatches", ics_.n_storage) - ->AddVal("recipe", ics_.storage_rec) - ->AddVal("commod", ics_.storage_commod) - ->Record(); - - // trade preferences - std::map::iterator it2 = commod_prefs_.begin(); - for (; it2 != commod_prefs_.end(); ++it2) { - di.NewDatum("CommodPrefs") - ->AddVal("incommodity", it2->first) - ->AddVal("preference", it2->second) - ->Record(); - } - - // pref changes - std::map > >::iterator it3; - for (it3 = pref_changes_.begin(); it3 != pref_changes_.end(); ++it3) { - int t = it3->first; - for (int i = 0; i < it3->second.size(); ++i) { - std::string commod = it3->second[i].first; - double pref = it3->second[i].second; - di.NewDatum("PrefChanges") - ->AddVal("incommodity", commod) - ->AddVal("new_pref", pref) - ->AddVal("time", t) - ->Record(); - } - } - - // recipe changes - std::map > >:: - iterator it4; - for (it4 = recipe_changes_.begin(); it4 != recipe_changes_.end(); ++it4) { - int t = it4->first; - for (int i = 0; i < it4->second.size(); ++i) { - std::string commod = it4->second[i].first; - std::string recipe = it4->second[i].second; - di.NewDatum("RecipeChanges") - ->AddVal("incommodity", commod) - ->AddVal("new_recipe", recipe) - ->AddVal("time", t) - ->Record(); - } - } -} - -void BatchReactor::InitInv(cyclus::Inventories& invs) { - reserves_.PushAll(invs["reserves"]); - core_.PushAll(invs["core"]); - spillover_ = cyclus::ResCast(invs["spillover"][0]); - - cyclus::Inventories::iterator it; - for (it = invs.begin(); it != invs.end(); ++it) { - std::string name = it->first; - if (name.find("storage-") == 0) { - storage_[name].PushAll(it->second); - } - } -} - -cyclus::Inventories BatchReactor::SnapshotInv() { - cyclus::Inventories invs; - invs["reserves"] = reserves_.PopN(reserves_.count()); - reserves_.PushAll(invs["reserves"]); - invs["core"] = core_.PopN(core_.count()); - core_.PushAll(invs["core"]); - std::vector v; - v.push_back(spillover_); - invs["spillover"] = v; - std::map::iterator it; - for (it = storage_.begin(); it != storage_.end(); ++it) { - std::string name = it->first; - invs["storage-" + name] = it->second.PopN(it->second.count()); - it->second.PushAll(invs["storage-" + name]); - } - return invs; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Agent* BatchReactor::Clone() { - BatchReactor* m = new BatchReactor(context()); - m->InitFrom(this); - return m; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::InitFrom(BatchReactor* m) { - Facility::InitFrom(m); - - // in/out - crctx_ = m->crctx_; - - // facility params - process_time(m->process_time()); - preorder_time(m->preorder_time()); - refuel_time(m->refuel_time()); - n_batches(m->n_batches()); - n_load(m->n_load()); - n_reserves(m->n_reserves()); - batch_size(m->batch_size()); - - // commodity production - cyclus::toolkit::CommodityProducer::Copy(m); - - // ics - ics(m->ics()); - - // trade preferences - commod_prefs(m->commod_prefs()); - pref_changes_ = m->pref_changes_; - - // recipe changes - recipe_changes_ = m->recipe_changes_; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::string BatchReactor::str() { - std::stringstream ss; - ss << cyclus::Facility::str(); - ss << " has facility parameters {" << "\n" - << " Process Time = " << process_time() << ",\n" - << " Refuel Time = " << refuel_time() << ",\n" - << " Preorder Time = " << preorder_time() << ",\n" - << " Core Loading = " << n_batches() * batch_size() << ",\n" - << " Batches Per Core = " << n_batches() << ",\n" - << " Batches Per Load = " << n_load() << ",\n" - << " Batches To Reserve = " << n_reserves() << ",\n" - << "'}"; - return ss.str(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::Build(cyclus::Agent* parent) { - using cyclus::Material; - - Facility::Build(parent); - phase(INITIAL); - std::string rec = crctx_.in_recipe(*crctx_.in_commods().begin()); - spillover_ = Material::Create(this, 0.0, context()->GetRecipe(rec)); - - Material::Ptr mat; - for (int i = 0; i < ics_.n_reserves; ++i) { - mat = Material::Create(this, - batch_size(), - context()->GetRecipe(ics_.reserves_rec)); - assert(ics_.reserves_commod != ""); - crctx_.AddRsrc(ics_.reserves_commod, mat); - reserves_.Push(mat); - } - for (int i = 0; i < ics_.n_core; ++i) { - mat = Material::Create(this, - batch_size(), - context()->GetRecipe(ics_.core_rec)); - assert(ics_.core_commod != ""); - crctx_.AddRsrc(ics_.core_commod, mat); - core_.Push(mat); - } - for (int i = 0; i < ics_.n_storage; ++i) { - mat = Material::Create(this, - batch_size(), - context()->GetRecipe(ics_.storage_rec)); - assert(ics_.storage_commod != ""); - crctx_.AddRsrc(ics_.storage_commod, mat); - storage_[ics_.storage_commod].Push(mat); - } - - LOG(cyclus::LEV_DEBUG2, "BReact") << "Batch Reactor entering the simuluation"; - LOG(cyclus::LEV_DEBUG2, "BReact") << str(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::Tick() { - int time = context()->time(); - LOG(cyclus::LEV_INFO3, "BReact") << prototype() << " is ticking at time " - << time << " {"; - - LOG(cyclus::LEV_DEBUG4, "BReact") << "Current facility parameters for " - << prototype() - << " at the beginning of the tick are:"; - LOG(cyclus::LEV_DEBUG4, "BReact") << " Phase: " << phase_names_[phase_]; - LOG(cyclus::LEV_DEBUG4, "BReact") << " Start time: " << start_time_; - LOG(cyclus::LEV_DEBUG4, "BReact") << " End time: " << end_time(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " Order time: " << order_time(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " NReserves: " << reserves_.count(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " NCore: " << core_.count(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " NStorage: " << StorageCount(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " Spillover Qty: " - << spillover_->quantity(); - - if (lifetime() != -1 && context()->time() >= enter_time() + lifetime()) { - int ncore = core_.count(); - LOG(cyclus::LEV_DEBUG1, "BReact") << "lifetime reached, moving out:" - << ncore << " batches."; - for (int i = 0; i < ncore; ++i) { - MoveBatchOut_(); // unload - } - } else { - switch (phase()) { - case WAITING: - if (n_core() == n_batches() && - to_begin_time() <= context()->time()) { - phase(PROCESS); - } - break; - - case INITIAL: - // special case for a core primed to go - if (n_core() == n_batches()) { - phase(PROCESS); - } - break; - } - } - - // change preferences if its time - if (pref_changes_.count(time)) { - std::vector< std::pair< std::string, double> >& - changes = pref_changes_[time]; - for (int i = 0; i < changes.size(); i++) { - commod_prefs_[changes[i].first] = changes[i].second; - } - } - - // change recipes if its time - if (recipe_changes_.count(time)) { - std::vector< std::pair< std::string, std::string> >& - changes = recipe_changes_[time]; - for (int i = 0; i < changes.size(); i++) { - assert(changes[i].first != ""); - assert(changes[i].second != ""); - crctx_.UpdateInRec(changes[i].first, changes[i].second); - } - } - - LOG(cyclus::LEV_DEBUG3, "BReact") << "Current facility parameters for " - << prototype() - << " at the end of the tick are:"; - LOG(cyclus::LEV_DEBUG3, "BReact") << " Phase: " << phase_names_[phase_]; - LOG(cyclus::LEV_DEBUG3, "BReact") << " Start time: " << start_time_; - LOG(cyclus::LEV_DEBUG3, "BReact") << " End time: " << end_time(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " Order time: " << order_time(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " NReserves: " << reserves_.count(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " NCore: " << core_.count(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " NStorage: " << StorageCount(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " Spillover Qty: " - << spillover_->quantity(); - LOG(cyclus::LEV_INFO3, "BReact") << "}"; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::Tock() { - int time = context()->time(); - LOG(cyclus::LEV_INFO3, "BReact") << prototype() << " is tocking {"; - LOG(cyclus::LEV_DEBUG4, "BReact") << "Current facility parameters for " - << prototype() - << " at the beginning of the tock are:"; - LOG(cyclus::LEV_DEBUG4, "BReact") << " Phase: " << phase_names_[phase_]; - LOG(cyclus::LEV_DEBUG4, "BReact") << " Start time: " << start_time_; - LOG(cyclus::LEV_DEBUG4, "BReact") << " End time: " << end_time(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " Order time: " << order_time(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " NReserves: " << reserves_.count(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " NCore: " << core_.count(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " NStorage: " << StorageCount(); - LOG(cyclus::LEV_DEBUG4, "BReact") << " Spillover Qty: " - << spillover_->quantity(); - - switch (phase()) { - case PROCESS: - if (time == end_time()) { - for (int i = 0; i < std::min(n_load(), core_.count()); ++i) { - MoveBatchOut_(); // unload - } - Refuel_(); // reload - phase(WAITING); - } - break; - default: - Refuel_(); // always try to reload if possible - break; - } - - LOG(cyclus::LEV_DEBUG3, "BReact") << "Current facility parameters for " - << prototype() - << " at the end of the tock are:"; - LOG(cyclus::LEV_DEBUG3, "BReact") << " Phase: " << phase_names_[phase_]; - LOG(cyclus::LEV_DEBUG3, "BReact") << " Start time: " << start_time_; - LOG(cyclus::LEV_DEBUG3, "BReact") << " End time: " << end_time(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " Order time: " << order_time(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " NReserves: " << reserves_.count(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " NCore: " << core_.count(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " NStorage: " << StorageCount(); - LOG(cyclus::LEV_DEBUG3, "BReact") << " Spillover Qty: " - << spillover_->quantity(); - LOG(cyclus::LEV_INFO3, "BReact") << "}"; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::set::Ptr> -BatchReactor::GetMatlRequests() { - using cyclus::RequestPortfolio; - using cyclus::Material; - - std::set::Ptr> set; - double order_size; - - switch (phase()) { - // the initial phase requests as much fuel as necessary to achieve an entire - // core - case INITIAL: - order_size = n_batches() * batch_size() - - core_.quantity() - reserves_.quantity() - - spillover_->quantity(); - if (preorder_time() == 0) { - order_size += batch_size() * n_reserves(); - } - if (order_size > 0) { - RequestPortfolio::Ptr p = GetOrder_(order_size); - set.insert(p); - } - break; - - // the default case is to request the reserve amount if the order time has - // been reached - default: - // double fuel_need = (n_reserves() + n_batches() - n_core()) * batch_size(); - double fuel_need = (n_reserves() + n_load()) * batch_size(); - double fuel_have = reserves_.quantity() + spillover_->quantity(); - order_size = fuel_need - fuel_have; - bool ordering = order_time() <= context()->time() && order_size > 0; - - LOG(cyclus::LEV_DEBUG5, "BReact") << "BatchReactor " << prototype() - << " is deciding whether to order -"; - LOG(cyclus::LEV_DEBUG5, "BReact") << " Needs fuel amt: " << fuel_need; - LOG(cyclus::LEV_DEBUG5, "BReact") << " Has fuel amt: " << fuel_have; - LOG(cyclus::LEV_DEBUG5, "BReact") << " Order amt: " << order_size; - LOG(cyclus::LEV_DEBUG5, "BReact") << " Order time: " << order_time(); - LOG(cyclus::LEV_DEBUG5, "BReact") << " Current time: " - << context()->time(); - LOG(cyclus::LEV_DEBUG5, "BReact") << " Ordering?: " - << ((ordering == true) ? "yes" : "no"); - - if (ordering) { - RequestPortfolio::Ptr p = GetOrder_(order_size); - set.insert(p); - } - break; - } - - return set; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::AcceptMatlTrades( - const std::vector< std::pair, - cyclus::Material::Ptr> >& responses) { - using cyclus::Material; - - std::map mat_commods; - - std::vector< std::pair, - cyclus::Material::Ptr> >::const_iterator trade; - - // blob each material by commodity - std::string commod; - Material::Ptr mat; - for (trade = responses.begin(); trade != responses.end(); ++trade) { - commod = trade->first.request->commodity(); - mat = trade->second; - if (mat_commods.count(commod) == 0) { - mat_commods[commod] = mat; - } else { - mat_commods[commod]->Absorb(mat); - } - } - - // add each blob to reserves - std::map::iterator it; - for (it = mat_commods.begin(); it != mat_commods.end(); ++it) { - AddBatches_(it->first, it->second); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::set::Ptr> -BatchReactor::GetMatlBids(cyclus::CommodMap::type& - commod_requests) { - using cyclus::BidPortfolio; - using cyclus::Material; - - std::set::Ptr> ports; - - const std::set& commods = crctx_.out_commods(); - std::set::const_iterator it; - for (it = commods.begin(); it != commods.end(); ++it) { - BidPortfolio::Ptr port = GetBids_(commod_requests, - *it, - &storage_[*it]); - if (!port->bids().empty()) { - ports.insert(port); - } - } - - return ports; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::GetMatlTrades( - const std::vector< cyclus::Trade >& trades, - std::vector, - cyclus::Material::Ptr> >& responses) { - using cyclus::Material; - using cyclus::Trade; - - std::vector< cyclus::Trade >::const_iterator it; - for (it = trades.begin(); it != trades.end(); ++it) { - LOG(cyclus::LEV_INFO5, "BReact") << prototype() - << " just received an order."; - - std::string commodity = it->request->commodity(); - double qty = it->amt; - Material::Ptr response = TradeResponse_(qty, &storage_[commodity]); - - responses.push_back(std::make_pair(*it, response)); - LOG(cyclus::LEV_INFO5, "BatchReactor") << prototype() - << " just received an order" - << " for " << qty - << " of " << commodity; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -int BatchReactor::StorageCount() { - int count = 0; - std::map::const_iterator it; - for (it = storage_.begin(); it != storage_.end(); ++it) { - count += it->second.count(); - } - return count; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::phase(BatchReactor::Phase p) { - LOG(cyclus::LEV_DEBUG2, "BReact") << "BatchReactor " << prototype() - << " is changing phases -"; - LOG(cyclus::LEV_DEBUG2, "BReact") << " * from phase: " << phase_names_[phase_]; - LOG(cyclus::LEV_DEBUG2, "BReact") << " * to phase: " << phase_names_[p]; - - switch (p) { - case PROCESS: - start_time(context()->time()); - } - phase_ = p; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::Refuel_() { - while (n_core() < n_batches() && reserves_.count() > 0) { - MoveBatchIn_(); - if (n_core() == n_batches()) { - to_begin_time_ = start_time_ + process_time_ + refuel_time_; - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::MoveBatchIn_() { - LOG(cyclus::LEV_DEBUG2, "BReact") << "BatchReactor " << prototype() - << " added a batch to its core."; - try { - core_.Push(reserves_.Pop()); - } catch (cyclus::Error& e) { - e.msg(Agent::InformErrorMsg(e.msg())); - throw e; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::MoveBatchOut_() { - using cyclus::Material; - using cyclus::ResCast; - - LOG(cyclus::LEV_DEBUG2, "BReact") << "BatchReactor " << prototype() - << " removed a batch from its core."; - try { - Material::Ptr mat = ResCast(core_.Pop()); - std::string incommod = crctx_.commod(mat); - assert(incommod != ""); - std::string outcommod = crctx_.out_commod(incommod); - assert(outcommod != ""); - std::string outrecipe = crctx_.out_recipe(incommod); - assert(outrecipe != ""); - mat->Transmute(context()->GetRecipe(outrecipe)); - crctx_.UpdateRsrc(outcommod, mat); - storage_[outcommod].Push(mat); - } catch (cyclus::Error& e) { - e.msg(Agent::InformErrorMsg(e.msg())); - throw e; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::RequestPortfolio::Ptr -BatchReactor::GetOrder_(double size) { - using cyclus::CapacityConstraint; - using cyclus::Material; - using cyclus::RequestPortfolio; - using cyclus::Request; - - RequestPortfolio::Ptr port(new RequestPortfolio()); - - const std::set& commods = crctx_.in_commods(); - std::set::const_iterator it; - std::string recipe; - Material::Ptr mat; - - std::vector*> mreqs; - for (it = commods.begin(); it != commods.end(); ++it) { - recipe = crctx_.in_recipe(*it); - assert(recipe != ""); - mat = Material::CreateUntracked(size, context()->GetRecipe(recipe)); - Request* r = port->AddRequest(mat, this, *it, commod_prefs_[*it]); - mreqs.push_back(r); - - LOG(cyclus::LEV_DEBUG3, "BReact") << "BatchReactor " << prototype() - << " is making an order:"; - LOG(cyclus::LEV_DEBUG3, "BReact") << " size: " << size; - LOG(cyclus::LEV_DEBUG3, "BReact") << " commodity: " << *it; - LOG(cyclus::LEV_DEBUG3, "BReact") << " preference: " - << commod_prefs_[*it]; - } - port->AddMutualReqs(mreqs); - - return port; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::AddBatches_(std::string commod, cyclus::Material::Ptr mat) { - using cyclus::Material; - using cyclus::ResCast; - - LOG(cyclus::LEV_DEBUG3, "BReact") << "BatchReactor " << prototype() - << " is adding " << mat->quantity() - << " of material to its reserves."; - - // this is a hack! Whatever *was* left in spillover now magically becomes this - // new commodity. We need to do something different (maybe) for recycle. - spillover_->Absorb(mat); - - while (!cyclus::IsNegative(spillover_->quantity() - batch_size())) { - Material::Ptr batch; - // this is a hack to deal with close-to-equal issues between batch size and - // the amount of fuel in spillover - if (spillover_->quantity() >= batch_size()) { - batch = spillover_->ExtractQty(batch_size()); - } else { - batch = spillover_->ExtractQty(spillover_->quantity()); - } - assert(commod != ""); - crctx_.AddRsrc(commod, batch); - reserves_.Push(batch); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::BidPortfolio::Ptr BatchReactor::GetBids_( - cyclus::CommodMap::type& commod_requests, - std::string commod, - cyclus::toolkit::ResourceBuff* buffer) { - using cyclus::Bid; - using cyclus::BidPortfolio; - using cyclus::CapacityConstraint; - using cyclus::Composition; - using cyclus::Converter; - using cyclus::Material; - using cyclus::Request; - using cyclus::ResCast; - using cyclus::toolkit::ResourceBuff; - - BidPortfolio::Ptr port(new BidPortfolio()); - - if (commod_requests.count(commod) > 0 && buffer->quantity() > 0) { - std::vector*>& requests = commod_requests[commod]; - - // get offer composition - Material::Ptr back = ResCast(buffer->Pop(ResourceBuff::BACK)); - Composition::Ptr comp = back->comp(); - buffer->Push(back); - - std::vector*>::iterator it; - for (it = requests.begin(); it != requests.end(); ++it) { - Request* req = *it; - double qty = std::min(req->target()->quantity(), buffer->quantity()); - Material::Ptr offer = Material::CreateUntracked(qty, comp); - port->AddBid(req, offer, this); - } - - CapacityConstraint cc(buffer->quantity()); - port->AddConstraint(cc); - } - - return port; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr BatchReactor::TradeResponse_( - double qty, - cyclus::toolkit::ResourceBuff* buffer) { - using cyclus::Material; - using cyclus::ResCast; - - std::vector manifest; - try { - // pop amount from inventory and blob it into one material - manifest = ResCast(buffer->PopQty(qty)); - } catch (cyclus::Error& e) { - e.msg(Agent::InformErrorMsg(e.msg())); - throw e; - } - - Material::Ptr response = manifest[0]; - crctx_.RemoveRsrc(response); - for (int i = 1; i < manifest.size(); i++) { - crctx_.RemoveRsrc(manifest[i]); - response->Absorb(manifest[i]); - } - return response; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactor::SetUpPhaseNames_() { - phase_names_.insert(std::make_pair(INITIAL, "initialization")); - phase_names_.insert(std::make_pair(PROCESS, "processing batch(es)")); - phase_names_.insert(std::make_pair(WAITING, "waiting for fuel")); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -extern "C" cyclus::Agent* ConstructBatchReactor(cyclus::Context* ctx) { - return new BatchReactor(ctx); -} - -} // namespace cycamore diff --git a/src/batch_reactor.h b/src/batch_reactor.h deleted file mode 100644 index e5d537219e..0000000000 --- a/src/batch_reactor.h +++ /dev/null @@ -1,445 +0,0 @@ -#ifndef CYCAMORE_SRC_BATCH_REACTOR_H_ -#define CYCAMORE_SRC_BATCH_REACTOR_H_ - -#include -#include -#include - -#include "cyclus.h" - -// forward declarations -namespace cycamore { -class BatchReactor; -} // namespace cycamore -namespace cyclus { -class Context; -} // namespace cyclus - -namespace cycamore { - -/// @class BatchReactor -/// -/// @section introduction Introduction -/// The BatchReactor is a facility that agents batch processing. It has three -/// storage areas which hold batches of materials: reserves, core, and -/// storage. Incoming material orders are placed into reserves, from which the -/// core is provided batches during refueling. When a process has been -/// completed, batches are moved from the core into storage. Requests for -/// material are bid upon based on the state of the material in storage. -/// -/// The Reactor can manage multiple input-output commodity pairs, and keeps -/// track of the pair that each batch belongs to. Batches move through the -/// system independently of their input/output commodity types, but when batches -/// reach the storage area, they are offered as bids dependent on their output -/// commodity type. -/// -/// @section params Parameters -/// A BatchReactor has the following tunable parameters: -/// #. batch_size : the size of batches -/// #. n_batches : the number of batches that constitute a full core -/// #. process_time : the number of timesteps a batch process takes -/// #. n_load : the number of batches processed at any given time (i.e., -/// n_load is unloaded and reloaded after a process is finished -/// #. n_reserves : the preferred number of batches in reserve -/// #. preorder_time : the amount of time before a process is finished to -/// order fuel -/// #. refuel_time : the number of timesteps required to reload the core after -/// a process has finished -/// -/// The BatchReactor also maintains a cyclus::CommodityRecipeContext, which -/// allows it to track incommodity-inrecipe/outcommodity-outrecipe groupings. -/// -/// @section operation Operation -/// After a BatchReactor enters the simulation, it will begin processing its -/// first batch load on the Tick after its core has been filled. -/// -/// It will maintain its "processing" state for process_time() time steps, -/// including the timestep on which it began. It will unload n_load() batches -/// from its core on the Tock of that time step. For example, if a reactor -/// begins its process at time 1 and has a process_time equal to 10, it will -/// unload batches on the Tock of time step 10. -/// -/// Starting at the next time step, the reactor will attempt to refuel itself -/// from whatever batches exist in its reserves container (i.e, already-ordered -/// fuel). Assuming its core buffer has been refueled, it will wait reload_time -/// timesteps. On the tick of the following timestep, the process will begin -/// again. Using the previous example, assume that the refuel_time is equal to -/// two and that the core buffer has been refueled appropriately. The refueling -/// "phase" will begin on time step 11, and will end on the Tock of time step -/// 12. The process will begin again on time step 13 (analogous to its state -/// originally at time step 1). -/// -/// @section end End of Life -/// If the current time step is equivalent to the facility's lifetime, the -/// reactor will move all material in its core to its storage containers. -/// -/// @section requests Requests -/// A BatchReactor will make as many requests as it has possible input -/// commodities. It provides a constraint based on a total request amount -/// determined by its batch_size, n_load, and n_reserves parameters. The -/// n_reserves parameter allows agenters to order fuel in advance of when it is -/// needed. The fuel order size is batch_size * (n_load + n_reserves). These -/// requests are made if the current simulation time is less than or equal to -/// the reactor's current order_time(), which is determined by the ending time -/// of the current process less a look ahead time, the preorder_time(). -/// -/// A special case exists when the reactor first enters the simulation, where it -/// will order as much fuel as is needed to fill its full core. -/// -/// @section bids Bids -/// A BatchReactor will bid on any request for any of its out_commodities, as -/// long as there is a positive quantity of material in its storage area -/// associated with that output commodity. -/// -/// @section ics Initial Conditions -/// A BatchReactor can be deployed with any number of batches in its reserve, -/// core, and storage buffers. Recipes and commodities for each of these batch -/// groupings must be specified. -/// -/// @todo add decommissioning behavior if material is still in storage -/// -/// @warning The BatchReactor is considered experimental. -/// @warning preference time changing is based on *full simulation time*, not -/// relative time -/// @warning the reactor's commodity context *can not* currently remove -/// resources reliably because of toolkit::ResourceBuff::PopQty()'s implementation. -/// Resource removal from the context requires pointer equality -/// in order to remove material, and PopQty will split resources, making new -/// pointers. -/// @warning the reactor uses a hackish way to input materials into its -/// reserves. See the AddBatches_ member function. -class BatchReactor - : public cyclus::Facility, - public cyclus::toolkit::CommodityProducer { - public: - /// @brief defines all possible phases this facility can be in - enum Phase { - INITIAL, ///< The initial phase, after the facility is built but before it is - /// filled - PROCESS, ///< The processing phase - WAITING, ///< The waiting phase, while the facility is waiting for fuel - /// between processes - }; - - /// @brief a struct for initial conditions - struct InitCond { - InitCond() : n_reserves(0), n_core(0), n_storage(0) {} - - void AddReserves(int n, std::string rec, std::string commod) { - n_reserves = n; - reserves_rec = rec; - reserves_commod = commod; - } - - void AddCore(int n, std::string rec, std::string commod) { - n_core = n; - core_rec = rec; - core_commod = commod; - } - - void AddStorage(int n, std::string rec, std::string commod) { - n_storage = n; - storage_rec = rec; - storage_commod = commod; - } - - int n_reserves; - std::string reserves_rec; - std::string reserves_commod; - - int n_core; - std::string core_rec; - std::string core_commod; - - int n_storage; - std::string storage_rec; - std::string storage_commod; - }; - - // --- Module Members --- - /// @param ctx the cyclus context for access to simulation-wide parameters - BatchReactor(cyclus::Context* ctx); - - virtual ~BatchReactor(); - - #pragma cyclus def annotations - - #pragma cyclus note {"doc": "A reactor facility that has three storage " \ - "areas that hold batches of materials: " \ - "reserves, core, and storage. It can manage " \ - "multiple input-output commodity pairs."} - - virtual cyclus::Agent* Clone(); - - virtual void InfileToDb(cyclus::InfileTree* qe, cyclus::DbInit di); - - virtual void InitFrom(cyclus::QueryableBackend* b); - - virtual void Snapshot(cyclus::DbInit di); - - virtual void InitInv(cyclus::Inventories& invs); - - virtual cyclus::Inventories SnapshotInv(); - - virtual std::string schema(); - - /// initialize members from a different agent - void InitFrom(BatchReactor* m); - - /// Print information about this agent - virtual std::string str(); - // --- - - // --- Facility Members --- - /// perform module-specific tasks when entering the simulation - virtual void Build(cyclus::Agent* parent); - // --- - - // --- Agent Members --- - /// The Tick function specific to the BatchReactor. - /// @param time the time of the tick - virtual void Tick(); - - /// The Tick function specific to the BatchReactor. - /// @param time the time of the tock - virtual void Tock(); - - /// @brief The BatchReactor requests Materials of its given - /// commodity. - virtual std::set::Ptr> - GetMatlRequests(); - - /// @brief The BatchReactor places accepted trade Materials in their - /// Inventory - virtual void AcceptMatlTrades( - const std::vector< std::pair, - cyclus::Material::Ptr> >& responses); - - /// @brief Responds to each request for this facility's commodity. If a given - /// request is more than this facility's inventory or SWU capacity, it will - /// offer its minimum of its capacities. - virtual std::set::Ptr> - GetMatlBids(cyclus::CommodMap::type& - commod_requests); - - /// @brief respond to each trade with a material enriched to the appropriate - /// level given this facility's inventory - /// - /// @param trades all trades in which this trader is the supplier - /// @param responses a container to populate with responses to each trade - virtual void GetMatlTrades( - const std::vector< cyclus::Trade >& trades, - std::vector, - cyclus::Material::Ptr> >& responses); - // --- - - // --- BatchReactor Members --- - /// @return the total number of batches in storage - int StorageCount(); - - /// @brief the processing time required for a full batch process before - /// refueling - inline void process_time(int t) { - process_time_ = t; - } - inline int process_time() const { - return process_time_; - } - - /// @brief the time it takes to refuel - inline void refuel_time(int t) { - refuel_time_ = t; - } - inline int refuel_time() const { - return refuel_time_; - } - - /// @brief the amount of time an order should be placed for new fuel before a - /// process is finished - inline void preorder_time(int t) { - preorder_time_ = t; - } - inline int preorder_time() const { - return preorder_time_; - } - - /// @brief the starting time of the last (current) process - inline void start_time(int t) { - start_time_ = t; - } - inline int start_time() const { - return start_time_; - } - - /// @brief the ending time of the last (current) process - /// @warning the - 1 is to ensure that a 1 period process time that begins on - /// the tick ends on the tock - inline int end_time() const { - return start_time() + process_time() - 1; - } - - /// @brief the beginning time for the next phase, set internally - inline int to_begin_time() const { - return to_begin_time_; - } - - /// @brief the time orders should be taking place for the next refueling - inline int order_time() const { - return end_time() - preorder_time(); - } - - /// @brief the number of batches in a full reactor - inline void n_batches(int n) { - n_batches_ = n; - } - inline int n_batches() const { - return n_batches_; - } - - /// @brief the number of batches in reactor refuel loading/unloading - inline void n_load(int n) { - n_load_ = n; - } - inline int n_load() const { - return n_load_; - } - - /// @brief the preferred number of fresh fuel batches to keep in reserve - inline void n_reserves(int n) { - n_reserves_ = n; - } - inline int n_reserves() const { - return n_reserves_; - } - - /// @brief the number of batches currently in the reactor - inline int n_core() const { - return core_.count(); - } - - /// @brief the size of a batch - inline void batch_size(double size) { - batch_size_ = size; - } - inline double batch_size() { - return batch_size_; - } - - /// @brief this facility's commodity-recipe context - inline void crctx(const cyclus::toolkit::CommodityRecipeContext& crctx) { - crctx_ = crctx; - } - inline cyclus::toolkit::CommodityRecipeContext crctx() const { - return crctx_; - } - - /// @brief this facility's initial conditions - inline void ics(const InitCond& ics) { - ics_ = ics; - } - inline InitCond ics() const { - return ics_; - } - - /// @brief the current phase - void phase(Phase p); - inline Phase phase() const { - return phase_; - } - - /// @brief this facility's preference for input commodities - inline void commod_prefs(const std::map& prefs) { - commod_prefs_ = prefs; - } - inline const std::map& commod_prefs() const { - return commod_prefs_; - } - - protected: - /// @brief moves a batch from core_ to storage_ - virtual void MoveBatchOut_(); - - /// @brief gets bids for a commodity from a buffer - cyclus::BidPortfolio::Ptr GetBids_( - cyclus::CommodMap::type& commod_requests, - std::string commod, - cyclus::toolkit::ResourceBuff* buffer); - - /// @brief returns a qty of material from a buffer - cyclus::Material::Ptr TradeResponse_(double qty, - cyclus::toolkit::ResourceBuff* buffer); - - /// @brief a cyclus::toolkit::ResourceBuff for material while they are inside the core, - /// with all materials guaranteed to be of batch_size_ - cyclus::toolkit::ResourceBuff core_; - - /// @brief a cyclus::toolkit::ResourceBuff for material once they leave the core. - /// there is one storage for each outcommodity - /// @warning no guarantee can be made to the size of each item in storage_, as - /// requests can be met that are larger or smaller than batch_size_ - std::map storage_; - - private: - /// @brief refuels the reactor until it is full or reserves_ is out of - /// batches. If the core is full after refueling, the Phase is set to PROCESS. - void Refuel_(); - - /// @brief moves a batch from reserves_ to core_ - void MoveBatchIn_(); - - /// @brief construct a request portfolio for an order of a given size - cyclus::RequestPortfolio::Ptr GetOrder_(double size); - - /// @brief Add a blob of incoming material to reserves_ - /// - /// The last material to join reserves_ is first investigated to see if it is - /// of batch_size_. If not, material from mat is added to it and it is - /// returned to reserves_. If more material remains, chunks of batch_size_ are - /// removed and added to reserves_. The final chunk may be <= batch_size_. - void AddBatches_(std::string commod, cyclus::Material::Ptr mat); - - /// @brief adds phase names to phase_names_ map - void SetUpPhaseNames_(); - - static std::map phase_names_; - int process_time_; - int preorder_time_; - int refuel_time_; - int start_time_; - int to_begin_time_; - int n_batches_; - int n_load_; - int n_reserves_; - double batch_size_; - Phase phase_; - - InitCond ics_; - - cyclus::toolkit::CommodityRecipeContext crctx_; - - /// @warning as is, the int key is **simulation time**, i.e., context()->time - /// == key. This should be fixed for future use! - std::map > > - recipe_changes_; - - /// @brief preferences for each input commodity - std::map commod_prefs_; - - /// @warning as is, the int key is **simulation time**, i.e., context()->time - /// == key. This should be fixed for future use! - std::map > > pref_changes_; - - /// @brief allows only batches to enter reserves_ - cyclus::Material::Ptr spillover_; - - /// @brief a cyclus::toolkit::ResourceBuff for material before they enter the core, - /// with all materials guaranteed to be of batch_size_ - cyclus::toolkit::ResourceBuff reserves_; - - friend class BatchReactorTest; - // --- -}; - -} // namespace cycamore - -#endif // CYCAMORE_SRC_BATCH_REACTOR_H_ diff --git a/src/batch_reactor_tests.cc b/src/batch_reactor_tests.cc deleted file mode 100644 index 5a3f374088..0000000000 --- a/src/batch_reactor_tests.cc +++ /dev/null @@ -1,400 +0,0 @@ -#include "batch_reactor_tests.h" - -#include - -#include "composition.h" -#include "error.h" -#include "facility_tests.h" -#include "agent_tests.h" -#include "agent.h" -#include "infile_tree.h" - -#include "toolkit/commodity.h" - -namespace cycamore { - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool operator==(const BatchReactor::InitCond& l, - const BatchReactor::InitCond& r) { - bool reserves = (l.n_reserves != 0 && - l.n_reserves == r.n_reserves && - l.reserves_rec == r.reserves_rec && - l.reserves_commod == r.reserves_commod); - bool core = (l.n_core != 0 && - l.n_core == r.n_core && - l.core_rec == r.core_rec && - l.core_commod == r.core_commod); - bool storage = (l.n_storage != 0 && - l.n_storage == r.n_storage && - l.storage_rec == r.storage_rec && - l.storage_commod == r.storage_commod); - return (reserves && core && storage); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::SetUp() { - src_facility = new BatchReactor(tc_.get()); - InitParameters(); - SetUpSource(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::TearDown() { - delete src_facility; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::InitParameters() { - // init params - in_c1 = "in_c1"; - in_c2 = "in_c2"; - out_c1 = "out_c1"; - out_c2 = "out_c2"; - in_r1 = "in_r1"; - in_r2 = "in_r2"; - out_r1 = "out_r1"; - out_r2 = "out_r2"; - crctx.AddInCommod(in_c1, in_r1, out_c1, out_r1); - crctx.AddInCommod(in_c2, in_r2, out_c2, out_r2); - - n_batches = 5; - n_load = 2; - n_reserves = 3; - process_time = 10; - refuel_time = 2; - preorder_time = 1; - batch_size = 4.5; - - commodity = "power"; - capacity = 200; - cost = capacity; - - // init conds - rsrv_c = in_c1; - rsrv_r = in_r1; - core_c = in_c2; - core_r = in_r2; - stor_c = out_c1; - stor_r = out_r1; - rsrv_n = 2; - core_n = 3; - stor_n = 1; - ics.AddReserves(rsrv_n, rsrv_r, rsrv_c); - ics.AddCore(core_n, core_r, core_c); - ics.AddStorage(stor_n, stor_r, stor_c); - - // commod prefs - frompref1 = 7.5; - topref1 = frompref1 - 1; - frompref2 = 5.5; - topref2 = frompref2 - 2; - commod_prefs[in_c1] = frompref1; - commod_prefs[in_c2] = frompref2; - - // changes - change_time = 5; - pref_changes[change_time].push_back(std::make_pair(in_c1, topref1)); - pref_changes[change_time].push_back(std::make_pair(in_c2, topref2)); - recipe_changes[change_time].push_back(std::make_pair(in_c1, in_r2)); - - cyclus::CompMap v; - v[922350000] = 1; - v[922380000] = 2; - cyclus::Composition::Ptr recipe = cyclus::Composition::CreateFromAtom(v); - tc_.get()->AddRecipe(in_r1, recipe); - tc_.get()->AddRecipe(in_r2, recipe); - - v[94239] = 0.25; - recipe = cyclus::Composition::CreateFromAtom(v); - tc_.get()->AddRecipe(out_r1, recipe); - tc_.get()->AddRecipe(out_r2, recipe); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::SetUpSource() { - src_facility->crctx(crctx); - src_facility->n_batches(n_batches); - src_facility->n_load(n_load); - src_facility->n_reserves(n_reserves); - src_facility->process_time(process_time); - src_facility->refuel_time(refuel_time); - src_facility->preorder_time(preorder_time); - src_facility->batch_size(batch_size); - src_facility->ics(ics); - - src_facility->Add(commodity); - src_facility->cyclus::toolkit::CommodityProducer:: - SetCapacity(commodity, capacity); - src_facility->cyclus::toolkit::CommodityProducer:: - SetCost(commodity, capacity); - - src_facility->commod_prefs(commod_prefs); - - src_facility->pref_changes_[change_time].push_back( - std::make_pair(in_c1, topref1)); - src_facility->pref_changes_[change_time].push_back( - std::make_pair(in_c2, topref2)); - src_facility->recipe_changes_[change_time].push_back( - std::make_pair(in_c1, in_r2)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::TestBuffs(int nreserves, int ncore, int nstorage) { - EXPECT_EQ(nreserves, src_facility->reserves_.count()); - EXPECT_EQ(ncore, src_facility->core_.count()); - EXPECT_EQ(nstorage, src_facility->storage_[out_c1].count()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::TestReserveBatches(cyclus::Material::Ptr mat, - std::string commod, - int n, - double qty) { - src_facility->AddBatches_(commod, mat); - EXPECT_EQ(n, src_facility->reserves_.count()); - EXPECT_DOUBLE_EQ(qty, src_facility->spillover_->quantity()); - - cyclus::Material::Ptr back = cyclus::ResCast( - src_facility->reserves_.Pop(cyclus::toolkit::ResourceBuff::BACK)); - EXPECT_EQ(commod, src_facility->crctx_.commod(back)); - src_facility->reserves_.Push(back); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::TestBatchIn(int n_core, int n_reserves) { - src_facility->MoveBatchIn_(); - EXPECT_EQ(n_core, src_facility->n_core()); - EXPECT_EQ(n_reserves, src_facility->reserves_.count()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::TestBatchOut(int n_core, int n_storage) { - src_facility->MoveBatchOut_(); - EXPECT_EQ(n_core, src_facility->n_core()); - EXPECT_EQ(n_storage, src_facility->storage_[out_c1].count()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BatchReactorTest::TestInitState(BatchReactor* fac) { - EXPECT_EQ(crctx, fac->crctx()); - EXPECT_EQ(n_batches, fac->n_batches()); - EXPECT_EQ(n_load, fac->n_load()); - EXPECT_EQ(n_reserves, fac->n_reserves()); - EXPECT_EQ(process_time, fac->process_time()); - EXPECT_EQ(refuel_time, fac->refuel_time()); - EXPECT_EQ(preorder_time, fac->preorder_time()); - EXPECT_EQ(batch_size, fac->batch_size()); - EXPECT_EQ(0, fac->n_core()); - EXPECT_EQ(BatchReactor::INITIAL, fac->phase()); - EXPECT_EQ(ics, fac->ics()); - - cyclus::toolkit::Commodity commod(commodity); - EXPECT_TRUE(fac->Produces(commod)); - EXPECT_EQ(capacity, fac->Capacity(commod)); - EXPECT_EQ(cost, fac->Cost(commod)); - - EXPECT_EQ(commod_prefs, fac->commod_prefs()); - - EXPECT_EQ(pref_changes, fac->pref_changes_); - EXPECT_EQ(recipe_changes, fac->recipe_changes_); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, InitialState) { - TestInitState(src_facility); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, DISABLED_XMLInit) { - std::stringstream ss; - ss << "" - << "fooname" - << "" - << "" - << " " - << " " << in_c1 << "" - << " " << in_r1 << "" - << " " << out_c1 << "" - << " " << out_r1 << "" - << " " - << " " - << " " << in_c2 << "" - << " " << in_r2 << "" - << " " << out_c2 << "" - << " " << out_r2 << "" - << " " - << " " << process_time << "" - << " " << n_batches << "" - << " " << batch_size << "" - << " " << refuel_time << "" - << " " << preorder_time << "" - << " " << n_reserves << "" - << " " << n_load << "" - << " " - << " " - << " " << rsrv_n << "" - << " " << rsrv_c << "" - << " " << rsrv_r << "" - << " " - << " " - << " " << core_n << "" - << " " << core_c << "" - << " " << core_r << "" - << " " - << " " - << " " << stor_n << "" - << " " << stor_c << "" - << " " << stor_r << "" - << " " - << " " - << " " - << " " << in_c1 << "" - << " " << in_r2 << "" - << " " - << " " - << " " - << " " << commodity << "" - << " " << capacity << "" - << " " << cost << "" - << " " - << " " - << " " << in_c1 << "" - << " " << frompref1 << "" - << " " - << " " - << " " << in_c2 << "" - << " " << frompref2 << "" - << " " - << " " - << " " << in_c1 << "" - << " " << topref1 << "" - << " " - << " " - << " " - << " " << in_c2 << "" - << " " << topref2 << "" - << " " - << " " - << "" - << "" - << ""; - - cyclus::XMLParser p; - p.Init(ss); - cyclus::InfileTree engine(p); - cycamore::BatchReactor* fac = new cycamore::BatchReactor(tc_.get()); - // fac->InitFrom(&engine); - - TestInitState(fac); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, Clone) { - cycamore::BatchReactor* cloned_fac = - dynamic_cast(src_facility->Clone()); - TestInitState(cloned_fac); - delete cloned_fac; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, Print) { - EXPECT_NO_THROW(std::string s = src_facility->str()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, Tick) { - EXPECT_EQ(src_facility->commod_prefs().at(in_c1), frompref1); - EXPECT_EQ(src_facility->commod_prefs().at(in_c2), frompref2); - EXPECT_EQ(src_facility->crctx().in_recipe(in_c1), in_r1); - tc_.get()->time(change_time); - EXPECT_NO_THROW(src_facility->Tick();); - EXPECT_EQ(src_facility->commod_prefs().at(in_c1), topref1); - EXPECT_EQ(src_facility->commod_prefs().at(in_c2), topref2); - EXPECT_EQ(src_facility->crctx().in_recipe(in_c1), in_r2); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, Tock) { - int time = 1; - EXPECT_NO_THROW(src_facility->Tock()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, StartProcess) { - int t = tc_.get()->time(); - src_facility->phase(BatchReactor::PROCESS); - EXPECT_EQ(t, src_facility->start_time()); - EXPECT_EQ(t + process_time - 1, src_facility->end_time()); - EXPECT_EQ(t + process_time - preorder_time - 1, src_facility->order_time()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, InitCond) { - cyclus::Env::SetNucDataPath(); - src_facility->Build(NULL); - TestBuffs(rsrv_n, core_n, stor_n); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, AddBatches) { - using cyclus::Material; - - Material::Ptr mat = cyclus::NewBlankMaterial(batch_size); - // mat to add, commodity, reserves, qty of spillover - TestReserveBatches(mat, in_c1, 1, 0); - - mat = cyclus::NewBlankMaterial(batch_size - (1 + cyclus::eps())); - TestReserveBatches(mat, in_c1, 1, batch_size - (1 + cyclus::eps())); - - mat = cyclus::NewBlankMaterial((1 + cyclus::eps())); - TestReserveBatches(mat, in_c1, 2, 0); - - mat = cyclus::NewBlankMaterial(batch_size + (1 + cyclus::eps())); - TestReserveBatches(mat, in_c1, 3, 1 + cyclus::eps()); - - mat = cyclus::NewBlankMaterial(batch_size - (1 + cyclus::eps())); - TestReserveBatches(mat, in_c1, 4, 0); - - mat = cyclus::NewBlankMaterial(1 + cyclus::eps()); - TestReserveBatches(mat, in_c1, 4, 1 + cyclus::eps()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(BatchReactorTest, BatchInOut) { - using cyclus::Material; - - EXPECT_THROW(TestBatchIn(1, 0), cyclus::Error); - - Material::Ptr mat = cyclus::NewBlankMaterial(batch_size); - TestReserveBatches(mat, in_c1, 1, 0); - TestBatchIn(1, 0); - - mat = cyclus::NewBlankMaterial(batch_size * 2); - TestReserveBatches(mat, in_c1, 2, 0); - TestBatchIn(2, 1); - - TestBatchOut(1, 1); - TestBatchOut(0, 2); - - EXPECT_THROW(TestBatchOut(1, 0), cyclus::Error); -} - -} // namespace cycamore - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Agent* BatchReactorConstructor(cyclus::Context* ctx) { - return new cycamore::BatchReactor(ctx); -} - -// required to get functionality in cyclus agent unit tests library -#ifndef CYCLUS_AGENT_TESTS_CONNECTED -int ConnectAgentTests(); -static int cyclus_agent_tests_connected = ConnectAgentTests(); -#define CYCLUS_AGENT_TESTS_CONNECTED cyclus_agent_tests_connected -#endif // CYCLUS_AGENT_TESTS_CONNECTED - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// INSTANTIATE_TEST_CASE_P(BatchReactor, FacilityTests, -// Values(&BatchReactorConstructor)); -INSTANTIATE_TEST_CASE_P(BatchReactor, AgentTests, - Values(&BatchReactorConstructor)); diff --git a/src/batch_reactor_tests.h b/src/batch_reactor_tests.h deleted file mode 100644 index 1390a4d948..0000000000 --- a/src/batch_reactor_tests.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef CYCAMORE_SRC_BATCH_REACTOR_TESTS_H_ -#define CYCAMORE_SRC_BATCH_REACTOR_TESTS_H_ - -#include - -#include -#include - -#include "batch_reactor.h" -#include "test_context.h" - -namespace cycamore { - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -class BatchReactorTest : public ::testing::Test { - protected: - cyclus::TestContext tc_; - BatchReactor* src_facility; - - // init params - std::string in_c1, in_c2, out_c1, out_c2; - std::string in_r1, in_r2, out_r1, out_r2; - cyclus::toolkit::CommodityRecipeContext crctx; - - int n_batches, n_load, n_reserves; - int process_time, refuel_time, preorder_time; - double batch_size; - - std::string commodity; - double capacity, cost; - - // init conds - std::string rsrv_c, rsrv_r, core_c, core_r, stor_c, stor_r; - int rsrv_n, core_n, stor_n; - BatchReactor::InitCond ics; - - // changes changes - int change_time; - double frompref1, topref1, frompref2, topref2; - std::map commod_prefs; - std::map > > pref_changes; - std::map > > - recipe_changes; - - virtual void SetUp(); - virtual void TearDown(); - void InitParameters(); - void SetUpSource(); - - /// @brief tests the initial state of a facility - void TestInitState(BatchReactor* fac); - - /// @brief tests the number of batches in each buffer - void TestBuffs(int nreserves, int ncore, int nstorage); - - /// @brief tests the BatchReactor's reserves_, by calling AddBatches_(mat), - /// and confirming that there are n items and the last item has quantity qty - void TestReserveBatches(cyclus::Material::Ptr mat, std::string commod, - int n, double qty); - - /// @brief calls MoveBatchIn_ and tests that the number of objects in core_ is - /// n_core and the number of objects in reserves_ is n_reserves - void TestBatchIn(int n_core, int n_reserves); - - /// @brief calls MoveBatchOut_ and tests that the number of objects in core_ is - /// n_core and the number of objects in storage_ is n_storage - void TestBatchOut(int n_core, int n_storage); -}; - -} // namespace cycamore - -#endif // CYCAMORE_SRC_BATCH_REACTOR_TESTS_H_ diff --git a/src/cycamore.h b/src/cycamore.h index 7f1efdc5fa..fbc9f5e0e3 100644 --- a/src/cycamore.h +++ b/src/cycamore.h @@ -6,8 +6,8 @@ #include "batch_reactor.h" #include "batch_reactor_tests.h" #include "deploy_inst.h" -#include "enrichment_facility.h" -#include "enrichment_facility_tests.h" +#include "enrichment.h" +#include "enrichment_tests.h" #include "growth_region.h" #include "growth_region_tests.h" #include "inpro_reactor.h" diff --git a/src/deploy_inst.cc b/src/deploy_inst.cc index f243d2e543..3abd2b87ac 100644 --- a/src/deploy_inst.cc +++ b/src/deploy_inst.cc @@ -7,82 +7,59 @@ DeployInst::DeployInst(cyclus::Context* ctx) : cyclus::Institution(ctx) {} DeployInst::~DeployInst() {} -#pragma cyclus def clone cycamore::DeployInst +void DeployInst::Build(cyclus::Agent* parent) { + cyclus::Institution::Build(parent); + BuildSched::iterator it; + std::set protos; + for (int i = 0; i < prototypes.size(); i++) { + std::string proto = prototypes[i]; -std::string DeployInst::schema() { - return - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n"; -} + std::stringstream ss; + ss << proto; -void DeployInst::InfileToDb(cyclus::InfileTree* qe, cyclus::DbInit di) { - cyclus::Institution::InfileToDb(qe, di); - qe = qe->SubTree("config/*"); + if (lifetimes.size() == prototypes.size()) { + cyclus::Agent* a = context()->CreateAgent(proto); + if (a->lifetime() != lifetimes[i]) { + a->lifetime(lifetimes[i]); - int nOrders = qe->NMatches("buildorder"); - for (int i = 0; i < nOrders; i++) { - cyclus::InfileTree* order = qe->SubTree("buildorder", i); - int n = cyclus::Query(order, "number"); - for (int j = 0; j < n; ++j) { - di.NewDatum("BuildOrder") - ->AddVal("prototype", order->GetString("prototype")) - ->AddVal("date", cyclus::Query(order, "date")) - ->Record(); + if (lifetimes[i] == -1) { + ss << "_life_forever"; + } else { + ss << "_life_" << lifetimes[i]; + } + proto = ss.str(); + if (protos.count(proto) == 0) { + protos.insert(proto); + context()->AddPrototype(proto, a); + } + } } - } -} - -void DeployInst::InitFrom(cyclus::QueryableBackend* b) { - cyclus::Institution::InitFrom(b); - cyclus::QueryResult qr = b->Query("BuildOrder", NULL); - for (int i = 0; i < qr.rows.size(); i++) { - std::string proto = qr.GetVal("prototype", i); - int t = qr.GetVal("date", i); - build_sched_[t].push_back(proto); - } -} -void DeployInst::InitFrom(DeployInst* m) { - cyclus::Institution::InitFrom(m); - build_sched_ = m->build_sched_; -} - -void DeployInst::Snapshot(cyclus::DbInit di) { - cyclus::Institution::Snapshot(di); - - BuildSched::iterator it; - for (it = build_sched_.begin(); it != build_sched_.end(); ++it) { - int t = it->first; - std::vector protos = it->second; - for (int i = 0; i < protos.size(); ++i) { - di.NewDatum("BuildOrder") - ->AddVal("prototype", protos[i]) - ->AddVal("date", t) - ->Record(); + int t = build_times[i]; + for (int j = 0; j < n_build[i]; j++) { + context()->SchedBuild(this, proto, t); } } } -void DeployInst::Build(cyclus::Agent* parent) { - cyclus::Institution::Build(parent); - BuildSched::iterator it; - for (it = build_sched_.begin(); it != build_sched_.end(); ++it) { - int t = it->first; - std::vector protos = it->second; - for (int i = 0; i < protos.size(); ++i) { - context()->SchedBuild(this, protos[i], t); - } +void DeployInst::EnterNotify() { + cyclus::Institution::EnterNotify(); + int n = prototypes.size(); + if (build_times.size() != n) { + std::stringstream ss; + ss << "prototype '" << prototype() << "' has " << build_times.size() + << " build_times vals, expected " << n; + throw cyclus::ValueError(ss.str()); + } else if (n_build.size() != n) { + std::stringstream ss; + ss << "prototype '" << prototype() << "' has " << n_build.size() + << " n_build vals, expected " << n; + throw cyclus::ValueError(ss.str()); + } else if (lifetimes.size() > 0 && lifetimes.size() != n) { + std::stringstream ss; + ss << "prototype '" << prototype() << "' has " << lifetimes.size() + << " lifetimes vals, expected " << n; + throw cyclus::ValueError(ss.str()); } } diff --git a/src/deploy_inst.h b/src/deploy_inst.h index 0526ff2fc1..87b86da8f7 100644 --- a/src/deploy_inst.h +++ b/src/deploy_inst.h @@ -11,41 +11,65 @@ namespace cycamore { typedef std::map > BuildSched; -/// @class DeployInst -/// The DeployInst class inherits from the Institution -/// class and is dynamically loaded by the Agent class when requested. -/// -/// This agent implements a simple institution agent that deploys -/// specific facilities as defined explicitly in the input file. +// Builds and manages agents (facilities) according to a manually specified +// deployment schedule. Deployed agents are automatically decommissioned at +// the end of their lifetime. The user specifies a list of prototypes for +// each and corresponding build times, number to build, and (optionally) +// lifetimes. The same prototype can be specified multiple times with any +// combination of the same or different build times, build number, and +// lifetimes. class DeployInst : public cyclus::Institution { + #pragma cyclus note { \ + "doc": \ + "Builds and manages agents (facilities) according to a manually specified" \ + " deployment schedule. Deployed agents are automatically decommissioned at" \ + " the end of their lifetime. The user specifies a list of prototypes for" \ + " each and corresponding build times, number to build, and (optionally)" \ + " lifetimes. The same prototype can be specified multiple times with any" \ + " combination of the same or different build times, build number, and" \ + " lifetimes. " \ + } public: DeployInst(cyclus::Context* ctx); virtual ~DeployInst(); - #pragma cyclus decl clone - - #pragma cyclus decl schema - - #pragma cyclus decl infiletodb - - #pragma cyclus decl initfromdb - - #pragma cyclus decl initfromcopy - - #pragma cyclus decl snapshot - - #pragma cyclus def annotations - - #pragma cyclus note {"doc": "An institution that owns, operates, and " \ - "deploys facilities manually defined in " \ - "the input file."} + #pragma cyclus virtual void Build(cyclus::Agent* parent); + virtual void EnterNotify(); + protected: - /// a collection of orders to build - BuildSched build_sched_; + #pragma cyclus var { \ + "doc": "Ordered list of prototypes to build.", \ + "uitype": ("oneormore", "prototype"), \ + "uilabel": "Prototypes to deploy", \ + } + std::vector prototypes; + + #pragma cyclus var { \ + "doc": "Time step on which to deploy agents given in prototype list (same order).", \ + "uilabel": "Deployment times", \ + } + std::vector build_times; + + #pragma cyclus var { \ + "doc": "Number of each prototype given in prototype list that should be deployed (same order).", \ + "uilabel": "Number to deploy", \ + } + std::vector n_build; + + #pragma cyclus var { \ + "doc": "Lifetimes for each prototype in prototype list (same order)." \ + " These lifetimes override the lifetimes in the original prototype definition." \ + " If unspecified, lifetimes from the original prototype definitions are used." \ + " Although a new prototype is created in the Prototypes table for each lifetime with the suffix '_life_[lifetime]'," \ + " all deployed agents themselves will have the same original prototype name (and so will the Agents tables).", \ + "default": [], \ + "uilabel": "Lifetimes" \ + } + std::vector lifetimes; }; } // namespace cycamore diff --git a/src/deploy_inst_tests.cc b/src/deploy_inst_tests.cc index 983250a599..49c870eae8 100644 --- a/src/deploy_inst_tests.cc +++ b/src/deploy_inst_tests.cc @@ -5,27 +5,150 @@ #include "institution_tests.h" #include "agent_tests.h" -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Agent* DeployInstitutionConstructor(cyclus::Context* ctx) { - return new cycamore::DeployInst(ctx); +// make sure that the deployed agent's prototype name is identical to the +// originally specified prototype name - this is important to test because +// DeployInst does some mucking around with registering name-modded prototypes +// in order to deal with lifetime setting. +TEST(DeployInstTests, ProtoNames) { + std::string config = + " foobar " + " 1 " + " 3 " + " 2 " + ; + + int simdur = 5; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:DeployInst"), config, simdur); + sim.DummyProto("foobar"); + int id = sim.Run(); + + cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry WHERE Prototype = 'foobar';" + ); + stmt->Step(); + EXPECT_EQ(3, stmt->GetInt(0)); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -class DeployInstTest : public ::testing::Test { - protected: - virtual void SetUp() {} +TEST(DeployInstTests, BuildTimes) { + std::string config = + " foobar foobar " + " 1 3 " + " 1 7 " + ; + + int simdur = 5; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:DeployInst"), config, simdur); + sim.DummyProto("foobar"); + int id = sim.Run(); + + cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry WHERE Prototype = 'foobar' AND EnterTime = 1;" + ); + stmt->Step(); + EXPECT_EQ(1, stmt->GetInt(0)); + + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry WHERE Prototype = 'foobar' AND EnterTime = 3;" + ); + stmt->Step(); + EXPECT_EQ(7, stmt->GetInt(0)); +} + +// make sure that specified lifetimes are honored both in agent's table record +// and in decommissioning. +TEST(DeployInstTests, FiniteLifetimes) { + std::string config = + " foobar foobar foobar " + " 1 1 2 " + " 1 7 3 " + " 1 2 -1 " + ; + + int simdur = 5; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:DeployInst"), config, simdur); + sim.DummyProto("foobar"); + int id = sim.Run(); + + // check agent deployment + cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry WHERE Prototype = 'foobar' AND EnterTime = 1 AND Lifetime = 1;" + ); + stmt->Step(); + EXPECT_EQ(1, stmt->GetInt(0)); + + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry WHERE Prototype = 'foobar' AND EnterTime = 1 AND Lifetime = 2;" + ); + stmt->Step(); + EXPECT_EQ(7, stmt->GetInt(0)); + + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry WHERE Prototype = 'foobar' AND EnterTime = 2 AND Lifetime = -1;" + ); + stmt->Step(); + EXPECT_EQ(3, stmt->GetInt(0)); - virtual void TearDown() {} -}; + // check decommissioning + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry As e JOIN AgentExit AS x ON x.AgentId = e.AgentId WHERE e.Prototype = 'foobar' AND x.ExitTime = 1;" + ); + stmt->Step(); + EXPECT_EQ(1, stmt->GetInt(0)); + + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentEntry As e JOIN AgentExit AS x ON x.AgentId = e.AgentId WHERE e.Prototype = 'foobar' AND x.ExitTime = 2;" + ); + stmt->Step(); + EXPECT_EQ(7, stmt->GetInt(0)); + + // agent with -1 lifetime should not be in AgentExit table + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM AgentExit;" + ); + stmt->Step(); + EXPECT_EQ(8, stmt->GetInt(0)); +} + +TEST(DeployInstTests, NoDupProtos) { + std::string config = + " foobar foobar foobar " + " 1 1 2 " + " 1 7 3 " + " 1 1 -1 " + ; + + int simdur = 5; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:DeployInst"), config, simdur); + sim.DummyProto("foobar"); + int id = sim.Run(); + + // don't duplicate same prototypes with same custom lifetime + cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM Prototypes WHERE Prototype = 'foobar_life_1';" + ); + stmt->Step(); + EXPECT_EQ(1, stmt->GetInt(0)); + + // don't duplicate custom lifetimes that are identical to original prototype + // lifetime. + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM Prototypes WHERE Prototype = 'foobar';" + ); + stmt->Step(); + EXPECT_EQ(1, stmt->GetInt(0)); +} // required to get functionality in cyclus agent unit tests library +cyclus::Agent* DeployInstitutionConstructor(cyclus::Context* ctx) { + return new cycamore::DeployInst(ctx); +} + #ifndef CYCLUS_AGENT_TESTS_CONNECTED int ConnectAgentTests(); static int cyclus_agent_tests_connected = ConnectAgentTests(); #define CYCLUS_AGENT_TESTS_CONNECTED cyclus_agent_tests_connected #endif // CYCLUS_AGENT_TESTS_CONNECTED -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSTANTIATE_TEST_CASE_P(DeployInst, InstitutionTests, Values(&DeployInstitutionConstructor)); INSTANTIATE_TEST_CASE_P(DeployInst, AgentTests, diff --git a/src/enrichment.cc b/src/enrichment.cc new file mode 100644 index 0000000000..39ed64373f --- /dev/null +++ b/src/enrichment.cc @@ -0,0 +1,465 @@ +// Implements the Enrichment class +#include "enrichment.h" + +#include +#include +#include +#include +#include +#include + +namespace cycamore { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Enrichment::Enrichment(cyclus::Context* ctx) + : cyclus::Facility(ctx), + tails_assay(0), + swu_capacity(0), + max_enrich(1), + initial_feed(0), + feed_commod(""), + feed_recipe(""), + product_commod(""), + tails_commod(""), + order_prefs(true){} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Enrichment::~Enrichment() {} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +std::string Enrichment::str() { + std::stringstream ss; + ss << cyclus::Facility::str() + << " with enrichment facility parameters:" + << " * SWU capacity: " << SwuCapacity() + << " * Tails assay: " << tails_assay + << " * Feed assay: " << FeedAssay() + << " * Input cyclus::Commodity: " << feed_commod + << " * Output cyclus::Commodity: " << product_commod + << " * Tails cyclus::Commodity: " << tails_commod; + return ss.str(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::Build(cyclus::Agent* parent) { + using cyclus::Material; + + Facility::Build(parent); + if (initial_feed > 0) { + inventory.Push( + Material::Create( + this, initial_feed, context()->GetRecipe(feed_recipe))); + } + + LOG(cyclus::LEV_DEBUG2, "EnrFac") << "Enrichment " + << " entering the simuluation: "; + LOG(cyclus::LEV_DEBUG2, "EnrFac") << str(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::Tick() { + current_swu_capacity = SwuCapacity(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::Tock() { + using cyclus::toolkit::RecordTimeSeries; + RecordTimeSeries(this, intra_timestep_swu_); + RecordTimeSeries(this, intra_timestep_feed_); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +std::set::Ptr> + Enrichment::GetMatlRequests() { + using cyclus::Material; + using cyclus::RequestPortfolio; + using cyclus::Request; + + std::set::Ptr> ports; + RequestPortfolio::Ptr port(new RequestPortfolio()); + Material::Ptr mat = Request_(); + double amt = mat->quantity(); + + if (amt > cyclus::eps()) { + port->AddRequest(mat, this, feed_commod); + ports.insert(port); + } + + return ports; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SortBids( + cyclus::Bid* i, cyclus::Bid* j) { + + cyclus::Material::Ptr mat_i = i->offer(); + cyclus::Material::Ptr mat_j = j->offer(); + + cyclus::toolkit::MatQuery mq_i(mat_i); + cyclus::toolkit::MatQuery mq_j(mat_j); + + return ((mq_i.mass(922350000) / mq_i.qty()) <= + (mq_j.mass(922350000) / mq_j.qty())); +} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Sort offers of input material to have higher preference for more +// U-235 content +void Enrichment::AdjustMatlPrefs( + cyclus::PrefMap::type& prefs) { + + using cyclus::Bid; + using cyclus::Material; + using cyclus::Request; + + if (order_prefs == false) { + return; + } + + cyclus::PrefMap::type::iterator reqit; + + // Loop over all requests + for (reqit = prefs.begin(); reqit != prefs.end(); ++reqit) { + std::vector* > bids_vector; + std::map*, double>::iterator mit; + for (mit = reqit->second.begin(); mit != reqit->second.end(); ++mit) { + Bid* bid = mit->first; + bids_vector.push_back(bid); + } + std::sort (bids_vector.begin(), bids_vector.end(), SortBids); + + // Assign preferences to the sorted vector + double n_bids = bids_vector.size(); + bool u235_mass = 0; + + for (int bidit = 0 ; bidit < bids_vector.size(); bidit++) { + int new_pref = bidit + 1; + + // For any bids with U-235 qty=0, set pref to zero. + if (!u235_mass) { + cyclus::Material::Ptr mat = bids_vector[bidit]->offer(); + cyclus::toolkit::MatQuery mq(mat); + if (mq.mass(922350000) == 0) { + new_pref = -1; + } + else { + u235_mass = true; + } + } + (reqit->second)[bids_vector[bidit]] = new_pref; + } // each bid + } // each Material Request +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::AcceptMatlTrades( + const std::vector< std::pair, + cyclus::Material::Ptr> >& responses) { + // see + // http://stackoverflow.com/questions/5181183/boostshared-ptr-and-inheritance + std::vector< std::pair, + cyclus::Material::Ptr> >::const_iterator it; + for (it = responses.begin(); it != responses.end(); ++it) { + AddMat_(it->second); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +std::set::Ptr> Enrichment::GetMatlBids( + cyclus::CommodMap::type& out_requests){ + using cyclus::Bid; + using cyclus::BidPortfolio; + using cyclus::CapacityConstraint; + using cyclus::Converter; + using cyclus::Material; + using cyclus::Request; + + std::set::Ptr> ports; + + if ((out_requests.count(tails_commod) > 0) && (tails.quantity() > 0)) { + BidPortfolio::Ptr tails_port(new BidPortfolio()); + + std::vector*>& tails_requests = + out_requests[tails_commod]; + std::vector*>::iterator it; + for (it = tails_requests.begin(); it != tails_requests.end(); ++it) { + Request* req = *it; + tails_port->AddBid(req, tails.Peek(), this); + } + // overbidding (bidding on every offer) + // add an overall capacity constraint + CapacityConstraint tails_constraint(tails.quantity()); + tails_port->AddConstraint(tails_constraint); + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() + << " adding tails capacity constraint of " + << tails.capacity(); + ports.insert(tails_port); + } + + if ((out_requests.count(product_commod) > 0) && (inventory.quantity() > 0)) { + BidPortfolio::Ptr commod_port(new BidPortfolio()); + + std::vector*>& commod_requests = + out_requests[product_commod]; + std::vector*>::iterator it; + for (it = commod_requests.begin(); it != commod_requests.end(); ++it) { + Request* req = *it; + Material::Ptr mat = req->target(); + double request_enrich = cyclus::toolkit::UraniumAssay(mat) ; + + if (ValidReq(req->target()) && (request_enrich <= max_enrich)) { + Material::Ptr offer = Offer_(req->target()); + commod_port->AddBid(req, offer, this); + } + } + Converter::Ptr sc(new SWUConverter(FeedAssay(), tails_assay)); + Converter::Ptr nc(new NatUConverter(FeedAssay(), tails_assay)); + CapacityConstraint swu(swu_capacity, sc); + CapacityConstraint natu(inventory.quantity(), nc); + commod_port->AddConstraint(swu); + commod_port->AddConstraint(natu); + + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() + << " adding a swu constraint of " + << swu.capacity(); + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() + << " adding a natu constraint of " + << natu.capacity(); + ports.insert(commod_port); + } + return ports; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Enrichment::ValidReq(const cyclus::Material::Ptr mat) { + cyclus::toolkit::MatQuery q(mat); + double u235 = q.atom_frac(922350000); + double u238 = q.atom_frac(922380000); + return (u238 > 0 && u235 / (u235 + u238) > tails_assay); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::GetMatlTrades( + const std::vector< cyclus::Trade >& trades, + std::vector, + cyclus::Material::Ptr> >& responses) { + + using cyclus::Material; + using cyclus::Trade; + + intra_timestep_swu_ = 0; + intra_timestep_feed_ = 0; + + std::vector< Trade >::const_iterator it; + for (it = trades.begin(); it != trades.end(); ++it) { + double qty = it->amt; + std::string commod_type = it->bid->request()->commodity(); + Material::Ptr response; + + // Figure out whether material is tails or enriched, + // if tails then make transfer of material + if (commod_type == tails_commod) { + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() + << " just received an order" + << " for " << it->amt + << " of " << tails_commod; + response = tails.Pop(qty); + } else { + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() + << " just received an order" + << " for " << it->amt + << " of " << product_commod; + response = Enrich_(it->bid->offer(), qty); + } + responses.push_back(std::make_pair(*it, response)); + } + + if (cyclus::IsNegative(tails.quantity())) { + std::stringstream ss; + ss << "is being asked to provide more than its current inventory."; + throw cyclus::ValueError(Agent::InformErrorMsg(ss.str())); + } + if (cyclus::IsNegative(current_swu_capacity)) { + throw cyclus::ValueError( + "EnrFac " + prototype() + + " is being asked to provide more than" + + " its SWU capacity."); + } +} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::AddMat_(cyclus::Material::Ptr mat) { + // Elements and isotopes other than U-235, U-238 are sent directly to tails + cyclus::CompMap cm = mat->comp()->atom(); + bool extra_u = false; + bool other_elem = false; + for (cyclus::CompMap::const_iterator it = cm.begin(); it != cm.end(); ++it) { + if (pyne::nucname::znum(it->first) == 92) { + if (pyne::nucname::anum(it->first) != 235 && + pyne::nucname::anum(it->first) != 238 && it->second > 0) { + extra_u = true; + } + } + else if (it->second > 0) { + other_elem = true ; + } + } + if (extra_u) { + cyclus::Warn ("More than 2 isotopes of U. " \ + "Istopes other than U-235, U-238 are sent directly to tails."); + } + if (other_elem) { + cyclus::Warn ("Non-uranium elements are " \ + "sent directly to tails."); + } + + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() << " is initially holding " + << inventory.quantity() << " total."; + + try { + inventory.Push(mat); + } + catch (cyclus::Error& e) { + e.msg(Agent::InformErrorMsg(e.msg())); + throw e; + } + + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() << " added " + << mat->quantity() << " of " << feed_commod + << " to its inventory, which is holding " + << inventory.quantity() << " total."; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr Enrichment::Request_() { + double qty = std::max(0.0, inventory.capacity() - inventory.quantity()); + return cyclus::Material::CreateUntracked(qty, + context()->GetRecipe(feed_recipe)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr Enrichment::Offer_(cyclus::Material::Ptr mat) { + cyclus::toolkit::MatQuery q(mat); + cyclus::CompMap comp; + comp[922350000] = q.atom_frac(922350000); + comp[922380000] = q.atom_frac(922380000); + return cyclus::Material::CreateUntracked( + mat->quantity(), cyclus::Composition::CreateFromAtom(comp)); +} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr Enrichment::Enrich_( + cyclus::Material::Ptr mat, + double qty) { + + using cyclus::Material; + using cyclus::ResCast; + using cyclus::toolkit::Assays; + using cyclus::toolkit::UraniumAssay; + using cyclus::toolkit::SwuRequired; + using cyclus::toolkit::FeedQty; + using cyclus::toolkit::TailsQty; + + // get enrichment parameters + Assays assays(FeedAssay(), UraniumAssay(mat), tails_assay); + double swu_req = SwuRequired(qty, assays); + double natu_req = FeedQty(qty, assays); + + // Determine the composition of the natural uranium + // (ie. U-235+U-238/TotalMass) + Material::Ptr natu_matl=inventory.Pop(inventory.quantity()); + inventory.Push(natu_matl); + + cyclus::toolkit::MatQuery mq(natu_matl); + std::set nucs; + nucs.insert(922350000); + nucs.insert(922380000); + double natu_frac = mq.mass_frac(nucs); + double feed_req = natu_req/natu_frac; + + // pop amount from inventory and blob it into one material + Material::Ptr r; + try { + // required so popping doesn't take out too much + if (cyclus::AlmostEq(feed_req, inventory.quantity())) { + r = cyclus::toolkit::Squash(inventory.PopN(inventory.count())); + } else { + r = inventory.Pop(feed_req); + } + } catch (cyclus::Error& e) { + NatUConverter nc(FeedAssay(), tails_assay); + std::stringstream ss; + ss << " tried to remove " << feed_req + << " from its inventory of size " << inventory.quantity() + << " and the conversion of the material into natu is " + << nc.convert(mat); + throw cyclus::ValueError(Agent::InformErrorMsg(ss.str())); + } + + // "enrich" it, but pull out the composition and quantity we require from the + // blob + cyclus::Composition::Ptr comp = mat->comp(); + Material::Ptr response = r->ExtractComp(qty, comp); + tails.Push(r); + + current_swu_capacity -= swu_req; + + intra_timestep_swu_ += swu_req; + intra_timestep_feed_ += feed_req; + RecordEnrichment_(feed_req, swu_req); + + LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() << + " has performed an enrichment: "; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Feed Qty: " + << feed_req; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Feed Assay: " + << assays.Feed() * 100; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Product Qty: " + << qty; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Product Assay: " + << assays.Product() * 100; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Tails Qty: " + << TailsQty(qty, assays); + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Tails Assay: " + << assays.Tails() * 100; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * SWU: " + << swu_req; + LOG(cyclus::LEV_INFO5, "EnrFac") << " * Current SWU capacity: " + << current_swu_capacity; + + return response; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Enrichment::RecordEnrichment_(double natural_u, double swu) { + using cyclus::Context; + using cyclus::Agent; + + LOG(cyclus::LEV_DEBUG1, "EnrFac") << prototype() + << " has enriched a material:"; + LOG(cyclus::LEV_DEBUG1, "EnrFac") << " * Amount: " << natural_u; + LOG(cyclus::LEV_DEBUG1, "EnrFac") << " * SWU: " << swu; + + Context* ctx = Agent::context(); + ctx->NewDatum("Enrichments") + ->AddVal("ID", id()) + ->AddVal("Time", ctx->time()) + ->AddVal("Natural_Uranium", natural_u) + ->AddVal("SWU", swu) + ->Record(); +} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +double Enrichment::FeedAssay() { + using cyclus::Material; + + if (inventory.empty()) { + return 0; + } + cyclus::Material::Ptr fission_matl = inventory.Pop(inventory.quantity()); + inventory.Push(fission_matl); + return cyclus::toolkit::UraniumAssay(fission_matl); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +extern "C" cyclus::Agent* ConstructEnrichment(cyclus::Context* ctx) { + return new Enrichment(ctx); +} + +} // namespace cycamore diff --git a/src/enrichment.h b/src/enrichment.h new file mode 100644 index 0000000000..277ed3a495 --- /dev/null +++ b/src/enrichment.h @@ -0,0 +1,364 @@ +#ifndef CYCAMORE_SRC_ENRICHMENT_H_ +#define CYCAMORE_SRC_ENRICHMENT_H_ + +#include + +#include "cyclus.h" + +namespace cycamore { + +/// @class SWUConverter +/// +/// @brief The SWUConverter is a simple Converter class for material to +/// determine the amount of SWU required for their proposed enrichment +class SWUConverter : public cyclus::Converter { + public: + SWUConverter(double feed_commod, double tails) : feed_(feed_commod), + tails_(tails) {} + virtual ~SWUConverter() {} + + /// @brief provides a conversion for the SWU required + virtual double convert( + cyclus::Material::Ptr m, + cyclus::Arc const * a = NULL, + cyclus::ExchangeTranslationContext + const * ctx = NULL) const { + cyclus::toolkit::Assays assays(feed_, cyclus::toolkit::UraniumAssay(m), + tails_); + return cyclus::toolkit::SwuRequired(m->quantity(), assays); + } + + /// @returns true if Converter is a SWUConverter and feed and tails equal + virtual bool operator==(Converter& other) const { + SWUConverter* cast = dynamic_cast(&other); + return cast != NULL && + feed_ == cast->feed_ && + tails_ == cast->tails_; + } + + private: + double feed_, tails_; +}; + +/// @class NatUConverter +/// +/// @brief The NatUConverter is a simple Converter class for material to +/// determine the amount of natural uranium required for their proposed +/// enrichment +class NatUConverter : public cyclus::Converter { + public: + NatUConverter(double feed_commod, double tails) : feed_(feed_commod), + tails_(tails) {} + virtual ~NatUConverter() {} + + /// @brief provides a conversion for the amount of natural Uranium required + virtual double convert( + cyclus::Material::Ptr m, + cyclus::Arc const * a = NULL, + cyclus::ExchangeTranslationContext + const * ctx = NULL) const { + cyclus::toolkit::Assays assays(feed_, cyclus::toolkit::UraniumAssay(m), + tails_); + cyclus::toolkit::MatQuery mq(m); + std::set nucs; + nucs.insert(922350000); + nucs.insert(922380000); + + double natu_frac = mq.mass_frac(nucs); + double natu_req = cyclus::toolkit::FeedQty(m->quantity(), assays); + return natu_req / natu_frac; + } + + /// @returns true if Converter is a NatUConverter and feed and tails equal + virtual bool operator==(Converter& other) const { + NatUConverter* cast = dynamic_cast(&other); + return cast != NULL && + feed_ == cast->feed_ && + tails_ == cast->tails_; + } + + private: + double feed_, tails_; +}; + +/// The Enrichment facility is a simple Agent that enriches natural +/// uranium in a Cyclus simulation. It does not explicitly compute +/// the physical enrichment process, rather it calculates the SWU +/// required to convert an source uranium recipe (ie. natural uranium) +/// into a requested enriched recipe (ie. 4% enriched uranium), given +/// the natural uranium inventory constraint and its SWU capacity +/// constraint. +/// +/// The Enrichment facility requests an input commodity and associated recipe +/// whose quantity is its remaining inventory capacity. All facilities +/// trading the same input commodity (even with different recipes) will +/// offer materials for trade. The Enrichment facility accepts any input +/// materials with enrichments less than its tails assay, as long as some +/// U235 is present, and preference increases with U235 content. If no +/// U235 is present in the offered material, the trade preference is set +/// to -1 and the material is not accepted. Any material components other +/// other than U235 and U238 are sent directly to the tails buffer. +/// +/// The Enrichment facility will bid on any request for its output commodity +/// up to the maximum allowed enrichment (if not specified, default is 100%) +/// It bids on either the request quantity, or the maximum quanity allowed +/// by its SWU constraint or natural uranium inventory, whichever is lower. +/// If multiple output commodities with different enrichment levels are +/// requested and the facility does not have the SWU or quantity capacity +/// to meet all requests, the requests are fully, then partially filled +/// in unspecified but repeatable order. +/// +/// The Enrichment facility also offers its tails as an output commodity with +/// no associated recipe. Bids for tails are constrained only by total +/// tails inventory. + +class Enrichment : public cyclus::Facility { +#pragma cyclus note { \ + "niche": "enrichment facility", \ + "doc": \ + "The Enrichment facility is a simple agent that enriches natural " \ + "uranium in a Cyclus simulation. It does not explicitly compute " \ + "the physical enrichment process, rather it calculates the SWU " \ + "required to convert an source uranium recipe (i.e. natural uranium) " \ + "into a requested enriched recipe (i.e. 4% enriched uranium), given " \ + "the natural uranium inventory constraint and its SWU capacity " \ + "constraint." \ + "\n\n" \ + "The Enrichment facility requests an input commodity and associated " \ + "recipe whose quantity is its remaining inventory capacity. All " \ + "facilities trading the same input commodity (even with different " \ + "recipes) will offer materials for trade. The Enrichment facility " \ + "accepts any input materials with enrichments less than its tails assay, "\ + "as long as some U235 is present, and preference increases with U235 " \ + "content. If no U235 is present in the offered material, the trade " \ + "preference is set to -1 and the material is not accepted. Any material " \ + "components other than U235 and U238 are sent directly to the tails buffer."\ + "\n\n" \ + "The Enrichment facility will bid on any request for its output commodity "\ + "up to the maximum allowed enrichment (if not specified, default is 100%) "\ + "It bids on either the request quantity, or the maximum quanity allowed " \ + "by its SWU constraint or natural uranium inventory, whichever is lower. " \ + "If multiple output commodities with different enrichment levels are " \ + "requested and the facility does not have the SWU or quantity capacity " \ + "to meet all requests, the requests are fully, then partially filled " \ + "in unspecified but repeatable order." \ + "\n\n" \ + "Accumulated tails inventory is offered for trading as a specifiable " \ + "output commodity.", \ +} + public: + // --- Module Members --- + /// Constructor for the Enrichment class + /// @param ctx the cyclus context for access to simulation-wide parameters + Enrichment(cyclus::Context* ctx); + + /// Destructor for the Enrichment class + virtual ~Enrichment(); + + #pragma cyclus + + /// Print information about this agent + virtual std::string str(); + // --- + + // --- Facility Members --- + /// perform module-specific tasks when entering the simulation + virtual void Build(cyclus::Agent* parent); + // --- + + // --- Agent Members --- + /// Each facility is prompted to do its beginning-of-time-step + /// stuff at the tick of the timer. + + /// @param time is the time to perform the tick + virtual void Tick(); + + /// Each facility is prompted to its end-of-time-step + /// stuff on the tock of the timer. + + /// @param time is the time to perform the tock + virtual void Tock(); + + /// @brief The Enrichment request Materials of its given + /// commodity. + virtual std::set::Ptr> + GetMatlRequests(); + + /// @brief The Enrichment adjusts preferences for offers of + /// natural uranium it has received to maximize U-235 content + /// Any offers that have zero U-235 content are not accepted + virtual void AdjustMatlPrefs(cyclus::PrefMap::type& prefs); + + /// @brief The Enrichment place accepted trade Materials in their + /// Inventory + virtual void AcceptMatlTrades( + const std::vector< std::pair, + cyclus::Material::Ptr> >& responses); + + /// @brief Responds to each request for this facility's commodity. If a given + /// request is more than this facility's inventory or SWU capacity, it will + /// offer its minimum of its capacities. + virtual std::set::Ptr> + GetMatlBids(cyclus::CommodMap::type& + commod_requests); + + /// @brief respond to each trade with a material enriched to the appropriate + /// level given this facility's inventory + /// + /// @param trades all trades in which this trader is the supplier + /// @param responses a container to populate with responses to each trade + virtual void GetMatlTrades( + const std::vector< cyclus::Trade >& trades, + std::vector, + cyclus::Material::Ptr> >& responses); + // --- + + /// @brief Determines if a particular material is a valid request to respond + /// to. Valid requests must contain U235 and U238 and must have a relative + /// U235-to-U238 ratio less than this facility's tails_assay(). + /// @return true if the above description is met by the material + bool ValidReq(const cyclus::Material::Ptr mat); + + inline void SetMaxInventorySize(double size) { + max_feed_inventory = size; + inventory.capacity(size); + } + + inline void SwuCapacity(double capacity) { + swu_capacity = capacity; + current_swu_capacity = swu_capacity; + } + + inline double SwuCapacity() const { return swu_capacity; } + + inline const cyclus::toolkit::ResBuf& Tails() const { + return tails; + } + + private: + /// @brief adds a material into the natural uranium inventory + /// @throws if the material is not the same composition as the feed_recipe + void AddMat_(cyclus::Material::Ptr mat); + + /// @brief generates a request for this facility given its current state. + /// Quantity of the material will be equal to remaining inventory size. + cyclus::Material::Ptr Request_(); + + /// @brief Generates a material offer for a given request. The response + /// composition will be comprised only of U235 and U238 at their relative + /// ratio in the requested material. The response quantity will be the + /// same as the requested commodity. + /// + /// @param req the requested material being responded to + cyclus::Material::Ptr Offer_(cyclus::Material::Ptr req); + + cyclus::Material::Ptr Enrich_(cyclus::Material::Ptr mat, double qty); + + /// @brief calculates the feed assay based on the unenriched inventory + double FeedAssay(); + + /// @brief records and enrichment with the cyclus::Recorder + void RecordEnrichment_(double natural_u, double swu); + + #pragma cyclus var { \ + "tooltip": "feed commodity", \ + "doc": "feed commodity that the enrichment facility accepts", \ + "uilabel": "Feed Commodity", \ + "uitype": "incommodity" \ + } + std::string feed_commod; + #pragma cyclus var { \ + "tooltip": "product commodity", \ + "doc": "product commodity that the enrichment facility generates", \ + "uilabel": "Product Commodity", \ + "uitype": "outcommodity" \ + } + std::string product_commod; + #pragma cyclus var { \ + "tooltip": "feed recipe", \ + "doc": "recipe for enrichment facility feed commodity", \ + "uilabel": "Feed Recipe", \ + "uitype": "recipe" \ + } + std::string feed_recipe; + #pragma cyclus var { \ + "tooltip": "tails commodity", \ + "doc": "tails commodity supplied by enrichment facility", \ + "uilabel": "Tails Commodity", \ + "uitype": "outcommodity" \ + } + std::string tails_commod; + #pragma cyclus var { \ + "default": 0.03, "tooltip": "tails assay", \ + "uilabel": "Tails Assay", \ + "doc": "tails assay from the enrichment process", \ + } + double tails_assay; + #pragma cyclus var { \ + "default": 1e299, \ + "tooltip": "SWU capacity (kgSWU/month)", \ + "uilabel": "SWU Capacity", \ + "doc": "separative work unit (SWU) capacity of enrichment " \ + "facility (kgSWU/month) " \ + } + double swu_capacity; + #pragma cyclus var { \ + "default": 1e299, "tooltip": "max inventory of feed material (kg)", \ + "uilabel": "Maximum Feed Inventory", \ + "doc": "maximum total inventory of natural uranium in " \ + "the enrichment facility (kg)" \ + } + double max_feed_inventory; + + #pragma cyclus var { \ + "default": 1.0, \ + "tooltip": "maximum allowed enrichment fraction", \ + "doc": "maximum allowed weight fraction of U235 in product",\ + "uilabel": "Maximum Allowed Enrichment", \ + "schema": '' \ + ' ' \ + ' ' \ + ' 0' \ + ' 1' \ + ' ' \ + ' ' \ + ' ' \ + } + double max_enrich; + + #pragma cyclus var { \ + "default": 0, "tooltip": "initial uranium reserves (kg)", \ + "uilabel": "Initial Feed Inventory", \ + "doc": "amount of natural uranium stored at the enrichment " \ + "facility at the beginning of the simulation (kg)" \ + } + double initial_feed; + #pragma cyclus var { \ + "default": 1, \ + "userlevel": 10, \ + "tooltip": "order material requests by U235 content", \ + "uilabel": "Prefer feed with higher U235 content", \ + "doc": "turn on preference ordering for input material " \ + "so that EF chooses higher U235 content first" \ + } + bool order_prefs; + + double current_swu_capacity; + + #pragma cyclus var { 'capacity': 'max_feed_inventory' } + cyclus::toolkit::ResBuf inventory; // natural u + #pragma cyclus var {} + cyclus::toolkit::ResBuf tails; // depleted u + + // used to total intra-timestep swu and natu usage for meeting requests - + // these help enable time series generation. + double intra_timestep_swu_; + double intra_timestep_feed_; + + friend class EnrichmentTest; + // --- +}; + +} // namespace cycamore + +#endif // CYCAMORE_SRC_ENRICHMENT_FACILITY_H_ diff --git a/src/enrichment_facility.cc b/src/enrichment_facility.cc deleted file mode 100644 index 87976a78ab..0000000000 --- a/src/enrichment_facility.cc +++ /dev/null @@ -1,320 +0,0 @@ -// Implements the EnrichmentFacility class -#include "enrichment_facility.h" - -#include -#include -#include -#include - -#include - -namespace cycamore { - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EnrichmentFacility::EnrichmentFacility(cyclus::Context* ctx) - : cyclus::Facility(ctx), - tails_assay(0), - feed_assay(0), - swu_capacity(0), - initial_reserves(0), - in_commod(""), - in_recipe(""), - out_commod("") {} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EnrichmentFacility::~EnrichmentFacility() {} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::string EnrichmentFacility::str() { - std::stringstream ss; - ss << cyclus::Facility::str() - << " with enrichment facility parameters:" - << " * SWU capacity: " << SwuCapacity() - << " * Tails assay: " << TailsAssay() - << " * Feed assay: " << FeedAssay() - << " * Input cyclus::Commodity: " << in_commodity() - << " * Output cyclus::Commodity: " << out_commodity(); - return ss.str(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::Build(cyclus::Agent* parent) { - using cyclus::Material; - - Facility::Build(parent); - if (initial_reserves > 0) { - inventory.Push( - Material::Create( - this, initial_reserves, context()->GetRecipe(in_recipe))); - } - - LOG(cyclus::LEV_DEBUG2, "EnrFac") << "EnrichmentFacility " - << " entering the simuluation: "; - LOG(cyclus::LEV_DEBUG2, "EnrFac") << str(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::Tick() { - LOG(cyclus::LEV_INFO3, "EnrFac") << prototype() << " is ticking {"; - LOG(cyclus::LEV_INFO3, "EnrFac") << "}"; - current_swu_capacity = SwuCapacity(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::Tock() { - LOG(cyclus::LEV_INFO3, "EnrFac") << prototype() << " is tocking {"; - LOG(cyclus::LEV_INFO3, "EnrFac") << "}"; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::set::Ptr> -EnrichmentFacility::GetMatlRequests() { - using cyclus::Material; - using cyclus::RequestPortfolio; - using cyclus::Request; - - std::set::Ptr> ports; - RequestPortfolio::Ptr port(new RequestPortfolio()); - Material::Ptr mat = Request_(); - double amt = mat->quantity(); - - if (amt > cyclus::eps()) { - port->AddRequest(mat, this, in_commod); - ports.insert(port); - } - - return ports; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::AcceptMatlTrades( - const std::vector< std::pair, - cyclus::Material::Ptr> >& responses) { - // see - // http://stackoverflow.com/questions/5181183/boostshared-ptr-and-inheritance - std::vector< std::pair, - cyclus::Material::Ptr> >::const_iterator it; - for (it = responses.begin(); it != responses.end(); ++it) { - AddMat_(it->second); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::set::Ptr> -EnrichmentFacility::GetMatlBids( - cyclus::CommodMap::type& commod_requests) { - using cyclus::Bid; - using cyclus::BidPortfolio; - using cyclus::CapacityConstraint; - using cyclus::Converter; - using cyclus::Material; - using cyclus::Request; - - std::set::Ptr> ports; - - if (commod_requests.count(out_commod) > 0 && inventory.quantity() > 0) { - BidPortfolio::Ptr port(new BidPortfolio()); - - std::vector*>& requests = - commod_requests[out_commod]; - - std::vector*>::iterator it; - for (it = requests.begin(); it != requests.end(); ++it) { - Request* req = *it; - if (ValidReq(req->target())) { - Material::Ptr offer = Offer_(req->target()); - port->AddBid(req, offer, this); - } - } - - Converter::Ptr sc(new SWUConverter(feed_assay, tails_assay)); - Converter::Ptr nc(new NatUConverter(feed_assay, tails_assay)); - CapacityConstraint swu(swu_capacity, sc); - CapacityConstraint natu(inventory.quantity(), nc); - port->AddConstraint(swu); - port->AddConstraint(natu); - - LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() - << " adding a swu constraint of " - << swu.capacity(); - LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() - << " adding a natu constraint of " - << natu.capacity(); - - ports.insert(port); - } - return ports; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool EnrichmentFacility::ValidReq(const cyclus::Material::Ptr mat) { - cyclus::toolkit::MatQuery q(mat); - double u235 = q.atom_frac(922350000); - double u238 = q.atom_frac(922380000); - return (u238 > 0 && u235 / (u235 + u238) > TailsAssay()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::GetMatlTrades( - const std::vector< cyclus::Trade >& trades, - std::vector, - cyclus::Material::Ptr> >& responses) { - using cyclus::Material; - using cyclus::Trade; - - std::vector< Trade >::const_iterator it; - for (it = trades.begin(); it != trades.end(); ++it) { - Material::Ptr mat = it->bid->offer(); - double qty = it->amt; - Material::Ptr response = Enrich_(mat, qty); - responses.push_back(std::make_pair(*it, response)); - LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() - << " just received an order" - << " for " << it->amt - << " of " << out_commod; - } - - if (cyclus::IsNegative(current_swu_capacity)) { - throw cyclus::ValueError( - "EnrFac " + prototype() - + " is being asked to provide more than its SWU capacity."); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::AddMat_(cyclus::Material::Ptr mat) { - if (mat->comp() != context()->GetRecipe(in_recipe)) { - throw cyclus::ValueError( - "EnrichmentFacility recipe and material composition not the same."); - } - - LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() << " is initially holding " - << inventory.quantity() << " total."; - - try { - inventory.Push(mat); - } catch (cyclus::Error& e) { - e.msg(Agent::InformErrorMsg(e.msg())); - throw e; - } - - LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() << " added " - << mat->quantity() << " of " << in_commod - << " to its inventory, which is holding " - << inventory.quantity() << " total."; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr EnrichmentFacility::Request_() { - double qty = std::max(0.0, MaxInventorySize() - InventorySize()); - return cyclus::Material::CreateUntracked(qty, - context()->GetRecipe(in_recipe)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr EnrichmentFacility::Offer_(cyclus::Material::Ptr mat) { - cyclus::toolkit::MatQuery q(mat); - cyclus::CompMap comp; - comp[922350000] = q.atom_frac(922350000); - comp[922380000] = q.atom_frac(922380000); - return cyclus::Material::CreateUntracked( - mat->quantity(), cyclus::Composition::CreateFromAtom(comp)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr EnrichmentFacility::Enrich_( - cyclus::Material::Ptr mat, - double qty) { - using cyclus::Material; - using cyclus::ResCast; - using cyclus::toolkit::Assays; - using cyclus::toolkit::UraniumAssay; - using cyclus::toolkit::SwuRequired; - using cyclus::toolkit::FeedQty; - using cyclus::toolkit::TailsQty; - - // get enrichment parameters - Assays assays(FeedAssay(), UraniumAssay(mat), TailsAssay()); - double swu_req = SwuRequired(qty, assays); - double natu_req = FeedQty(qty, assays); - - // pop amount from inventory and blob it into one material - std::vector manifest; - try { - // required so popping doesn't take out too much - if (cyclus::AlmostEq(natu_req, inventory.quantity())) { - manifest = ResCast(inventory.PopN(inventory.count())); - } else { - manifest = ResCast(inventory.PopQty(natu_req)); - } - } catch (cyclus::Error& e) { - NatUConverter nc(feed_assay, tails_assay); - std::stringstream ss; - ss << " tried to remove " << natu_req - << " from its inventory of size " << inventory.quantity() - << " and the conversion of the material into natu is " - << nc.convert(mat); - throw cyclus::ValueError(Agent::InformErrorMsg(ss.str())); - } - Material::Ptr r = manifest[0]; - for (int i = 1; i < manifest.size(); ++i) { - r->Absorb(manifest[i]); - } - - // "enrich" it, but pull out the composition and quantity we require from the - // blob - cyclus::Composition::Ptr comp = mat->comp(); - Material::Ptr response = r->ExtractComp(qty, comp); - tails.Push(r); // add remainder to tails buffer - - current_swu_capacity -= swu_req; - - RecordEnrichment_(natu_req, swu_req); - - LOG(cyclus::LEV_INFO5, "EnrFac") << prototype() << - " has performed an enrichment: "; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Feed Qty: " - << natu_req; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Feed Assay: " - << assays.Feed() * 100; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Product Qty: " - << qty; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Product Assay: " - << assays.Product() * 100; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Tails Qty: " - << TailsQty(qty, assays); - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Tails Assay: " - << assays.Tails() * 100; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * SWU: " - << swu_req; - LOG(cyclus::LEV_INFO5, "EnrFac") << " * Current SWU capacity: " - << CurrentSwuCapacity(); - - return response; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacility::RecordEnrichment_(double natural_u, double swu) { - using cyclus::Context; - using cyclus::Agent; - - LOG(cyclus::LEV_DEBUG1, "EnrFac") << prototype() - << " has enriched a material:"; - LOG(cyclus::LEV_DEBUG1, "EnrFac") << " * Amount: " << natural_u; - LOG(cyclus::LEV_DEBUG1, "EnrFac") << " * SWU: " << swu; - - Context* ctx = Agent::context(); - ctx->NewDatum("Enrichments") - ->AddVal("ID", id()) - ->AddVal("Time", ctx->time()) - ->AddVal("Natural_Uranium", natural_u) - ->AddVal("SWU", swu) - ->Record(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -extern "C" cyclus::Agent* ConstructEnrichmentFacility(cyclus::Context* ctx) { - return new EnrichmentFacility(ctx); -} - -} // namespace cycamore diff --git a/src/enrichment_facility.h b/src/enrichment_facility.h deleted file mode 100644 index b0d1bb0eec..0000000000 --- a/src/enrichment_facility.h +++ /dev/null @@ -1,313 +0,0 @@ -#ifndef CYCAMORE_SRC_ENRICHMENT_FACILITY_H_ -#define CYCAMORE_SRC_ENRICHMENT_FACILITY_H_ - -#include - -#include "cyclus.h" - -namespace cycamore { - -/// @class SWUConverter -/// -/// @brief The SWUConverter is a simple Converter class for material to -/// determine the amount of SWU required for their proposed enrichment -class SWUConverter : public cyclus::Converter { - public: - SWUConverter(double feed, double tails) : feed_(feed), tails_(tails) {} - virtual ~SWUConverter() {} - - /// @brief provides a conversion for the SWU required - virtual double convert( - cyclus::Material::Ptr m, - cyclus::Arc const * a = NULL, - cyclus::ExchangeTranslationContext - const * ctx = NULL) const { - cyclus::toolkit::Assays assays(feed_, cyclus::toolkit::UraniumAssay(m), - tails_); - return cyclus::toolkit::SwuRequired(m->quantity(), assays); - } - - /// @returns true if Converter is a SWUConverter and feed and tails equal - virtual bool operator==(Converter& other) const { - SWUConverter* cast = dynamic_cast(&other); - return cast != NULL && - feed_ == cast->feed_ && - tails_ == cast->tails_; - } - - private: - double feed_, tails_; -}; - -/// @class NatUConverter -/// -/// @brief The NatUConverter is a simple Converter class for material to -/// determine the amount of natural uranium required for their proposed -/// enrichment -class NatUConverter : public cyclus::Converter { - public: - NatUConverter(double feed, double tails) : feed_(feed), tails_(tails) {} - virtual ~NatUConverter() {} - - /// @brief provides a conversion for the amount of natural Uranium required - virtual double convert( - cyclus::Material::Ptr m, - cyclus::Arc const * a = NULL, - cyclus::ExchangeTranslationContext - const * ctx = NULL) const { - cyclus::toolkit::Assays assays(feed_, cyclus::toolkit::UraniumAssay(m), - tails_); - return cyclus::toolkit::FeedQty(m->quantity(), assays); - } - - /// @returns true if Converter is a NatUConverter and feed and tails equal - virtual bool operator==(Converter& other) const { - NatUConverter* cast = dynamic_cast(&other); - return cast != NULL && - feed_ == cast->feed_ && - tails_ == cast->tails_; - } - - private: - double feed_, tails_; -}; - -/// @class EnrichmentFacility -/// -/// @section introduction Introduction -/// The EnrichmentFacility is a simple Agent to agent the enriching of natural -/// Uranium in a Cyclus simulation. It requests its input recipe (nominally -/// natural Uranium), and produces any amount of enriched Uranium, given the its -/// natural uranium inventory constraint and its SWU capacity constraint. -/// -/// @section requests Requests -/// The EnrichmentFacility will request from the cyclus::ResourceExchange a -/// cyclus::Material whose quantity is its remaining inventory capacity and whose -/// composition is that of its input recipe. -/// -/// @section acctrade Accepting Trades -/// The EnrichmentFacility adds any accepted trades to its inventory. -/// -/// @section bids Bids -/// The EnrichmentFacility will bid on any request for its output commodity. It -/// will bid either the request quantity, or the quanity associated with either -/// its SWU constraint or natural uranium constraint, whichever is lower. -/// -/// @section extrades Executing Trades -/// The EnrichmentFacility will execute trades for its output commodity in the -/// following manner: -/// #. Determine the trade's quantity and product assay -/// #. Determine the natural Uranium and SWU requires to create that product -/// #. Remove the required quantity of natural Uranium from its inventory -/// #. Extract the appropriate composition of enriched Uranium -/// #. Send the enriched Uranium as the trade resource -/// -/// @section gotchas Gotchas -/// #. In its current form, the EnrichmentFacility can only accept -/// cyclus::Material having the composition of its input recipe. If a -/// cyclus::Material of a different composition is sent to it, an exception will -/// be thrown. -/// -/// #. During the trading phase, an exception will be thrown if either the -/// EnrichmentFacility's SWU or inventory constraint is breached. -/// -/// @section improvements Improvments -/// The primary improvement to the EnrichmentFacility would be to relax the -/// requirement that all input material have the in_recipe composition (i.e., -/// allow different base enrichments of Uranium). -/// -/// How would I go about doing so? I'd likely develop an EnrichmentBuffer-type -/// class that can be queried as to its SWU and natural Uranium capacity. -class EnrichmentFacility : public cyclus::Facility { - public: - // --- Module Members --- -/// Constructor for the EnrichmentFacility class -/// @param ctx the cyclus context for access to simulation-wide parameters - EnrichmentFacility(cyclus::Context* ctx); - -/// Destructor for the EnrichmentFacility class - virtual ~EnrichmentFacility(); - - #pragma cyclus - - #pragma cyclus note {"doc": "An enrichment facility that intakes a commodity " \ - "(usually natural uranium) and supplies a user-" \ - "specified enriched product based on SWU capacity", \ - "niche": "enrichment"} - -/// Print information about this agent - virtual std::string str(); - // --- - - // --- Facility Members --- - /// perform module-specific tasks when entering the simulation - virtual void Build(cyclus::Agent* parent); - // --- - - // --- Agent Members --- - /// Each facility is prompted to do its beginning-of-time-step - /// stuff at the tick of the timer. - - /// @param time is the time to perform the tick - virtual void Tick(); - - /// Each facility is prompted to its end-of-time-step - /// stuff on the tock of the timer. - - /// @param time is the time to perform the tock - virtual void Tock(); - - /// @brief The EnrichmentFacility request Materials of its given - /// commodity. - virtual std::set::Ptr> - GetMatlRequests(); - - /// @brief The EnrichmentFacility place accepted trade Materials in their - /// Inventory - virtual void AcceptMatlTrades( - const std::vector< std::pair, - cyclus::Material::Ptr> >& responses); - - /// @brief Responds to each request for this facility's commodity. If a given - /// request is more than this facility's inventory or SWU capacity, it will - /// offer its minimum of its capacities. - virtual std::set::Ptr> - GetMatlBids(cyclus::CommodMap::type& - commod_requests); - - /// @brief respond to each trade with a material enriched to the appropriate - /// level given this facility's inventory - /// - /// @param trades all trades in which this trader is the supplier - /// @param responses a container to populate with responses to each trade - virtual void GetMatlTrades( - const std::vector< cyclus::Trade >& trades, - std::vector, - cyclus::Material::Ptr> >& responses); - // --- - - // --- EnrichmentFacility Members --- - /// @brief Determines if a particular material is a valid request to respond - /// to. Valid requests must contain U235 and U238 and must have a relative - /// U235-to-U238 ratio less than this facility's tails_assay(). - /// @return true if the above description is met by the material - bool ValidReq(const cyclus::Material::Ptr mat); - - inline void in_commodity(std::string in_com) { in_commod = in_com; } - - inline std::string in_commodity() const { return in_commod; } - - inline void out_commodity(std::string out_com) { - out_commod = out_com; - } - - inline std::string out_commodity() const { return out_commod; } - - inline void InRecipe(std::string in_rec) { in_recipe = in_rec; } - - inline std::string InRecipe() const { return in_recipe; } - - inline void SetMaxInventorySize(double size) { - max_inv_size = size; - inventory.set_capacity(size); - } - - inline double MaxInventorySize() const { return inventory.capacity(); } - - inline double InventorySize() const { return inventory.quantity(); } - - inline void FeedAssay(double assay) { feed_assay = assay; } - - inline double FeedAssay() const { return feed_assay; } - - inline void TailsAssay(double assay) { tails_assay = assay; } - - inline double TailsAssay() const { return tails_assay; } - - inline void SwuCapacity(double capacity) { - swu_capacity = capacity; - current_swu_capacity = swu_capacity; - } - - inline double SwuCapacity() const { return swu_capacity; } - - inline double CurrentSwuCapacity() const { return current_swu_capacity; } - - /// @brief this facility's initial conditions - inline void InitialReserves(double qty) { initial_reserves = qty; } - inline double InitialReserves() const { return initial_reserves; } - - inline const cyclus::toolkit::ResourceBuff& Tails() const { return tails; } - - private: - /// @brief adds a material into the natural uranium inventory - /// @throws if the material is not the same composition as the in_recipe - void AddMat_(cyclus::Material::Ptr mat); - - /// @brief generates a request for this facility given its current state. The - /// quantity of the material will be equal to the remaining inventory size. - cyclus::Material::Ptr Request_(); - - /// @brief Generates a material offer for a given request. The response - /// composition will be comprised only of U235 and U238 at their relative ratio - /// in the requested material. The response quantity will be the same as the - /// requested commodity. - /// - /// @param req the requested material being responded to - cyclus::Material::Ptr Offer_(cyclus::Material::Ptr req); - - cyclus::Material::Ptr Enrich_(cyclus::Material::Ptr mat, double qty); - - /// @brief records and enrichment with the cyclus::Recorder - void RecordEnrichment_(double natural_u, double swu); - - #pragma cyclus var {"tooltip": "input commodity", \ - "doc": "commodity that the enrichment facility accepts", \ - "uitype": "incommodity"} - std::string in_commod; - #pragma cyclus var {"tooltip": "output commodity", \ - "doc": "commodity that the enrichment facility supplies", \ - "uitype": "outcommodity"} - std::string out_commod; - #pragma cyclus var {"tooltip": "input commodity recipe", \ - "doc": "recipe for enrichment facility's input commodity", \ - "uitype": "recipe"} - std::string in_recipe; - - #pragma cyclus var {"default": 0.03, "tooltip": "tails assay", \ - "doc": "tails assay from the enrichment process"} - double tails_assay; - #pragma cyclus var {"default": 1e299, "tooltip": "SWU capacity", \ - "doc": "separative work unit (SWU) capcity of " \ - "enrichment facility"} - double swu_capacity; - #pragma cyclus var {"default": 1e299, "tooltip": "maximum inventory size", \ - "doc": "maximum inventory capacity of natural uranium in " \ - "the enrichment facility"} - double max_inv_size; - #pragma cyclus var {"default": 0, "tooltip": "initial uranium reserves", \ - "doc": "amount of natural uranium stored at the " \ - "enrichment facility at the beginning of the " \ - "simulation"} - double initial_reserves; - #pragma cyclus var {'derived_init': 'current_swu_capacity = swu_capacity;'} - double current_swu_capacity; - #pragma cyclus var {\ - 'derived_init': "cyclus::Material::Ptr feed = "\ - "cyclus::Material::CreateUntracked(0, context()->GetRecipe(in_recipe)); "\ - "feed_assay = cyclus::toolkit::UraniumAssay(feed);", \ - "tooltip": "feed assay", \ - "doc": "feed assay for the enrichment process"} - double feed_assay; - #pragma cyclus var {'capacity': 'max_inv_size'} - cyclus::toolkit::ResourceBuff inventory; // of natl u - #pragma cyclus var {} - cyclus::toolkit::ResourceBuff tails; // depleted u - - friend class EnrichmentFacilityTest; - // --- -}; - -} // namespace cycamore - -#endif // CYCAMORE_SRC_ENRICHMENT_FACILITY_H_ diff --git a/src/enrichment_facility_tests.cc b/src/enrichment_facility_tests.cc deleted file mode 100644 index 2b29e622ae..0000000000 --- a/src/enrichment_facility_tests.cc +++ /dev/null @@ -1,613 +0,0 @@ -#include - -#include - -#include "facility_tests.h" -#include "toolkit/mat_query.h" -#include "agent_tests.h" -#include "resource_helpers.h" -#include "infile_tree.h" -#include "env.h" - -#include "enrichment_facility_tests.h" - -namespace cycamore { - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacilityTest::SetUp() { - cyclus::Env::SetNucDataPath(); - cyclus::Context* ctx = tc_.get(); - src_facility = new EnrichmentFacility(ctx); - trader = tc_.trader(); - InitParameters(); - SetUpSource(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacilityTest::TearDown() { - delete src_facility; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacilityTest::InitParameters() { - cyclus::Context* ctx = tc_.get(); - - in_commod = "incommod"; - out_commod = "outcommod"; - - in_recipe = "recipe"; - feed_assay = 0.0072; - - cyclus::CompMap v; - v[922350000] = feed_assay; - v[922380000] = 1 - feed_assay; - recipe = cyclus::Composition::CreateFromAtom(v); - ctx->AddRecipe(in_recipe, recipe); - - tails_assay = 0.002; - swu_capacity = 100; - inv_size = 5; - - reserves = 105.5; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacilityTest::SetUpSource() { - src_facility->InRecipe(in_recipe); - src_facility->in_commodity(in_commod); - src_facility->out_commodity(out_commod); - src_facility->TailsAssay(tails_assay); - src_facility->FeedAssay(feed_assay); - src_facility->SetMaxInventorySize(inv_size); - src_facility->SwuCapacity(swu_capacity); - src_facility->InitialReserves(reserves); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr EnrichmentFacilityTest::GetMat(double qty) { - return cyclus::Material::CreateUntracked(qty, - tc_.get()->GetRecipe(in_recipe)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr EnrichmentFacilityTest::GetReqMat(double qty, - double enr) { - cyclus::CompMap v; - v[922350000] = enr; - v[922380000] = 1 - enr; - return cyclus::Material::CreateUntracked( - qty, cyclus::Composition::CreateFromAtom(v)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EnrichmentFacilityTest::DoAddMat(cyclus::Material::Ptr mat) { - src_facility->AddMat_(mat); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr EnrichmentFacilityTest::DoRequest() { - return src_facility->Request_(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr -EnrichmentFacilityTest::DoOffer(cyclus::Material::Ptr mat) { - return src_facility->Offer_(mat); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr -EnrichmentFacilityTest::DoEnrich(cyclus::Material::Ptr mat, double qty) { - return src_facility->Enrich_(mat, qty); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, InitialState) { - EXPECT_EQ(in_recipe, src_facility->InRecipe()); - EXPECT_EQ(in_commod, src_facility->in_commodity()); - EXPECT_EQ(out_commod, src_facility->out_commodity()); - EXPECT_DOUBLE_EQ(tails_assay, src_facility->TailsAssay()); - EXPECT_DOUBLE_EQ(feed_assay, src_facility->FeedAssay()); - EXPECT_DOUBLE_EQ(inv_size, src_facility->MaxInventorySize()); - EXPECT_DOUBLE_EQ(0.0, src_facility->InventorySize()); - EXPECT_DOUBLE_EQ(swu_capacity, src_facility->SwuCapacity()); - EXPECT_EQ(reserves, src_facility->InitialReserves()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, DISABLED_XMLInit) { - std::stringstream ss; - ss << "" - << "fooname" - << "" - << "" - << " " - << " " << in_commod << "" - << " " << in_recipe << "" - << " " << inv_size << "" - << " " - << " " - << " " << out_commod << "" - << " " << tails_assay << "" - << " " << swu_capacity << "" - << " " - << " " - << " " << reserves << "" - << " " - << "" - << "" - << ""; - - cyclus::XMLParser p; - p.Init(ss); - cyclus::InfileTree engine(p); - cycamore::EnrichmentFacility fac(tc_.get()); - - // EXPECT_NO_THROW(fac.InitFrom(&engine);); - EXPECT_EQ(in_recipe, fac.InRecipe()); - EXPECT_EQ(in_commod, fac.in_commodity()); - EXPECT_EQ(out_commod, fac.out_commodity()); - EXPECT_DOUBLE_EQ(tails_assay, fac.TailsAssay()); - EXPECT_DOUBLE_EQ(feed_assay, fac.FeedAssay()); - EXPECT_DOUBLE_EQ(inv_size, fac.MaxInventorySize()); - EXPECT_DOUBLE_EQ(0.0, fac.InventorySize()); - EXPECT_DOUBLE_EQ(swu_capacity, fac.SwuCapacity()); - EXPECT_EQ(reserves, fac.InitialReserves()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Clone) { - cyclus::Context* ctx = tc_.get(); - - cycamore::EnrichmentFacility* cloned_fac = - dynamic_cast(src_facility->Clone()); - - EXPECT_EQ(in_recipe, cloned_fac->InRecipe()); - EXPECT_EQ(in_commod, cloned_fac->in_commodity()); - EXPECT_EQ(out_commod, cloned_fac->out_commodity()); - EXPECT_DOUBLE_EQ(tails_assay, cloned_fac->TailsAssay()); - EXPECT_DOUBLE_EQ(feed_assay, cloned_fac->FeedAssay()); - EXPECT_DOUBLE_EQ(inv_size, cloned_fac->MaxInventorySize()); - EXPECT_DOUBLE_EQ(0.0, cloned_fac->InventorySize()); - EXPECT_DOUBLE_EQ(swu_capacity, cloned_fac->SwuCapacity()); - EXPECT_EQ(reserves, cloned_fac->InitialReserves()); - - delete cloned_fac; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, AddMat) { - EXPECT_THROW(DoAddMat(test_helpers::get_mat()), cyclus::ValueError); - EXPECT_THROW(DoAddMat(GetMat(inv_size + 1)), cyclus::Error); - EXPECT_NO_THROW(DoAddMat(GetMat(inv_size))); - EXPECT_THROW(DoAddMat(GetMat(1)), cyclus::Error); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Request) { - double req = inv_size; - double add = 0; - cyclus::Material::Ptr mat = DoRequest(); - EXPECT_DOUBLE_EQ(mat->quantity(), req); - EXPECT_EQ(mat->comp(), tc_.get()->GetRecipe(in_recipe)); - - add = 2 * inv_size / 3; - req -= add; - DoAddMat(GetMat(add)); - mat = DoRequest(); - EXPECT_DOUBLE_EQ(mat->quantity(), req); - EXPECT_EQ(mat->comp(), tc_.get()->GetRecipe(in_recipe)); - - add = inv_size / 3; - req = 0; - DoAddMat(GetMat(add)); - mat = DoRequest(); - EXPECT_DOUBLE_EQ(mat->quantity(), req); - EXPECT_EQ(mat->comp(), tc_.get()->GetRecipe(in_recipe)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Offer) { - using cyclus::CompMap; - using cyclus::Composition; - using cyclus::Material; - using cyclus::toolkit::MatQuery; - - double qty = 4.5; - double u234 = 1.0; - double u235 = 1.0; - double u238 = 2.0; - cyclus::CompMap v; - v[94239] = u234; - v[922350000] = u235; - v[922380000] = u238; - Material::Ptr mat = - DoOffer(Material::CreateUntracked(qty, Composition::CreateFromAtom(v))); - - MatQuery q(mat); - - EXPECT_DOUBLE_EQ(q.atom_frac(94239), 0.0); - EXPECT_DOUBLE_EQ(q.atom_frac(922350000), u235 / (u235 + u238)); - EXPECT_DOUBLE_EQ(q.atom_frac(922380000), u238 / (u235 + u238)); - EXPECT_DOUBLE_EQ(mat->quantity(), qty); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, ValidReq) { - using cyclus::CompMap; - using cyclus::Composition; - using cyclus::Material; - - double qty = 4.5; // some magic number - - cyclus::CompMap v1; - v1[922350000] = 1; - Material::Ptr mat = Material::CreateUntracked(qty, - Composition::CreateFromAtom(v1)); - EXPECT_TRUE(!src_facility->ValidReq(mat)); // u238 = 0 - - cyclus::CompMap v2; - v2[922350000] = tails_assay; - v2[922380000] = 1 - tails_assay; - mat = Material::CreateUntracked(qty, Composition::CreateFromAtom(v2)); - EXPECT_TRUE(!src_facility->ValidReq(mat)); // u235 / (u235 + u238) <= tails_assay - - cyclus::CompMap v3; - v3[922350000] = 1; - v3[922380000] = 1; - mat = Material::CreateUntracked(qty, Composition::CreateFromAtom(v3)); - EXPECT_TRUE(src_facility->ValidReq(mat)); // valid -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, EmptyRequests) { - using cyclus::Material; - using cyclus::RequestPortfolio; - - src_facility->SetMaxInventorySize(src_facility->InventorySize()); - std::set::Ptr> ports = - src_facility->GetMatlRequests(); - ports = src_facility->GetMatlRequests(); - EXPECT_TRUE(ports.empty()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, AddRequests) { - using cyclus::Request; - using cyclus::RequestPortfolio; - using cyclus::CapacityConstraint; - using cyclus::Converter; - using cyclus::Material; - - // a request is made for the current available inventory amount - - std::set::Ptr> ports = - src_facility->GetMatlRequests(); - - ASSERT_EQ(ports.size(), 1); - ASSERT_EQ(ports.begin()->get()->qty(), inv_size); - - const std::vector*>& requests = - ports.begin()->get()->requests(); - ASSERT_EQ(requests.size(), 1); - - Request* req = *requests.begin(); - EXPECT_EQ(req->requester(), src_facility); - EXPECT_EQ(req->commodity(), in_commod); - - const std::set< CapacityConstraint >& constraints = - ports.begin()->get()->constraints(); - EXPECT_EQ(constraints.size(), 0); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Extract) { - using cyclus::Material; - cyclus::Env::SetNucDataPath(); - double qty = 1000; // 5 kg - Material::Ptr base = GetMat(qty); - double time = tc_.get()->time(); - // cyclus::Material::Create(src_facility, qty, - // tc_.get()->GetRecipe(in_recipe)); - Material::Ptr base2 = GetMat(qty); - base->Absorb(base2); - double product_assay = 0.05; // of 5 w/o enriched U - cyclus::CompMap v; - v[922350000] = product_assay; - v[922380000] = 1 - product_assay; - // target qty need not be = to request qty - Material::Ptr target = cyclus::Material::CreateUntracked( - 5, cyclus::Composition::CreateFromMass(v)); - Material::Ptr response = base->ExtractComp(6, target->comp()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Accept) { - using cyclus::Bid; - using cyclus::Material; - using cyclus::Request; - using cyclus::Trade; - - // an enrichment facility gets two trades, each for 1/3 of its inv size - // note that comp != recipe is covered by AddMat tests - // note that qty >= inv capacity is covered by toolkit::ResourceBuff tests - - double qty = inv_size / 3; - std::vector< std::pair, - cyclus::Material::Ptr> > responses; - - Request* req1 = - Request::Create(DoRequest(), src_facility, in_commod); - Bid* bid1 = - Bid::Create(req1, GetMat(qty), trader); - - Request* req2 = - Request::Create(DoRequest(), src_facility, in_commod); - Bid* bid2 = Bid::Create(req2, GetMat(qty), trader); - - Trade trade1(req1, bid1, qty); - responses.push_back(std::make_pair(trade1, GetMat(qty))); - Trade trade2(req2, bid2, qty); - responses.push_back(std::make_pair(trade2, GetMat(qty))); - - EXPECT_DOUBLE_EQ(0.0, src_facility->InventorySize()); - src_facility->AcceptMatlTrades(responses); - EXPECT_DOUBLE_EQ(qty * 2, src_facility->InventorySize()); - - delete bid2; - delete bid1; - delete req2; - delete req1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, AddBids) { - using cyclus::Bid; - using cyclus::BidPortfolio; - using cyclus::CapacityConstraint; - using cyclus::Converter; - using cyclus::ExchangeContext; - using cyclus::Material; - cyclus::Env::SetNucDataPath(); - // an enrichment facility bids on nreqs requests - // note that bid response is covered by Bid tests - // note that validity of requests is covered by ValidReq tests - int nreqs = 5; - int nvalid = 4; - - // set up inventory - double current_size = inv_size / 2; // test something other than max size - DoAddMat(GetMat(current_size)); - - boost::shared_ptr< cyclus::ExchangeContext > - ec = GetContext(nreqs, nvalid); - - std::set::Ptr> ports = - src_facility->GetMatlBids(ec.get()->commod_requests); - - ASSERT_TRUE(ports.size() > 0); - EXPECT_EQ(ports.size(), 1); - - BidPortfolio::Ptr port = *ports.begin(); - EXPECT_EQ(port->bidder(), src_facility); - EXPECT_EQ(port->bids().size(), nvalid); - - const std::set< CapacityConstraint >& constrs = port->constraints(); - Converter::Ptr sc(new SWUConverter(feed_assay, tails_assay)); - Converter::Ptr nc(new NatUConverter(feed_assay, tails_assay)); - CapacityConstraint swu(swu_capacity, sc); - CapacityConstraint natu(current_size, nc); - EXPECT_EQ(constrs.size(), 2); - EXPECT_TRUE(*constrs.begin() == swu || *(++constrs.begin()) == swu); - EXPECT_TRUE(*constrs.begin() == natu || *(++constrs.begin()) == natu); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -boost::shared_ptr< cyclus::ExchangeContext > -EnrichmentFacilityTest::GetContext(int nreqs, int nvalid) { - using cyclus::ExchangeContext; - using cyclus::Material; - using cyclus::Request; - using test_helpers::get_mat; - - boost::shared_ptr< ExchangeContext > - ec(new ExchangeContext()); - for (int i = 0; i < nvalid; i++) { - ec->AddRequest( - Request::Create(GetReqMat(1.0, 0.05), trader, out_commod)); - } - for (int i = 0; i < nreqs - nvalid; i++) { - ec->AddRequest( - // get_mat returns a material of only u235, which is not valid - Request::Create(get_mat(), trader, out_commod)); - } - return ec; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, BidConverters) { - // this test is designed to confirm that the bid response behavior matches the - // converter behavior. - using cyclus::CompMap; - using cyclus::Material; - using cyclus::toolkit::MatQuery; - using cyclus::Composition; - using cyclus::toolkit::Assays; - using cyclus::toolkit::UraniumAssay; - using cyclus::toolkit::SwuRequired; - using cyclus::toolkit::FeedQty; - cyclus::Env::SetNucDataPath(); - - double qty = 5; // 5 kg - double product_assay = 0.05; // of 5 w/o enriched U - CompMap v; - v[922350000] = product_assay; - v[922380000] = 1 - product_assay; - v[94239] = 0.5; // 94239 shouldn't be taken into account - Material::Ptr target = Material::CreateUntracked( - qty, Composition::CreateFromMass(v)); - - SWUConverter swuc(feed_assay, tails_assay); - NatUConverter natuc(feed_assay, tails_assay); - - Material::Ptr offer = DoOffer(target); - - EXPECT_NEAR(swuc.convert(target), swuc.convert(offer), 0.001); - EXPECT_NEAR(natuc.convert(target), natuc.convert(offer), 0.001); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Enrich) { - // this test asks the facility to enrich a material that results in an amount - // of natural uranium required that is exactly its inventory level. that - // inventory will be comprised of two materials to test the manifest/absorb - // strategy employed in Enrich_. - using cyclus::CompMap; - using cyclus::Material; - using cyclus::toolkit::MatQuery; - using cyclus::Composition; - using cyclus::toolkit::Assays; - using cyclus::toolkit::UraniumAssay; - using cyclus::toolkit::SwuRequired; - using cyclus::toolkit::FeedQty; - - double qty = 5; // 5 kg - double product_assay = 0.05; // of 5 w/o enriched U - cyclus::CompMap v; - v[922350000] = product_assay; - v[922380000] = 1 - product_assay; - // target qty need not be = to request qty - Material::Ptr target = cyclus::Material::CreateUntracked( - qty + 10, cyclus::Composition::CreateFromMass(v)); - - Assays assays(feed_assay, UraniumAssay(target), tails_assay); - double swu_req = SwuRequired(qty, assays); - double natu_req = FeedQty(qty, assays); - double tails_qty = TailsQty(qty, assays); - - double swu_cap = swu_req * 5; - src_facility->SwuCapacity(swu_cap); - src_facility->SetMaxInventorySize(natu_req); - DoAddMat(GetMat(natu_req / 2)); - DoAddMat(GetMat(natu_req / 2)); - - Material::Ptr response; - EXPECT_NO_THROW(response = DoEnrich(target, qty)); - EXPECT_DOUBLE_EQ(src_facility->CurrentSwuCapacity(), swu_cap - swu_req); - EXPECT_DOUBLE_EQ(src_facility->Tails().quantity(), tails_qty); - - MatQuery q(response); - EXPECT_EQ(response->quantity(), qty); - EXPECT_EQ(q.mass_frac(922350000), product_assay); - EXPECT_EQ(q.mass_frac(922380000), 1 - product_assay); - - // test too much natu request - DoAddMat(GetMat(natu_req - 1)); - EXPECT_THROW(response = DoEnrich(target, qty), cyclus::Error); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(EnrichmentFacilityTest, Response) { - // this test asks the facility to respond to multiple requests for enriched - // uranium. two requests are provided, whose total equals the swu capacity of - // the facility while not exceeding its inventory capacity (that's taken care - // of in the Enrich tests). - // - // note that response quantity and quality need not be tested, because they - // are covered by the Enrich and Offer tests - using cyclus::Bid; - using cyclus::CompMap; - using cyclus::Composition; - using cyclus::Material; - using cyclus::Request; - using cyclus::Trade; - using cyclus::toolkit::MatQuery; - using cyclus::toolkit::Assays; - using cyclus::toolkit::FeedQty; - using cyclus::toolkit::SwuRequired; - using cyclus::toolkit::UraniumAssay; - using test_helpers::get_mat; - - // problem set up - std::vector< cyclus::Trade > trades; - std::vector, - cyclus::Material::Ptr> > responses; - - double qty = 5; // 5 kg - double trade_qty = qty / 3; - double product_assay = 0.05; // of 5 w/o enriched U - cyclus::CompMap v; - v[922350000] = product_assay; - v[922380000] = 1 - product_assay; - // target qty need not be = to request qty - Material::Ptr target = cyclus::Material::CreateUntracked( - qty + 10, cyclus::Composition::CreateFromMass(v)); - - Assays assays(feed_assay, UraniumAssay(target), tails_assay); - double swu_req = SwuRequired(qty, assays); - double natu_req = FeedQty(qty, assays); - - src_facility->SetMaxInventorySize(natu_req * 4); // not capacitated by nat u - src_facility->SwuCapacity(swu_req); // swu capacitated - - // Null response - src_facility->GetMatlTrades(trades, responses); - EXPECT_NO_THROW(); - EXPECT_EQ(responses.size(), 0); - - // set up state - DoAddMat(GetMat(natu_req * 2)); - - Request* req = - Request::Create(target, trader, out_commod); - Bid* bid = Bid::Create(req, target, src_facility); - Trade trade(req, bid, trade_qty); - trades.push_back(trade); - - // 1 trade, SWU < SWU cap - ASSERT_DOUBLE_EQ(src_facility->CurrentSwuCapacity(), swu_req); - src_facility->GetMatlTrades(trades, responses); - ASSERT_EQ(responses.size(), 1); - EXPECT_DOUBLE_EQ(src_facility->CurrentSwuCapacity(), - swu_req - SwuRequired(trade_qty, assays)); - - // 2 trades, SWU = SWU cap - ASSERT_GT(src_facility->CurrentSwuCapacity() - 2 * swu_req / 3, - -1 * cyclus::eps()); - trades.push_back(trade); - responses.clear(); - EXPECT_NO_THROW(src_facility->GetMatlTrades(trades, responses)); - EXPECT_EQ(responses.size(), 2); - EXPECT_TRUE(cyclus::AlmostEq(src_facility->CurrentSwuCapacity(), 0)); - - // too much qty, capn! - trade = Trade(req, bid, 1); // a small number - trades.clear(); - trades.push_back(trade); - EXPECT_THROW(src_facility->GetMatlTrades(trades, responses), - cyclus::ValueError); - - // reset! - src_facility->Tick(); - EXPECT_DOUBLE_EQ(src_facility->CurrentSwuCapacity(), swu_req); -} - -} // namespace cycamore - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Agent* EnrichmentFacilityConstructor(cyclus::Context* ctx) { - return new cycamore::EnrichmentFacility(ctx); -} - -// required to get functionality in cyclus agent unit tests library -#ifndef CYCLUS_AGENT_TESTS_CONNECTED -int ConnectAgentTests(); -static int cyclus_agent_tests_connected = ConnectAgentTests(); -#define CYCLUS_AGENT_TESTS_CONNECTED cyclus_agent_tests_connected -#endif // CYCLUS_AGENT_TESTS_CONNECTED - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -INSTANTIATE_TEST_CASE_P(EnrichmentFac, FacilityTests, - Values(&EnrichmentFacilityConstructor)); -INSTANTIATE_TEST_CASE_P(EnrichmentFac, AgentTests, - Values(&EnrichmentFacilityConstructor)); diff --git a/src/enrichment_tests.cc b/src/enrichment_tests.cc new file mode 100644 index 0000000000..ac84221b33 --- /dev/null +++ b/src/enrichment_tests.cc @@ -0,0 +1,689 @@ +#include + +#include + +#include "facility_tests.h" +#include "toolkit/mat_query.h" +#include "agent_tests.h" +#include "resource_helpers.h" +#include "infile_tree.h" +#include "env.h" + +#include "enrichment_tests.h" + +using cyclus::QueryResult; +using cyclus::Cond; +using cyclus::CompMap; +using cyclus::toolkit::MatQuery; +using pyne::nucname::id; +using cyclus::Material; + +namespace cycamore { + +Composition::Ptr c_nou235() { + cyclus::CompMap m; + m[922380000] = 1.0; + return Composition::CreateFromMass(m); +}; +Composition::Ptr c_natu1() { + cyclus::CompMap m; + m[922350000] = 0.007; + m[922380000] = 0.993; + return Composition::CreateFromMass(m); +}; +Composition::Ptr c_natu2() { + cyclus::CompMap m; + m[922350000] = 0.01; + m[922380000] = 0.99; + return Composition::CreateFromMass(m); +}; +Composition::Ptr c_leu() { + cyclus::CompMap m; + m[922350000] = 0.04; + m[922380000] = 0.96; + return Composition::CreateFromMass(m); +}; +Composition::Ptr c_heu() { + cyclus::CompMap m; + m[922350000] = 0.20; + m[922380000] = 0.80; + return Composition::CreateFromMass(m); +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, RequestQty) { + // this tests verifies that requests for input material are fulfilled + // without providing any extra + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 1.0 " + " 0.003 "; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + sim.AddRecipe("natu1", c_natu1()); + + sim.AddSource("natu") + .recipe("natu1") + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("natu"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + // Should be only one transaction into the EF, + // and it should be exactly 1kg of natu + EXPECT_EQ(1.0, qr.rows.size()); + EXPECT_NEAR(1.0, m->quantity(), 1e-10) << + "matched trade provides the wrong quantity of material"; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, CheckSWUConstraint) { + // Tests that request for enrichment that exceeds the SWU constraint + // fulfilled only up to the available SWU. + // Also confirms that initial_feed flag works. + // 388 SWU = 10kg 80% enriched HEU from 486kg feed matl + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 " + " 1000 " + " 195 "; + + int simdur = 1; + + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + + sim.AddRecipe("natu1", c_natu1()); + sim.AddRecipe("heu", c_heu()); + + sim.AddSink("enr_u") + .recipe("heu") + .capacity(10) + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("enr_u"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + EXPECT_EQ(1.0, qr.rows.size()); + EXPECT_NEAR(5.0, m->quantity(), 0.1) << + "traded quantity exceeds SWU constraint"; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, CheckCapConstraint) { + // Tests that a request for more material than is available in + // inventory is partially filled with only the inventory quantity. + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 " + " 243 "; + + int simdur = 1; + + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + + + sim.AddRecipe("natu1", c_natu1()); + sim.AddRecipe("heu", c_heu()); + + sim.AddSink("enr_u") + .recipe("heu") + .capacity(10) + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("enr_u"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + EXPECT_EQ(1.0, qr.rows.size()); + EXPECT_NEAR(5.0, m->quantity(), 0.01) << + "traded quantity exceeds capacity constraint"; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, RequestEnrich) { + // this tests verifies that requests for output material exceeding + // the maximum allowed enrichment are not fulfilled. + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 " + " 0.20 "; + + int simdur = 2; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + sim.AddRecipe("natu1", c_natu1()); + sim.AddRecipe("leu", c_leu()); + sim.AddRecipe("heu", c_heu()); + + sim.AddSource("natu") + .recipe("natu1") + .Finalize(); + sim.AddSink("enr_u") + .recipe("leu") + .capacity(1.0) + .Finalize(); + sim.AddSink("enr_u") + .recipe("heu") + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("enr_u"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + // Should be only one transaction out of the EF, + // and it should be 1kg of LEU + EXPECT_EQ(1.0, qr.rows.size()); + EXPECT_NEAR(1.0, m->quantity(), 0.01) << + "Not providing the requested quantity" ; + + CompMap got = m->comp()->mass(); + CompMap want = c_leu()->mass(); + cyclus::compmath::Normalize(&got); + cyclus::compmath::Normalize(&want); + + CompMap::iterator it; + for (it = want.begin(); it != want.end(); ++it) { + EXPECT_DOUBLE_EQ(it->second, got[it->first]) << + "nuclide qty off: " << pyne::nucname::name(it->first); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, TradeTails) { + // this tests whether tails are being traded. + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 "; + + // 1-source to EF, 2-Enrich, add to tails, 3-tails avail. for trade + int simdur = 3; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + sim.AddRecipe("natu1", c_natu1()); + sim.AddRecipe("leu", c_leu()); + + sim.AddSource("natu") + .recipe("natu1") + .Finalize(); + sim.AddSink("enr_u") + .recipe("leu") + .Finalize(); + sim.AddSink("tails") + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("tails"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + + // Should be exactly one tails transaction + EXPECT_EQ(1, qr.rows.size()); + +} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, BidPrefs) { + // This tests that natu sources are preference-ordered by + // U235 content + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 " + " 1.0 "; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + sim.AddRecipe("natu1", c_natu1()); + sim.AddRecipe("natu2", c_natu2()); + + sim.AddSource("natu") + .recipe("natu1") + .capacity(1) + .Finalize(); + + sim.AddSource("natu") + .recipe("natu2") + .capacity(1) + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("natu"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + + // should trade only with #2 since it has higher U235 + EXPECT_EQ(1, qr.rows.size()); + + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + CompMap got = m->comp()->mass(); + CompMap want = c_natu2()->mass(); + cyclus::compmath::Normalize(&got); + cyclus::compmath::Normalize(&want); + + CompMap::iterator it; + for (it = want.begin(); it != want.end(); ++it) { + EXPECT_DOUBLE_EQ(it->second, got[it->first]) << + "nuclide qty off: " << pyne::nucname::name(it->first); + } + +} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + TEST_F(EnrichmentTest, NoBidPrefs) { + // This tests that preference-ordering for sources + // turns off correctly if flag is used + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 " + " 2.0 " + " 0 "; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + sim.AddRecipe("natu1", c_natu1()); + sim.AddRecipe("natu2", c_natu2()); + + sim.AddSource("natu") + .recipe("natu1") + .capacity(1) + .Finalize(); + + sim.AddSource("natu") + .recipe("natu2") + .capacity(1) + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("natu"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + + // should trade with both to meet its capacity limit + EXPECT_EQ(2, qr.rows.size()); + } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, ZeroU235) { + // Test that offers of natu with no u235 content are rejected + + std::string config = + " natu " + " natu1 " + " enr_u " + " tails " + " 0.003 " + " 1.0 "; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Enrichment"), config, simdur); + sim.AddRecipe("no_u235", c_nou235()); + sim.AddRecipe("natu1", c_natu1()); + + sim.AddSource("natu") + .recipe("no_u235") + .capacity(1) + .Finalize(); + + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("natu"))); + // DB table should be empty since there are no transactions + EXPECT_THROW(sim.db().Query("Transactions", &conds), + std::exception); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EnrichmentTest::SetUp() { + cyclus::Env::SetNucDataPath(); + cyclus::Context* ctx = tc_.get(); + src_facility = new Enrichment(ctx); + trader = tc_.trader(); + InitParameters(); + SetUpSource(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EnrichmentTest::TearDown() { + delete src_facility; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EnrichmentTest::InitParameters() { + cyclus::Context* ctx = tc_.get(); + + feed_commod = "incommod"; + product_commod = "outcommod"; + tails_commod = "tailscommod"; + + feed_recipe = "recipe"; + feed_assay = 0.0072; + + cyclus::CompMap v; + v[922350000] = feed_assay; + v[922380000] = 1 - feed_assay; + recipe = cyclus::Composition::CreateFromAtom(v); + ctx->AddRecipe(feed_recipe, recipe); + + tails_assay = 0.002; + swu_capacity = 100; //** + inv_size = 5; + + reserves = 105.5; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EnrichmentTest::SetUpSource() { + src_facility->feed_recipe = feed_recipe; + src_facility->feed_commod = feed_commod; + src_facility->product_commod = product_commod; + src_facility->tails_commod = tails_commod; + src_facility->tails_assay = tails_assay; + src_facility->max_enrich = max_enrich; + src_facility->SetMaxInventorySize(inv_size); + src_facility->SwuCapacity(swu_capacity); + src_facility->initial_feed = reserves; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr EnrichmentTest::GetMat(double qty) { + return cyclus::Material::CreateUntracked(qty, + tc_.get()->GetRecipe(feed_recipe)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EnrichmentTest::DoAddMat(cyclus::Material::Ptr mat) { + src_facility->AddMat_(mat); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr EnrichmentTest::DoRequest() { + return src_facility->Request_(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr +EnrichmentTest::DoOffer(cyclus::Material::Ptr mat) { + return src_facility->Offer_(mat); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Material::Ptr +EnrichmentTest::DoEnrich(cyclus::Material::Ptr mat, double qty) { + return src_facility->Enrich_(mat, qty); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, Request) { + // Tests that quantity in material request is accurate + double req = inv_size; + double add = 0; + cyclus::Material::Ptr mat = DoRequest(); + EXPECT_DOUBLE_EQ(mat->quantity(), req); + EXPECT_EQ(mat->comp(), tc_.get()->GetRecipe(feed_recipe)); + + add = 2 * inv_size / 3; + req -= add; + DoAddMat(GetMat(add)); + mat = DoRequest(); + EXPECT_DOUBLE_EQ(mat->quantity(), req); + EXPECT_EQ(mat->comp(), tc_.get()->GetRecipe(feed_recipe)); + + add = inv_size / 3; + req = 0; + DoAddMat(GetMat(add)); + mat = DoRequest(); + EXPECT_DOUBLE_EQ(mat->quantity(), req); + EXPECT_EQ(mat->comp(), tc_.get()->GetRecipe(feed_recipe)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, ValidReq) { + // Tests that material requests have U235/(U235+U238) > tails assay + using cyclus::CompMap; + using cyclus::Composition; + using cyclus::Material; + + double qty = 4.5; // some magic number + + cyclus::CompMap v1; + v1[922350000] = 1; + Material::Ptr mat = Material::CreateUntracked + (qty,Composition::CreateFromAtom(v1)); + EXPECT_FALSE(src_facility->ValidReq(mat)); // u238 = 0 + + cyclus::CompMap v2; + v2[922350000] = tails_assay; + v2[922380000] = 1 - tails_assay; + mat = Material::CreateUntracked(qty, Composition::CreateFromAtom(v2)); + // u235 / (u235 + u238) <= tails_assay + EXPECT_FALSE(src_facility->ValidReq(mat)); + + cyclus::CompMap v3; + v3[922350000] = 1; + v3[922380000] = 1; + mat = Material::CreateUntracked(qty, Composition::CreateFromAtom(v3)); + EXPECT_TRUE(src_facility->ValidReq(mat)); // valid +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + TEST_F(EnrichmentTest, ConstraintConverters) { + // Tests the SWU and NatU converters to make sure that amount of + // feed and SWU required are correct to fulfill the enrichment request. + using cyclus::CompMap; + using cyclus::Material; + using cyclus::toolkit::MatQuery; + using cyclus::Composition; + using cyclus::toolkit::Assays; + using cyclus::toolkit::UraniumAssay; + using cyclus::toolkit::SwuRequired; + using cyclus::toolkit::FeedQty; + using cyclus::toolkit::MatQuery; + cyclus::Env::SetNucDataPath(); + + double qty = 5; // 5 kg + double product_assay = 0.05; // of 5 w/o enriched U + CompMap v; + v[922350000] = product_assay; + v[922380000] = 1 - product_assay; + v[94239] = 0.5; // 94239 shouldn't be taken into account + Material::Ptr target = Material::CreateUntracked( + qty, Composition::CreateFromMass(v)); + + std::set nucs; + nucs.insert(922350000); + nucs.insert(922380000); + + MatQuery mq(target); + double mass_frac = mq.mass_frac(nucs); + + SWUConverter swuc(feed_assay, tails_assay); + NatUConverter natuc(feed_assay, tails_assay); + + Material::Ptr offer = DoOffer(target); + + EXPECT_NEAR(swuc.convert(target), swuc.convert(offer), 0.001); + EXPECT_NEAR(natuc.convert(target) * mass_frac, natuc.convert(offer), 0.001); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, Enrich) { + // this test asks the facility to enrich a material that results in an amount + // of natural uranium required that is exactly its inventory level. that + // inventory will be comprised of two materials to test the manifest/absorb + // strategy employed in Enrich_. + using cyclus::CompMap; + using cyclus::Material; + using cyclus::toolkit::MatQuery; + using cyclus::Composition; + using cyclus::toolkit::Assays; + using cyclus::toolkit::UraniumAssay; + using cyclus::toolkit::SwuRequired; + using cyclus::toolkit::FeedQty; + + double qty = 5; // kg + double product_assay = 0.05; // of 5 w/o enriched U + cyclus::CompMap v; + v[922350000] = product_assay; + v[922380000] = 1 - product_assay; + // target qty need not be = to request qty + Material::Ptr target = cyclus::Material::CreateUntracked( + qty + 10, cyclus::Composition::CreateFromMass(v)); + + Assays assays(feed_assay, UraniumAssay(target), tails_assay); + double swu_req = SwuRequired(qty, assays); + double natu_req = FeedQty(qty, assays); + double tails_qty = TailsQty(qty, assays); + + double swu_cap = swu_req * 5; + src_facility->SwuCapacity(swu_cap); + src_facility->SetMaxInventorySize(natu_req); + DoAddMat(GetMat(natu_req / 2)); + DoAddMat(GetMat(natu_req / 2)); + + Material::Ptr response; + EXPECT_NO_THROW(response = DoEnrich(target, qty)); + EXPECT_DOUBLE_EQ(src_facility->Tails().quantity(), tails_qty); + + MatQuery q(response); + EXPECT_EQ(response->quantity(), qty); + EXPECT_EQ(q.mass_frac(922350000), product_assay); + EXPECT_EQ(q.mass_frac(922380000), 1 - product_assay); + + // test too much natu request + DoAddMat(GetMat(natu_req - 1)); + EXPECT_THROW(response = DoEnrich(target, qty), cyclus::Error); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(EnrichmentTest, Response) { + // this test asks the facility to respond to multiple requests for enriched + // uranium. two requests are provided, whose total equals the swu capacity of + // the facility while not exceeding its inventory capacity (that's taken care + // of in the Enrich tests). + // + // note that response quantity and quality need not be tested, because they + // are covered by the Enrich and RequestEnrich tests + using cyclus::Bid; + using cyclus::CompMap; + using cyclus::Material; + using cyclus::Request; + using cyclus::Trade; + using cyclus::toolkit::Assays; + using cyclus::toolkit::FeedQty; + using cyclus::toolkit::SwuRequired; + using cyclus::toolkit::UraniumAssay; + + // problem set up + std::vector< cyclus::Trade > trades; + std::vector, + cyclus::Material::Ptr> > responses; + + double qty = 5; // kg + double trade_qty = qty / 3; + double product_assay = 0.05; // of 5 w/o enriched U + + cyclus::CompMap v; + v[922350000] = product_assay; + v[922380000] = 1 - product_assay; + // target qty need not be = to request qty + Material::Ptr target = cyclus::Material::CreateUntracked( + qty + 10, cyclus::Composition::CreateFromMass(v)); + + Assays assays(feed_assay, UraniumAssay(target), tails_assay); + double swu_req = SwuRequired(qty, assays); + double natu_req = FeedQty(qty, assays); + + src_facility->SetMaxInventorySize(natu_req * 4); // not capacitated by nat + src_facility->SwuCapacity(swu_req); // swu capacitated + + src_facility->GetMatlTrades(trades, responses); + + // set up state + DoAddMat(GetMat(natu_req * 2)); + + src_facility->GetMatlTrades(trades, responses); + + Request* req = + Request::Create(target, trader, product_commod); + Bid* bid = Bid::Create(req, target, src_facility); + Trade trade(req, bid, trade_qty); + trades.push_back(trade); + + // 2 trades, SWU = SWU cap + ASSERT_GT(src_facility->SwuCapacity() - 2 * swu_req / 3, + -1 * cyclus::eps()); + trades.push_back(trade); + responses.clear(); + EXPECT_NO_THROW(src_facility->GetMatlTrades(trades, responses)); + EXPECT_EQ(responses.size(), 2); +} + +} // namespace cycamore + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +cyclus::Agent* EnrichmentConstructor(cyclus::Context* ctx) { + return new cycamore::Enrichment(ctx); +} + +// required to get functionality in cyclus agent unit tests library +#ifndef CYCLUS_AGENT_TESTS_CONNECTED +int ConnectAgentTests(); +static int cyclus_agent_tests_connected = ConnectAgentTests(); +#define CYCLUS_AGENT_TESTS_CONNECTED cyclus_agent_tests_connected +#endif // CYCLUS_AGENT_TESTS_CONNECTED + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +INSTANTIATE_TEST_CASE_P(EnrichmentFac, FacilityTests, + Values(&EnrichmentConstructor)); +INSTANTIATE_TEST_CASE_P(EnrichmentFac, AgentTests, + Values(&EnrichmentConstructor)); diff --git a/src/enrichment_facility_tests.h b/src/enrichment_tests.h similarity index 77% rename from src/enrichment_facility_tests.h rename to src/enrichment_tests.h index c771bdd86d..4811392480 100644 --- a/src/enrichment_facility_tests.h +++ b/src/enrichment_tests.h @@ -1,5 +1,5 @@ -#ifndef CYCAMORE_SRC_ENRICHMENT_FACILITY_TESTS_ -#define CYCAMORE_SRC_ENRICHMENT_FACILITY_TESTS_ +#ifndef CYCAMORE_SRC_ENRICHMENT_TESTS_ +#define CYCAMORE_SRC_ENRICHMENT_TESTS_ #include @@ -10,21 +10,23 @@ #include "exchange_context.h" #include "material.h" -#include "enrichment_facility.h" +#include "enrichment.h" namespace cycamore { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -class EnrichmentFacilityTest : public ::testing::Test { +class EnrichmentTest : public ::testing::Test { protected: cyclus::TestContext tc_; - EnrichmentFacility* src_facility; - std::string in_commod, out_commod, in_recipe; + Enrichment* src_facility; + std::string feed_commod, product_commod, feed_recipe, tails_commod; cyclus::Composition::Ptr recipe; TestFacility* trader; - double tails_assay, feed_assay, inv_size, commodity_price, swu_capacity; + double feed_assay, tails_assay, inv_size, swu_capacity, max_enrich; + bool order_prefs; + double reserves; virtual void SetUp(); diff --git a/src/fuel_fab.cc b/src/fuel_fab.cc new file mode 100644 index 0000000000..98da0c3ffe --- /dev/null +++ b/src/fuel_fab.cc @@ -0,0 +1,715 @@ +#include "fuel_fab.h" +#include + +using cyclus::Material; +using cyclus::Composition; +using pyne::simple_xs; + +#define SHOW(X) \ + std::cout << std::setprecision(17) << __FILE__ << ":" << __LINE__ \ + << ": " #X " = " << X << "\n" + +namespace cycamore { + +class FissConverter : public cyclus::Converter { + public: + FissConverter(Composition::Ptr c_fill, Composition::Ptr c_fiss, + Composition::Ptr c_topup, std::string spectrum) + : c_fiss_(c_fiss), c_topup_(c_topup), c_fill_(c_fill), spec_(spectrum) { + w_fiss_ = CosiWeight(c_fiss, spectrum); + w_fill_ = CosiWeight(c_fill, spectrum); + w_topup_ = CosiWeight(c_topup, spectrum); + } + + virtual ~FissConverter() {} + + virtual double convert(cyclus::Material::Ptr m, cyclus::Arc const* a = NULL, + cyclus::ExchangeTranslationContext< + cyclus::Material> const* ctx = NULL) const { + double w_tgt = CosiWeight(m->comp(), spec_); + if (ValidWeights(w_fill_, w_tgt, w_fiss_)) { + double frac = HighFrac(w_fill_, w_tgt, w_fiss_); + return AtomToMassFrac(frac, c_fiss_, c_fill_) * m->quantity(); + } else if (ValidWeights(w_fiss_, w_tgt, w_topup_)) { + // use fiss inventory as filler, and topup as fissile + double frac = LowFrac(w_fiss_, w_tgt, w_topup_); + return AtomToMassFrac(frac, c_fiss_, c_topup_) * m->quantity(); + } else { + // don't bid at all + return 1e200; + } + } + + private: + std::string spec_; + double w_fiss_; + double w_topup_; + double w_fill_; + Composition::Ptr c_fiss_; + Composition::Ptr c_fill_; + Composition::Ptr c_topup_; +}; + +class FillConverter : public cyclus::Converter { + public: + FillConverter(Composition::Ptr c_fill, Composition::Ptr c_fiss, + Composition::Ptr c_topup, std::string spectrum) + : c_fiss_(c_fiss), c_topup_(c_topup), c_fill_(c_fill), spec_(spectrum) { + w_fiss_ = CosiWeight(c_fiss, spectrum); + w_fill_ = CosiWeight(c_fill, spectrum); + w_topup_ = CosiWeight(c_topup, spectrum); + } + + virtual ~FillConverter() {} + + virtual double convert(cyclus::Material::Ptr m, cyclus::Arc const* a = NULL, + cyclus::ExchangeTranslationContext< + cyclus::Material> const* ctx = NULL) const { + double w_tgt = CosiWeight(m->comp(), spec_); + if (ValidWeights(w_fill_, w_tgt, w_fiss_)) { + double frac = LowFrac(w_fill_, w_tgt, w_fiss_); + return AtomToMassFrac(frac, c_fill_, c_fiss_) * m->quantity(); + } else if (ValidWeights(w_fiss_, w_tgt, w_topup_)) { + // switched fissile inventory to filler so don't need any filler inventory + return 0; + } else { + // don't bid at all + return 1e200; + } + } + + private: + std::string spec_; + double w_fiss_; + double w_topup_; + double w_fill_; + Composition::Ptr c_fiss_; + Composition::Ptr c_fill_; + Composition::Ptr c_topup_; +}; + +class TopupConverter : public cyclus::Converter { + public: + TopupConverter(Composition::Ptr c_fill, Composition::Ptr c_fiss, + Composition::Ptr c_topup, std::string spectrum) + : c_fiss_(c_fiss), c_topup_(c_topup), c_fill_(c_fill), spec_(spectrum) { + w_fiss_ = CosiWeight(c_fiss, spectrum); + w_fill_ = CosiWeight(c_fill, spectrum); + w_topup_ = CosiWeight(c_topup, spectrum); + } + + virtual ~TopupConverter() {} + + virtual double convert(cyclus::Material::Ptr m, cyclus::Arc const* a = NULL, + cyclus::ExchangeTranslationContext< + cyclus::Material> const* ctx = NULL) const { + double w_tgt = CosiWeight(m->comp(), spec_); + if (ValidWeights(w_fill_, w_tgt, w_fiss_)) { + return 0; + } else if (ValidWeights(w_fiss_, w_tgt, w_topup_)) { + // switched fissile inventory to filler and topup as fissile + double frac = HighFrac(w_fiss_, w_tgt, w_topup_); + return AtomToMassFrac(frac, c_topup_, c_fiss_) * m->quantity(); + } else { + // don't bid at all + return 1e200; + } + } + + private: + std::string spec_; + double w_fiss_; + double w_topup_; + double w_fill_; + Composition::Ptr c_fiss_; + Composition::Ptr c_fill_; + Composition::Ptr c_topup_; +}; + +FuelFab::FuelFab(cyclus::Context* ctx) + : cyclus::Facility(ctx), fill_size(0), fiss_size(0), throughput(0) { + cyclus::Warn( + "the FuelFab archetype " + "is experimental"); +} + +void FuelFab::EnterNotify() { + cyclus::Facility::EnterNotify(); + + if (fiss_commod_prefs.empty()) { + for (int i = 0; i < fiss_commods.size(); i++) { + fiss_commod_prefs.push_back(1); + } + } else if (fiss_commod_prefs.size() != fiss_commods.size()) { + std::stringstream ss; + ss << "prototype '" << prototype() << "' has " << fiss_commod_prefs.size() + << " fiss_commod_prefs vals, expected " << fiss_commods.size(); + throw cyclus::ValidationError(ss.str()); + } + + if (fill_commod_prefs.empty()) { + for (int i = 0; i < fill_commods.size(); i++) { + fill_commod_prefs.push_back(1); + } + } else if (fill_commod_prefs.size() != fill_commods.size()) { + std::stringstream ss; + ss << "prototype '" << prototype() << "' has " << fill_commod_prefs.size() + << " fill_commod_prefs vals, expected " << fill_commods.size(); + throw cyclus::ValidationError(ss.str()); + } +} + +std::set::Ptr> FuelFab::GetMatlRequests() { + using cyclus::RequestPortfolio; + + std::set::Ptr> ports; + + bool exclusive = false; + + if (fiss.space() > cyclus::eps()) { + RequestPortfolio::Ptr port(new RequestPortfolio()); + + Material::Ptr m = cyclus::NewBlankMaterial(fiss.space()); + if (!fiss_recipe.empty()) { + Composition::Ptr c = context()->GetRecipe(fiss_recipe); + m = Material::CreateUntracked(fiss.space(), c); + } + + std::vector*> reqs; + for (int i = 0; i < fiss_commods.size(); i++) { + std::string commod = fiss_commods[i]; + double pref = fiss_commod_prefs[i]; + reqs.push_back(port->AddRequest(m, this, commod, pref, exclusive)); + req_inventories_[reqs.back()] = "fiss"; + } + port->AddMutualReqs(reqs); + ports.insert(port); + } + + if (fill.space() > cyclus::eps()) { + RequestPortfolio::Ptr port(new RequestPortfolio()); + + Material::Ptr m = cyclus::NewBlankMaterial(fill.space()); + if (!fill_recipe.empty()) { + Composition::Ptr c = context()->GetRecipe(fill_recipe); + m = Material::CreateUntracked(fill.space(), c); + } + + std::vector*> reqs; + for (int i = 0; i < fill_commods.size(); i++) { + std::string commod = fill_commods[i]; + double pref = fill_commod_prefs[i]; + reqs.push_back(port->AddRequest(m, this, commod, pref, exclusive)); + req_inventories_[reqs.back()] = "fill"; + } + port->AddMutualReqs(reqs); + ports.insert(port); + } + + if (topup.space() > cyclus::eps()) { + RequestPortfolio::Ptr port(new RequestPortfolio()); + + Material::Ptr m = cyclus::NewBlankMaterial(topup.space()); + if (!topup_recipe.empty()) { + Composition::Ptr c = context()->GetRecipe(topup_recipe); + m = Material::CreateUntracked(topup.space(), c); + } + cyclus::Request* r = + port->AddRequest(m, this, topup_commod, topup_pref, exclusive); + req_inventories_[r] = "topup"; + ports.insert(port); + } + + return ports; +} + +bool Contains(std::vector vec, std::string s) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == s) { + return true; + } + } + return false; +} + +void FuelFab::AcceptMatlTrades(const std::vector< + std::pair, Material::Ptr> >& responses) { + std::vector, + cyclus::Material::Ptr> >::const_iterator trade; + + for (trade = responses.begin(); trade != responses.end(); ++trade) { + std::string commod = trade->first.request->commodity(); + double req_qty = trade->first.request->target()->quantity(); + cyclus::Request* req = trade->first.request; + Material::Ptr m = trade->second; + if (req_inventories_[req] == "fill") { + fill.Push(m); + } else if (req_inventories_[req] == "topup") { + topup.Push(m); + } else if (req_inventories_[req] == "fiss") { + fiss.Push(m); + } else { + throw cyclus::ValueError("cycamore::FuelFab was overmatched on requests"); + } + } + + req_inventories_.clear(); + + // IMPORTANT - each buffer needs to be a single homogenous composition or + // the inventory mixing constraints for bids don't work + if (fill.count() > 1) { + fill.Push(cyclus::toolkit::Squash(fill.PopN(fill.count()))); + } + if (fiss.count() > 1) { + fiss.Push(cyclus::toolkit::Squash(fiss.PopN(fiss.count()))); + } + if (topup.count() > 1) { + topup.Push(cyclus::toolkit::Squash(topup.PopN(topup.count()))); + } +} + +std::set::Ptr> FuelFab::GetMatlBids( + cyclus::CommodMap::type& commod_requests) { + using cyclus::BidPortfolio; + + std::set::Ptr> ports; + std::vector*>& reqs = commod_requests[outcommod]; + + if (throughput == 0) { + return ports; + } else if (reqs.size() == 0) { + return ports; + } + + double w_fill = 0; + Composition::Ptr + c_fill; // no default needed - this is non-optional parameter + if (fill.count() > 0) { + c_fill = fill.Peek()->comp(); + w_fill = CosiWeight(c_fill, spectrum); + } else { + c_fill = context()->GetRecipe(fill_recipe); + w_fill = CosiWeight(c_fill, spectrum); + } + + double w_topup = 0; + Composition::Ptr c_topup = c_fill; + if (topup.count() > 0) { + c_topup = topup.Peek()->comp(); + w_topup = CosiWeight(c_topup, spectrum); + } else if (!topup_recipe.empty()) { + c_topup = context()->GetRecipe(topup_recipe); + w_topup = CosiWeight(c_topup, spectrum); + } + + double w_fiss = + w_fill; // this allows trading just fill with no fiss inventory + Composition::Ptr c_fiss = c_fill; + if (fiss.count() > 0) { + c_fiss = fiss.Peek()->comp(); + w_fiss = CosiWeight(c_fiss, spectrum); + } else if (!fiss_recipe.empty()) { + c_fiss = context()->GetRecipe(fiss_recipe); + w_fiss = CosiWeight(c_fiss, spectrum); + } + + BidPortfolio::Ptr port(new BidPortfolio()); + for (int j = 0; j < reqs.size(); j++) { + cyclus::Request* req = reqs[j]; + + Composition::Ptr tgt = req->target()->comp(); + double w_tgt = CosiWeight(tgt, spectrum); + double tgt_qty = req->target()->quantity(); + if (ValidWeights(w_fill, w_tgt, w_fiss)) { + double fiss_frac = HighFrac(w_fill, w_tgt, w_fiss); + double fill_frac = 1 - fiss_frac; + fiss_frac = AtomToMassFrac(fiss_frac, c_fiss, c_fill); + fill_frac = AtomToMassFrac(fill_frac, c_fill, c_fiss); + Material::Ptr m1 = Material::CreateUntracked(fiss_frac * tgt_qty, c_fiss); + Material::Ptr m2 = Material::CreateUntracked(fill_frac * tgt_qty, c_fill); + m1->Absorb(m2); + + bool exclusive = false; + port->AddBid(req, m1, this, exclusive); + } else if (topup.count() > 0 && ValidWeights(w_fiss, w_tgt, w_topup)) { + // only bid with topup if we have filler - otherwise we might be able to + // meet target with filler when we get it. we should only use topup + // when the fissile has too poor neutronics. + double topup_frac = HighFrac(w_fiss, w_tgt, w_topup); + double fiss_frac = 1 - topup_frac; + fiss_frac = AtomToMassFrac(fiss_frac, c_fiss, c_topup); + topup_frac = AtomToMassFrac(topup_frac, c_topup, c_fiss); + Material::Ptr m1 = + Material::CreateUntracked(topup_frac * tgt_qty, c_topup); + Material::Ptr m2 = Material::CreateUntracked(fiss_frac * tgt_qty, c_fiss); + m1->Absorb(m2); + + bool exclusive = false; + port->AddBid(req, m1, this, exclusive); + } // else can't meet the target - don't bid + } + + cyclus::Converter::Ptr fissconv( + new FissConverter(c_fill, c_fiss, c_topup, spectrum)); + cyclus::Converter::Ptr fillconv( + new FillConverter(c_fill, c_fiss, c_topup, spectrum)); + cyclus::Converter::Ptr topupconv( + new TopupConverter(c_fill, c_fiss, c_topup, spectrum)); + // important! - the std::max calls prevent CapacityConstraint throwing a zero + // cap exception + cyclus::CapacityConstraint fissc(std::max(fiss.quantity(), 1e-10), + fissconv); + cyclus::CapacityConstraint fillc(std::max(fill.quantity(), 1e-10), + fillconv); + cyclus::CapacityConstraint topupc(std::max(topup.quantity(), 1e-10), + topupconv); + port->AddConstraint(fillc); + port->AddConstraint(fissc); + port->AddConstraint(topupc); + + cyclus::CapacityConstraint cc(throughput); + port->AddConstraint(cc); + ports.insert(port); + return ports; +} + +void FuelFab::GetMatlTrades( + const std::vector >& trades, + std::vector, Material::Ptr> >& + responses) { + using cyclus::Trade; + + // guard against cases where a buffer is empty - this is okay because some trades + // may not need that particular buffer. + double w_fill = 0; + if (fill.count() > 0) { + w_fill = CosiWeight(fill.Peek()->comp(), spectrum); + } + double w_topup = 0; + if (topup.count() > 0) { + w_topup = CosiWeight(topup.Peek()->comp(), spectrum); + } + double w_fiss = 0; + if (fiss.count() > 0) { + w_fiss = CosiWeight(fiss.Peek()->comp(), spectrum); + } + + std::vector >::const_iterator it; + double tot = 0; + for (int i = 0; i < trades.size(); i++) { + Material::Ptr tgt = trades[i].request->target(); + + double w_tgt = CosiWeight(tgt->comp(), spectrum); + double qty = trades[i].amt; + double wfiss = w_fiss; + + tot += qty; + if (tot > throughput + cyclus::eps()) { + std::stringstream ss; + ss << "FuelFab was matched above throughput limit: " << tot << " > " + << throughput; + throw cyclus::ValueError(ss.str()); + } + + if (fiss.count() == 0) { + // use straight filler to satisfy this request + double fillqty = qty; + if (std::abs(fillqty - fill.quantity()) < cyclus::eps()) { + fillqty = std::min(fill.quantity(), qty); + } + responses.push_back(std::make_pair(trades[i], fill.Pop(fillqty))); + } else if (fill.count() == 0 && ValidWeights(w_fill, w_tgt, w_fiss)) { + // use straight fissile to satisfy this request + double fissqty = qty; + if (std::abs(fissqty - fiss.quantity()) < cyclus::eps()) { + fissqty = std::min(fiss.quantity(), qty); + } + responses.push_back(std::make_pair(trades[i], fiss.Pop(fissqty))); + } else if (ValidWeights(w_fill, w_tgt, w_fiss)) { + double fiss_frac = HighFrac(w_fill, w_tgt, w_fiss); + double fill_frac = LowFrac(w_fill, w_tgt, w_fiss); + fiss_frac = + AtomToMassFrac(fiss_frac, fiss.Peek()->comp(), fill.Peek()->comp()); + fill_frac = + AtomToMassFrac(fill_frac, fill.Peek()->comp(), fiss.Peek()->comp()); + + double fissqty = fiss_frac*qty; + if (std::abs(fissqty - fiss.quantity()) < cyclus::eps()) { + fissqty = std::min(fiss.quantity(), fiss_frac*qty); + } + double fillqty = fill_frac*qty; + if (std::abs(fillqty - fill.quantity()) < cyclus::eps()) { + fillqty = std::min(fill.quantity(), fill_frac*qty); + } + + Material::Ptr m = fiss.Pop(fissqty); + // this if block prevents zero qty ResBuf pop exceptions + if (fill_frac > 0) { + m->Absorb(fill.Pop(fillqty)); + } + responses.push_back(std::make_pair(trades[i], m)); + } else { + double topup_frac = HighFrac(w_fiss, w_tgt, w_topup); + double fiss_frac = 1 - topup_frac; + topup_frac = + AtomToMassFrac(topup_frac, topup.Peek()->comp(), fiss.Peek()->comp()); + fiss_frac = + AtomToMassFrac(fiss_frac, fiss.Peek()->comp(), topup.Peek()->comp()); + + double fissqty = fiss_frac*qty; + if (std::abs(fissqty - fiss.quantity()) < cyclus::eps()) { + fissqty = std::min(fiss.quantity(), fiss_frac*qty); + } + double topupqty = topup_frac*qty; + if (std::abs(topupqty - topup.quantity()) < cyclus::eps()) { + topupqty = std::min(topup.quantity(), topup_frac*qty); + } + + Material::Ptr m = fiss.Pop(fissqty); + // this if block prevents zero qty ResBuf pop exceptions + if (topup_frac > 0) { + m->Absorb(topup.Pop(topupqty)); + } + responses.push_back(std::make_pair(trades[i], m)); + } + } +} + +extern "C" cyclus::Agent* ConstructFuelFab(cyclus::Context* ctx) { + return new FuelFab(ctx); +} + +// Returns the weight of c using 1 group cross sections of type spectrum +// which must be one of: +// +// * thermal +// * thermal_maxwell_ave +// * fission_spectrum_ave +// * resonance_integral +// * fourteen_MeV +// +// The weight is calculated as "(nu*sigma_f - sigma_a) * N". Since weights +// are computed based on nuclide atom fractions, corresponding computed +// material/mixing fractions will also be atom-based naturally and will need +// to be converted to mass-based for actual material object mixing. +double CosiWeight(cyclus::Composition::Ptr c, const std::string& spectrum) { + cyclus::CompMap cm = c->atom(); + cyclus::compmath::Normalize(&cm); + + if (spectrum == "thermal") { + double nu_pu239 = 2.85; + double nu_u233 = 2.5; + double nu_u235 = 2.43; + double nu_u238 = 0; + double nu_pu241 = nu_pu239; + + static std::map absorb_xs; + static std::map fiss_xs; + static double p_u238 = 0; + static double p_pu239 = 0; + if (p_u238 == 0) { + double fiss_u238 = simple_xs(922380000, "fission", "thermal"); + double absorb_u238 = simple_xs(922380000, "absorption", "thermal"); + p_u238 = nu_u238 * fiss_u238 - absorb_u238; + + double fiss_pu239 = simple_xs(942390000, "fission", "thermal"); + double absorb_pu239 = simple_xs(942390000, "absorption", "thermal"); + p_pu239 = nu_pu239 * fiss_pu239 - absorb_pu239; + } + + cyclus::CompMap::iterator it; + double w = 0; + for (it = cm.begin(); it != cm.end(); ++it) { + cyclus::Nuc nuc = it->first; + double nu = 0; + if (nuc == 922350000) { + nu = nu_u235; + } else if (nuc == 922330000) { + nu = nu_u233; + } else if (nuc == 942390000) { + nu = nu_pu239; + } else if (nuc == 942410000) { + nu = nu_pu241; + } + + double fiss = 0; + double absorb = 0; + if (absorb_xs.count(nuc) == 0) { + try { + fiss = simple_xs(nuc, "fission", "thermal"); + absorb = simple_xs(nuc, "absorption", "thermal"); + absorb_xs[nuc] = absorb; + fiss_xs[nuc] = fiss; + } catch (pyne::InvalidSimpleXS err) { + fiss = 0; + absorb = 0; + } + } else { + fiss = fiss_xs[nuc]; + absorb = absorb_xs[nuc]; + } + + double p = nu * fiss - absorb; + w += it->second * (p - p_u238) / (p_pu239 - p_u238); + } + return w; + } else if (spectrum == "fission_spectrum_ave") { + double nu_pu239 = 3.1; + double nu_u233 = 2.63; + double nu_u235 = 2.58; + double nu_u238 = 0; + double nu_pu241 = nu_pu239; + + static std::map absorb_xs; + static std::map fiss_xs; + static double p_u238 = 0; + static double p_pu239 = 0; + if (p_u238 == 0) { + double fiss_u238 = + simple_xs(922380000, "fission", "fission_spectrum_ave"); + double absorb_u238 = + simple_xs(922380000, "absorption", "fission_spectrum_ave"); + p_u238 = nu_u238 * fiss_u238 - absorb_u238; + + double fiss_pu239 = + simple_xs(942390000, "fission", "fission_spectrum_ave"); + double absorb_pu239 = + simple_xs(942390000, "absorption", "fission_spectrum_ave"); + p_pu239 = nu_pu239 * fiss_pu239 - absorb_pu239; + } + + cyclus::CompMap::iterator it; + double w = 0; + for (it = cm.begin(); it != cm.end(); ++it) { + cyclus::Nuc nuc = it->first; + double nu = 0; + if (nuc == 922350000) { + nu = nu_u235; + } else if (nuc == 922330000) { + nu = nu_u233; + } else if (nuc == 942390000) { + nu = nu_pu239; + } else if (nuc == 942410000) { + nu = nu_pu241; + } + + double fiss = 0; + double absorb = 0; + if (absorb_xs.count(nuc) == 0) { + try { + fiss = simple_xs(nuc, "fission", "fission_spectrum_ave"); + absorb = simple_xs(nuc, "absorption", "fission_spectrum_ave"); + absorb_xs[nuc] = absorb; + fiss_xs[nuc] = fiss; + } catch (pyne::InvalidSimpleXS err) { + fiss = 0; + absorb = 0; + } + } else { + fiss = fiss_xs[nuc]; + absorb = absorb_xs[nuc]; + } + + double p = nu * fiss - absorb; + w += it->second * (p - p_u238) / (p_pu239 - p_u238); + } + return w; + } else { + double nu_pu239 = 3.1; + double nu_u233 = 2.63; + double nu_u235 = 2.58; + double nu_u238 = 0; + double nu_pu241 = nu_pu239; + + double fiss_u238 = simple_xs(922380000, "fission", spectrum); + double absorb_u238 = simple_xs(922380000, "absorption", spectrum); + double p_u238 = nu_u238 * fiss_u238 - absorb_u238; + + double fiss_pu239 = simple_xs(942390000, "fission", spectrum); + double absorb_pu239 = simple_xs(942390000, "absorption", spectrum); + double p_pu239 = nu_pu239 * fiss_pu239 - absorb_pu239; + + cyclus::CompMap::iterator it; + double w = 0; + for (it = cm.begin(); it != cm.end(); ++it) { + cyclus::Nuc nuc = it->first; + double nu = 0; + if (nuc == 922350000) { + nu = nu_u235; + } else if (nuc == 922330000) { + nu = nu_u233; + } else if (nuc == 942390000) { + nu = nu_pu239; + } else if (nuc == 942410000) { + nu = nu_pu241; + } + + double fiss = 0; + double absorb = 0; + try { + fiss = simple_xs(nuc, "fission", spectrum); + absorb = simple_xs(nuc, "absorption", spectrum); + } catch (pyne::InvalidSimpleXS err) { + fiss = 0; + absorb = 0; + } + + double p = nu * fiss - absorb; + w += it->second * (p - p_u238) / (p_pu239 - p_u238); + } + return w; + } +} + +// Convert an atom frac (n1/(n1+n2) to a mass frac (m1/(m1+m2) given +// corresponding compositions c1 and c2. +double AtomToMassFrac(double atomfrac, Composition::Ptr c1, + Composition::Ptr c2) { + cyclus::CompMap n1 = c1->atom(); + cyclus::CompMap n2 = c2->atom(); + cyclus::compmath::Normalize(&n1, atomfrac); + cyclus::compmath::Normalize(&n2, 1 - atomfrac); + + cyclus::CompMap::iterator it; + + double mass1 = 0; + for (it = n1.begin(); it != n1.end(); ++it) { + mass1 += it->second * pyne::atomic_mass(it->first); + } + + double mass2 = 0; + for (it = n2.begin(); it != n2.end(); ++it) { + mass2 += it->second * pyne::atomic_mass(it->first); + } + + return mass1 / (mass1 + mass2); +} + +double HighFrac(double w_low, double w_target, double w_high, double eps) { + if (!ValidWeights(w_low, w_target, w_high)) { + throw cyclus::ValueError("low and high weights cannot meet target"); + } else if (w_low == w_high && w_target == w_low) { + return 1; + } + double f = std::abs((w_target - w_low) / (w_high - w_low)); + if (1 - f < eps) { + return 1; + } else if (f < eps) { + return 0; + } + return f; +} + +double LowFrac(double w_low, double w_target, double w_high, double eps) { + return 1 - HighFrac(w_low, w_target, w_high, eps); +} + +// Returns true if the given weights can be used to linearly interpolate valid +// mixing fractions of the filler and fissile streams to hit the target. +bool ValidWeights(double w_low, double w_target, double w_high) { + // w_tgt must be in between w_fill and w_fiss for the equivalence + // interpolation to work. + return w_low <= w_target && w_target <= w_high; +} + +} // namespace cycamore diff --git a/src/fuel_fab.h b/src/fuel_fab.h new file mode 100644 index 0000000000..5facb27cff --- /dev/null +++ b/src/fuel_fab.h @@ -0,0 +1,256 @@ +#ifndef CYCAMORE_SRC_FUEL_FAB_H_ +#define CYCAMORE_SRC_FUEL_FAB_H_ + +#include +#include "cyclus.h" + +namespace cycamore { + +/// FuelFab takes in 2 streams of material and mixes them in ratios in order to +/// supply material that matches some neutronics properties of reqeusted +/// material. It uses an equivalence type method [1] +/// inspired by a similar approach in the COSI fuel cycle simulator. +/// +/// The FuelFab has 3 input inventories: fissile stream, filler stream, and an +/// optional top-up inventory. All materials received into each inventory are +/// always combined into a single material (i.e. a single fissile material, a +/// single filler material, etc.). The input streams and requested fuel +/// composition are each assigned weights based on summing: +/// +/// N * (p_i - p_U238) / (p_Pu239 - p_U238) +/// +/// for each nuclide where: +/// +/// - p = nu*sigma_f - sigma_a for the nuclide +/// - p_U238 is p for pure U238 +/// - p_Pu239 is p for pure Pu239 +/// - N is the nuclide's atom fraction +/// - nu is the average # neutrons per fission +/// - sigma_f is the microscopic fission cross-section +/// - sigma_a is the microscopic neutron absorption cross-section +/// +/// The cross sections are from the simple cross section library in PyNE. They +/// can be set to either a thermal or fast neutron spectrum. A linear +/// interpolation is performed using the weights of the fissile, filler, and +/// target streams. The interpolation is used to compute a mixing ratio of the +/// input streams that matches the target weight. In the event that the target +/// weight is higher than the fissile stream weight, the FuelFab will attempt +/// to use the top-up and fissile input streams together instead of the fissile +/// and filler streams. All supplied material will always have the same weight +/// as the requested material. +/// +/// The supplying of mixed material is constrained by available inventory +/// quantities and a per time step throughput limit. Requests for fuel +/// material larger than the throughput can never be met. Fissile inventory +/// can be requested/received via one or more commodities. The DRE request +/// preference for each of these commodities can also optionally be specified. +/// By default, the top-up inventory size is zero, and it is not used for +/// mixing. +/// +/// @code +/// [1] Baker, A. R., and R. W. Ross. "Comparison of the value of plutonium and +/// uranium isotopes in fast reactors." Proceedings of the Conference on +/// Breeding. Economics, and Safety in Large Fast Power Reactors. 1963. +/// @endcode +class FuelFab : public cyclus::Facility { +#pragma cyclus note { \ +"niche": "fabrication", \ +"doc": \ + "FuelFab takes in 2 streams of material and mixes them in ratios in order to" \ + " supply material that matches some neutronics properties of reqeusted" \ + " material. It uses an equivalence type method [1]" \ + " inspired by a similar approach in the COSI fuel cycle simulator." \ + "\n\n" \ + "The FuelFab has 3 input inventories: fissile stream, filler stream, and an" \ + " optional top-up inventory. All materials received into each inventory are" \ + " always combined into a single material (i.e. a single fissile material, a" \ + " single filler material, etc.). The input streams and requested fuel" \ + " composition are each assigned weights based on summing:" \ + "\n\n" \ + " N * (p_i - p_U238) / (p_Pu239 - p_U238)" \ + "\n\n" \ + "for each nuclide where:" \ + "\n" \ + "\n - p = nu*sigma_f - sigma_a for the nuclide" \ + "\n - p_U238 is p for pure U238" \ + "\n - p_Pu239 is p for pure Pu239" \ + "\n - N is the nuclide's atom fraction" \ + "\n - nu is the average # neutrons per fission" \ + "\n - sigma_f is the microscopic fission cross-section" \ + "\n - sigma_a is the microscopic neutron absorption cross-section" \ + "\n\n" \ + "The cross sections are from the simple cross section library in PyNE. They" \ + " can be set to either a thermal or fast neutron spectrum. A linear" \ + " interpolation is performed using the weights of the fissile, filler, and" \ + " target streams. The interpolation is used to compute a mixing ratio of the" \ + " input streams that matches the target weight. In the event that the target" \ + " weight is higher than the fissile stream weight, the FuelFab will attempt" \ + " to use the top-up and fissile input streams together instead of the fissile" \ + " and filler streams. All supplied material will always have the same weight" \ + " as the requested material." \ + "\n\n" \ + "The supplying of mixed material is constrained by available inventory" \ + " quantities and a per time step throughput limit. Requests for fuel" \ + " material larger than the throughput can never be met. Fissile inventory" \ + " can be requested/received via one or more commodities. The DRE request" \ + " preference for each of these commodities can also optionally be specified." \ + " By default, the top-up inventory size is zero, and it is not used for" \ + " mixing. " \ + "\n\n" \ + "[1] Baker, A. R., and R. W. Ross. \"Comparison of the value of plutonium and" \ + " uranium isotopes in fast reactors.\" Proceedings of the Conference on" \ + " Breeding. Economics, and Safety in Large Fast Power Reactors. 1963." \ + "", \ +} + public: + FuelFab(cyclus::Context* ctx); + virtual ~FuelFab(){}; + +#pragma cyclus + + virtual void Tick(){}; + virtual void Tock(){}; + virtual void EnterNotify(); + + virtual std::set::Ptr> GetMatlBids( + cyclus::CommodMap::type& commod_requests); + + virtual void GetMatlTrades( + const std::vector >& trades, + std::vector, + cyclus::Material::Ptr> >& responses); + + virtual void AcceptMatlTrades(const std::vector, cyclus::Material::Ptr> >& responses); + + virtual std::set::Ptr> + GetMatlRequests(); + + private: + #pragma cyclus var { \ + "doc": "Ordered list of commodities on which to requesting filler stream material.", \ + "uilabel": "Filler Stream Commodities", \ + "uitype": ["oneormore", "incommodity"], \ + } + std::vector fill_commods; + #pragma cyclus var { \ + "doc": "Name of recipe to be used in filler material stream requests.", \ + "uilabel": "Filler Stream Recipe", \ + "uitype": "recipe", \ + } + std::string fill_recipe; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Filler Stream Preferences", \ + "doc": "Filler stream commodity request preferences for each of the given filler commodities (same order)." \ + " If unspecified, default is to use 1.0 for all preferences.", \ + } + std::vector fill_commod_prefs; + #pragma cyclus var { \ + "doc": "Size of filler material stream inventory.", \ + "uilabel": "Filler Stream Inventory Capacity", \ + "units": "kg", \ + } + double fill_size; + #pragma cyclus var {"capacity": "fill_size"} + cyclus::toolkit::ResBuf fill; + + #pragma cyclus var { \ + "doc": "Ordered list of commodities on which to requesting fissile stream material.", \ + "uilabel": "Fissile Stream Commodities", \ + "uitype": ["oneormore", "incommodity"], \ + } + std::vector fiss_commods; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Fissile Stream Preferences", \ + "doc": "Fissile stream commodity request preferences for each of the given fissile commodities (same order)." \ + " If unspecified, default is to use 1.0 for all preferences.", \ + } + std::vector fiss_commod_prefs; + #pragma cyclus var { \ + "doc": "Name for recipe to be used in fissile stream requests." \ + " Empty string results in use of an empty dummy recipe.", \ + "uitype": "recipe", \ + "uilabel": "Fissile Stream Recipes", \ + "default": "", \ + } + std::string fiss_recipe; + #pragma cyclus var { \ + "doc": "Size of fissile material stream inventory.", \ + "uilabel": "Fissile Stream Inventory Capacity", \ + "units": "kg", \ + } + double fiss_size; + #pragma cyclus var {"capacity": "fiss_size"} + cyclus::toolkit::ResBuf fiss; + + #pragma cyclus var { \ + "doc": "Commodity on which to request material for top-up stream." \ + " This MUST be set if 'topup_size > 0'.", \ + "uilabel": "Top-up Stream Commodity", \ + "default": "", \ + "uitype": "incommodity", \ + } + std::string topup_commod; + #pragma cyclus var { \ + "doc": "Name of recipe to be used in top-up material stream requests." \ + " This MUST be set if 'topup_size > 0'.", \ + "uilabel": "Top-up Stream Recipe", \ + "uitype": "recipe", \ + "default": "", \ + } + std::string topup_recipe; + #pragma cyclus var { \ + "doc": "Top-up material stream request preference.", \ + "uilabel": "Top-up Stream Preference", \ + "default": 0, \ + } + double topup_pref; + #pragma cyclus var { \ + "doc": "Size of top-up material stream inventory.", \ + "uilabel": "Top-up Stream Inventory Capacity", \ + "units": "kg", \ + "default": 0, \ + } + double topup_size; + #pragma cyclus var {"capacity": "topup_size"} + cyclus::toolkit::ResBuf topup; + + #pragma cyclus var { \ + "uilabel": "Spectrum type", \ + "categorical": ['fission_spectrum_ave','thermal'], \ + "doc": "The type of cross-sections to use for composition property calculation." \ + " Use 'fission_spectrum_ave' for fast reactor compositions or 'thermal' for thermal reactors.", \ + } + std::string spectrum; + + #pragma cyclus var { \ + "doc": "Commodity on which to offer/supply mixed fuel material.", \ + "uilabel": "Output Commodity", \ + "uitype": "outcommodity", \ + } + std::string outcommod; + + #pragma cyclus var { \ + "doc": "Maximum number of kg of fuel material that can be supplied per time step.", \ + "uilabel": "Maximum Throughput", \ + "units": "kg", \ + } + double throughput; + + // intra-time-step state - no need to be a state var + // map + std::map*, std::string> req_inventories_; +}; + +double CosiWeight(cyclus::Composition::Ptr c, const std::string& spectrum); +bool ValidWeights(double w_low, double w_tgt, double w_high); +double LowFrac(double w_low, double w_tgt, double w_high, double eps = 1e-6); +double HighFrac(double w_low, double w_tgt, double w_high, double eps = 1e-6); +double AtomToMassFrac(double atomfrac, cyclus::Composition::Ptr c1, cyclus::Composition::Ptr c2); + +} // namespace cycamore + + +#endif // CYCAMORE_SRC_FUEL_FAB_H_ diff --git a/src/fuel_fab_tests.cc b/src/fuel_fab_tests.cc new file mode 100644 index 0000000000..810783c846 --- /dev/null +++ b/src/fuel_fab_tests.cc @@ -0,0 +1,888 @@ +#include "fuel_fab.h" + +#include +#include +#include "cyclus.h" + +using pyne::nucname::id; +using cyclus::Composition; +using cyclus::CompMap; +using cyclus::Material; +using cyclus::QueryResult; +using cyclus::Cond; +using cyclus::toolkit::MatQuery; + +namespace cycamore { +namespace fuelfabtests { + +Composition::Ptr c_uox() { + CompMap m; + m[id("u235")] = 0.04; + m[id("u238")] = 0.96; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_mox() { + CompMap m; + m[id("u235")] = .7; + m[id("u238")] = 100; + m[id("pu239")] = 3.3; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_natu() { + CompMap m; + m[id("u235")] = .007; + m[id("u238")] = .993; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_pustream() { + CompMap m; + m[id("pu239")] = 100; + m[id("pu240")] = 10; + m[id("pu241")] = 1; + m[id("pu242")] = 1; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_pustreamlow() { + CompMap m; + m[id("pu239")] = 80; + m[id("pu240")] = 10; + m[id("pu241")] = 1; + m[id("pu242")] = 1; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_pustreambad() { + CompMap m; + m[id("pu239")] = 1; + m[id("pu240")] = 10; + m[id("pu241")] = 1; + m[id("pu242")] = 1; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_water() { + CompMap m; + m[id("O16")] = 1; + m[id("H1")] = 2; + return Composition::CreateFromAtom(m); +}; + +TEST(FuelFabTests, CosiWeight) { + cyclus::Env::SetNucDataPath(); + CompMap m; + m[942390000] = 1; + Composition::Ptr c = Composition::CreateFromMass(m); + double w = CosiWeight(c, "thermal"); + EXPECT_DOUBLE_EQ(1.0, w); + + m.clear(); + m[922380000] = 1; + c = Composition::CreateFromMass(m); + w = CosiWeight(c, "thermal"); + EXPECT_DOUBLE_EQ(0.0, w); + + m.clear(); + m[942390000] = 1; + c = Composition::CreateFromMass(m); + w = CosiWeight(c, "fission_spectrum_ave"); + EXPECT_DOUBLE_EQ(1.0, w); + + m.clear(); + m[922380000] = 1; + c = Composition::CreateFromMass(m); + w = CosiWeight(c, "fission_spectrum_ave"); + EXPECT_DOUBLE_EQ(0.0, w); + + m.clear(); + m[922380000] = 1; + m[942390000] = 1; + c = Composition::CreateFromAtom(m); + w = CosiWeight(c, "thermal"); + EXPECT_DOUBLE_EQ(0.5, w) << "might be using mass-fractions instead of atom"; + + m.clear(); + m[922380000] = 1; + m[942390000] = 1; + c = Composition::CreateFromAtom(m); + w = CosiWeight(c, "fission_spectrum_ave"); + EXPECT_DOUBLE_EQ(0.5, w) << "might be using mass-fractions instead of atom"; + + m.clear(); + m[922380000] = 1; + m[942390000] = 1; + m[922350000] = 1; + c = Composition::CreateFromAtom(m); + double w_therm = CosiWeight(c, "thermal"); + double w_fast = CosiWeight(c, "fission_spectrum_ave"); + EXPECT_GT(w_therm, w_fast); +} + +TEST(FuelFabTests, CosiWeight_Mixed) { + double w_fill = CosiWeight(c_natu(), "thermal"); + double w_fiss = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + + double fiss_frac = HighFrac(w_fill, w_target, w_fiss); + double fill_frac = LowFrac(w_fill, w_target, w_fiss); + + // correct frac's from atom-based into mass-based for mixing + fiss_frac = AtomToMassFrac(fiss_frac, c_pustream(), c_natu()); + fill_frac = AtomToMassFrac(fill_frac, c_natu(), c_pustream()); + + Material::Ptr m1 = Material::CreateUntracked(fiss_frac, c_pustream()); + Material::Ptr m2 = Material::CreateUntracked(fill_frac, c_natu()); + m1->Absorb(m2); + double got = CosiWeight(m1->comp(), "thermal"); + EXPECT_LT(std::abs((w_target-got)/w_target), 0.00001) << "mixed composition not within 0.001% of target"; +} + +TEST(FuelFabTests, HighFrac) { + double w_fill = CosiWeight(c_natu(), "thermal"); + double w_fiss = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + + EXPECT_THROW(HighFrac(w_fiss, w_target, w_fill), cyclus::ValueError); + EXPECT_THROW(HighFrac(w_target, w_fill, w_fiss), cyclus::ValueError); + EXPECT_THROW(HighFrac(w_target, w_fiss, w_fill), cyclus::ValueError); + + double f = HighFrac(w_fill, w_target, w_fiss); + EXPECT_DOUBLE_EQ((w_target-w_fill)/(w_fiss-w_fill), f); +} + +TEST(FuelFabTests, LowFrac) { + double w_fill = CosiWeight(c_natu(), "thermal"); + double w_fiss = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + + EXPECT_THROW(LowFrac(w_fiss, w_target, w_fill), cyclus::ValueError); + EXPECT_THROW(LowFrac(w_target, w_fill, w_fiss), cyclus::ValueError); + EXPECT_THROW(LowFrac(w_target, w_fiss, w_fill), cyclus::ValueError); + + double f = LowFrac(w_fill, w_target, w_fiss); + EXPECT_DOUBLE_EQ((w_fiss-w_target)/(w_fiss-w_fill), f); +} + +TEST(FuelFabTests, ValidWeights) { + double w_fill = CosiWeight(c_natu(), "thermal"); + double w_fiss = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + + EXPECT_EQ(true, ValidWeights(w_fill, w_target, w_fiss)); + EXPECT_EQ(false, ValidWeights(w_fiss, w_target, w_fill)); + EXPECT_EQ(false, ValidWeights(w_target, w_fill, w_fiss)); + EXPECT_EQ(false, ValidWeights(w_fiss, w_fill, w_target)); + EXPECT_EQ(false, ValidWeights(w_target, w_fiss, w_fill)); + EXPECT_EQ(false, ValidWeights(w_fill, w_fiss, w_target)); +} + +// request (and receive) a specific recipe for fissile stream correctly. +TEST(FuelFabTests, FissRecipe) { + std::string config = + " dummy " + "natu" + "1" + "" + " stream1 stream2 stream3 " + "2.5" + "spentuox" + "" + "dummyout" + "thermal" + "0" + ; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("stream1").Finalize(); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + int resid = sim.db().Query("Transactions", NULL).GetVal("ResourceId"); + CompMap got = sim.GetMaterial(resid)->comp()->mass(); + CompMap want = c_pustream()->mass(); + cyclus::compmath::Normalize(&got); + cyclus::compmath::Normalize(&want); + CompMap::iterator it; + for (it = want.begin(); it != want.end(); ++it) { + EXPECT_DOUBLE_EQ(it->second, got[it->first]) << "nuclide qty off: " << pyne::nucname::name(it->first); + } +} + +// multiple fissile streams can be correctly requested and used as +// fissile material inventory. +TEST(FuelFabTests, MultipleFissStreams) { + std::string config = + " dummy " + "natu" + "1" + "" + " stream1 stream2 stream3 " + "2.5" + "" + "dummyout" + "thermal" + "0" + ; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("stream1").recipe("spentuox").capacity(1).Finalize(); + sim.AddSource("stream2").recipe("spentuox").capacity(1).Finalize(); + sim.AddSource("stream3").recipe("spentuox").capacity(1).Finalize(); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(3, qr.rows.size()); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("stream1"))); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(1, qr.rows.size()); + + conds[0] = Cond("Commodity", "==", std::string("stream2")); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(1, qr.rows.size()); + + conds[0] = Cond("Commodity", "==", std::string("stream3")); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(1, qr.rows.size()); +} + +// fissile stream preferences can be specified. +TEST(FuelFabTests, FissStreamPrefs) { + std::string config = + " dummy " + "natu" + "1" + "" + " stream1 stream2 stream3 " + " 1.0 0.0 2.0 " + "1.5" + "" + "dummyout" + "thermal" + "0" + ; + + int simdur = 1; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("stream1").recipe("spentuox").capacity(1).Finalize(); + sim.AddSource("stream2").recipe("spentuox").capacity(1).Finalize(); + sim.AddSource("stream3").recipe("spentuox").capacity(1).Finalize(); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("stream1"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + EXPECT_DOUBLE_EQ(0.5, m->quantity()); + + conds[0] = Cond("Commodity", "==", std::string("stream2")); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(0, qr.rows.size()); + + conds[0] = Cond("Commodity", "==", std::string("stream3")); + qr = sim.db().Query("Transactions", &conds); + m = sim.GetMaterial(qr.GetVal("ResourceId")); + EXPECT_DOUBLE_EQ(1.0, m->quantity()); +} + +// zero throughput must not result in a zero capacity constraint excception. +TEST(FuelFabTests, ZeroThroughput) { + std::string config = + " natu " + "natu" + "3.9" + "" + " spentuox " + "spentuox" + "3.5" + "" + "uox" + "uox" + "3.3" + "" + "dummyout" + "thermal" + "0" + ; + + int simdur = 10; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("uox").capacity(1).Finalize(); + sim.AddSource("spentuox").capacity(1).Finalize(); + sim.AddSource("natu").capacity(1).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + EXPECT_NO_THROW(sim.Run()); +} + +// fill, fiss, and topup inventories are all requested for and +// filled as expected. Inventory size constraints are properly +// enforced after they are full. +TEST(FuelFabTests, FillAllInventories) { + std::string config = + " natu " + "natu" + "3.9" + "" + " spentuox " + "spentuox" + "3.5" + "" + "uox" + "uox" + "3.3" + "" + "dummyout" + "thermal" + "1" + ; + + int simdur = 10; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("uox").capacity(1).Finalize(); + sim.AddSource("spentuox").capacity(1).Finalize(); + sim.AddSource("natu").capacity(1).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare( + "SELECT SUM(r.Quantity) FROM Transactions AS t" + " INNER JOIN Resources AS r ON r.ResourceId = t.ResourceId" + " WHERE t.Commodity = ?;" + ); + + stmt->BindText(1, "natu"); + stmt->Step(); + EXPECT_DOUBLE_EQ(3.9, stmt->GetDouble(0)); + stmt->Reset(); + stmt->BindText(1, "spentuox"); + stmt->Step(); + EXPECT_DOUBLE_EQ(3.5, stmt->GetDouble(0)); + stmt->Reset(); + stmt->BindText(1, "uox"); + stmt->Step(); + EXPECT_DOUBLE_EQ(3.3, stmt->GetDouble(0)); +} + +// Meet a request requiring zero fill inventory when we have zero fill +// inventory quantity. +TEST(FuelFabTests, ProvideStraightFiss_WithZeroFill) { + std::string config = + " nothing " + "natu" + "100" + "" + " anything " + "spentuox" + "100" + "" + "recyclefuel" + "thermal" + "100" + ; + int simdur = 6; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("anything").Finalize(); + sim.AddSink("recyclefuel").recipe("spentuox").capacity(100).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", NULL); + // 6 = 3 receives of inventory, 3 sells of recycled fuel + EXPECT_EQ(6, qr.rows.size()); +} + +TEST(FuelFabTests, ProvideStraightFill_ZeroFiss) { + std::string config = + " anything " + "natu" + "100" + "" + " nothing " + "spentuox" + "100" + "" + "recyclefuel" + "thermal" + "100" + ; + int simdur = 6; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("anything").Finalize(); + sim.AddSink("recyclefuel").recipe("natu").capacity(100).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", NULL); + // 6 = 3 receives of inventory, 3 sells of recycled fuel + EXPECT_EQ(6, qr.rows.size()); +} + +// throughput is properly restricted when faced with many fuel +// requests and with ample material inventory. +TEST(FuelFabTests, ThroughputLimit) { + std::string config = + " anything " + "natu" + "100" + "" + " anything " + "pustream" + "100" + "" + "recyclefuel" + "thermal" + "3" + ; + double throughput = 3; + + int simdur = 5; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("anything").lifetime(1).Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(2*throughput).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_LT(2, qr.rows.size()); + + cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare( + "SELECT SUM(r.Quantity) FROM Transactions AS t" + " INNER JOIN Resources AS r ON r.ResourceId = t.ResourceId" + " WHERE t.Commodity = 'recyclefuel';" + ); + + stmt->Step(); + EXPECT_DOUBLE_EQ(throughput * (simdur-1), stmt->GetDouble(0)); + + stmt = sim.db().db().Prepare( + "SELECT COUNT(*) FROM Transactions WHERE Commodity = 'recyclefuel';" + ); + + stmt->Step(); + EXPECT_DOUBLE_EQ(simdur-1, stmt->GetDouble(0)); +} + +// supplied fuel has proper equivalence weights as requested. +TEST(FuelFabTests, CorrectMixing) { + std::string config = + " natu " + "natu" + "100" + "" + " pustream " + "pustream" + "100" + "" + "recyclefuel" + "thermal" + "100" + ; + int simdur = 3; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").Finalize(); + sim.AddSource("natu").Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(10).lifetime(2).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(1, qr.rows.size()); + + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + double got = CosiWeight(m->comp(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + EXPECT_LT(std::abs((w_target-got)/w_target), 0.00001) << "mixed composition not within 0.001% of target"; + + conds.push_back(Cond("Time", "==", 2)); + + // TODO: do hand calcs to verify expected vals below - they are just regression values currently. + conds[0] = Cond("Commodity", "==", std::string("natu")); + qr = sim.db().Query("Transactions", &conds); + m = sim.GetMaterial(qr.GetVal("ResourceId")); + EXPECT_NEAR(9.7463873197, m->quantity(), 1e-6) << "mixed wrong amount of Nat. U stream"; + + conds[0] = Cond("Commodity", "==", std::string("pustream")); + qr = sim.db().Query("Transactions", &conds); + m = sim.GetMaterial(qr.GetVal("ResourceId")); + EXPECT_NEAR(0.25361268029, m->quantity(), 1e-6) << "mixed wrong amount of Pu stream"; +} + +// fuel is requested requiring more filler than is available with plenty of +// fissile. +TEST(FuelFabTests, FillConstrained) { + std::string config = + " natu " + "natu" + "1" + "" + " pustream " + "pustream" + "10000" + "" + "recyclefuel" + "thermal" + "10000" + ; + double fillinv = 1; + int simdur = 2; + + double w_fill = CosiWeight(c_natu(), "thermal"); + double w_fiss = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + double fiss_frac = HighFrac(w_fill, w_target, w_fiss); + double fill_frac = LowFrac(w_fill, w_target, w_fiss); + fiss_frac = AtomToMassFrac(fiss_frac, c_pustream(), c_natu()); + fill_frac = AtomToMassFrac(fill_frac, c_natu(), c_pustream()); + double max_provide = fillinv / fill_frac; + + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").lifetime(1).Finalize(); + sim.AddSource("natu").lifetime(1).Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fill than available"; +} + +// fuel is requested requiring more fissile material than is available with +// plenty of filler. +TEST(FuelFabTests, FissConstrained) { + std::string config = + " natu " + "natu" + "10000" + "" + " pustream " + "pustream" + "1" + "" + "recyclefuel" + "thermal" + "10000" + ; + double fissinv = 1; + int simdur = 2; + + double w_fill = CosiWeight(c_natu(), "thermal"); + double w_fiss = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + double fiss_frac = HighFrac(w_fill, w_target, w_fiss); + double fill_frac = LowFrac(w_fill, w_target, w_fiss); + fiss_frac = AtomToMassFrac(fiss_frac, c_pustream(), c_natu()); + fill_frac = AtomToMassFrac(fill_frac, c_natu(), c_pustream()); + double max_provide = fissinv / fiss_frac; + + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").lifetime(1).Finalize(); + sim.AddSource("natu").lifetime(1).Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fill than available"; +} + +// swap to topup inventory because fissile has too low reactivity. +TEST(FuelFabTests, SwapTopup) { + std::string config = + " natu " + "natu" + "10000" + "" + " pustreambad " + "pustreambad" + "10000" + "" + "pustream" + "pustream" + "10000" + "" + "recyclefuel" + "thermal" + "10000" + ; + int simdur = 3; + double sink_cap = 10; + + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").Finalize(); + sim.AddSource("pustreambad").Finalize(); + sim.AddSource("natu").Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(sink_cap).lifetime(2).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("pustreambad", c_pustreambad()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request"; + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + EXPECT_NEAR(sink_cap, m->quantity(), 1e-10) << "supplied fuel was constrained too much"; + + conds[0] = Cond("Commodity", "==", std::string("natu")); + conds.push_back(Cond("Time", "==", 2)); + qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(0, qr.rows.size()) << "failed to swith to topup - used fill - uh oh"; + + conds[0] = Cond("Commodity", "==", std::string("pustream")); + conds.push_back(Cond("Time", "==", 2)); + qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "didn't get more topup after supposedly using it - why?"; +} + +TEST(FuelFabTests, SwapTopup_ZeroFill) { + std::string config = + " natu " + "natu" + "0" + "" + " pustreambad " + "pustreambad" + "10000" + "" + "pustream" + "pustream" + "10000" + "" + "recyclefuel" + "thermal" + "10000" + ; + int simdur = 3; + double sink_cap = 10; + + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").Finalize(); + sim.AddSource("pustreambad").Finalize(); + sim.AddSource("natu").Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(sink_cap).lifetime(2).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("pustreambad", c_pustreambad()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request"; + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + EXPECT_NEAR(sink_cap, m->quantity(), 1e-10) << "supplied fuel was constrained too much"; + + conds[0] = Cond("Commodity", "==", std::string("pustream")); + conds.push_back(Cond("Time", "==", 2)); + qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "failed to use topup - why?"; + + conds[0] = Cond("Commodity", "==", std::string("pustreambad")); + conds.push_back(Cond("Time", "==", 2)); + qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "failed to use fiss - why?"; +} + +// swap to topup inventory but are limited by topup inventory quantity. This +// test makes sure the provided fuel is much less than requested due to a +// small topup inventory despite a surplus of fill that can't be used due to +// the fiss stream not having a high enough weight (so we must use topup with +// fiss). +TEST(FuelFabTests, SwapTopup_TopupConstrained) { + std::string config = + " natu " + "natu" + "10000" + "" + " pustreambad " + "pustreambad" + "10000" + "" + "pustream" + "pustream" + "1" + "" + "recyclefuel" + "thermal" + "10000" + ; + double topupinv = 1; + int simdur = 2; + + // compute max fuel mass providable in a single time step + double w_fiss = CosiWeight(c_pustreambad(), "thermal"); + double w_topup = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + double topup_frac = HighFrac(w_fiss, w_target, w_topup); + double fiss_frac = LowFrac(w_fiss, w_target, w_topup); + fiss_frac = AtomToMassFrac(fiss_frac, c_pustreambad(), c_pustream()); + topup_frac = AtomToMassFrac(topup_frac, c_pustream(), c_pustreambad()); + double max_provide = topupinv / topup_frac; + + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").Finalize(); + sim.AddSource("pustreambad").Finalize(); + sim.AddSource("natu").Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("pustreambad", c_pustreambad()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request"; + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fiss than available"; +} + +// swap to topup inventory but are limited by fiss inventory quantity. This +// test makes sure the provided fuel is much less than requested due to a +// small fiss inventory. +TEST(FuelFabTests, SwapTopup_FissConstrained) { + std::string config = + " natu " + "natu" + "0" + "" + " pustreambad " + "pustreambad" + "1" + "" + "pustream" + "pustream" + "10000" + "" + "recyclefuel" + "thermal" + "10000" + ; + double fissinv = 1; + int simdur = 2; + + // compute max fuel mass providable in a single time step + double w_fiss = CosiWeight(c_pustreambad(), "thermal"); + double w_topup = CosiWeight(c_pustream(), "thermal"); + double w_target = CosiWeight(c_uox(), "thermal"); + double topup_frac = HighFrac(w_fiss, w_target, w_topup); + double fiss_frac = LowFrac(w_fiss, w_target, w_topup); + fiss_frac = AtomToMassFrac(fiss_frac, c_pustreambad(), c_pustream()); + topup_frac = AtomToMassFrac(topup_frac, c_pustream(), c_pustreambad()); + double max_provide = fissinv / fiss_frac; + + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("pustream").Finalize(); + sim.AddSource("pustreambad").Finalize(); + sim.AddSource("natu").Finalize(); + sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("pustream", c_pustream()); + sim.AddRecipe("pustreambad", c_pustreambad()); + sim.AddRecipe("natu", c_natu()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("Commodity", "==", std::string("recyclefuel"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request"; + Material::Ptr m = sim.GetMaterial(qr.GetVal("ResourceId")); + + EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fiss than available"; +} + +// Before this test and a fix, the fuel fab (partially) assumed each entire material +// buffer had the same composition as the material on top of the buffer when +// calculating stream mixing ratios for material to supply. This problem was +// compounded by the fact that material weights are computed on an atom basis +// and mixing is done on a mass basis - corresponding conversions resulted in +// the fab being matched for more than it could actually supply - due to +// thinking it had an inventory of higher quality material than was actually +// the case. This test makes sure that doesn't happen again. +TEST(FuelFabTests, HomogenousBuffers) { + std::string config = + " natu " + "natu" + "40" + "" + " stream1 " + "4" + "spentuox" + "" + "out" + "thermal" + "1e10" + ; + + CompMap m; + m[id("u235")] = 7; + m[id("u238")] = 86; + // the zr90 is important to force the atom-mass basis conversion to push the + // dre to overmatch in the direction we want. + m[id("zr90")] = 7; + Composition::Ptr c = Composition::CreateFromMass(m); + + int simdur = 5; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur); + sim.AddSource("stream1").start(0).lifetime(1).capacity(.01).recipe("special").Finalize(); + sim.AddSource("stream1").start(1).lifetime(1).capacity(3.98).recipe("natu").Finalize(); + sim.AddSource("natu").lifetime(1).Finalize(); + sim.AddSink("out").start(2).capacity(4).lifetime(1).recipe("uox").Finalize(); + sim.AddSink("out").start(2).capacity(4).lifetime(1).recipe("uox").Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_pustream()); + sim.AddRecipe("natu", c_natu()); + sim.AddRecipe("special", c); + ASSERT_NO_THROW(sim.Run()); +} + +} // namespace fuelfabtests +} // namespace cycamore + + diff --git a/src/growth_region.cc b/src/growth_region.cc index 4f09f8c8d6..6fbd41f0e3 100644 --- a/src/growth_region.cc +++ b/src/growth_region.cc @@ -3,53 +3,51 @@ namespace cycamore { -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - GrowthRegion::GrowthRegion(cyclus::Context* ctx) : cyclus::Region(ctx) { cyclus::Warn("the GrowthRegion is experimental."); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - GrowthRegion::~GrowthRegion() {} -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void GrowthRegion::AddCommodityDemand(cyclus::toolkit::Commodity commod) { - // instantiate demand function +void GrowthRegion::AddCommodityDemand_(std::string commod, + Demand& demand) { + + cyclus::toolkit::PiecewiseFunctionFactory pff; - int ndemands = demand_types.size(); - for (int i = 0; i < ndemands; i++) { - cyclus::toolkit::BasicFunctionFactory bff; - bool continuous = (i != 0); // the first entry is not continuous - pff.AddFunction(bff.GetFunctionPtr(demand_types[i], demand_params[i]), - demand_times[i], continuous); + cyclus::toolkit::BasicFunctionFactory bff; + bool continuous = false; + int time; + std::string type, params; + Demand::const_iterator it; + for (it = demand.begin(); it != demand.end(); it++) { + time = it->first; + type = it->second.first; + params = it->second.second; + pff.AddFunction(bff.GetFunctionPtr(type, params), time, continuous); + continuous = true; // only the first entry is not continuous } - // register the commodity anddemand - sdmanager_.RegisterCommodity(commod, pff.GetFunctionPtr()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void GrowthRegion::Build(cyclus::Agent* parent) { - cyclus::Region::Build(parent); - commod_ = cyclus::toolkit::Commodity(commodity_name); - AddCommodityDemand(commod_); + // register the commodity and demand + cyclus::toolkit::Commodity c(commod); + sdmanager_.RegisterCommodity(c, pff.GetFunctionPtr()); } void GrowthRegion::EnterNotify() { cyclus::Region::EnterNotify(); - std::set::iterator it; - for (it = cyclus::Agent::children().begin(); - it != cyclus::Agent::children().end(); - ++it) { - Agent* a = *it; + std::set::iterator ait; + for (ait = cyclus::Agent::children().begin(); + ait != cyclus::Agent::children().end(); + ++ait) { + Agent* a = *ait; Register_(a); } - commod_ = cyclus::toolkit::Commodity(commodity_name); - AddCommodityDemand(commod_); -} - -void GrowthRegion::BuildNotify(Agent* a) { - Register_(a); + std::map::iterator it; + for (it = commodity_demand.begin(); it != commodity_demand.end(); ++it) { + LOG(cyclus::LEV_INFO3, "greg") << "Adding demand for commodity " + << it->first; + AddCommodityDemand_(it->first, it->second); + } } void GrowthRegion::DecomNotify(Agent* a) { @@ -92,60 +90,69 @@ void GrowthRegion::Unregister_(cyclus::Agent* agent) { buildmanager_.Unregister(b_cast); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void GrowthRegion::Tick() { + double demand, supply, unmetdemand; + cyclus::toolkit::Commodity commod; int time = context()->time(); - double demand = sdmanager_.Demand(commod_, time); - double supply = sdmanager_.Supply(commod_); - double unmetdemand = demand - supply; - - LOG(cyclus::LEV_INFO3, "greg") << "GrowthRegion: " << prototype() - << " at time: " << time - << " has the following values regaring " - << " commodity: " << commod_.name(); - LOG(cyclus::LEV_INFO3, "greg") << " *demand = " << demand; - LOG(cyclus::LEV_INFO3, "greg") << " *supply = " << supply; - LOG(cyclus::LEV_INFO3, "greg") << " * unmetdemand = " << unmetdemand; - - if (unmetdemand > 0) { - OrderBuilds(commod_, unmetdemand); + std::map::iterator it; + for (it = commodity_demand.begin(); it != commodity_demand.end(); ++it) { + commod = cyclus::toolkit::Commodity(it->first); + demand = sdmanager_.Demand(commod, time); + supply = sdmanager_.Supply(commod); + unmetdemand = demand - supply; + + LOG(cyclus::LEV_INFO3, "greg") << "GrowthRegion: " << prototype() + << " at time: " << time + << " has the following values regarding " + << " commodity: " << commod.name(); + LOG(cyclus::LEV_INFO3, "greg") << " * demand = " << demand; + LOG(cyclus::LEV_INFO3, "greg") << " * supply = " << supply; + LOG(cyclus::LEV_INFO3, "greg") << " * unmet demand = " << unmetdemand; + + if (unmetdemand > 0) { + OrderBuilds(commod, unmetdemand); + } } cyclus::Region::Tick(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void GrowthRegion::OrderBuilds(cyclus::toolkit::Commodity& commodity, double unmetdemand) { using std::vector; vector orders = buildmanager_.MakeBuildDecision(commodity, unmetdemand); - LOG(cyclus::LEV_INFO3, "greg") << "The build orders have been determined. " - << orders.size() - << " different type(s) of prototypes will be built."; + LOG(cyclus::LEV_INFO3, "greg") + << "The build orders have been determined. " + << orders.size() + << " different type(s) of prototypes will be built."; + cyclus::toolkit::BuildOrder* order; + cyclus::Institution* instcast; + cyclus::Agent* agentcast; for (int i = 0; i < orders.size(); i++) { - cyclus::toolkit::BuildOrder order = orders.at(i); - cyclus::Institution* instcast = dynamic_cast(order.builder); - cyclus::Agent* agentcast = dynamic_cast(order.producer); + order = &orders.at(i); + instcast = dynamic_cast(order->builder); + agentcast = dynamic_cast(order->producer); if (!instcast || !agentcast) { - throw cyclus::CastError("growth_region.has tried to incorrectly cast an already known entity."); + throw cyclus::CastError("growth_region has tried to incorrectly " + "cast an already known entity."); } - LOG(cyclus::LEV_INFO3, "greg") << "A build order for " << order.number - << " prototype(s) of type " - << dynamic_cast(agentcast)->prototype() - << " from builder " << instcast->prototype() - << " is being placed."; + LOG(cyclus::LEV_INFO3, "greg") + << "A build order for " << order->number + << " prototype(s) of type " + << dynamic_cast(agentcast)->prototype() + << " from builder " << instcast->prototype() + << " is being placed."; - for (int j = 0; j < order.number; j++) { + for (int j = 0; j < order->number; j++) { LOG(cyclus::LEV_DEBUG2, "greg") << "Ordering build number: " << j + 1; context()->SchedBuild(instcast, agentcast->prototype()); } } } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - extern "C" cyclus::Agent* ConstructGrowthRegion(cyclus::Context* ctx) { return new GrowthRegion(ctx); } diff --git a/src/growth_region.h b/src/growth_region.h index 87526c349c..42523334f1 100644 --- a/src/growth_region.h +++ b/src/growth_region.h @@ -1,12 +1,12 @@ #ifndef CYCAMORE_SRC_GROWTH_REGION_H_ #define CYCAMORE_SRC_GROWTH_REGION_H_ -#include +#include +#include #include #include "cyclus.h" - // forward declarations namespace cycamore { class GrowthRegion; @@ -16,6 +16,11 @@ class GrowthRegion; #include "growth_region_tests.h" namespace cycamore { + +/// A container of (time, (demand type, demand parameters)) +typedef std::vector< + std::pair > > Demand; + /// This region determines if there is a need to meet a certain /// capacity (as defined via input) at each time step. If there is /// such a need, the region will determine how many of each facility @@ -43,13 +48,6 @@ class GrowthRegion : public cyclus::Region { #pragma cyclus note {"doc": "A region that governs a scenario in which " \ "there is growth in demand for a commodity. "} - /// add a demand for a commodity on which this region request that - /// facilities be built - void AddCommodityDemand(cyclus::toolkit::Commodity commod); - - /// perform module-specific tasks when entering the simulation - virtual void Build(cyclus::Agent* parent); - /// On each tick, the GrowthRegion queries its supply demand manager /// to determine if there exists some demand. If demand for a /// commodity exists, then the correct build order for that demand @@ -60,62 +58,69 @@ class GrowthRegion : public cyclus::Region { /// enter the simulation and register any children present virtual void EnterNotify(); - /// register a new child - virtual void BuildNotify(Agent* m); - /// unregister a child virtual void DecomNotify(Agent* m); inline cyclus::toolkit::SupplyDemandManager* sdmanager() { return &sdmanager_; } - - protected: - #pragma cyclus var {"tooltip": "commodity in demand", \ - "doc": "name of the commodity experiencing a " \ - "growth in demand", \ - "uitype": "commodity"} - std::string commodity_name; - - #pragma cyclus var {"tooltip": "demand type", \ - "doc": "mathematical description of demand growth " \ - "(i.e., linear, exponential, piecewise)"} - std::vector demand_types; - - #pragma cyclus var {"tooltip": "demand parameters", \ - "doc": "parameters that define the behavior of the " \ - "demand type function"} - std::vector demand_params; - - #pragma cyclus var {"tooltip": "demand times", \ - "doc": "vector describing the length of times " \ - "regarding the piecewise demand type"} - std::vector demand_times; - - cyclus::toolkit::Commodity commod_; - + + protected: + #pragma cyclus var { \ + "alias": ["growth", "commod", \ + ["piecewise_function", \ + ["piece", "start", ["function", "type", "params"]]]], \ + "uitype": ["oneormore", "string", \ + ["oneormore", \ + ["pair", "int", ["pair", "string", "string"]]]], \ + "uilabel": "Growth Demand Curves", \ + "doc": "Nameplate capacity demand functions." \ + "\n\n" \ + "Each demand type must be for a commodity for which capacity can be built "\ + "(e.g., 'power' from cycamore::Reactors). Any archetype that implements the "\ + "cyclus::toolkit::CommodityProducer interface can interact with the "\ + "GrowthRegion in the manner." \ + "\n\n" \ + "Demand functions are defined as piecewise functions. Each piece must "\ + "be provided a starting time and function description. Each function "\ + "description is comprised of a function type and associated parameters. "\ + "\n\n" \ + " * Start times are inclusive. For a start time :math:`t_0`, the demand "\ + "function is evaluated on :math:`[t_0, \infty)`." \ + "\n\n" \ + " * Supported function types are based on the "\ + "`cyclus::toolkit::BasicFunctionFactory "\ + "types `_. " \ + "\n\n" \ + " * The type name is the lower-case name of the function (e.g., " \ + "'linear', 'exponential', etc.)." \ + "\n\n" \ + " * The parameters associated with each function type can be found on their " \ + "respective documentation pages.", \ + } + std::map > > > commodity_demand; // must match Demand typedef + /// manager for building things cyclus::toolkit::BuildingManager buildmanager_; /// manager for Supply and demand cyclus::toolkit::SupplyDemandManager sdmanager_; - + /// register a child void Register_(cyclus::Agent* agent); /// unregister a child void Unregister_(cyclus::Agent* agent); + /// add a demand for a commodity on which this region request that + /// facilities be built + void AddCommodityDemand_(std::string commod, Demand& demand); + /// orders builds given a commodity and an unmet demand for production /// capacity of that commodity /// @param commodity the commodity being demanded /// @param unmetdemand the unmet demand void OrderBuilds(cyclus::toolkit::Commodity& commodity, double unmetdemand); - - /// orders builder to build a prototype - /// @param builder the agent that can build buildee - /// @param prototype the agent to be built - void OrderBuild(cyclus::Agent* builder, cyclus::Agent* prototype); }; } // namespace cycamore diff --git a/src/manager_inst.h b/src/manager_inst.h index 3b1142b854..3e1d294a79 100644 --- a/src/manager_inst.h +++ b/src/manager_inst.h @@ -47,9 +47,14 @@ class ManagerInst /// unregister a child void Unregister_(cyclus::Agent* agent); - #pragma cyclus var {"tooltip": "facility prototypes", \ - "doc": "a facility to be managed by the institution", \ - "uitype": ["none", "prototype"]} + #pragma cyclus var { \ + "tooltip": "producer facility prototypes", \ + "uilabel": "Producer Prototype List", \ + "uitype": ["oneormore", "prototype"], \ + "doc": "A set of facility prototypes that this institution can build. " \ + "All prototypes in this list must be based on an archetype that " \ + "implements the cyclus::toolkit::CommodityProducer interface", \ + } std::vector prototypes; }; diff --git a/src/reactor.cc b/src/reactor.cc new file mode 100644 index 0000000000..e8d30ecb4d --- /dev/null +++ b/src/reactor.cc @@ -0,0 +1,502 @@ +#include "reactor.h" + +using cyclus::Material; +using cyclus::Composition; +using cyclus::toolkit::ResBuf; +using cyclus::toolkit::MatVec; +using cyclus::KeyError; +using cyclus::ValueError; +using cyclus::Request; + +namespace cycamore { + +Reactor::Reactor(cyclus::Context* ctx) + : cyclus::Facility(ctx), + n_assem_batch(0), + assem_size(0), + n_assem_core(0), + n_assem_spent(0), + n_assem_fresh(0), + cycle_time(0), + refuel_time(0), + cycle_step(0), + power_cap(0), + power_name("power"), + discharged(false) { + cyclus::Warn( + "the Reactor archetype " + "is experimental"); +} + +#pragma cyclus def clone cycamore::Reactor + +#pragma cyclus def schema cycamore::Reactor + +#pragma cyclus def annotations cycamore::Reactor + +#pragma cyclus def infiletodb cycamore::Reactor + +#pragma cyclus def snapshot cycamore::Reactor + +#pragma cyclus def snapshotinv cycamore::Reactor + +#pragma cyclus def initinv cycamore::Reactor + +void Reactor::InitFrom(Reactor* m) { + #pragma cyclus impl initfromcopy cycamore::Reactor + cyclus::toolkit::CommodityProducer::Copy(m); +} + +void Reactor::InitFrom(cyclus::QueryableBackend* b) { + #pragma cyclus impl initfromdb cycamore::Reactor + + namespace tk = cyclus::toolkit; + tk::CommodityProducer::Add(tk::Commodity(power_name), + tk::CommodInfo(power_cap, power_cap)); +} + +void Reactor::EnterNotify() { + cyclus::Facility::EnterNotify(); + + // If the user ommitted fuel_prefs, we set it to zeros for each fuel + // type. Without this segfaults could occur - yuck. + if (fuel_prefs.size() == 0) { + for (int i = 0; i < fuel_outcommods.size(); i++) { + fuel_prefs.push_back(0); + } + } + + // input consistency checking: + int n = recipe_change_times.size(); + std::stringstream ss; + if (recipe_change_commods.size() != n) { + ss << "prototype '" << prototype() << "' has " + << recipe_change_commods.size() + << " recipe_change_commods vals, expected " << n << "\n"; + } + if (recipe_change_in.size() != n) { + ss << "prototype '" << prototype() << "' has " << recipe_change_in.size() + << " recipe_change_in vals, expected " << n << "\n"; + } + if (recipe_change_out.size() != n) { + ss << "prototype '" << prototype() << "' has " << recipe_change_out.size() + << " recipe_change_out vals, expected " << n << "\n"; + } + + n = pref_change_times.size(); + if (pref_change_commods.size() != n) { + ss << "prototype '" << prototype() << "' has " << pref_change_commods.size() + << " pref_change_commods vals, expected " << n << "\n"; + } + if (pref_change_values.size() != n) { + ss << "prototype '" << prototype() << "' has " << pref_change_values.size() + << " pref_change_values vals, expected " << n << "\n"; + } + + if (ss.str().size() > 0) { + throw cyclus::ValueError(ss.str()); + } +} + +bool Reactor::CheckDecommissionCondition() { + return core.count() == 0 && spent.count() == 0; +} + +void Reactor::Tick() { + // The following code must go in the Tick so they fire on the time step + // following the cycle_step update - allowing for the all reactor events to + // occur and be recorded on the "beginning" of a time step. Another reason + // they + // can't go at the beginnin of the Tock is so that resource exchange has a + // chance to occur after the discharge on this same time step. + + if (retired()) { + Record("RETIRED", ""); + + if (context()->time() == exit_time()) { // only need to transmute once + Transmute(ceil(static_cast(n_assem_core) / 2.0)); + } + while (core.count() > 0) { + if (!Discharge()) { + break; + } + } + // in case a cycle lands exactly on our last time step, we will need to + // burn a batch from fresh inventory on this time step. When retired, + // this batch also needs to be discharged to spent fuel inventory. + while (fresh.count() > 0 && spent.space() >= assem_size) { + spent.Push(fresh.Pop()); + } + return; + } + + if (cycle_step == cycle_time) { + Transmute(); + Record("CYCLE_END", ""); + } + + if (cycle_step >= cycle_time && !discharged) { + discharged = Discharge(); + } + if (cycle_step >= cycle_time) { + Load(); + } + + int t = context()->time(); + + // update preferences + for (int i = 0; i < pref_change_times.size(); i++) { + int change_t = pref_change_times[i]; + if (t != change_t) { + continue; + } + + std::string incommod = pref_change_commods[i]; + for (int j = 0; j < fuel_incommods.size(); j++) { + if (fuel_incommods[j] == incommod) { + fuel_prefs[j] = pref_change_values[i]; + break; + } + } + } + + // update recipes + for (int i = 0; i < recipe_change_times.size(); i++) { + int change_t = recipe_change_times[i]; + if (t != change_t) { + continue; + } + + std::string incommod = recipe_change_commods[i]; + for (int j = 0; j < fuel_incommods.size(); j++) { + if (fuel_incommods[j] == incommod) { + fuel_inrecipes[j] = recipe_change_in[i]; + fuel_outrecipes[j] = recipe_change_out[i]; + break; + } + } + } +} + +std::set::Ptr> Reactor::GetMatlRequests() { + using cyclus::RequestPortfolio; + + std::set::Ptr> ports; + Material::Ptr m; + + // second min expression reduces assembles to amount needed until + // retirement if it is near. + int n_assem_order = n_assem_core - core.count() + n_assem_fresh - fresh.count(); + + if (exit_time() != -1) { + // the +1 accounts for the fact that the reactor is alive and gets to + // operate during its exit_time time step. + int t_left = exit_time() - context()->time() + 1; + int t_left_cycle = cycle_time + refuel_time - cycle_step; + double n_cycles_left = static_cast(t_left - t_left_cycle) / + static_cast(cycle_time + refuel_time); + n_cycles_left = ceil(n_cycles_left); + int n_need = std::max(0.0, n_cycles_left * n_assem_batch - n_assem_fresh); + n_assem_order = std::min(n_assem_order, n_need); + } + + if (n_assem_order == 0) { + return ports; + } else if (retired()) { + return ports; + } + + for (int i = 0; i < n_assem_order; i++) { + RequestPortfolio::Ptr port(new RequestPortfolio()); + std::vector*> mreqs; + for (int j = 0; j < fuel_incommods.size(); j++) { + std::string commod = fuel_incommods[j]; + double pref = fuel_prefs[j]; + Composition::Ptr recipe = context()->GetRecipe(fuel_inrecipes[j]); + m = Material::CreateUntracked(assem_size, recipe); + Request* r = port->AddRequest(m, this, commod, pref, true); + mreqs.push_back(r); + } + port->AddMutualReqs(mreqs); + ports.insert(port); + } + + return ports; +} + +void Reactor::GetMatlTrades( + const std::vector >& trades, + std::vector, Material::Ptr> >& + responses) { + using cyclus::Trade; + + std::map mats = PopSpent(); + for (int i = 0; i < trades.size(); i++) { + std::string commod = trades[i].request->commodity(); + Material::Ptr m = mats[commod].back(); + mats[commod].pop_back(); + responses.push_back(std::make_pair(trades[i], m)); + res_indexes.erase(m->obj_id()); + } + PushSpent(mats); // return leftovers back to spent buffer +} + +void Reactor::AcceptMatlTrades(const std::vector< + std::pair, Material::Ptr> >& responses) { + std::vector, + cyclus::Material::Ptr> >::const_iterator trade; + + std::stringstream ss; + int nload = std::min((int)responses.size(), n_assem_core - core.count()); + if (nload > 0) { + ss << nload << " assemblies"; + Record("LOAD", ss.str()); + } + + for (trade = responses.begin(); trade != responses.end(); ++trade) { + std::string commod = trade->first.request->commodity(); + Material::Ptr m = trade->second; + index_res(m, commod); + + if (core.count() < n_assem_core) { + core.Push(m); + } else { + fresh.Push(m); + } + } +} + +std::set::Ptr> Reactor::GetMatlBids( + cyclus::CommodMap::type& commod_requests) { + using cyclus::BidPortfolio; + + std::set::Ptr> ports; + + bool gotmats = false; + std::map all_mats; + + if (uniq_outcommods_.empty()) { + for (int i = 0; i < fuel_outcommods.size(); i++) { + uniq_outcommods_.insert(fuel_outcommods[i]); + } + } + + std::set::iterator it; + for (it = uniq_outcommods_.begin(); it != uniq_outcommods_.end(); ++it) { + std::string commod = *it; + std::vector*>& reqs = commod_requests[commod]; + if (reqs.size() == 0) { + continue; + } else if (!gotmats) { + all_mats = PeekSpent(); + } + + MatVec mats = all_mats[commod]; + if (mats.size() == 0) { + continue; + } + + BidPortfolio::Ptr port(new BidPortfolio()); + + for (int j = 0; j < reqs.size(); j++) { + Request* req = reqs[j]; + double tot_bid = 0; + for (int k = 0; k < mats.size(); k++) { + Material::Ptr m = mats[k]; + tot_bid += m->quantity(); + port->AddBid(req, m, this, true); + if (tot_bid >= req->target()->quantity()) { + break; + } + } + } + + double tot_qty = 0; + for (int j = 0; j < mats.size(); j++) { + tot_qty += mats[j]->quantity(); + } + cyclus::CapacityConstraint cc(tot_qty); + port->AddConstraint(cc); + ports.insert(port); + } + + return ports; +} + +void Reactor::Tock() { + if (retired()) { + return; + } + + if (cycle_step >= cycle_time + refuel_time && core.count() == n_assem_core) { + discharged = false; + cycle_step = 0; + } + + if (cycle_step == 0 && core.count() == n_assem_core) { + Record("CYCLE_START", ""); + } + + if (cycle_step >= 0 && cycle_step < cycle_time && + core.count() == n_assem_core) { + cyclus::toolkit::RecordTimeSeries(this, power_cap); + } else { + cyclus::toolkit::RecordTimeSeries(this, 0); + } + + // "if" prevents starting cycle after initial deployment until core is full + // even though cycle_step is its initial zero. + if (cycle_step > 0 || core.count() == n_assem_core) { + cycle_step++; + } +} + +void Reactor::Transmute() { Transmute(n_assem_batch); } + +void Reactor::Transmute(int n_assem) { + MatVec old = core.PopN(std::min(n_assem, core.count())); + core.Push(old); + if (core.count() > old.size()) { + // rotate untransmuted mats back to back of buffer + core.Push(core.PopN(core.count() - old.size())); + } + + std::stringstream ss; + ss << old.size() << " assemblies"; + Record("TRANSMUTE", ss.str()); + + for (int i = 0; i < old.size(); i++) { + old[i]->Transmute(context()->GetRecipe(fuel_outrecipe(old[i]))); + } +} + +std::map Reactor::PeekSpent() { + std::map mapped; + MatVec mats = spent.PopN(spent.count()); + spent.Push(mats); + for (int i = 0; i < mats.size(); i++) { + std::string commod = fuel_outcommod(mats[i]); + mapped[commod].push_back(mats[i]); + } + return mapped; +} + +bool Reactor::Discharge() { + int npop = std::min(n_assem_batch, core.count()); + if (n_assem_spent - spent.count() < npop) { + Record("DISCHARGE", "failed"); + return false; // not enough room in spent buffer + } + + std::stringstream ss; + ss << npop << " assemblies"; + Record("DISCHARGE", ss.str()); + + spent.Push(core.PopN(npop)); + return true; +} + +void Reactor::Load() { + int n = std::min(n_assem_core - core.count(), fresh.count()); + if (n == 0) { + return; + } + + std::stringstream ss; + ss << n << " assemblies"; + Record("LOAD", ss.str()); + core.Push(fresh.PopN(n)); +} + +std::string Reactor::fuel_incommod(Material::Ptr m) { + int i = res_indexes[m->obj_id()]; + if (i >= fuel_incommods.size()) { + throw KeyError("cycamore::Reactor - no incommod for material object"); + } + return fuel_incommods[i]; +} + +std::string Reactor::fuel_outcommod(Material::Ptr m) { + int i = res_indexes[m->obj_id()]; + if (i >= fuel_outcommods.size()) { + throw KeyError("cycamore::Reactor - no outcommod for material object"); + } + return fuel_outcommods[i]; +} + +std::string Reactor::fuel_inrecipe(Material::Ptr m) { + int i = res_indexes[m->obj_id()]; + if (i >= fuel_inrecipes.size()) { + throw KeyError("cycamore::Reactor - no inrecipe for material object"); + } + return fuel_inrecipes[i]; +} + +std::string Reactor::fuel_outrecipe(Material::Ptr m) { + int i = res_indexes[m->obj_id()]; + if (i >= fuel_outrecipes.size()) { + throw KeyError("cycamore::Reactor - no outrecipe for material object"); + } + return fuel_outrecipes[i]; +} + +double Reactor::fuel_pref(Material::Ptr m) { + int i = res_indexes[m->obj_id()]; + if (i >= fuel_prefs.size()) { + return 0; + } + return fuel_prefs[i]; +} + +void Reactor::index_res(cyclus::Resource::Ptr m, std::string incommod) { + for (int i = 0; i < fuel_incommods.size(); i++) { + if (fuel_incommods[i] == incommod) { + res_indexes[m->obj_id()] = i; + return; + } + } + throw ValueError( + "cycamore::Reactor - received unsupported incommod material"); +} + +std::map Reactor::PopSpent() { + MatVec mats = spent.PopN(spent.count()); + std::map mapped; + for (int i = 0; i < mats.size(); i++) { + std::string commod = fuel_outcommod(mats[i]); + mapped[commod].push_back(mats[i]); + } + + // needed so we trade away oldest assemblies first + std::map::iterator it; + for (it = mapped.begin(); it != mapped.end(); ++it) { + std::reverse(it->second.begin(), it->second.end()); + } + + return mapped; +} + +void Reactor::PushSpent(std::map leftover) { + std::map::iterator it; + for (it = leftover.begin(); it != leftover.end(); ++it) { + // undo reverse in PopSpent to make sure oldest assemblies come out first + std::reverse(it->second.begin(), it->second.end()); + spent.Push(it->second); + } +} + +void Reactor::Record(std::string name, std::string val) { + context() + ->NewDatum("ReactorEvents") + ->AddVal("AgentId", id()) + ->AddVal("Time", context()->time()) + ->AddVal("Event", name) + ->AddVal("Value", val) + ->Record(); +} + +extern "C" cyclus::Agent* ConstructReactor(cyclus::Context* ctx) { + return new Reactor(ctx); +} + +} // namespace cycamore diff --git a/src/reactor.h b/src/reactor.h new file mode 100644 index 0000000000..a6aa645dbf --- /dev/null +++ b/src/reactor.h @@ -0,0 +1,372 @@ +#ifndef CYCAMORE_SRC_REACTOR_H_ +#define CYCAMORE_SRC_REACTOR_H_ + +#include "cyclus.h" + +namespace cycamore { + +/// Reactor is a simple, general reactor based on static compositional +/// transformations to model fuel burnup. The user specifies a set of input +/// fuels and corresponding burnt compositions that fuel is transformed to when +/// it is discharged from the core. No incremental transmutation takes place. +/// Rather, at the end of an operational cycle, the batch being discharged from +/// the core is instantaneously transmuted from its original fresh fuel +/// composition into its spent fuel form. +/// +/// Each fuel is identified by a specific input commodity and has an associated +/// input recipe (nuclide composition), output recipe, output commidity, and +/// preference. The preference identifies which input fuels are preferred when +/// requesting. Changes in these preferences can be specified as a function of +/// time using the pref_change variables. Changes in the input-output recipe +/// compositions can also be specified as a function of time using the +/// recipe_change variables. +/// +/// The reactor treats fuel as individual assemblies that are never split, +/// combined or otherwise treated in any non-discrete way. Fuel is requested +/// in full-or-nothing assembly sized quanta. If real-world assembly modeling +/// is unnecessary, parameters can be adjusted (e.g. n_assem_core, assem_size, +/// n_assem_batch). At the end of every cycle, a full batch is discharged from +/// the core consisting of n_assem_batch assemblies of assem_size kg. The +/// reactor also has a specifiable refueling time period following the end of +/// each cycle at the end of which it will resume operation on the next cycle +/// *if* it has enough fuel for a full core; otherwise it waits until it has +/// enough fresh fuel assemblies. +/// +/// In addition to its core, the reactor has an on-hand fresh fuel inventory +/// and a spent fuel inventory whose capacities are specified by n_assem_fresh +/// and n_assem_spent respectively. Each time step the reactor will attempt to +/// acquire enough fresh fuel to fill its fresh fuel inventory (and its core if +/// the core isn't currently full). If the fresh fuel inventory has zero +/// capacity, fuel will be ordered just-in-time after the end of each +/// operational cycle before the next begins. If the spent fuel inventory +/// becomes full, the reactor will halt operation at the end of the next cycle +/// until there is more room. Each time step, the reactor will try to trade +/// away as much of its spent fuel inventory as possible. +/// +/// When the reactor reaches the end of its lifetime, it will discharge all +/// material from its core and trade away all its spent fuel as quickly as +/// possible. Full decommissioning will be delayed until all spent fuel is +/// gone. If the reactor has a full core when it is decommissioned (i.e. is +/// mid-cycle) when the reactor is decommissioned, half (rounded up to nearest +/// int) of its assemblies are transmuted to their respective burnt +/// compositions. + +class Reactor : public cyclus::Facility, + public cyclus::toolkit::CommodityProducer { +#pragma cyclus note { \ +"niche": "reactor", \ +"doc": \ + "Reactor is a simple, general reactor based on static compositional" \ + " transformations to model fuel burnup. The user specifies a set of input" \ + " fuels and corresponding burnt compositions that fuel is transformed to when" \ + " it is discharged from the core. No incremental transmutation takes place." \ + " Rather, at the end of an operational cycle, the batch being discharged from" \ + " the core is instantaneously transmuted from its original fresh fuel" \ + " composition into its spent fuel form." \ + "\n\n" \ + "Each fuel is identified by a specific input commodity and has an associated" \ + " input recipe (nuclide composition), output recipe, output commidity, and" \ + " preference. The preference identifies which input fuels are preferred when" \ + " requesting. Changes in these preferences can be specified as a function of" \ + " time using the pref_change variables. Changes in the input-output recipe" \ + " compositions can also be specified as a function of time using the" \ + " recipe_change variables." \ + "\n\n" \ + "The reactor treats fuel as individual assemblies that are never split," \ + " combined or otherwise treated in any non-discrete way. Fuel is requested" \ + " in full-or-nothing assembly sized quanta. If real-world assembly modeling" \ + " is unnecessary, parameters can be adjusted (e.g. n_assem_core, assem_size," \ + " n_assem_batch). At the end of every cycle, a full batch is discharged from" \ + " the core consisting of n_assem_batch assemblies of assem_size kg. The" \ + " reactor also has a specifiable refueling time period following the end of" \ + " each cycle at the end of which it will resume operation on the next cycle" \ + " *if* it has enough fuel for a full core; otherwise it waits until it has" \ + " enough fresh fuel assemblies." \ + "\n\n" \ + "In addition to its core, the reactor has an on-hand fresh fuel inventory" \ + " and a spent fuel inventory whose capacities are specified by n_assem_fresh" \ + " and n_assem_spent respectively. Each time step the reactor will attempt to" \ + " acquire enough fresh fuel to fill its fresh fuel inventory (and its core if" \ + " the core isn't currently full). If the fresh fuel inventory has zero" \ + " capacity, fuel will be ordered just-in-time after the end of each" \ + " operational cycle before the next begins. If the spent fuel inventory" \ + " becomes full, the reactor will halt operation at the end of the next cycle" \ + " until there is more room. Each time step, the reactor will try to trade" \ + " away as much of its spent fuel inventory as possible." \ + "\n\n" \ + "When the reactor reaches the end of its lifetime, it will discharge all" \ + " material from its core and trade away all its spent fuel as quickly as" \ + " possible. Full decommissioning will be delayed until all spent fuel is" \ + " gone. If the reactor has a full core when it is decommissioned (i.e. is" \ + " mid-cycle) when the reactor is decommissioned, half (rounded up to nearest" \ + " int) of its assemblies are transmuted to their respective burnt" \ + " compositions." \ + "", \ +} + + public: + Reactor(cyclus::Context* ctx); + virtual ~Reactor(){}; + + virtual void Tick(); + virtual void Tock(); + virtual void EnterNotify(); + virtual bool CheckDecommissionCondition(); + + virtual void AcceptMatlTrades(const std::vector, cyclus::Material::Ptr> >& responses); + + virtual std::set::Ptr> + GetMatlRequests(); + + virtual std::set::Ptr> GetMatlBids( + cyclus::CommodMap::type& commod_requests); + + virtual void GetMatlTrades( + const std::vector >& trades, + std::vector, + cyclus::Material::Ptr> >& responses); + + #pragma cyclus decl + + private: + std::string fuel_incommod(cyclus::Material::Ptr m); + std::string fuel_outcommod(cyclus::Material::Ptr m); + std::string fuel_inrecipe(cyclus::Material::Ptr m); + std::string fuel_outrecipe(cyclus::Material::Ptr m); + double fuel_pref(cyclus::Material::Ptr m); + + bool retired() { + return exit_time() != -1 && context()->time() >= exit_time(); + } + + /// Store fuel info index for the given resource received on incommod. + void index_res(cyclus::Resource::Ptr m, std::string incommod); + + /// Discharge a batch from the core if there is room in the spent fuel + /// inventory. Returns true if a batch was successfully discharged. + bool Discharge(); + + /// Top up core inventory as much as possible. + void Load(); + + /// Transmute the batch that is about to be discharged from the core to its + /// fully burnt state as defined by its outrecipe. + void Transmute(); + + /// Transmute the specified number of assemblies in the core to their + /// fully burnt state as defined by their outrecipe. + void Transmute(int n_assem); + + /// Records a reactor event to the output db with the given name and note val. + void Record(std::string name, std::string val); + + /// Complement of PopSpent - must be called with all materials passed that + /// were not traded away to other agents. + void PushSpent(std::map leftover); + + /// Returns all spent assemblies indexed by outcommod - removing them from + /// the spent fuel buffer. + std::map PopSpent(); + + /// Returns all spent assemblies indexed by outcommod without removing them + /// from the spent fuel buffer. + std::map PeekSpent(); + + + //////////// power params //////////// + #pragma cyclus var { \ + "default": 0, \ + "doc": "Amount of electrical power the facility produces when operating normally.", \ + "uilabel": "Nominal Reactor Power", \ + "units": "MWe", \ + } + double power_cap; + + #pragma cyclus var { \ + "default": "power", \ + "uilabel": "Power Commodity Name", \ + "doc": "The name of the 'power' commodity used in conjunction with a deployment curve.", \ + } + std::string power_name; + + //////////// inventory and core params //////////// + #pragma cyclus var { \ + "uilabel": "Number of Assemblies per Batch", \ + "doc": "Number of assemblies that constitute a single batch." \ + "This is the number of assemblies discharged from the core fully burned each cycle." \ + "Batch size is equivalent to ``n_assem_batch / n_assem_core``.", \ + } + int n_assem_batch; + #pragma cyclus var { \ + "doc": "Mass (kg) of a single assembly.", \ + "uilabels": "Assembly Mass", \ + "units": "kg", \ + } + double assem_size; + #pragma cyclus var { \ + "uilabel": "Number of Assemblies in Core", \ + "doc": "Number of assemblies that constitute a full core.", \ + } + int n_assem_core; + #pragma cyclus var { \ + "default": 1000000000, \ + "uilabel": "Maximum Spent Fuel Inventory", \ + "units": "assemblies", \ + "doc": "Number of spent fuel assemblies that can be stored on-site before reactor operation stalls.", \ + } + int n_assem_spent; + #pragma cyclus var { \ + "default": 0, \ + "uilabel": "Minimum Fresh Fuel Inventory", \ + "units": "assemblies", \ + "doc": "Number of fresh fuel assemblies to keep on-hand if possible.", \ + } + int n_assem_fresh; + + ///////// cycle params /////////// + #pragma cyclus var { \ + "doc": "The duration of a full operational cycle (excluding refueling time) in time steps.", \ + "uilabel": "Cycle Length", \ + "units": "time steps", \ + } + int cycle_time; + #pragma cyclus var { \ + "doc": "The duration of a full refueling period - the minimum time between" \ + " a cycle end and the start of the next cycle.", \ + "uilabel": "Refueling Outage Duration", \ + "units": "time steps", \ + } + int refuel_time; + #pragma cyclus var { \ + "default": 0, \ + "doc": "Number of time steps since the start of the last cycle." \ + " Only set this if you know what you are doing", \ + "uilabel": "Time Since Start of Last Cycle", \ + "units": "time steps", \ + } + int cycle_step; + + /////// fuel specifications ///////// + #pragma cyclus var { \ + "uitype": ["oneormore", "incommodity"], \ + "uilabel": "Fresh Fuel Commodity List", \ + "doc": "Ordered list of input commodities on which to requesting fuel.", \ + } + std::vector fuel_incommods; + #pragma cyclus var { \ + "uitype": ["oneormore", "recipe"], \ + "uilabel": "Fresh Fuel Recipe List", \ + "doc": "Fresh fuel recipes to request for each of the given fuel input commodities (same order).", \ + } + std::vector fuel_inrecipes; + #pragma cyclus var { \ + "uitype": ["oneormore", "recipe"], \ + "uilabel": "Spent Fuel Recipe List", \ + "doc": "Spent fuel recipes corresponding to the given fuel input commodities (same order)." \ + " Fuel received via a particular input commodity is transmuted to the recipe specified" \ + " here after being burned during a cycle.", \ + } + std::vector fuel_outrecipes; + #pragma cyclus var { \ + "uitype": ["oneormore", "outcommodity"], \ + "uilabel": "Spent Fuel Commodity List", \ + "doc": "Output commodities on which to offer spent fuel originally received as each particular " \ + " input commodity (same order)." \ + } + std::vector fuel_outcommods; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Fresh Fuel Preference List", \ + "doc": "The preference for each type of fresh fuel requested corresponding to each input" \ + " commodity (same order). If no preferences are specified, zero is" \ + " used for all fuel requests (default).", \ + } + std::vector fuel_prefs; + + // Resource inventories - these must be defined AFTER/BELOW the member vars + // referenced (e.g. n_batch_fresh, assem_size, etc.). + #pragma cyclus var {"capacity": "n_assem_fresh * assem_size"} + cyclus::toolkit::ResBuf fresh; + #pragma cyclus var {"capacity": "n_assem_core * assem_size"} + cyclus::toolkit::ResBuf core; + #pragma cyclus var {"capacity": "n_assem_spent * assem_size"} + cyclus::toolkit::ResBuf spent; + + /////////// preference changes /////////// + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Time to Change Fresh Fuel Preference", \ + "doc": "A time step on which to change the request preference for a " \ + "particular fresh fuel type.", \ + } + std::vector pref_change_times; + #pragma cyclus var { \ + "default": [], \ + "doc": "The input commodity for a particular fuel preference change." \ + " Same order as and direct correspondence to the specified preference change times.", \ + "uilabel": "Commodity for Changed Fresh Fuel Preference", \ + "uitype": ["oneormore", "incommodity"], \ + } + std::vector pref_change_commods; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Changed Fresh Fuel Preference", \ + "doc": "The new/changed request preference for a particular fresh fuel." \ + " Same order as and direct correspondence to the specified preference change times.", \ + } + std::vector pref_change_values; + + ///////////// recipe changes /////////// + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Time to Change Fresh/Spent Fuel Recipe", \ + "doc": "A time step on which to change the input-output recipe pair for a requested fresh fuel.", \ + } + std::vector recipe_change_times; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Commodity for Changed Fresh/Spent Fuel Recipe", \ + "doc": "The input commodity indicating fresh fuel for which recipes will be changed." \ + " Same order as and direct correspondence to the specified recipe change times.", \ + "uitype": ["oneormore", "incommodity"], \ + } + std::vector recipe_change_commods; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "New Recipe for Fresh Fuel", \ + "doc": "The new input recipe to use for this recipe change." \ + " Same order as and direct correspondence to the specified recipe change times.", \ + "uitype": ["oneormore", "recipe"], \ + } + std::vector recipe_change_in; + #pragma cyclus var { \ + "default": [], \ + "uilabel": "New Recipe for Spent Fuel", \ + "doc": "The new output recipe to use for this recipe change." \ + " Same order as and direct correspondence to the specified recipe change times.", \ + "uitype": ["oneormore", "recipe"], \ + } + std::vector recipe_change_out; + + // should be hidden in ui (internal only). True if fuel has already been + // discharged this cycle. + #pragma cyclus var {"default": 0, "doc": "This should NEVER be set manually.", \ + "internal": True \ + } + bool discharged; + + // This variable should be hidden/unavailable in ui. Maps resource object + // id's to the index for the incommod through which they were received. + #pragma cyclus var {"default": {}, "doc": "This should NEVER be set manually.", \ + "internal": True \ + } + std::map res_indexes; + + // populated lazily and no need to persist. + std::set uniq_outcommods_; +}; + +} // namespace cycamore + +#endif // CYCAMORE_SRC_REACTOR_H_ diff --git a/src/reactor_tests.cc b/src/reactor_tests.cc new file mode 100644 index 0000000000..d67d14d897 --- /dev/null +++ b/src/reactor_tests.cc @@ -0,0 +1,512 @@ +#include + +#include + +#include "cyclus.h" + +using pyne::nucname::id; +using cyclus::Composition; +using cyclus::Material; +using cyclus::QueryResult; +using cyclus::Cond; +using cyclus::toolkit::MatQuery; + +namespace cycamore { +namespace reactortests { + +Composition::Ptr c_uox() { + cyclus::CompMap m; + m[id("u235")] = 0.04; + m[id("u238")] = 0.96; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_mox() { + cyclus::CompMap m; + m[id("u235")] = .7; + m[id("u238")] = 100; + m[id("pu239")] = 3.3; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_spentuox() { + cyclus::CompMap m; + m[id("u235")] = .8; + m[id("u238")] = 100; + m[id("pu239")] = 1; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_spentmox() { + cyclus::CompMap m; + m[id("u235")] = .2; + m[id("u238")] = 100; + m[id("pu239")] = .9; + return Composition::CreateFromMass(m); +}; + +Composition::Ptr c_water() { + cyclus::CompMap m; + m[id("O16")] = 1; + m[id("H1")] = 2; + return Composition::CreateFromAtom(m); +}; + +// Test that with a zero refuel_time and a zero capacity fresh fuel buffer +// (the default), fuel can be ordered and the cycle started with no time step +// delay. +TEST(ReactorTests, JustInTimeOrdering) { + std::string config = + " lwr_fresh " + " lwr_spent " + " enriched_u " + " waste " + " 1.0 " + "" + " 1 " + " 0 " + " 300 " + " 1 " + " 1 "; + + int simdur = 50; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("enriched_u").Finalize(); + sim.AddRecipe("lwr_fresh", c_uox()); + sim.AddRecipe("lwr_spent", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(simdur, qr.rows.size()) << "failed to order+run on fresh fuel inside 1 time step"; +} + +// tests that the correct number of assemblies are popped from the core each +// cycle. +TEST(ReactorTests, BatchSizes) { + std::string config = + " uox " + " spentuox " + " uox " + " waste " + "" + " 1 " + " 0 " + " 1 " + " 7 " + " 3 "; + + int simdur = 50; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + // 7 for initial core, 3 per time step for each new batch for remainder + EXPECT_EQ(7+3*(simdur-1), qr.rows.size()); +} + +// tests that the refueling period between cycle end and start of the next +// cycle is honored. +TEST(ReactorTests, RefuelTimes) { + std::string config = + " uox " + " spentuox " + " uox " + " waste " + "" + " 4 " + " 3 " + " 1 " + " 1 " + " 1 "; + + int simdur = 49; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + int cyclet = 4; + int refuelt = 3; + int n_assem_want = simdur/(cyclet+refuelt)+1; // +1 for initial core + EXPECT_EQ(n_assem_want, qr.rows.size()); +} + +// tests that new fuel is ordered immediately following cycle end - at the +// start of the refueling period - not before and not after. - thie is subtly +// different than RefuelTimes test and is not a duplicate of it. +TEST(ReactorTests, OrderAtRefuelStart) { + std::string config = + " uox " + " spentuox " + " uox " + " waste " + "" + " 4 " + " 3 " + " 1 " + " 1 " + " 1 "; + + int simdur = 7; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + int cyclet = 4; + int refuelt = 3; + int n_assem_want = simdur/(cyclet+refuelt)+1; // +1 for initial core + EXPECT_EQ(n_assem_want, qr.rows.size()); +} + +// tests that the reactor handles requesting multiple types of fuel correctly +// - with proper inventory constraint honoring, etc. +TEST(ReactorTests, MultiFuelMix) { + std::string config = + " uox mox " + " spentuox spentmox " + " uox mox " + " waste waste " + "" + " 1 " + " 0 " + " 1 " + " 3 " + " 3 " + " 3 "; + + // it is important that the sources have cumulative capacity greater than + // the reactor can take on a single time step - to test that inventory + // capacity constraints are being set properly. It is also important that + // each source is smaller capacity thatn the reactor orders on each time + // step to make it easy to compute+check the number of transactions. + int simdur = 50; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").capacity(2).Finalize(); + sim.AddSource("mox").capacity(2).Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + sim.AddRecipe("mox", c_spentuox()); + sim.AddRecipe("spentmox", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + // +3 is for fresh fuel inventory + EXPECT_EQ(3*simdur+3, qr.rows.size()); +} + +// tests that the reactor halts operation when it has no more room in its +// spent fuel inventory buffer. +TEST(ReactorTests, FullSpentInventory) { + std::string config = + " uox " + " spentuox " + " uox " + " waste " + "" + " 1 " + " 0 " + " 1 " + " 1 " + " 1 " + " 3 "; + + int simdur = 10; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + int n_assem_spent = 3; + + // +1 is for the assembly in the core + the three in spent + EXPECT_EQ(n_assem_spent+1, qr.rows.size()); +} + +// tests that the reactor cycle is delayed as expected when it is unable to +// acquire fuel in time for the next cycle start. This checks that after a +// cycle is delayed past an original scheduled start time, as soon as enough fuel is +// received, a new cycle pattern is established starting from the delayed +// start time. +TEST(ReactorTests, FuelShortage) { + std::string config = + " uox " + " spentuox " + " uox " + " waste " + "" + " 7 " + " 0 " + " 1 " + " 3 " + " 3 "; + + int simdur = 50; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").lifetime(1).Finalize(); // provide initial full batch + sim.AddSource("uox").start(9).lifetime(1).capacity(2).Finalize(); // provide partial batch post cycle-end + sim.AddSource("uox").start(15).Finalize(); // provide remainder of batch much later + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + int id = sim.Run(); + + // check that we never got a full refueled batch during refuel period + std::vector conds; + conds.push_back(Cond("Time", "<", 15)); + QueryResult qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(5, qr.rows.size()); + + // after being delayed past original scheduled start of new cycle, we got + // final assembly for core. + conds.clear(); + conds.push_back(Cond("Time", "==", 15)); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(1, qr.rows.size()); + + // all during the next (delayed) cycle we shouldn't be requesting any new fuel + conds.clear(); + conds.push_back(Cond("Time", "<", 21)); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(6, qr.rows.size()); + + // as soon as this delayed cycle ends, we should be requesting/getting 3 new batches + conds.clear(); + conds.push_back(Cond("Time", "==", 22)); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(3, qr.rows.size()); +} + +// tests that discharged fuel is transmuted properly immediately at cycle end. +TEST(ReactorTests, DischargedFuelTransmute) { + std::string config = + " uox " + " spentuox " + " uox " + " waste " + "" + " 4 " + " 3 " + " 1 " + " 1 " + " 1 "; + + int simdur = 7; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").Finalize(); + sim.AddSink("waste").Finalize(); + sim.AddRecipe("uox", c_uox()); + Composition::Ptr spentuox = c_spentuox(); + sim.AddRecipe("spentuox", spentuox); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("SenderId", "==", id)); + int resid = sim.db().Query("Transactions", &conds).GetVal("ResourceId"); + Material::Ptr m = sim.GetMaterial(resid); + MatQuery mq(m); + EXPECT_EQ(spentuox->id(), m->comp()->id()); + EXPECT_TRUE(mq.mass(942390000) > 0) << "transmuted spent fuel doesn't have Pu239"; +} + +// tests that spent fuel is offerred on correct commods according to the +// incommod it was received on - esp when dealing with multiple fuel commods +// simultaneously. +TEST(ReactorTests, SpentFuelProperCommodTracking) { + std::string config = + " uox mox " + " spentuox spentmox " + " uox mox " + " waste1 waste2 " + "" + " 1 " + " 0 " + " 1 " + " 3 " + " 3 "; + + int simdur = 7; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("uox").capacity(1).Finalize(); + sim.AddSource("mox").capacity(2).Finalize(); + sim.AddSink("waste1").Finalize(); + sim.AddSink("waste2").Finalize(); + sim.AddRecipe("uox", c_uox()); + sim.AddRecipe("spentuox", c_spentuox()); + sim.AddRecipe("mox", c_mox()); + sim.AddRecipe("spentmox", c_spentmox()); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("SenderId", "==", id)); + conds.push_back(Cond("Commodity", "==", std::string("waste1"))); + QueryResult qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(simdur-1, qr.rows.size()); + + conds[1] = Cond("Commodity", "==", std::string("waste2")); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(2*(simdur-1), qr.rows.size()); +} + +// The user can optionally omit fuel preferences. In the case where +// preferences are adjusted, the ommitted preference vector must be populated +// with default values - if it wasn't then preferences won't be adjusted +// correctly and the reactor could segfault. Check that this doesn't happen. +TEST(ReactorTests, PrefChange) { + // it is important that the fuel_prefs not be present in the config below. + std::string config = + " lwr_fresh " + " lwr_spent " + " enriched_u " + " waste " + "" + " 1 " + " 0 " + " 300 " + " 1 " + " 1 " + "" + " 25 " + " enriched_u " + " -1 "; + + int simdur = 50; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("enriched_u").Finalize(); + sim.AddRecipe("lwr_fresh", c_uox()); + sim.AddRecipe("lwr_spent", c_spentuox()); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(25, qr.rows.size()) << "failed to adjust preferences properly"; +} + +TEST(ReactorTests, RecipeChange) { + // it is important that the fuel_prefs not be present in the config below. + std::string config = + " lwr_fresh " + " lwr_spent " + " enriched_u " + " waste " + "" + " 1 " + " 0 " + " 300 " + " 1 " + " 1 " + "" + " 25 35 " + " enriched_u enriched_u " + " water water " + " lwr_spent water "; + + int simdur = 50; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, simdur); + sim.AddSource("enriched_u").Finalize(); + sim.AddSink("waste").Finalize(); + sim.AddRecipe("lwr_fresh", c_uox()); + sim.AddRecipe("lwr_spent", c_spentuox()); + sim.AddRecipe("water", c_water()); + int aid = sim.Run(); + + std::vector conds; + QueryResult qr; + + // check that received recipe is not water + conds.clear(); + conds.push_back(Cond("Time", "==", 24)); + conds.push_back(Cond("ReceiverId", "==", aid)); + qr = sim.db().Query("Transactions", &conds); + MatQuery mq = MatQuery(sim.GetMaterial(qr.GetVal("ResourceId"))); + + EXPECT_TRUE(0 < mq.qty()); + EXPECT_TRUE(0 == mq.mass(id("H1"))); + + // check that received recipe changed to water + conds.clear(); + conds.push_back(Cond("Time", "==", 26)); + conds.push_back(Cond("ReceiverId", "==", aid)); + qr = sim.db().Query("Transactions", &conds); + mq = MatQuery(sim.GetMaterial(qr.GetVal("ResourceId"))); + + EXPECT_TRUE(0 < mq.qty()); + EXPECT_TRUE(0 < mq.mass(id("H1"))); + + // check that sent recipe is not water + conds.clear(); + conds.push_back(Cond("Time", "==", 34)); + conds.push_back(Cond("SenderId", "==", aid)); + qr = sim.db().Query("Transactions", &conds); + mq = MatQuery(sim.GetMaterial(qr.GetVal("ResourceId"))); + + EXPECT_TRUE(0 < mq.qty()); + EXPECT_TRUE(0 == mq.mass(id("H1"))); + + // check that sent recipe changed to water + conds.clear(); + conds.push_back(Cond("Time", "==", 36)); + conds.push_back(Cond("SenderId", "==", aid)); + qr = sim.db().Query("Transactions", &conds); + mq = MatQuery(sim.GetMaterial(qr.GetVal("ResourceId"))); + + EXPECT_TRUE(0 < mq.qty()); + EXPECT_TRUE(0 < mq.mass(id("H1"))); +} + +TEST(ReactorTests, Retire) { + std::string config = + " lwr_fresh " + " lwr_spent " + " enriched_u " + " waste " + "" + " 7 " + " 0 " + " 300 " + " 1 " + " 3 " + " 1 " + ""; + + int dur = 50; + int life = 36; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, dur, life); + sim.AddSource("enriched_u").Finalize(); + sim.AddSink("waste").Finalize(); + sim.AddRecipe("lwr_fresh", c_uox()); + sim.AddRecipe("lwr_spent", c_spentuox()); + int id = sim.Run(); + + int ncore = 3; + int nbatch = 1; + + // reactor should stop requesting new fresh fuel as it approaches retirement + int nassem_recv = + static_cast(ceil(static_cast(life) / 7.0)) * nbatch + + (ncore - nbatch); + + std::vector conds; + conds.push_back(Cond("ReceiverId", "==", id)); + QueryResult qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(nassem_recv, qr.rows.size()) + << "failed to stop ordering near retirement"; + + // reactor should discharge all fuel before/by retirement + conds.clear(); + conds.push_back(Cond("SenderId", "==", id)); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(nassem_recv, qr.rows.size()) + << "failed to discharge all material by retirement time"; +} + +} // namespace reactortests +} // namespace cycamore + diff --git a/src/separations.cc b/src/separations.cc new file mode 100644 index 0000000000..a81e26e211 --- /dev/null +++ b/src/separations.cc @@ -0,0 +1,288 @@ +#include "separations.h" + +using cyclus::Material; +using cyclus::Composition; +using cyclus::toolkit::ResBuf; +using cyclus::toolkit::MatVec; +using cyclus::KeyError; +using cyclus::ValueError; +using cyclus::Request; +using cyclus::CompMap; + +namespace cycamore { + +Separations::Separations(cyclus::Context* ctx) : cyclus::Facility(ctx) { + cyclus::Warn( + "the Separations archetype " + "is experimental"); +} + +cyclus::Inventories Separations::SnapshotInv() { + cyclus::Inventories invs; + + // these inventory names are intentionally convoluted so as to not clash + // with the user-specified stream commods that are used as the separations + // streams inventory names. + invs["leftover-inv-name"] = leftover.PopNRes(leftover.count()); + leftover.Push(invs["leftover-inv-name"]); + invs["feed-inv-name"] = feed.PopNRes(feed.count()); + feed.Push(invs["feed-inv-name"]); + + std::map >::iterator it; + for (it = streambufs.begin(); it != streambufs.end(); ++it) { + invs[it->first] = it->second.PopNRes(it->second.count()); + it->second.Push(invs[it->first]); + } + + return invs; +} + +void Separations::InitInv(cyclus::Inventories& inv) { + leftover.Push(inv["leftover-inv-name"]); + feed.Push(inv["feed-inv-name"]); + + cyclus::Inventories::iterator it; + for (it = inv.begin(); it != inv.end(); ++it) { + streambufs[it->first].Push(it->second); + } +} + +typedef std::pair > Stream; +typedef std::map StreamSet; + +void Separations::EnterNotify() { + cyclus::Facility::EnterNotify(); + StreamSet::iterator it; + for (it = streams_.begin(); it != streams_.end(); ++it) { + std::string name = it->first; + Stream stream = it->second; + double cap = stream.first; + if (cap >= 0) { + streambufs[name].capacity(cap); + } + } + + if (feed_commod_prefs.size() == 0) { + for (int i = 0; i < feed_commods.size(); i++) { + feed_commod_prefs.push_back(0); + } + } +} + +void Separations::Tick() { + if (feed.count() == 0) { + return; + } + + Material::Ptr mat = feed.Pop(std::min(throughput, feed.quantity())); + double orig_qty = mat->quantity(); + + StreamSet::iterator it; + double maxfrac = 1; + std::map stagedsep; + for (it = streams_.begin(); it != streams_.end(); ++it) { + Stream info = it->second; + std::string name = it->first; + stagedsep[name] = SepMaterial(info.second, mat); + double frac = streambufs[name].space() / stagedsep[name]->quantity(); + if (frac < maxfrac) { + maxfrac = frac; + } + } + + std::map::iterator itf; + for (itf = stagedsep.begin(); itf != stagedsep.end(); ++itf) { + std::string name = itf->first; + Material::Ptr m = itf->second; + if (m->quantity() > 0) { + streambufs[name].Push( + mat->ExtractComp(m->quantity() * maxfrac, m->comp())); + } + } + + if (maxfrac == 1) { + if (mat->quantity() > 0) { + // unspecified separations fractions go to leftovers + leftover.Push(mat); + } + } else { // maxfrac is < 1 + // push back any leftover feed due to separated stream inv size constraints + feed.Push(mat->ExtractQty((1 - maxfrac) * orig_qty)); + if (mat->quantity() > 0) { + // unspecified separations fractions go to leftovers + leftover.Push(mat); + } + } +} + +// Note that this returns an untracked material that should just be used for +// its composition and qty - not in any real inventories, etc. +Material::Ptr SepMaterial(std::map effs, Material::Ptr mat) { + CompMap cm = mat->comp()->mass(); + cyclus::compmath::Normalize(&cm, mat->quantity()); + double tot_qty = 0; + CompMap sepcomp; + + CompMap::iterator it; + for (it = cm.begin(); it != cm.end(); ++it) { + int nuc = it->first; + int elem = (nuc / 10000000) * 10000000; + double eff = 0; + if (effs.count(nuc) > 0) { + eff = effs[nuc]; + } else if (effs.count(elem) > 0) { + eff = effs[elem]; + } else { + continue; + } + + double qty = it->second; + double sepqty = qty * eff; + sepcomp[nuc] = sepqty; + tot_qty += sepqty; + } + + Composition::Ptr c = Composition::CreateFromMass(sepcomp); + return Material::CreateUntracked(tot_qty, c); +}; + +std::set::Ptr> +Separations::GetMatlRequests() { + using cyclus::RequestPortfolio; + std::set::Ptr> ports; + bool exclusive = false; + + if (feed.space() > cyclus::eps()) { + RequestPortfolio::Ptr port(new RequestPortfolio()); + + Material::Ptr m = cyclus::NewBlankMaterial(feed.space()); + if (!feed_recipe.empty()) { + Composition::Ptr c = context()->GetRecipe(feed_recipe); + m = Material::CreateUntracked(feed.space(), c); + } + + std::vector*> reqs; + for (int i = 0; i < feed_commods.size(); i++) { + std::string commod = feed_commods[i]; + double pref = feed_commod_prefs[i]; + reqs.push_back(port->AddRequest(m, this, commod, pref, exclusive)); + } + port->AddMutualReqs(reqs); + ports.insert(port); + } + + return ports; +} + +void Separations::GetMatlTrades( + const std::vector >& trades, + std::vector, Material::Ptr> >& + responses) { + using cyclus::Trade; + + std::vector >::const_iterator it; + for (int i = 0; i < trades.size(); i++) { + std::string commod = trades[i].request->commodity(); + if (commod == leftover_commod) { + double amt = std::min(leftover.quantity(), trades[i].amt); + Material::Ptr m = leftover.Pop(amt); + responses.push_back(std::make_pair(trades[i], m)); + } else if (streambufs.count(commod) > 0) { + double amt = std::min(streambufs[commod].quantity(), trades[i].amt); + Material::Ptr m = streambufs[commod].Pop(amt); + responses.push_back(std::make_pair(trades[i], m)); + } else { + throw ValueError("invalid commodity " + commod + + " on trade matched to prototype " + prototype()); + } + } +} + +void Separations::AcceptMatlTrades(const std::vector< + std::pair, Material::Ptr> >& responses) { + std::vector, + cyclus::Material::Ptr> >::const_iterator trade; + + for (trade = responses.begin(); trade != responses.end(); ++trade) { + feed.Push(trade->second); + } +} + +std::set::Ptr> Separations::GetMatlBids( + cyclus::CommodMap::type& commod_requests) { + using cyclus::BidPortfolio; + + bool exclusive = false; + std::set::Ptr> ports; + + // bid streams + std::map >::iterator it; + for (it = streambufs.begin(); it != streambufs.end(); ++it) { + std::string commod = it->first; + std::vector*>& reqs = commod_requests[commod]; + if (reqs.size() == 0) { + continue; + } else if (streambufs[commod].quantity() < cyclus::eps()) { + continue; + } + + MatVec mats = streambufs[commod].PopN(streambufs[commod].count()); + streambufs[commod].Push(mats); + + BidPortfolio::Ptr port(new BidPortfolio()); + + for (int j = 0; j < reqs.size(); j++) { + Request* req = reqs[j]; + double tot_bid = 0; + for (int k = 0; k < mats.size(); k++) { + Material::Ptr m = mats[k]; + tot_bid += m->quantity(); + port->AddBid(req, m, this, exclusive); + if (tot_bid >= req->target()->quantity()) { + break; + } + } + } + + double tot_qty = streambufs[commod].quantity(); + cyclus::CapacityConstraint cc(tot_qty); + port->AddConstraint(cc); + ports.insert(port); + } + + // bid leftovers + std::vector*>& reqs = commod_requests[leftover_commod]; + if (reqs.size() > 0 && leftover.quantity() >= cyclus::eps()) { + MatVec mats = leftover.PopN(leftover.count()); + leftover.Push(mats); + + BidPortfolio::Ptr port(new BidPortfolio()); + + for (int j = 0; j < reqs.size(); j++) { + Request* req = reqs[j]; + double tot_bid = 0; + for (int k = 0; k < mats.size(); k++) { + Material::Ptr m = mats[k]; + tot_bid += m->quantity(); + port->AddBid(req, m, this, exclusive); + if (tot_bid >= req->target()->quantity()) { + break; + } + } + } + + cyclus::CapacityConstraint cc(leftover.quantity()); + port->AddConstraint(cc); + ports.insert(port); + } + + return ports; +} + +void Separations::Tock() {} + +extern "C" cyclus::Agent* ConstructSeparations(cyclus::Context* ctx) { + return new Separations(ctx); +} + +} // namespace cycamore diff --git a/src/separations.h b/src/separations.h new file mode 100644 index 0000000000..809f7b212b --- /dev/null +++ b/src/separations.h @@ -0,0 +1,191 @@ +#ifndef CYCAMORE_SRC_SEPARATIONS_H_ +#define CYCAMORE_SRC_SEPARATIONS_H_ + +#include "cyclus.h" + +namespace cycamore { + +/// SepMaterial returns a material object that represents the composition and +/// quantity resulting from the separation of material from mat using the given +/// mass-based efficiencies. Each key in effs represents a nuclide or element +/// (canonical PyNE form), and each value is the corresponding mass-based +/// separations efficiency for that nuclide or element. Note that this returns +/// an untracked material that should only be used for its composition and qty +/// - not in any real inventories, etc. +cyclus::Material::Ptr SepMaterial(std::map effs, + cyclus::Material::Ptr mat); + +/// Separations processes feed material into one or more streams containing +/// specific elements and/or nuclides. It uses mass-based efficiencies. +/// +/// User defined separations streams are specified as groups of +/// component-efficiency pairs where 'component' means either a particular +/// element or a particular nuclide. Each component's paired efficiency +/// represents the mass fraction of that component in the feed that is +/// separated into that stream. The efficiencies of a particular component +/// across all streams must sum up to less than or equal to one. If less than +/// one, the remainining material is sent to a waste inventory and +/// (potentially) traded away from there. +/// +/// The facility receives material into a feed inventory that it processes with +/// a specified throughput each time step. Each output stream has a +/// corresponding output inventory size/limit. If the facility is unable to +/// reduce its stocks by trading and hits this limit for any of its output +/// streams, further processing/separations of feed material will halt until +/// room is again available in the output streams. +class Separations : public cyclus::Facility { +#pragma cyclus note { \ + "niche": "separations", \ + "doc": \ + "Separations processes feed material into one or more streams containing" \ + " specific elements and/or nuclides. It uses mass-based efficiencies." \ + "\n\n" \ + "User defined separations streams are specified as groups of" \ + " component-efficiency pairs where 'component' means either a particular" \ + " element or a particular nuclide. Each component's paired efficiency" \ + " represents the mass fraction of that component in the feed that is" \ + " separated into that stream. The efficiencies of a particular component" \ + " across all streams must sum up to less than or equal to one. If less than" \ + " one, the remainining material is sent to a waste inventory and" \ + " (potentially) traded away from there." \ + "\n\n" \ + "The facility receives material into a feed inventory that it processes with" \ + " a specified throughput each time step. Each output stream has a" \ + " corresponding output inventory size/limit. If the facility is unable to" \ + " reduce its stocks by trading and hits this limit for any of its output" \ + " streams, further processing/separations of feed material will halt until" \ + " room is again available in the output streams." \ + "", \ +} + public: + Separations(cyclus::Context* ctx); + virtual ~Separations(){}; + + virtual void Tick(); + virtual void Tock(); + virtual void EnterNotify(); + + virtual void AcceptMatlTrades(const std::vector, cyclus::Material::Ptr> >& responses); + + virtual std::set::Ptr> + GetMatlRequests(); + + virtual std::set::Ptr> GetMatlBids( + cyclus::CommodMap::type& commod_requests); + + virtual void GetMatlTrades( + const std::vector >& trades, + std::vector, + cyclus::Material::Ptr> >& responses); + + #pragma cyclus clone + #pragma cyclus initfromcopy + #pragma cyclus infiletodb + #pragma cyclus initfromdb + #pragma cyclus schema + #pragma cyclus annotations + #pragma cyclus snapshot + // the following pragmas are ommitted and the functions are written + // manually in order to handle the vector of resource buffers: + // + // #pragma cyclus snapshotinv + // #pragma cyclus initinv + + virtual cyclus::Inventories SnapshotInv(); + virtual void InitInv(cyclus::Inventories& inv); + + private: + #pragma cyclus var { \ + "doc" : "Maximum quantity of feed material that can be processed per time step.", \ + "uilabel": "Maximum Separations Throughput", \ + "units": "kg", \ + } + double throughput; + + #pragma cyclus var { \ + "doc": "Ordered list of commodities on which to request feed material to separate." \ + " Order only matters for matching up with feed commodity preferences if specified.", \ + "uilabel": "Feed Commodity List", \ + "uitype": ["oneormore", "incommodity"], \ + } + std::vector feed_commods; + + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Feed Commodity Preference List", \ + "doc": "Feed commodity request preferences for each of the given feed commodities (same order)." \ + " If unspecified, default is to use zero for all preferences.", \ + } + std::vector feed_commod_prefs; + + #pragma cyclus var { \ + "doc": "Name for recipe to be used in feed requests." \ + " Empty string results in use of a dummy recipe.", \ + "uilabel": "Feed Commodity Recipe List", \ + "uitype": "recipe", \ + "default": "", \ + } + std::string feed_recipe; + + #pragma cyclus var { \ + "doc" : "Maximum amount of feed material to keep on hand.", \ + "uilabel": "Maximum Feed Inventory", \ + "units" : "kg", \ + } + double feedbuf_size; + + #pragma cyclus var { \ + "capacity" : "feedbuf_size", \ + } + cyclus::toolkit::ResBuf feed; + + #pragma cyclus var { \ + "doc" : "Maximum amount of leftover separated material (not included in" \ + " any other stream) that can be stored." \ + " If full, the facility halts operation until space becomes available.", \ + "uilabel": "Maximum Leftover Inventory", \ + "default": 1e299, \ + "units": "kg", \ + } + double leftoverbuf_size; + + #pragma cyclus var { \ + "doc": "Commodity on which to trade the leftover separated material stream." \ + " This MUST NOT be the same as any commodity used to define the other separations streams.", \ + "uitype": "outcommodity", \ + "uilabel": "Leftover Commodity", \ + "default": "default-waste-stream", \ + } + std::string leftover_commod; + + #pragma cyclus var { \ + "capacity" : "leftoverbuf_size", \ + } + cyclus::toolkit::ResBuf leftover; + + #pragma cyclus var { \ + "alias": ["streams", "commod", ["info", "buf_size", ["efficiencies", "comp", "eff"]]], \ + "uitype": ["oneormore", "outcommodity", ["pair", "double", ["oneormore", "nuclide", "double"]]], \ + "uilabel": "Separations Streams and Efficiencies", \ + "doc": "Output streams for separations." \ + " Each stream must have a unique name identifying the commodity on which its material is traded," \ + " a max buffer capacity in kg (neg values indicate infinite size)," \ + " and a set of component efficiencies." \ + " 'comp' is a component to be separated into the stream" \ + " (e.g. U, Pu, etc.) and 'eff' is the mass fraction of the component" \ + " that is separated from the feed into this output stream." \ + " If any stream buffer is full, the facility halts operation until space becomes available." \ + " The sum total of all component efficiencies across streams must be less than or equal to 1" \ + " (e.g. sum of U efficiencies for all streams must be <= 1).", \ + } + std::map > > streams_; + + // custom SnapshotInv and InitInv and EnterNotify are used to persist this + // state var. + std::map > streambufs; +}; + +} // namespace cycamore + +#endif // CYCAMORE_SRC_SEPARATIONS_H_ diff --git a/src/separations_tests.cc b/src/separations_tests.cc new file mode 100644 index 0000000000..0b1eb62588 --- /dev/null +++ b/src/separations_tests.cc @@ -0,0 +1,92 @@ +#include "separations.h" + +#include +#include +#include "cyclus.h" + +using pyne::nucname::id; +using cyclus::Composition; +using cyclus::CompMap; +using cyclus::Material; +using cyclus::QueryResult; +using cyclus::Cond; +using cyclus::toolkit::MatQuery; + +namespace cycamore { + +TEST(SeparationsTests, SepMaterial) { + CompMap comp; + comp[id("U235")] = 10; + comp[id("U238")] = 90; + comp[id("Pu239")] = 1; + comp[id("Pu240")] = 2; + comp[id("Am241")] = 3; + comp[id("Am242")] = 2.8; + double qty = 100; + Composition::Ptr c = Composition::CreateFromMass(comp); + Material::Ptr mat = Material::CreateUntracked(qty, c); + + std::map effs; + effs[id("U")] = .7; + effs[id("Pu")] = .4; + effs[id("Am241")] = .4; + + Material::Ptr sep = SepMaterial(effs, mat); + MatQuery mqorig(mat); + MatQuery mqsep(sep); + + EXPECT_DOUBLE_EQ(effs[id("U")] * mqorig.mass("U235"), mqsep.mass("U235")); + EXPECT_DOUBLE_EQ(effs[id("U")] * mqorig.mass("U238"), mqsep.mass("U238")); + EXPECT_DOUBLE_EQ(effs[id("Pu")] * mqorig.mass("Pu239"), mqsep.mass("Pu239")); + EXPECT_DOUBLE_EQ(effs[id("Pu")] * mqorig.mass("Pu240"), mqsep.mass("Pu240")); + EXPECT_DOUBLE_EQ(effs[id("Am241")] * mqorig.mass("Am241"), mqsep.mass("Am241")); + EXPECT_DOUBLE_EQ(0, mqsep.mass("Am242")); +} + +TEST(SeparationsTests, SepMixElemAndNuclide) { + std::string config = + "" + " " + " stream1" + " " + " -1" + " " + " U 0.6" + " Pu239 .7" + " " + " " + " " + "" + "" + "waste" + "100" + "100" + " feed " + ; + + CompMap m; + m[id("u235")] = 0.08; + m[id("u238")] = 0.9; + m[id("Pu239")] = .01; + m[id("Pu240")] = .01; + Composition::Ptr c = Composition::CreateFromMass(m); + + int simdur = 2; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Separations"), config, simdur); + sim.AddSource("feed").recipe("recipe1").Finalize(); + sim.AddSink("stream1").capacity(100).Finalize(); + sim.AddRecipe("recipe1", c); + int id = sim.Run(); + + std::vector conds; + conds.push_back(Cond("SenderId", "==", id)); + int resid = sim.db().Query("Transactions", &conds).GetVal("ResourceId"); + MatQuery mq (sim.GetMaterial(resid)); + EXPECT_DOUBLE_EQ(m[922350000]*0.6*100, mq.mass("U235")); + EXPECT_DOUBLE_EQ(m[922380000]*0.6*100, mq.mass("U238")); + EXPECT_DOUBLE_EQ(m[942390000]*0.7*100, mq.mass("Pu239")); + EXPECT_DOUBLE_EQ(0, mq.mass("Pu240")); +} + +} // namespace cycamore + diff --git a/src/sink.cc b/src/sink.cc index 18d6bbdb58..0d4a18bdb4 100644 --- a/src/sink.cc +++ b/src/sink.cc @@ -62,11 +62,19 @@ Sink::GetMatlRequests() { using cyclus::Material; using cyclus::RequestPortfolio; using cyclus::Request; + using cyclus::Composition; std::set::Ptr> ports; RequestPortfolio::Ptr port(new RequestPortfolio()); double amt = RequestAmt(); - Material::Ptr mat = cyclus::NewBlankMaterial(amt); + Material::Ptr mat; + + if (recipe_name.empty()) { + mat = cyclus::NewBlankMaterial(amt); + } else { + Composition::Ptr rec = this->context()->GetRecipe(recipe_name); + mat = cyclus::Material::CreateUntracked(amt, rec); + } if (amt > cyclus::eps()) { std::vector::const_iterator it; diff --git a/src/sink.h b/src/sink.h index 174b334c36..7c07609d9b 100644 --- a/src/sink.h +++ b/src/sink.h @@ -12,98 +12,34 @@ namespace cycamore { class Context; -/// @class Sink -/// This cyclus::Facility requests a finite amount of its input commodity. -/// It offers nothing. -/// -/// The Sink class inherits from the cyclus::Facility class and is -/// dynamically loaded by the Agent class when requested. -/// -/// @section intro Introduction -/// The Sink is a facility type in *Cyclus* capable of accepting -/// a finite or infinite quantity of some commodity produced in the -/// simulation. A Sink requests an amount of that commodity from -/// the appropriate market. It then receives that commodity when the -/// market issues an order that the request has been matched with a -/// corresponding offer. -/// @section agentparams Agent Parameters -/// Sink behavior is comprehensively defined by the following -/// parameters: -/// - double capacity: The acceptance capacity of the facility (units -/// vary, but typically kg/month). Capacity is infinite if a positive -/// value is provided. -/// - int startDate: The date on which the facility begins to operate -/// (months). - int lifeTime: The length of time that the facility -/// operates (months). - std::string inCommod: The commodity type this -/// facility accepts. -/// @section optionalparams Optional Parameters -/// Sink behavior may also be specified with the following -/// optional parameters which have default values listed here. -/// - double capacityFactor: The ratio of actual acceptance capacity to -/// the rated acceptance capacity. Default is 1 (actual/rated). -/// - double AvailFactor: The percent of time the facility operates at -/// its capacity factor. Default is 100%. -/// - double capitalCost: The cost of constructing and commissioning this -/// facility. Default is 0 ($). -/// - double opCost: The annual cost of operation and maintenance of this -/// facility. Default is 0 ($/year). -/// - int constrTime: The number of months it takes to construct and -/// commission this facility. Default is 0 (months). -/// - int decomTime: The number of months it takes to deconstruct and -/// decommission this facility. Default is 0 (months). -/// - Inst* inst: The institution responsible for this facility. -/// - string name: A non-generic name for this facility. -/// -/// @section detailed Detailed Behavior -/// @subsection finite If Finite Capacity: -/// The Sink starts operation when the simulation reaches the -/// month specified as the startDate. It immediately begins to request -/// the inCommod commodity type at the rate defined by the Sink -/// capacity. If a request is matched with an offer from another -/// facility, the Sink executes that order by adding that -/// quantity to its stocks. When the simulation time equals the startDate -/// plus the lifeTime, the facility ceases to operate. -/// -/// @subsection infinite If Infinite Capacity: -/// The Sink starts operation when the simulation reaches the -/// month specified as the startDate. Each month the Sink -/// requests an infinite amount of the inCommod commodity from the -/// appropriate market. If there is a corresponding offer for that -/// commodity type from another facility, the Sink executes that -/// order by adding that quantity to its stocks. When the simulation time -/// equals the startDate plus the lifeTime, the facility ceases to -/// operate. -/// @subsection question Question: -/// What is the best way to allow requests of an infinite amount of -/// material on a market? +/// This facility acts as a sink of materials and products with a fixed +/// throughput (per time step) capacity and a lifetime capacity defined by a +/// total inventory size. The inventory size and throughput capacity both +/// default to infinite. If a recipe is provided, it will request material with +/// that recipe. Requests are made for any number of specified commodities. class Sink : public cyclus::Facility { public: - // --- Module Members --- - /// Constructor for the Sink class. - /// @param ctx the cyclus context for access to simulation-wide parameters + Sink(cyclus::Context* ctx); - /// Destructor for the Sink class. virtual ~Sink(); - #pragma cyclus decl + #pragma cyclus note { \ + "doc": \ + " A sink facility that accepts materials and products with a fixed\n"\ + " throughput (per time step) capacity and a lifetime capacity defined by\n"\ + " a total inventory size. The inventory size and throughput capacity\n"\ + " both default to infinite. If a recipe is provided, it will request\n"\ + " material with that recipe. Requests are made for any number of\n"\ + " specified commodities.\n" \ + } - #pragma cyclus note {"doc": "A sink facility that accepts specified " \ - "amounts of commodities from other agents"} + #pragma cyclus decl - /// A verbose printer for the Sink Facility. virtual std::string str(); - // --- - // --- Agent Members --- - /// The Sink can handle the Tick. - - /// @param time the current simulation time. virtual void Tick(); - /// The Sink can handle the Tock. - - /// @param time the current simulation time. virtual void Tock(); /// @brief SinkFacilities request Materials of their given commodity. Note @@ -126,9 +62,7 @@ class Sink : public cyclus::Facility { virtual void AcceptGenRsrcTrades( const std::vector< std::pair, cyclus::Product::Ptr> >& responses); - // --- - // --- Sink Members --- /// add a commodity to the set of input commodities /// @param name the commodity name inline void AddCommodity(std::string name) { in_commods.push_back(name); } @@ -137,7 +71,7 @@ class Sink : public cyclus::Facility { /// @param size the storage size inline void SetMaxInventorySize(double size) { max_inv_size = size; - inventory.set_capacity(size); + inventory.capacity(size); } /// @return the maximum inventory storage size @@ -166,24 +100,34 @@ class Sink : public cyclus::Facility { /// all facilities must have at least one input commodity #pragma cyclus var {"tooltip": "input commodities", \ "doc": "commodities that the sink facility accepts", \ + "uilabel": "List of Input Commodities", \ "uitype": ["oneormore", "incommodity"]} std::vector in_commods; /// monthly acceptance capacity #pragma cyclus var {"default": 1e299, "tooltip": "sink capacity", \ + "uilabel": "Maximum Throughput", \ "doc": "capacity the sink facility can " \ "accept at each time step"} double capacity; + #pragma cyclus var {"default": "", "tooltip": "requested composition", \ + "doc": "name of recipe to use for material requests, where " \ + "the default (empty string) is to accept everything", \ + "uilabel": "Input Recipe", \ + "uitype": "recipe"} + std::string recipe_name; + /// max inventory size #pragma cyclus var {"default": 1e299, \ "tooltip": "sink maximum inventory size", \ + "uilabel": "Maximum Inventory", \ "doc": "total maximum inventory size of sink facility"} double max_inv_size; /// this facility holds material in storage. #pragma cyclus var {'capacity': 'max_inv_size'} - cyclus::toolkit::ResourceBuff inventory; + cyclus::toolkit::ResBuf inventory; }; } // namespace cycamore diff --git a/src/sink_tests.cc b/src/sink_tests.cc index 4e2a7d4ebf..1926840c14 100644 --- a/src/sink_tests.cc +++ b/src/sink_tests.cc @@ -176,6 +176,40 @@ TEST_F(SinkTest, Accept) { src_facility->AcceptMatlTrades(responses); EXPECT_DOUBLE_EQ(qty, src_facility->InventorySize()); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(SinkTest, InRecipe){ +// Create a context + using cyclus::RequestPortfolio; + using cyclus::Material; + using cyclus::Request; + cyclus::Recorder rec; + cyclus::Timer ti; + cyclus::Context ctx(&ti, &rec); + + // define some test material in the context + cyclus::CompMap m; + m[922350000] = 1; + m[922580000] = 2; + + cyclus::Composition::Ptr c = cyclus::Composition::CreateFromMass(m); + ctx.AddRecipe("some_u",c) ; + + // create a sink facility to interact with the DRE + cycamore::Sink* snk = new cycamore::Sink(&ctx); + snk->AddCommodity("some_u"); + + std::set::Ptr> ports = + snk->GetMatlRequests(); + ASSERT_EQ(ports.size(), 1); + + const std::vector*>& requests = + ports.begin()->get()->requests(); + ASSERT_EQ(requests.size(), 1); + + Request* req = *requests.begin(); + EXPECT_EQ(req->requester(), snk); + EXPECT_EQ(req->commodity(),"some_u"); +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TEST_F(SinkTest, Print) { diff --git a/src/source.cc b/src/source.cc index 0cfe5d3aec..a74a7e275c 100644 --- a/src/source.cc +++ b/src/source.cc @@ -7,31 +7,13 @@ namespace cycamore { -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Source::Source(cyclus::Context* ctx) : cyclus::Facility(ctx), - out_commod(""), - recipe_name(""), - capacity(std::numeric_limits::max()) {} + throughput(std::numeric_limits::max()), + inventory_size(std::numeric_limits::max()) {} -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Source::~Source() {} -#pragma cyclus def clone cycamore::Source - -#pragma cyclus def schema cycamore::Source - -#pragma cyclus def annotations cycamore::Source - -#pragma cyclus def infiletodb cycamore::Source - -#pragma cyclus def snapshot cycamore::Source - -#pragma cyclus def snapshotinv cycamore::Source - -#pragma cyclus def initinv cycamore::Source - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Source::InitFrom(Source* m) { #pragma cyclus impl initfromcopy cycamore::Source cyclus::toolkit::CommodityProducer::Copy(m); @@ -39,71 +21,32 @@ void Source::InitFrom(Source* m) { void Source::InitFrom(cyclus::QueryableBackend* b) { #pragma cyclus impl initfromdb cycamore::Source - - commod_ = cyclus::toolkit::Commodity(out_commod); - cyclus::toolkit::CommodityProducer::Add(commod_); - cyclus::toolkit::CommodityProducer::SetCapacity(commod_, capacity); - cyclus::toolkit::CommodityProducer::SetCost(commod_, capacity); -} - -void Source::EnterNotify() { - Facility::EnterNotify(); - commod_ = cyclus::toolkit::Commodity(out_commod); - cyclus::toolkit::CommodityProducer::Add(commod_); - cyclus::toolkit::CommodityProducer::SetCapacity(commod_, capacity); - cyclus::toolkit::CommodityProducer::SetCost(commod_, capacity); + namespace tk = cyclus::toolkit; + tk::CommodityProducer::Add(tk::Commodity(outcommod), + tk::CommodInfo(throughput, throughput)); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - std::string Source::str() { + namespace tk = cyclus::toolkit; std::stringstream ss; std::string ans; - if (cyclus::toolkit::CommodityProducer:: - Produces(cyclus::toolkit::Commodity(out_commod))) { + if (cyclus::toolkit::CommodityProducer::Produces( + cyclus::toolkit::Commodity(outcommod))) { ans = "yes"; } else { ans = "no"; } - ss << cyclus::Facility::str() - << " supplies commodity '" - << out_commod << "' with recipe '" - << recipe_name << "' at a capacity of " - << capacity << " kg per time step " - << " commod producer members: " << " produces " - << out_commod << "?: " << ans - << " capacity: " << cyclus::toolkit::CommodityProducer::Capacity(commod_) - << " cost: " << cyclus::toolkit::CommodityProducer::Cost(commod_); + ss << cyclus::Facility::str() << " supplies commodity '" << outcommod + << "' with recipe '" << outrecipe << "' at a throughput of " + << throughput << " kg per time step " + << " commod producer members: " + << " produces " << outcommod << "?: " << ans + << " throughput: " << cyclus::toolkit::CommodityProducer::Capacity(outcommod) + << " cost: " << cyclus::toolkit::CommodityProducer::Cost(outcommod); return ss.str(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Source::Tick() { - LOG(cyclus::LEV_INFO3, "SrcFac") << prototype() << " is ticking {"; - LOG(cyclus::LEV_INFO4, "SrcFac") << "will offer " << capacity - << " kg of " - << out_commod << "."; - LOG(cyclus::LEV_INFO3, "SrcFac") << "Stats: " << str(); - LOG(cyclus::LEV_INFO3, "SrcFac") << "}"; - current_capacity = capacity; // reset capacity -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Source::Tock() { - LOG(cyclus::LEV_INFO3, "SrcFac") << prototype() << " is tocking {"; - LOG(cyclus::LEV_INFO3, "SrcFac") << "}"; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -cyclus::Material::Ptr Source::GetOffer( - const cyclus::Material::Ptr target) const { - using cyclus::Material; - double qty = std::min(target->quantity(), capacity); - return Material::CreateUntracked(qty, context()->GetRecipe(recipe_name)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::set::Ptr> -Source::GetMatlBids( +std::set::Ptr> Source::GetMatlBids( cyclus::CommodMap::type& commod_requests) { using cyclus::Bid; using cyclus::BidPortfolio; @@ -111,59 +54,62 @@ Source::GetMatlBids( using cyclus::Material; using cyclus::Request; - std::set::Ptr> ports; - - if (commod_requests.count(out_commod) > 0) { - BidPortfolio::Ptr port(new BidPortfolio()); + double max_qty = std::min(throughput, inventory_size); + LOG(cyclus::LEV_INFO3, "Source") << prototype() << " is bidding up to " + << max_qty << " kg of " << outcommod; + LOG(cyclus::LEV_INFO5, "Source") << "stats: " << str(); - std::vector*>& requests = - commod_requests[out_commod]; + std::set::Ptr> ports; + if (max_qty < cyclus::eps()) { + return ports; + } else if (commod_requests.count(outcommod) == 0) { + return ports; + } - std::vector*>::iterator it; - for (it = requests.begin(); it != requests.end(); ++it) { - Request* req = *it; - Material::Ptr offer = GetOffer(req->target()); - port->AddBid(req, offer, this); + BidPortfolio::Ptr port(new BidPortfolio()); + std::vector*>& requests = commod_requests[outcommod]; + std::vector*>::iterator it; + for (it = requests.begin(); it != requests.end(); ++it) { + Request* req = *it; + Material::Ptr target = req->target(); + double qty = std::min(target->quantity(), max_qty); + Material::Ptr m = Material::CreateUntracked(qty, target->comp()); + if (!outrecipe.empty()) { + m = Material::CreateUntracked(qty, context()->GetRecipe(outrecipe)); } - - CapacityConstraint cc(capacity); - port->AddConstraint(cc); - ports.insert(port); + port->AddBid(req, m, this); } + + CapacityConstraint cc(max_qty); + port->AddConstraint(cc); + ports.insert(port); return ports; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Source::GetMatlTrades( - const std::vector< cyclus::Trade >& trades, + const std::vector >& trades, std::vector, cyclus::Material::Ptr> >& responses) { using cyclus::Material; using cyclus::Trade; - double provided = 0; - std::vector< cyclus::Trade >::const_iterator it; + std::vector >::const_iterator it; for (it = trades.begin(); it != trades.end(); ++it) { double qty = it->amt; - current_capacity -= qty; - provided += qty; - // @TODO we need a policy on negatives.. - Material::Ptr response = Material::Create(this, qty, - context()->GetRecipe(recipe_name)); + inventory_size -= qty; + + Material::Ptr response; + if (!outrecipe.empty()) { + response = Material::Create(this, qty, context()->GetRecipe(outrecipe)); + } else { + response = Material::Create(this, qty, it->request->target()->comp()); + } responses.push_back(std::make_pair(*it, response)); - LOG(cyclus::LEV_INFO5, "SrcFac") << prototype() << " just received an order" - << " for " << qty - << " of " << out_commod; - } - if (cyclus::IsNegative(current_capacity)) { - std::stringstream ss; - ss << "is being asked to provide " << provided - << " but its capacity is " << capacity << "."; - throw cyclus::ValueError(Agent::InformErrorMsg(ss.str())); + LOG(cyclus::LEV_INFO5, "Source") << prototype() << " sent an order" + << " for " << qty << " of " << outcommod; } } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - extern "C" cyclus::Agent* ConstructSource(cyclus::Context* ctx) { return new Source(ctx); } diff --git a/src/source.h b/src/source.h index 9331128d2f..fdc5d27008 100644 --- a/src/source.h +++ b/src/source.h @@ -10,193 +10,101 @@ namespace cycamore { class Context; -/// @class Source -/// This cyclus::Facility provides a simple source of some capacity -/// (possibly infinite) of some commodity/Recipe. - -/// The Source class inherits from the cyclus::Facility class and is -/// dynamically loaded by the Agent class when requested. - -/// @section introduction Introduction -/// The Source is a facility type in Cyclus capable of providing -/// a finite or infinite.Supply of a particular material to the -/// simulation. A Source generates material of a certain -/// composition and commodity type, then offers that material on the -/// appropriate market. Shipments of this material are executed when the -/// market issues an order that the offer has been matched with a -/// request. - -/// @section agentparams Agent Parameters -/// Source behavior is comprehensively defined by the following -/// parameters: -/// - double capacity: The production capacity of the facility (units -/// vary, but typically kg/month). Capacity is infinite if a negative -/// value is provided. -/// - int startDate: The date on which the facility begins to operate -/// (months). -/// - int lifeTime: The length of time that the facility operates -/// (months). - std::string outCommod: the commodity that this facility -/// produces - double inventorysize: the maximum quantity of material to -/// be held in the inventory -/// - double commodprice: the price of the output material PER UNIT -/// - map outComp - -/// @section optionalparams Optional Parameters -/// Source behavior may also be specified with the following -/// optional parameters which have default values listed here. -/// - double capacityFactor: The ratio of actual production capacity to -/// the rated production capacity. Default is 1 (actual/rated). -/// - double availFactor: The percent of time the facility operates at -/// its capacity factor. Default is 100%. -/// - double capitalCost: The cost of constructing and commissioning -/// this facility. Default is 0 ($). -/// - double opCost: The annual cost of operation and maintenance of -/// this facility. Default is 0 ( $/year). -/// - int constrTime: The number of months it takes to construct and -/// commission this facility. Default is 0 (months). -/// - int decomTime: The number of months it takes to deconstruct and -/// decommission this facility. Default is 0 (months). -/// - Inst* inst: The institution responsible for this facility. -/// - string name: A non-generic name for this facility. - -/// @section detailed Detailed Behavior -/// @subsection finite If Finite Capacity: -/// The Source starts operation when the simulation reaches the -/// month specified as the startDate. It immediately begins to produce -/// material at the rate defined by its capacity. Each month the -/// Source adds the amount it has produced to its inventory. It -/// then offers to the appropriate market exactly as much material as it -/// has in its inventory. If an offer is matched with a request, the -/// Source executes that order by subtracting the quantity from -/// its inventory and sending that amount to the requesting facility. -/// When the simulation time equals the startDate plus the lifeTime, the -/// facility ceases to operate. -/// @subsection infinite If Infinite Capacity: -/// The Source starts operation when the simulation reaches the -/// month specified as the startDate. Each month the Source -/// offers an infinite amount of material to the appropriate market. If -/// there is a request for that material, the Source executes -/// that order by sending that amount to the requesting facility. When -/// the simulation time equals the startDate plus the lifeTime, the -/// facility ceases to operate. - -/// @subsection question Question: -/// What is the best way to allow offers of an infinite amount of -/// material on a market? - +/// This facility acts as a source of material with a fixed throughput (per +/// time step) capacity and a lifetime capacity defined by a total inventory +/// size. It offers its material as a single commodity. If a composition +/// recipe is specified, it provides that single material composition to +/// requesters. If unspecified, the source provides materials with the exact +/// requested compositions. The inventory size and throughput both default to +/// infinite. Supplies material results in corresponding decrease in +/// inventory, and when the inventory size reaches zero, the source can provide +/// no more material. class Source : public cyclus::Facility, public cyclus::toolkit::CommodityProducer { + friend class SourceTest; public: - // --- Module Members --- - /// Constructor for the Source class - /// @param ctx the cyclus context for access to simulation-wide parameters + Source(cyclus::Context* ctx); virtual ~Source(); - #pragma cyclus decl - - #pragma cyclus note {"doc": "A source facility that provides a " \ - "commodity with a given capacity"} + #pragma cyclus note { \ + "doc": "This facility acts as a source of material with a fixed throughput (per\n" \ + "time step) capacity and a lifetime capacity defined by a total inventory\n" \ + "size. It offers its material as a single commodity. If a composition\n" \ + "recipe is specified, it provides that single material composition to\n" \ + "requesters. If unspecified, the source provides materials with the exact\n" \ + "requested compositions. The inventory size and throughput both default to\n" \ + "infinite. Supplies material results in corresponding decrease in\n" \ + "inventory, and when the inventory size reaches zero, the source can provide\n" \ + "no more material.\n" \ + "", \ + } - /// Print information about this agent - virtual std::string str(); - // --- + #pragma cyclus def clone + #pragma cyclus def schema + #pragma cyclus def annotations + #pragma cyclus def infiletodb + #pragma cyclus def snapshot + #pragma cyclus def snapshotinv + #pragma cyclus def initinv - // --- Agent Members --- - virtual void EnterNotify(); + virtual void InitFrom(Source* m); - /// Each facility is prompted to do its beginning-of-time-step - /// stuff at the tick of the timer. + virtual void InitFrom(cyclus::QueryableBackend* b); - /// @param time is the time to perform the tick - virtual void Tick(); + virtual void Tick() {}; - /// Each facility is prompted to its end-of-time-step - /// stuff on the tock of the timer. + virtual void Tock() {}; - /// @param time is the time to perform the tock - virtual void Tock(); + virtual std::string str(); - /// @brief Responds to each request for this source facility's commodity. - /// If a given request is more than this facility's capacity, it will offer - /// its capacity. virtual std::set::Ptr> GetMatlBids(cyclus::CommodMap::type& commod_requests); - /// @brief respond to each trade with a material made from this facility's - /// recipe - /// - /// @param trades all trades in which this trader is the supplier - /// @param responses a container to populate with responses to each trade virtual void GetMatlTrades( const std::vector< cyclus::Trade >& trades, std::vector, cyclus::Material::Ptr> >& responses); - // --- - - // --- Source Members --- - /// @brief creates a material object to offer to a requester - /// @param target the material target a request desires - cyclus::Material::Ptr GetOffer(const cyclus::Material::Ptr target) const; - - /// sets the output commodity name - /// @param name the commodity name - inline void commodity(std::string name) { out_commod = name; } - - /// @return the output commodity - inline std::string commodity() const { return out_commod; } - - /// sets the capacity of a material generated at any given time step - /// @param capacity the production capacity - inline void Capacity(double cap) { - capacity = cap; - current_capacity = capacity; - } - - /// @return the production capacity at any given time step - inline double Capacity() const { return capacity; } - - /// sets the name of the recipe to be produced - /// @param name the recipe name - inline void recipe(std::string name) { recipe_name = name; } - - /// @return the name of the output recipe - inline std::string recipe() const { return recipe_name; } - - /// @return the current timestep's capacity - inline double CurrentCapacity() const { return current_capacity; } private: - cyclus::toolkit::Commodity commod_; - - /// This facility has only one output commodity - #pragma cyclus var {"tooltip": "source output commodity", \ - "doc": "output commodity that the source facility " \ - "supplies", \ - "uitype": "outcommodity"} - std::string out_commod; - - /// Name of the recipe this facility uses. - #pragma cyclus var {"tooltip": "commodity recipe name", \ - "doc": "recipe name for source facility's commodity", \ - "uitype": "recipe"} - std::string recipe_name; - - /// The capacity is defined in terms of the number of units of the - /// recipe that can be provided each time step. A very large number - /// can be provided to represent infinte capacity. - #pragma cyclus var {"default": 1e299, "tooltip": "source capacity", \ - "doc": "amount of commodity that can be supplied " \ - "at each time step"} - double capacity; - - /// The capacity at the current time step - #pragma cyclus var {'derived_init': 'current_capacity = capacity;'} - double current_capacity; - - // --- + #pragma cyclus var { \ + "tooltip": "source output commodity", \ + "doc": "Output commodity on which the source offers material.", \ + "uilabel": "Output Commodity", \ + "uitype": "outcommodity", \ + } + std::string outcommod; + + #pragma cyclus var { \ + "tooltip": "name of material recipe to provide", \ + "doc": "Name of composition recipe that this source provides regardless of requested composition." \ + " If empty, source creates and provides whatever compositions are requested.", \ + "uilabel": "Output Recipe", \ + "default": "", \ + "uitype": "recipe", \ + } + std::string outrecipe; + + #pragma cyclus var { \ + "default": 1e299, \ + "tooltip": "per time step throughput", \ + "units": "kg/(time step)", \ + "uilabel": "Maximum Throughput", \ + "doc": "amount of commodity that can be supplied at each time step", \ + } + double throughput; + + #pragma cyclus var { \ + "doc": "Total amount of material this source has remaining." \ + " Every trade decreases this value by the supplied material quantity'." \ + " When it reaches zero, the source cannot provide any more material.", \ + "default": 1e299, \ + "uilabel": "Initial Inventory", \ + "units": "kg", \ + } + double inventory_size; }; } // namespace cycamore diff --git a/src/source_tests.cc b/src/source_tests.cc index 947dcd429b..eea1d9c38d 100644 --- a/src/source_tests.cc +++ b/src/source_tests.cc @@ -8,7 +8,8 @@ #include "resource_helpers.h" #include "test_context.h" -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +namespace cycamore { + void SourceTest::SetUp() { src_facility = new cycamore::Source(tc.get()); trader = tc.trader(); @@ -16,12 +17,10 @@ void SourceTest::SetUp() { SetUpSource(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SourceTest::TearDown() { delete src_facility; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SourceTest::InitParameters() { commod = "commod"; recipe_name = "recipe"; @@ -31,64 +30,28 @@ void SourceTest::InitParameters() { tc.get()->AddRecipe(recipe_name, recipe); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SourceTest::SetUpSource() { - src_facility->commodity(commod); - src_facility->recipe(recipe_name); - src_facility->Capacity(capacity); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(SourceTest, InitialState) { - EXPECT_EQ(src_facility->Capacity(), capacity); - EXPECT_EQ(src_facility->commodity(), commod); - EXPECT_EQ(src_facility->recipe(), recipe_name); - EXPECT_EQ(src_facility->CurrentCapacity(), capacity); + outcommod(src_facility, commod); + outrecipe(src_facility, recipe_name); + throughput(src_facility, capacity); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TEST_F(SourceTest, Clone) { cyclus::Context* ctx = tc.get(); cycamore::Source* cloned_fac = dynamic_cast (src_facility->Clone()); - EXPECT_EQ(src_facility->commodity(), cloned_fac->commodity()); - EXPECT_EQ(src_facility->Capacity(), cloned_fac->Capacity()); - EXPECT_EQ(src_facility->recipe(), cloned_fac->recipe()); - EXPECT_EQ(src_facility->Capacity(), cloned_fac->CurrentCapacity()); + EXPECT_EQ(outcommod(src_facility), outcommod(cloned_fac)); + EXPECT_EQ(throughput(src_facility), throughput(cloned_fac)); + EXPECT_EQ(outrecipe(src_facility), outrecipe(cloned_fac)); delete cloned_fac; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TEST_F(SourceTest, Print) { EXPECT_NO_THROW(std::string s = src_facility->str()); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TEST_F(SourceTest, GetOffer) { - using cyclus::Material; - - double qty = capacity - 1; - Material::Ptr mat = cyclus::NewBlankMaterial(qty); - Material::Ptr obs_mat = src_facility->GetOffer(mat); - EXPECT_EQ(obs_mat->quantity(), qty); - EXPECT_EQ(obs_mat->comp(), recipe); - - qty = capacity + 1; - mat = cyclus::NewBlankMaterial(qty); - obs_mat = src_facility->GetOffer(mat); - EXPECT_EQ(obs_mat->quantity(), capacity); - EXPECT_EQ(obs_mat->comp(), recipe); - - qty = capacity; - mat = cyclus::NewBlankMaterial(qty); - obs_mat = src_facility->GetOffer(mat); - EXPECT_EQ(obs_mat->quantity(), capacity); - EXPECT_EQ(obs_mat->comp(), recipe); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TEST_F(SourceTest, AddBids) { using cyclus::Bid; using cyclus::BidPortfolio; @@ -117,7 +80,6 @@ TEST_F(SourceTest, AddBids) { EXPECT_EQ(*constrs.begin(), CapacityConstraint(capacity)); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TEST_F(SourceTest, Response) { using cyclus::Bid; using cyclus::Material; @@ -143,34 +105,24 @@ TEST_F(SourceTest, Response) { trades.push_back(trade); // 1 trade - ASSERT_EQ(src_facility->CurrentCapacity(), capacity); src_facility->GetMatlTrades(trades, responses); EXPECT_EQ(responses.size(), 1); EXPECT_EQ(responses[0].second->quantity(), qty); EXPECT_EQ(responses[0].second->comp(), recipe); // 2 trades, total qty = capacity - ASSERT_DOUBLE_EQ(src_facility->CurrentCapacity(), capacity - qty); - ASSERT_GT(src_facility->CurrentCapacity() - 2 * qty, -1 * cyclus::eps()); trades.push_back(trade); responses.clear(); EXPECT_NO_THROW(src_facility->GetMatlTrades(trades, responses)); EXPECT_EQ(responses.size(), 2); - ASSERT_TRUE(cyclus::AlmostEq(src_facility->CurrentCapacity(), 0)); - - // too much qty, capn! - EXPECT_THROW(src_facility->GetMatlTrades(trades, responses), - cyclus::ValueError); // reset! src_facility->Tick(); - ASSERT_DOUBLE_EQ(src_facility->CurrentCapacity(), capacity); delete request; delete bid; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - boost::shared_ptr< cyclus::ExchangeContext > SourceTest::GetContext(int nreqs, std::string commod) { using cyclus::Material; @@ -187,7 +139,8 @@ SourceTest::GetContext(int nreqs, std::string commod) { return ec; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +} // namespace cycamore + cyclus::Agent* SourceConstructor(cyclus::Context* ctx) { return new cycamore::Source(ctx); } @@ -199,6 +152,6 @@ static int cyclus_agent_tests_connected = ConnectAgentTests(); #define CYCLUS_AGENT_TESTS_CONNECTED cyclus_agent_tests_connected #endif // CYCLUS_AGENT_TESTS_CONNECTED -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSTANTIATE_TEST_CASE_P(SourceFac, FacilityTests, Values(&SourceConstructor)); INSTANTIATE_TEST_CASE_P(SourceFac, AgentTests, Values(&SourceConstructor)); + diff --git a/src/source_tests.h b/src/source_tests.h index 9eb2aeb400..52413a21e5 100644 --- a/src/source_tests.h +++ b/src/source_tests.h @@ -12,7 +12,8 @@ #include "facility_tests.h" #include "material.h" -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +namespace cycamore { + class SourceTest : public ::testing::Test { public: cyclus::TestContext tc; @@ -27,8 +28,22 @@ class SourceTest : public ::testing::Test { void InitParameters(); void SetUpSource(); - boost::shared_ptr< cyclus::ExchangeContext > - GetContext(int nreqs, std::string commodity); + std::string outrecipe(cycamore::Source* s) { return s->outrecipe; } + std::string outcommod(cycamore::Source* s) { return s->outcommod; } + double throughput(cycamore::Source* s) { return s->throughput; } + + void outrecipe(cycamore::Source* s, std::string recipe) { + s->outrecipe = recipe; + } + void outcommod(cycamore::Source* s, std::string commod) { + s->outcommod = commod; + } + void throughput(cycamore::Source* s, double val) { s->throughput = val; } + + boost::shared_ptr > GetContext( + int nreqs, std::string commodity); }; +} // namespace cycamore + #endif // CYCAMORE_SRC_SOURCE_TESTS_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 934ff21b51..2ec825551b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ SET(cyclus_path ${CYCLUS_ROOT_DIR}/bin) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/run_inputs.py.in ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/run_inputs.py @ONLY) -SET(input_path ${PROJECT_SOURCE_DIR}/../input) +SET(input_path ${PROJECT_SOURCE_DIR}/input) SET(cyclus_path ${CYCLUS_ROOT_DIR}/bin) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/run_inputs.py.in ${CMAKE_CURRENT_SOURCE_DIR}/run_inputs.py @ONLY) diff --git a/tests/analysis.py b/tests/analysis.py deleted file mode 100644 index 5f29c319e6..0000000000 --- a/tests/analysis.py +++ /dev/null @@ -1,150 +0,0 @@ -from __future__ import print_function -from __future__ import division - -import subprocess -from multiprocessing import Pool, Manager, cpu_count -from collections import defaultdict -import argparse as ap -import time - -import test_regression as tst - -diff_tbl = """table is different""" -diff_col = """Column""" - -def collect(args): - """collects information on a determinisitic regression test run - """ - tbl_freq, col_freq = args - - rtn = subprocess.Popen( - ["python", "-c", - "import test_regression as t; " + - "t.setup(); obj = t.TestRegression();" + - "obj.test_regression(check_deterministic=True)"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = rtn.communicate() - #print out, err - - for line in out.split("\n"): - line = line.strip() - if diff_tbl in line.strip(): - tbl_name = line.split()[0] - tbl_freq[tbl_name] = \ - tbl_freq[tbl_name] + 1 if tbl_name in tbl_freq else 1 - if diff_col in line.strip(): - col_name = line.split()[1] - col_freq.append((tbl_name, col_name)) - -def proxy_lst_to_dict(lst): - """converts the col_freq list into a dictionary for easier processing - """ - col_freq = defaultdict(lambda: defaultdict(int)) - for tbl, col in lst: - col_freq[tbl][col] += 1 - return col_freq - -def determ_analysis(niter=1000): - """ - Calls deterministic regression tests for a number of iterations and reports - findings of nondeterminism to a file. - - Parameters - ---------- - niter : int - The number of times to run regression tests - - fname : str - The output filename to report to - - Returns - ------- - tbl_freq, col_freq : 2-tuple of dicts - tbl_freq is a frequency map of nondeterministic tables - col_freq is a frequency map of nondeterminisitc columns, - per table - """ - m = Manager() - - tbl_freq = m.dict() - col_freq = m.list() - - # collect - nproc = cpu_count() - count = nproc if nproc == 1 else nproc - 1 - pool = Pool(count) - - print("Beginning iterations on " + str(nproc) + " processors.") - args = ((tbl_freq, col_freq) for i in range(niter)) - jobs = pool.map_async(collect, args) - while not jobs.ready(): - print('{0:.1%} of jobs left to start.'.format( - jobs._number_left / niter)) - time.sleep(5.0) - if not jobs.successful(): - raise ValueError("At least one job failed.") - pool.close() - pool.join() - print("Finished iterations.") - - # convert from proxy - col_freq = proxy_lst_to_dict(col_freq) - tbl_freq = {item[0]: item[1] for item in tbl_freq.items()} - - # normalize - for tbl, dic in col_freq.iteritems(): - for col, freq in dic.iteritems(): - dic[col] = "{0:.2f}".format(float(freq) / tbl_freq[tbl]) - for k, v in tbl_freq.iteritems(): - tbl_freq[k] = "{0:.2f}".format(float(v) / niter) - - return tbl_freq, col_freq - -def report(tbl_freq, col_freq, fname="report"): - """ - Prints the results of determ_analysis to a file - - Parameters - ---------- - tbl_freq : dict - the table frequency output from determ_analysis - - col_freq : dict - the column frequency output from determ_analysis - - fname : str - the output file name to print to - """ - lines = [] - lines.append("Table values are reported as percent nondeterministic" + - " of total runs.\n\n") - lines.append("Column values are reported as percent nondeterministic" + - " of all table nondeterminism occurrences.\n\n") - if len(tbl_freq) == 0: - lines.append("No nondeterminism found.") - for tbl, freq in tbl_freq.iteritems(): - lines.append(tbl + " " + freq + "\n") - for col, freq in col_freq[tbl].iteritems(): - lines.append(" " + col + " " + freq + "\n") - with open(fname, "w") as f: - f.writelines(lines) - - -def main(): - description = "A module for analyzing the (non)determinism of Cyclus output." - - parser = ap.ArgumentParser(description=description) - - niter = 'the number of regression test runs to perform' - parser.add_argument('-n', '--niterations', type=int, help=niter, - default=100) - - out = 'the file to write the report to' - parser.add_argument('--out', help=out, default='report') - - args = parser.parse_args() - tbl_freq, col_freq = determ_analysis(args.niterations) - report(tbl_freq, col_freq, args.out) - -if __name__ == "__main__": - main() diff --git a/tests/create_references.py b/tests/create_references.py deleted file mode 100755 index 5c27bea806..0000000000 --- a/tests/create_references.py +++ /dev/null @@ -1,17 +0,0 @@ -#! /usr/bin/env python - -import os - -from cyclus_tools import run_cyclus -from testcases import sim_files - -def main(): - """Creates reference databases. Assumes that cyclus is included into PATH. - """ - cwd = os.getcwd() - - # Run cyclus - run_cyclus("cyclus", cwd, sim_files) - -if __name__ == "__main__": - main() diff --git a/tests/cyclus_tools.py b/tests/cyclus_tools.py deleted file mode 100644 index 913d45535d..0000000000 --- a/tests/cyclus_tools.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -from tools import check_cmd - -import numpy as np -import tables - -import visitors - -def run_cyclus(cyclus, cwd, in_path, out_path): - """Runs cyclus with various inputs and creates output databases - """ - holdsrtn = [1] # needed because nose does not send() to test generator - # make sure the output target directory exists - cmd = [cyclus, "-o", out_path, "--input-file", in_path] - check_cmd(cmd, cwd, holdsrtn) - -def compare_nondeterm(path1, path2): - """Compares two Cyclus HDF5 databases assuming non-deterministic AgentIDs - and TransactionIDs. - - Returns - ------- - rtn : bool - True if both databases are the same, taking into account - nondeterministic id assignments. - """ - v1 = visitors.HDF5RegressionVisitor(path1) - v2 = visitors.HDF5RegressionVisitor(path2) - return v1.walk() == v2.walk() - -def compare_determ(path1, path2, verbose=True): - """Compares two Cyclus HDF5 databases assuming deterministic AgentIDs and - TransactionIDs - - Returns - ------- - rtn : bool - True if both databases are identical other than their SimIDs - """ - dbs_same = True - db_one = tables.open_file(path1, mode = "r") - db_two = tables.open_file(path2, mode = "r") - path_one = [] - path_two = [] - - for node in db_one.walk_nodes(classname = "Table"): - path_one.append(node._v_pathname) - - for node in db_two.walk_nodes(classname = "Table"): - path_two.append(node._v_pathname) - - # Check if databases contain the same tables - if not np.all(path_one == path_two): - if verbose: - print("The number or names of tables in databases are not the same.") - print(path_one) - print(path_two) - # Close databases - db_one.close() - db_two.close() - dbs_same = False - return dbs_same - - paths = path_one - - for path in paths: - data_one = db_one.get_node(path)[:] - data_two = db_two.get_node(path)[:] - names = [] - - for name in data_one.dtype.names: - if name != "SimID": - names.append(name) - - data_one = data_one[names] - data_two = data_two[names] - - if np.all(data_one == data_two): - continue - - dbs_same = False - if verbose: - msg = "" - msg += path.replace("/", "") - msg += " table is different in the databases.\n" - msg += determ_err_msg(names, data_one, data_two) - print(msg) - - # Close databases - db_one.close() - db_two.close() - return dbs_same - -def determ_err_msg(names, data_one, data_two): - """Returns a string describing the deterministic difference between two - databases. - """ - msg = "" - # Investigation of the differences - # check if the lengths are different - if len(data_one) != len(data_two): - msg += "Length mismatch: " + str(len(data_one)) + ", " + str(len(data_two)) - else: - for name in names: - column_one = data_one[name] - column_two = data_two[name] - # check if data types are the same - if column_one.dtype != column_two.dtype: - msg += "Datatypes in column " + name +" are different." - msg += str(column_one.dtype) - msg += str(column_two.dtype) - elif not np.all(column_one == column_two): - msg += "Column " + name - diff = np.equal(column_one, column_two) - # find indices and elements for numerical values - indices = np.where(diff==False) - # check if whole table is different - if len(indices) == len(column_one): - msg += " is completely different" - else: - # provide mismatch percentage - mismatch = 100*float(len(indices))/len(column_one) - msg += " has a mismatch of" - msg += " {0:.2f}".format(mismatch) + "% \n" - msg += "Indices of different objects are:\n" - msg += str(indices[0]) + "\n" - msg += "The different elements on these indices: \n" - msg += str(column_one[indices]) + "\n" - msg += str(column_two[indices]) + "\n" - return msg diff --git a/tests/helper.py b/tests/helper.py index af5d9ebd61..831f98facc 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -1,9 +1,17 @@ """A set of tools for use in integration tests.""" import os +import tempfile +import subprocess +import sys from hashlib import sha1 - import numpy as np import tables +from nose.tools import assert_equal + +if sys.version_info[0] >= 3: + str_types = (bytes, str) +else: + str_types = (str, unicode) def hasher(x): return int(sha1(x.encode()).hexdigest(), 16) @@ -49,38 +57,26 @@ def exit_times(agent_id, exit_table): return exit_times -def create_sim_input(ref_input, k_factor_in, k_factor_out): - """Creates xml input file from a reference xml input file. - - Changes k_factor_in and k_factor_out in a simulation input - files for KFacility. - - Args: - ref_input: A reference xml input file with k_factors. - k_factor_in: A new k_factor for requests. - k_factor_out: A new conversion factor for offers. - - Returns: - A path to the created file. It is created in the same - directory as the reference input file. +def run_cyclus(cyclus, cwd, in_path, out_path): + """Runs cyclus with various inputs and creates output databases """ - # File to be created - fw_path = ref_input.split(".xml")[0] + "_" + str(k_factor_in) + \ - "_" + str(k_factor_out) + ".xml" - fw = open(fw_path, "w") - fr = open(ref_input, "r") - for f in fr: - if f.count("k_factor_in"): - f = f.split("<")[0] + "" + str(k_factor_in) + \ - "\n" - elif f.count("k_factor_out"): - f = f.split("<")[0] + "" + str(k_factor_out) + \ - "\n" - - fw.write(f) + holdsrtn = [1] # needed because nose does not send() to test generator + # make sure the output target directory exists + cmd = [cyclus, "-o", out_path, "--input-file", in_path] + check_cmd(cmd, cwd, holdsrtn) - # Closing open files - fr.close() - fw.close() - - return fw_path +def check_cmd(args, cwd, holdsrtn): + """Runs a command in a subprocess and verifies that it executed properly. + """ + if not isinstance(args, str_types): + args = " ".join(args) + print("TESTING: running command in {0}:\n\n{1}\n".format(cwd, args)) + env = dict(os.environ) + env['_'] = subprocess.check_output(['which', 'cyclus'], cwd=cwd).strip() + with tempfile.NamedTemporaryFile() as f: + rtn = subprocess.call(args, shell=True, cwd=cwd, stdout=f, stderr=f, env=env) + if rtn != 0: + f.seek(0) + print("STDOUT + STDERR:\n\n" + f.read().decode()) + holdsrtn[0] = rtn + assert_equal(rtn, 0) diff --git a/tests/input/dynamic_capacitated.xml b/tests/input/dynamic_capacitated.xml index 980e3da2e3..ccc69f1747 100644 --- a/tests/input/dynamic_capacitated.xml +++ b/tests/input/dynamic_capacitated.xml @@ -8,8 +8,8 @@ - agentsSink - agentsSource + cycamoreSink + cycamoreSource agentsNullRegion cycamoreDeployInst @@ -18,16 +18,16 @@ Source - commodity - commod_recipe - 1.0 + commodity + commod_recipe + 1.0 Sink - 1 + 2 @@ -47,21 +47,23 @@ SingleInstitution - - Source - 3 - 1 - - - Sink - 2 - 1 - - - Sink - 2 - 2 - + + Source + Sink + Sink + + + + 1 + 1 + 2 + + + + 3 + 2 + 2 + diff --git a/tests/input/growth.xml b/tests/input/growth.xml index 590f04149c..7bc3fd874b 100644 --- a/tests/input/growth.xml +++ b/tests/input/growth.xml @@ -30,9 +30,9 @@ Source1 - commodity - commod_recipe - 1.1 + commodity1 + commod_recipe + 1.1 @@ -41,9 +41,20 @@ Source2 - commodity - commod_recipe - 2 + commodity1 + commod_recipe + 2 + + + + + + Source3 + + + commodity2 + commod_recipe + 1 @@ -53,7 +64,8 @@ - commodity + commodity1 + commodity2 @@ -63,16 +75,32 @@ SingleRegion - commodity - - linear - - - 1 2 - - - 0 - + + + commodity1 + + + 0 + + linear + 1 2 + + + + + + commodity2 + + + 1 + + linear + 0 3 + + + + + @@ -89,6 +117,7 @@ Sink Source1 Source2 + Source3 diff --git a/tests/ref.py b/tests/ref.py deleted file mode 100644 index 12c2440b7d..0000000000 --- a/tests/ref.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/python - -from __future__ import print_function -import hashlib -import subprocess as sp -import argparse -import json -import os.path -import uuid -import multiprocessing -import shutil -import importlib -import sys -import pyrax -pyrax.set_setting("identity_type", "rackspace") - -def gen_main(args): - # create sandbox dir - sandbox_path = '/tmp/refgen-' + str(uuid.uuid4()) - install_path = os.path.join(sandbox_path, 'install') - os.makedirs(install_path) - - # fetch and build cyclus - cyclus_path = os.path.join(sandbox_path, 'cyclus') - build_path = prepare_repo(args.cyclus_repo, cyclus_path) - tags = repo_tags(cyclus_path) - if args.cyclus_refspec == '': - args.cyclus_refspec = tags[-1] - checkout(cyclus_path, args.cyclus_refspec) - build_cyclus(build_path, install_path, prefix_path = args.cmake_prefix, boost = args.boost_root, coin = args.coin_root) - - # fetch and build cycamore - cycamore_path = os.path.join(sandbox_path, 'cycamore') - build_path = prepare_repo(args.cycamore_repo, cycamore_path) - tags = repo_tags(cycamore_path) - if args.cycamore_refspec == '': - args.cycamore_refspec = tags[-1] - checkout(cycamore_path, args.cycamore_refspec) - build_cycamore(build_path, install_path, prefix_path = args.cmake_prefix, boost = args.boost_root, coin = args.coin_root) - - # run cyclus simulations - sys.path.insert(0, os.path.join(cycamore_path, "tests")) - mod = importlib.import_module("test_cases") - for infile in mod.sim_files: - run_cyclus(install_path, infile, args.cyclus_refspec, args.cycamore_refspec) - - # cleanup - shutil.rmtree(sandbox_path) - -def prepare_repo(url, dst_path): - """ Clones a repo, creates a build dir. - """ - sp.check_call(['git', 'clone', url, dst_path]) - build_path = os.path.join(dst_path, "build") - os.makedirs(build_path) - return build_path - -def checkout(repo_path, commit): - """ Checks out the specified commit in a repo. - """ - cwd = os.getcwd() - os.chdir(repo_path) - sp.check_call(['git', 'checkout', commit]) - os.chdir(cwd) - -def repo_tags(repo_path): - """ Returns a sorted list of all tags in the repo (most recent last). - """ - #git tag | xargs -I@ git log --format=format:"%ai @%n" -1 @ | sort | awk '{print $4}' - cwd = os.getcwd() - os.chdir(repo_path) - git = sp.Popen(['git', 'tag'], stdout = sp.PIPE) - xargs = sp.Popen(['xargs', '-I@', 'git', 'log', '--format=format:"%ai @%n"', '-1', '@'], stdin = git.stdout, stdout = sp.PIPE) - sort = sp.Popen(['sort'], stdin = xargs.stdout, stdout = sp.PIPE) - awk = sp.Popen(['awk', '{print $4}'], stdin = sort.stdout, stdout = sp.PIPE) - (tagstext, _) = awk.communicate() - tags = tagstext.decode().split('\n')[1:-1] - os.chdir(cwd) - return tags - -def build_cyclus(build_path, install_path, prefix_path = '', boost = '', coin = ''): - cmd = ['cmake', '..', '-DCMAKE_INSTALL_PREFIX=' + os.path.abspath(install_path)] - if prefix_path != '': - cmd.append('-DCMAKE_PREFIX_PATH=' + os.path.abspath(prefix_path)) - if boost != '': - cmd.append('-DBOOST_ROOT=' + os.path.abspath(boost)) - if coin != '': - cmd.append('-DCOIN_ROOT_DIR=' + os.path.abspath(coin)) - cwd = os.getcwd() - os.chdir(build_path) - - sp.check_call(cmd) - sp.check_call(['make', '-j', str(multiprocessing.cpu_count())]) - sp.check_call(['make', 'install']) - os.chdir(cwd) - -def build_cycamore(build_path, install_path, prefix_path = '', boost = '', coin = ''): - cmd = ['cmake', '..', '-DCMAKE_INSTALL_PREFIX=' + os.path.abspath(install_path), '-DCYCLUS_ROOT_DIR=' + install_path] - if prefix_path != '': - cmd.append('-DCMAKE_PREFIX_PATH=' + os.path.abspath(prefix_path)) - if boost != '': - cmd.append('-DBOOST_ROOT=' + os.path.abspath(boost)) - if coin != '': - cmd.append('-DCOIN_ROOT_DIR=' + os.path.abspath(coin)) - cwd = os.getcwd() - os.chdir(build_path) - - sp.check_call(cmd) - sp.check_call(['make', '-j', str(multiprocessing.cpu_count())]) - sp.check_call(['make', 'install']) - os.chdir(cwd) - -def run_cyclus(install_path, input_file, cyclus_ref, cycamore_ref): - out_file = encode_dbname(cyclus_ref, cycamore_ref, input_file) - cyclus = os.path.join(install_path, 'bin', 'cyclus') - sp.check_call([cyclus, '-o', out_file, input_file]) - -def encode_dbname(cyclus_ref, cycamore_ref, input_file): - in_name, _ = os.path.splitext(os.path.basename(input_file)) - return cyclus_ref + '_' + cycamore_ref + '_' + in_name + '.h5' - -def decode_dbname(fname): - base, _ = os.path.splitext(fname) - parts = base.split('_') - cyclus_ref = parts[0] - cycamore_ref = parts[1] - infile = '_'.join(parts[2:]) + '.xml' - return (cyclus_ref, cycamore_ref, infile) - -def add_main(args): - # retrieve existing reflist - reflist = [] - if os.path.exists(args.reflist): - with open(args.reflist) as f: - data = f.read() - reflist = json.loads(data) - - # add new ref dbs and hashes to reflist - for refname in args.ref_dbs: - if refname in [entry['fname'] for entry in reflist]: - print(refname + ' is already in reflist \'' + args.reflist + '\'') - - with open(refname, 'rb') as f: - data = f.read() - h = hashlib.sha1() - h.update(data) - (cyclus_ref, cycamore_ref, infile) = decode_dbname(refname) - reflist.append({ - 'fname': refname, - 'sha1-checksum': h.hexdigest(), - 'cyclus-ref': cyclus_ref, - 'cycamore-ref': cycamore_ref, - 'input-file': infile - }) - - push_rackspace(refname, args.rs_cred) - - # update reflist - data = json.dumps(reflist, indent=4) - with open(args.reflist, 'w') as f: - f.write(data) - -def push_rackspace(fname, cred_file='rs.cred'): - creds_file = os.path.expanduser(cred_file) - pyrax.set_credential_file(cred_file) - cf = pyrax.cloudfiles - with open(fname, 'rb') as f: - fdata = f.read() - obj = cf.store_object("cyclus", fname, fdata) - -def fetch_refdbs(cyclus_ref, cycamore_ref, dst_path = '.'): - cwd = os.getcwd() - os.chdir(dst_path) - os.chdir(cwd) - -if __name__ == '__main__': - desc = 'Generates cyclus reference databases for regression testing' - p = argparse.ArgumentParser(description=desc) - subs = p.add_subparsers(help='sub-command help') - - # gen subcommand - sub_gen = subs.add_parser('gen', help='create a new reference db set') - help = 'force generate new ref-db rather than fetch remotely' - sub_gen.add_argument('-f,--force', type=bool, help=help, default=False) - help = 'location of cycamore repo' - sub_gen.add_argument('--cyclus-repo', help=help, default='https://github.com/cyclus/cyclus.git') - help = 'location of cycamore repo' - sub_gen.add_argument('--cycamore-repo', help=help, default='https://github.com/cyclus/cycamore.git') - help = 'git refspec for cyclus commit to generate reference from. Blank defaults to the most recent tag.' - sub_gen.add_argument('--cyclus-refspec', help=help, default='') - help = 'git refspec for cycamore commit to generate reference from. Blank defaults to the most recent tag.' - sub_gen.add_argument('--cycamore-refspec', help=help, default='') - help = 'the relative path to the Coin-OR libraries directory' - sub_gen.add_argument('--coin-root', help=help, default = '') - help = 'the relative path to the Boost libraries directory' - sub_gen.add_argument('--boost-root', help=help, default = '') - help = 'the cmake prefix path for use with FIND_PACKAGE, ' - help += 'FIND_PATH, FIND_PROGRAM, or FIND_LIBRARY macros' - sub_gen.add_argument('--cmake-prefix', help=help, default = '') - sub_gen.set_defaults(func=gen_main) - - # add subcommand - sub_add = subs.add_parser('add', help='add a new reference db set to the project') - sub_add.add_argument('ref_dbs', metavar='FILE', nargs='+', help='list of reference db files') - sub_add.add_argument('--reflist', help='filename of reflist', default='./reflist.json') - sub_add.add_argument('--rs-cred', help='rackspace credentials file', default='rs.cred') - sub_add.set_defaults(func=add_main) - - args = p.parse_args() - args.func(args) - diff --git a/tests/reflist.json b/tests/reflist.json deleted file mode 100644 index f0620067f3..0000000000 --- a/tests/reflist.json +++ /dev/null @@ -1,170 +0,0 @@ -[ - { - "cyclus-ref": "fc5bbee", - "sha1-checksum": "451b951e378c2883c506568ff7763119e23281db", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "fc5bbee_8b2d61c_1_Enrichment_2_Reactor.h5", - "cycamore-ref": "8b2d61c" - }, - { - "cyclus-ref": "fc5bbee", - "sha1-checksum": "e7028d6c70ff6606792d90045d0a3495590e46d9", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "fc5bbee_8b2d61c_2_Sources_3_Reactors.h5", - "cycamore-ref": "8b2d61c" - }, - { - "cycamore-ref": "v0.4-rc1", - "sha1-checksum": "0060c462e82a5c23db7cf5f10b2b1a1e67462125", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "v0.4-rc1_v0.4-rc1_1_Enrichment_2_Reactor.h5", - "cyclus-ref": "v0.4-rc1" - }, - { - "cycamore-ref": "v0.4-rc1", - "sha1-checksum": "be0d48a5bc229c4d46d8d72de63fa6da4f4097ae", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "v0.4-rc1_v0.4-rc1_2_Sources_3_Reactors.h5", - "cyclus-ref": "v0.4-rc1" - }, - { - "cyclus-ref": "v0.4", - "sha1-checksum": "3249ab2b874e71aef722e292986a96263b96c6c5", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "v0.4_v0.4_1_Enrichment_2_Reactor.h5", - "cycamore-ref": "v0.4" - }, - { - "cyclus-ref": "v0.4", - "sha1-checksum": "89b930987402f04c84831d8ec4e8b2e61e0116e8", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "v0.4_v0.4_2_Sources_3_Reactors.h5", - "cycamore-ref": "v0.4" - }, - { - "cycamore-ref": "0.4.1", - "sha1-checksum": "5b747f39b8c8ddd5e26563f8090384eb290b8248", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "0.4.1_0.4.1_1_Enrichment_2_Reactor.h5", - "cyclus-ref": "0.4.1" - }, - { - "cycamore-ref": "0.4.1", - "sha1-checksum": "1aa69d570a672ebff869c04de64e178c7bc4d488", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "0.4.1_0.4.1_2_Sources_3_Reactors.h5", - "cyclus-ref": "0.4.1" - }, - { - "cyclus-ref": "0.4.2", - "sha1-checksum": "4e95d446a2dc167786845974119584804b3c3eca", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "0.4.2_0.4.2_1_Enrichment_2_Reactor.h5", - "cycamore-ref": "0.4.2" - }, - { - "cyclus-ref": "0.4.2", - "sha1-checksum": "c64b84195f17f5ab68e3efc409044d35b3ce134d", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "0.4.2_0.4.2_2_Sources_3_Reactors.h5", - "cycamore-ref": "0.4.2" - }, - { - "cycamore-ref": "0.4.3", - "sha1-checksum": "687c40669300671cc7e81cc6f8b3b0e467fc880c", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "0.4.3_0.4.3_1_Enrichment_2_Reactor.h5", - "cyclus-ref": "0.4.3" - }, - { - "cycamore-ref": "0.4.3", - "sha1-checksum": "d7acf9f1ec5bd90059bb38640f3ea6d0d64126f9", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "0.4.3_0.4.3_2_Sources_3_Reactors.h5", - "cyclus-ref": "0.4.3" - }, - { - "cyclus-ref": "0.4.4", - "sha1-checksum": "e0b9bae64c15d1b3de4204836228b35c06a82fe2", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "0.4.4_0.4.4_1_Enrichment_2_Reactor.h5", - "cycamore-ref": "0.4.4" - }, - { - "cyclus-ref": "0.4.4", - "sha1-checksum": "c179b2297b923664906e77ea23045d4e1c442773", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "0.4.4_0.4.4_2_Sources_3_Reactors.h5", - "cycamore-ref": "0.4.4" - }, - { - "cycamore-ref": "1.0", - "sha1-checksum": "1ff53f7af4fe9357ee724264b6b895cd6a5c955d", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "1.0_1.0_1_Enrichment_2_Reactor.h5", - "cyclus-ref": "1.0" - }, - { - "cycamore-ref": "1.0", - "sha1-checksum": "41d798d76de4ce6277880c125b058c1bc9a7db94", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "1.0_1.0_2_Sources_3_Reactors.h5", - "cyclus-ref": "1.0" - }, - { - "cyclus-ref": "1.0.0-rc4", - "sha1-checksum": "2df6acb1aeb8304fea6dbf91f6773dcee4b421e0", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "1.0.0-rc4_1.0.0-rc4_1_Enrichment_2_Reactor.h5", - "cycamore-ref": "1.0.0-rc4" - }, - { - "cyclus-ref": "1.0.0-rc4", - "sha1-checksum": "9de63fb594eeef3da316e73ea3dd05035a476548", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "1.0.0-rc4_1.0.0-rc4_2_Sources_3_Reactors.h5", - "cycamore-ref": "1.0.0-rc4" - }, - { - "cycamore-ref": "1.1.0", - "sha1-checksum": "ef73b03e4236f707d9b618ffbaa8f3acd715d37e", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "1.1.0_1.1.0_1_Enrichment_2_Reactor.h5", - "cyclus-ref": "1.1.0" - }, - { - "cycamore-ref": "1.1.0", - "sha1-checksum": "5e27c9ad6ba2dd80de0060a986939619e2d5a490", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "1.1.0_1.1.0_2_Sources_3_Reactors.h5", - "cyclus-ref": "1.1.0" - }, - { - "cyclus-ref": "v1.1.1", - "sha1-checksum": "2218597542aafd19e558580eee696f0faad99c63", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "v1.1.1_v1.1.1_1_Enrichment_2_Reactor.h5", - "cycamore-ref": "v1.1.1" - }, - { - "cyclus-ref": "v1.1.1", - "sha1-checksum": "ec3bf30bad33ec6babc320458aac56910f0ff77a", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "v1.1.1_v1.1.1_2_Sources_3_Reactors.h5", - "cycamore-ref": "v1.1.1" - }, - { - "cycamore-ref": "v1.2.0", - "sha1-checksum": "d6b317d8bb28197e33af8cdd6f497b7383c84539", - "input-file": "1_Enrichment_2_Reactor.xml", - "fname": "v1.2.0_v1.2.0_1_Enrichment_2_Reactor.h5", - "cyclus-ref": "v1.2.0" - }, - { - "cycamore-ref": "v1.2.0", - "sha1-checksum": "966d77cb9a62effd5888537a39abb20853c85b62", - "input-file": "2_Sources_3_Reactors.xml", - "fname": "v1.2.0_v1.2.0_2_Sources_3_Reactors.h5", - "cyclus-ref": "v1.2.0" - } -] \ No newline at end of file diff --git a/tests/run_inputs.py.in b/tests/run_inputs.py.in index b6aae68bf1..49be1fe64c 100644 --- a/tests/run_inputs.py.in +++ b/tests/run_inputs.py.in @@ -5,12 +5,11 @@ import subprocess from subprocess import Popen, PIPE, STDOUT import os import re - -def main(): - """This function finds input files, runs them, and prints a summary""" - flag = check_inputs() - input_path = "@input_path@" - cyclus_path = "@cyclus_path@/cyclus" + +cyclus_path = "@cyclus_path@/cyclus" +input_path = "@input_path@" + +def main_body(flag): files, catalogs, catalognames = get_files(input_path) copy_catalogs(catalogs,cyclus_path.strip("cyclus")) summ = Summary() @@ -20,7 +19,12 @@ def main(): summ.add_to_summary(file_to_test) clean_catalogs(cyclus_path.strip("cyclus"),catalognames) summ.print_summary() - + +def main(): + """This function finds input files, runs them, and prints a summary""" + flag = check_inputs() + main_body(flag) + def check_inputs(): """This function checks the input arguments""" if len(sys.argv) > 2: @@ -38,19 +42,19 @@ def check_inputs(): def print_usage() : """This prints the proper way to treat the command line interface""" - print 'Usage: python run_inputs.py\n' + \ - 'Allowed Options : \n' + \ - '-v arg output log verbosity. \n' + \ - ' \ - Can be text: \n' + \ - ' \ - LEV_ERROR (least verbose, default), LEV_WARN,\n' + \ - ' \ - LEV_INFO1 (through 5), and LEV_DEBUG1 (through 5).\n' + \ - ' \ - Or an integer:\n'+ \ - ' \ - 0 (LEV_ERROR equiv) through 11 (LEV_DEBUG5 equiv)\n' + print(""" Usage: python run_inputs.py\n + Allowed Options : \n + -v arg output log verbosity. \n + + Can be text: \n + + LEV_ERROR (least verbose, default), LEV_WARN,\n + + LEV_INFO1 (through 5), and LEV_DEBUG1 (through 5).\n + + Or an integer:\n + + 0 (LEV_ERROR equiv) through 11 (LEV_DEBUG5 equiv)\n""") def get_files(path): """This function walks the 'path' tree and finds input files""" @@ -70,10 +74,10 @@ def get_files(path): inputs.append(os.path.join(root, name)) else : files.remove(name) - print "The catalogs to be moved are:" - print catalogs - print "The files to be tested are:" - print inputs + print("The catalogs to be moved are:") + print(catalogs) + print("The files to be tested are:") + print(inputs) return inputs, catalogs, catalognames def copy_catalogs(catalogs,cyclus_path) : @@ -103,11 +107,11 @@ class Summary(): def print_summary(self) : """Prints the summary""" - print "Input files passed = " + str(len(self.passed)) - print "Input files failed = " + str(len(self.failed)) - print "Failed input files : " + print("Input files passed = " + str(len(self.passed))) + print("Input files failed = " + str(len(self.failed))) + print("Failed input files : ") for test in self.failed : - print test + print(test) class TestFile(): """An object representing the inputxml file to test""" @@ -132,22 +136,22 @@ class TestFile(): shell=True, stdout=PIPE, stderr=STDOUT) io_tuple = p.communicate() output = io_tuple[0] - except subprocess.CalledProcessError, e: + except subprocess.CalledProcessError as e: print(e) - return output + return str(output) def no_errors(self, output): """returns true if there were no errors or segfaults running this TestFile""" to_ret = True - print "Input file " + self.infile + print("Input file " + self.infile) if re.search("No such file or directory",output) : - print "Cyclus executable not found in path." + print("Cyclus executable not found in path.") elif re.search("ERROR",output) or re.search("Segmentation fault",output): to_ret = False - print " resulted in errors: " - print output + print(" resulted in errors: ") + print(output) else : - print " passed. " + print(" passed. ") return to_ret if __name__ == '__main__' : main() diff --git a/tests/test_cases.py b/tests/test_cases.py deleted file mode 100644 index 2829c1e4aa..0000000000 --- a/tests/test_cases.py +++ /dev/null @@ -1,6 +0,0 @@ - -#List of input files and reference databases -sim_files = [ - '../input/physor/1_Enrichment_2_Reactor.xml', - '../input/physor/2_Sources_3_Reactors.xml' - ] diff --git a/tests/test_dynamic_capacitated.py b/tests/test_dynamic_capacitated.py deleted file mode 100644 index ab3d180971..0000000000 --- a/tests/test_dynamic_capacitated.py +++ /dev/null @@ -1,141 +0,0 @@ -#! /usr/bin/env python - -from nose.tools import assert_equal, assert_true -import os -import tables -import numpy as np -import uuid -from tools import check_cmd -from helper import table_exist, find_ids - -def test_dynamic_capacitated(): - """Tests dynamic capacity restraints involving changes in the number of - source and sink facilities. - - A source facility is expected to offer a commodity of amount 1, - and a sink facility is expected to request for a commodity of amount 1. - Therefore, number of facilities correspond to the amounts of offers - and requests. - - At time step 1, 3 source facilities and 2 sink facilities are deployed, and - at time step 2, additional 2 sink facilities are deployed. After time - step 2, the older 2 sink facilities are decommissioned. - According to this deployment schedule, at time step 1, only 2 transactions - are expected, the number of sink facilities being the constraint; whereas, - at time step 2, only 3 transactions are expected, the number of source - facilities being the constraint. At time step 3, after decommissioning 2 - older sink facilities, the remaining number of sink facilities becomes - the constraint, resulting in the same transaction amount as in time step 1. - """ - # Cyclus simulation input for dynamic capacitated - sim_inputs = ["./input/dynamic_capacitated.xml"] - - for sim_input in sim_inputs: - holdsrtn = [1] # needed because nose does not send() to test generator - tmp_file = str(uuid.uuid4()) + ".h5" - cmd = ["cyclus", "-o", tmp_file, "--input-file", sim_input] - yield check_cmd, cmd, '.', holdsrtn - rtn = holdsrtn[0] - if rtn != 0: - return # don't execute further commands - - output = tables.open_file(tmp_file, mode = "r") - # Tables of interest - paths = ["/AgentEntry", "/Resources", "/Transactions", "/AgentExit"] - # Check if these tables exist - yield assert_true, table_exist(output, paths) - if not table_exist(output, paths): - output.close() - if os.path.isfile(tmp_file): - print("removing {0}".format(tmp_file)) - os.remove(tmp_file) - return # don't execute further commands - - # Get specific tables and columns - agent_entry = output.get_node("/AgentEntry")[:] - agent_exit = output.get_node("/AgentExit")[:] - resources = output.get_node("/Resources")[:] - transactions = output.get_node("/Transactions")[:] - - # Find agent ids of source and sink facilities - agent_ids = agent_entry["AgentId"] - agent_impl = agent_entry["Spec"] - depl_time = agent_entry["EnterTime"] - exit_time = agent_exit["ExitTime"] - exit_ids = agent_exit["AgentId"] - - source_id = find_ids(":agents:Source", agent_impl, agent_ids) - sink_id = find_ids(":agents:Sink", agent_impl, agent_ids) - - # Test for 3 sources and 4 sinks are deployed in the simulation - yield assert_equal, len(source_id), 3 - yield assert_equal, len(sink_id), 4 - - # Test that source facilities are all deployed at time step 1 - for s in source_id: - yield assert_equal, depl_time[np.where(agent_ids == s)], 1 - # Test that first 2 sink facilities are deployed at time step 1 - # and decommissioned at time step 2 - for i in [0, 1]: - yield assert_equal,\ - depl_time[np.where(agent_ids == sink_id[i])], 1 - yield assert_equal,\ - exit_time[np.where(exit_ids == sink_id[i])], 2 - # Test that second 2 sink facilities are deployed at time step 2 - # and decommissioned at time step 3 - for i in [2, 3]: - yield assert_equal,\ - depl_time[np.where(agent_ids == sink_id[i])], 2 - yield assert_equal,\ - exit_time[np.where(exit_ids == sink_id[i])], 3 - - # Check transactions - sender_ids = transactions["SenderId"] - receiver_ids = transactions["ReceiverId"] - trans_time = transactions["Time"] - trans_resource = transactions["ResourceId"] - # Track transacted resources - resource_ids = resources["ResourceId"] - quantities = resources["Quantity"] - - # Check that transactions are between sources and sinks only - for s in sender_ids: - yield assert_equal, len(np.where(source_id == s)[0]), 1 - - for r in receiver_ids: - yield assert_equal, len(np.where(sink_id == r)[0]), 1 - - # Total expected number of transactions - yield assert_equal, len(trans_time), 7 - # Check that at time step 1, there are 2 transactions - yield assert_equal, len(np.where(trans_time == 1)[0]), 2 - # Check that at time step 2, there are 3 transactions - yield assert_equal, len(np.where(trans_time == 2)[0]), 3 - # Check that at time step 3, there are 2 transactions - yield assert_equal, len(np.where(trans_time == 3)[0]), 2 - - # Check that at time step 1, there are 2 transactions with total - # amount of 2 - quantity = 0 - for t in np.where(trans_time == 1)[0]: - quantity += quantities[np.where(resource_ids == trans_resource[t])] - yield assert_equal, quantity, 2 - - # Check that at time step 2, there are 3 transactions with total - # amount of 3 - quantity = 0 - for t in np.where(trans_time == 2)[0]: - quantity += quantities[np.where(resource_ids == trans_resource[t])] - yield assert_equal, quantity, 3 - - # Check that at time step 3, there are 2 transactions with total - # amount of 2 - quantity = 0 - for t in np.where(trans_time == 3)[0]: - quantity += quantities[np.where(resource_ids == trans_resource[t])] - yield assert_equal, quantity, 2 - - output.close() - if os.path.isfile(tmp_file): - print("removing {0}".format(tmp_file)) - os.remove(tmp_file) diff --git a/tests/test_growth.py b/tests/test_growth.py deleted file mode 100644 index 8b6c4e57b7..0000000000 --- a/tests/test_growth.py +++ /dev/null @@ -1,62 +0,0 @@ -#! /usr/bin/env python - -from nose.tools import assert_equal, assert_true -import os -import tables -import numpy as np -import uuid -from tools import check_cmd -from helper import table_exist, find_ids - -def test_growth(): - """Tests GrowthRegion, ManagerInst, and Source over a 4-time step - simulation. - - A linear growth demand (y = x + 2) is provided to the growth region. Two - Sources are allowed in the ManagerInst, with capacities of 2 and 1.1, - respectively. At t=1, a 2-capacity Source is expected to be built, and at - t=2 and t=3, 1-capacity Sources are expected to be built. - """ - holdsrtn = [1] # needed because nose does not send() to test generator - sim_input = "./input/growth.xml" - tmp_file = str(uuid.uuid4()) + ".h5" - cmd = ["cyclus", "-o", tmp_file, "--input-file", sim_input] - yield check_cmd, cmd, '.', holdsrtn - rtn = holdsrtn[0] - if rtn != 0: - return # don't execute further commands - - output = tables.open_file(tmp_file, mode = "r") - # Tables of interest - paths = ["/AgentEntry",] - # Check if these tables exist - yield assert_true, table_exist(output, paths) - if not table_exist(output, paths): - output.close() - if os.path.isfile(tmp_file): - print("removing {0}".format(tmp_file)) - os.remove(tmp_file) - return # don't execute further commands - - # Get specific tables and columns - agent_entry = output.get_node("/AgentEntry")[:] - - # Find agent ids of source and sink facilities - agent_ids = agent_entry["AgentId"] - proto = agent_entry["Prototype"] - depl_time = agent_entry["EnterTime"] - - source1_id = find_ids("Source1", proto, agent_ids) - source2_id = find_ids("Source2", proto, agent_ids) - - assert_equal(len(source2_id), 1) - assert_equal(len(source1_id), 2) - - assert_equal(depl_time[np.where(agent_ids == source2_id[0])], 1) - assert_equal(depl_time[np.where(agent_ids == source1_id[0])], 2) - assert_equal(depl_time[np.where(agent_ids == source1_id[1])], 3) - - output.close() - if os.path.isfile(tmp_file): - print("removing {0}".format(tmp_file)) - os.remove(tmp_file) diff --git a/tests/test_regression.py b/tests/test_regression.py index 454990193d..9c70bff2cc 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -1,100 +1,476 @@ #! /usr/bin/env python import os -import json -import hashlib -import urllib +import platform +from unittest import TestCase + +import tables import uuid -from nose.tools import assert_true -from nose import with_setup - -from cyclus_tools import run_cyclus, compare_determ, compare_nondeterm - -sim_files = {} -fetchdir = "fetch" - -def setup(): - global sim_files - if not os.path.isdir(fetchdir): - os.makedirs(fetchdir) - with open("reflist.json") as f: - refs = json.load(f) - cyclus_ref = refs[-1]["cyclus-ref"] - cycamore_ref = refs[-1]["cycamore-ref"] - refs = [r for r in refs - if r["cyclus-ref"] == cyclus_ref - and r["cycamore-ref"] == cycamore_ref] - base_url = "http://regtests.fuelcycle.org/" - for r in refs: - fpath = os.path.join(fetchdir, r["fname"]) - if not os.path.exists(fpath): - try: - urllib.urlretrieve(base_url+r["fname"], fpath) - except AttributeError: # try python 3.1+ api version - urllib.request.urlretrieve(base_url+r["fname"], fpath) - h = hashlib.sha1() - with open(fpath, "rb") as f: - h.update(f.read()) - if h.hexdigest() != r["sha1-checksum"]: - raise RuntimeError("They tooks our data!!! All our rackspace are belong to them.") - sim_files[r["input-file"]] = fpath - -class TestRegression(object): - def __init__(self): - self.in_dir_ = "../input" - self.tmp_files_ = {} - for root, dirs, files in os.walk(self.in_dir_): - for f in files: - self.tmp_files_[f] = str(uuid.uuid4()) + ".h5" +import sqlite3 +import numpy as np +from numpy.testing import assert_array_almost_equal +from numpy.testing import assert_almost_equal +from nose.tools import assert_equal, assert_true +import helper +from helper import check_cmd, run_cyclus, table_exist + +class TestRegression(TestCase): + """A base class for all regression tests. A derived class is required for + each new input file to be tested. Each derived class *must* declare an `inf` + member in their `__init__` function that points to the input file to be + tested, e.g., `self.inf_ = ./path/to/my/input_file.xml. See below for + examples. + """ + def __init__(self, *args, **kwargs): + super(TestRegression, self).__init__(*args, **kwargs) + self.ext = '.sqlite' + self.outf = str(uuid.uuid4()) + self.ext + self.inf = None def __del__(self): - for inf, outf in self.tmp_files_.items(): - if os.path.isfile(outf): - print("removing {0}".format(outf)) - os.remove(outf) - - def teardown(self): - for inf, outf in self.tmp_files_.items(): - if os.path.isfile(outf): - print("removing {0}".format(outf)) - os.remove(outf) - - def test_regression(self, check_deterministic=False): - """Test for all inputs in sim_files. Checks if reference and current cyclus - output is the same. - - Parameters - ---------- - check_deterministic : bool - If True, also test determinisitc equality of simulations + if os.path.isfile(self.outf): + print("removing {0}".format(self.outf)) + os.remove(self.outf) + + def setUp(self): + if not self.inf: + raise TypeError(("self.inf must be set in derived classes " + "to run regression tests.")) + run_cyclus("cyclus", os.getcwd(), self.inf, self.outf) + + # Get specific tables and columns + if self.ext == '.h5': + with tables.open_file(self.outf, mode="r") as f: + # Get specific tables and columns + self.agent_entry = f.get_node("/AgentEntry")[:] + self.agent_exit = f.get_node("/AgentExit")[:] \ + if "/AgentExit" in f \ + else None + self.enrichments = f.get_node("/Enrichments")[:] \ + if "/Enrichments" in f \ + else None + self.resources = f.get_node("/Resources")[:] + self.transactions = f.get_node("/Transactions")[:] + self.compositions = f.get_node("/Compositions")[:] + self.info = f.get_node("/Info")[:] + self.rsrc_qtys = { + x["ResourceId"]: x["Quantity"] for x in self.resources} + else: + self.conn = sqlite3.connect(self.outf) + self.conn.row_factory = sqlite3.Row + self.cur = self.conn.cursor() + exc = self.cur.execute + self.agent_entry = exc('SELECT * FROM AgentEntry').fetchall() + self.agent_exit = exc('SELECT * FROM AgentExit').fetchall() \ + if len(exc( + ("SELECT * FROM sqlite_master WHERE " + "type='table' AND name='AgentExit'")).fetchall()) > 0 \ + else None + self.enrichments = exc('SELECT * FROM Enrichments').fetchall() \ + if len(exc( + ("SELECT * FROM sqlite_master WHERE " + "type='table' AND name='Enrichments'")).fetchall()) > 0 \ + else None + self.resources = exc('SELECT * FROM Resources').fetchall() + self.transactions = exc('SELECT * FROM Transactions').fetchall() + self.compositions = exc('SELECT * FROM Compositions').fetchall() + self.info = exc('SELECT * FROM Info').fetchall() + self.rsrc_qtys = { + x["ResourceId"]: x["Quantity"] for x in self.resources} + + def find_ids(self, spec, a, spec_col="Spec", id_col="AgentId"): + if self.ext == '.h5': + return helper.find_ids(spec, a[spec_col], a[id_col]) + else: + return [x[id_col] for x in a if x[spec_col] == spec] + + def to_ary(self, a, k): + if self.ext == '.sqlite': + return np.array([x[k] for x in a]) + else: + return a[k] + + def tearDown(self): + if self.ext == '.sqlite': + self.conn.close() + if os.path.isfile(self.outf): + print("removing {0}".format(self.outf)) + os.remove(self.outf) + +class TestPhysorEnrichment(TestRegression): + """This class tests the 1_Enrichment_2_Reactor.xml file related to the + Cyclus Physor 2014 publication. The number of key facilities, the enrichment + values, and the transactions to each reactor are tested. + """ + def __init__(self, *args, **kwargs): + super(TestPhysorEnrichment, self).__init__(*args, **kwargs) + self.inf = "../input/physor/1_Enrichment_2_Reactor.xml" + + def setUp(self): + super(TestPhysorEnrichment, self).setUp() + tbl = self.agent_entry + self.rx_id = self.find_ids(":cycamore:Reactor", tbl) + self.enr_id = self.find_ids(":cycamore:Enrichment", tbl) + + def test_deploy(self): + assert_equal(len(self.rx_id), 2) + assert_equal(len(self.enr_id), 1) + + def test_swu(self): + enr = self.enrichments + # this can be updated if/when we can call into the cyclus::toolkit's + # enrichment module from python + # with old BatchReactor: exp = [6.9, 10, 4.14, 6.9] + exp = [6.9, 10., 4.14, 6.9] + obs = [np.sum(self.to_ary(enr, "SWU")[ + self.to_ary(enr, "Time") == t]) for t in range(4)] + assert_array_almost_equal(exp, obs, decimal=2) + + def test_nu(self): + enr = self.enrichments + # this can be updated if/when we can call into the cyclus::toolkit's + # enrichment module from python + + # with old BatchReactor: exp = [13.03, 16.54, 7.83, 13.03] + exp = [13.03, 16.55, 7.82, 13.03] + obs = [np.sum(self.to_ary(enr, "Natural_Uranium")[ + self.to_ary(enr, "Time") == t]) for t in range(4)] + assert_array_almost_equal(exp, obs, decimal=2) + + def test_xactions(self): + # reactor 1 transactions + exp = [1, 1, 1, 1] + txs = [0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.rx_id[0]: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + + msg = "Testing that first reactor gets less than it wants." + assert_array_almost_equal(exp, txs, decimal=2, err_msg=msg) + + # reactor 2 transactions + exp = [1, 0.8, 0.2, 1] + txs = [0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.rx_id[1]: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + + msg = "Testing that second reactor gets what it wants." + assert_array_almost_equal(exp, txs, decimal=2, err_msg=msg) + +class TestPhysorSources(TestRegression): + """This class tests the 2_Sources_3_Reactor.xml file related to the Cyclus + Physor 2014 publication. Reactor deployment and transactions between + suppliers and reactors are tested. + """ + def __init__(self, *args, **kwargs): + super(TestPhysorSources, self).__init__(*args, **kwargs) + self.inf = "../input/physor/2_Sources_3_Reactors.xml" + + def setUp(self): + super(TestPhysorSources, self).setUp() + + # identify each reactor and supplier by id + tbl = self.agent_entry + rx_id = self.find_ids(":cycamore:Reactor", tbl) + self.r1, self.r2, self.r3 = tuple(rx_id) + s_id = self.find_ids(":cycamore:Source", tbl) + self.smox = self.transactions[0]["SenderId"] + s_id.remove(self.smox) + self.suox = s_id[0] + + def test_rxtr_deployment(self): + depl_time = {x["AgentId"]: x["EnterTime"] for x in self.agent_entry} + + assert_equal(depl_time[self.r1], 1) + assert_equal(depl_time[self.r2], 2) + assert_equal(depl_time[self.r3], 3) + + def test_rxtr1_xactions(self): + mox_exp = [0, 1, 1, 1, 0] + txs = [0, 0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.r1 and tx['SenderId'] == self.smox: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + assert_array_almost_equal(mox_exp, txs) + + uox_exp = [0, 0, 0, 0, 1] + txs = [0, 0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.r1 and tx['SenderId'] == self.suox: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + assert_array_almost_equal(uox_exp, txs) + + def test_rxtr2_xactions(self): + mox_exp = [0, 0, 1, 1, 1] + txs = [0, 0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.r2 and tx['SenderId'] == self.smox: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + assert_array_almost_equal(mox_exp, txs) + + uox_exp = [0, 0, 0, 0, 0] + txs = [0, 0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.r2 and tx['SenderId'] == self.suox: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + assert_array_almost_equal(uox_exp, txs) + + def test_rxtr3_xactions(self): + mox_exp = [0, 0, 0, 0.5, 1] + txs = [0, 0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.r3 and tx['SenderId'] == self.smox: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + assert_array_almost_equal(mox_exp, txs) + + uox_exp = [0, 0, 0, 0.5, 0] + txs = [0, 0, 0, 0, 0] + for tx in self.transactions: + if tx['ReceiverId'] == self.r3 and tx['SenderId'] == self.suox: + txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']] + assert_array_almost_equal(uox_exp, txs) + +class TestDynamicCapacitated(TestRegression): + """Tests dynamic capacity restraints involving changes in the number of + source and sink facilities. + + A source facility is expected to offer a commodity of amount 1, + and a sink facility is expected to request for a commodity of amount 1. + Therefore, number of facilities correspond to the amounts of offers + and requests. + + At time step 1, 3 source facilities and 2 sink facilities are deployed, and + at time step 2, additional 2 sink facilities are deployed. After time + step 2, the older 2 sink facilities are decommissioned. + According to this deployment schedule, at time step 1, only 2 transactions + are expected, the number of sink facilities being the constraint; whereas, + at time step 2, only 3 transactions are expected, the number of source + facilities being the constraint. At time step 3, after decommissioning 2 + older sink facilities, the remaining number of sink facilities becomes + the constraint, resulting in the same transaction amount as in time step 1. + """ + def __init__(self, *args, **kwargs): + super(TestDynamicCapacitated, self).__init__(*args, **kwargs) + self.inf = "./input/dynamic_capacitated.xml" + + def setUp(self): + super(TestDynamicCapacitated, self).setUp() + + # Find agent ids of source and sink facilities + self.agent_ids = self.to_ary(self.agent_entry, "AgentId") + self.agent_impl = self.to_ary(self.agent_entry, "Spec") + self.depl_time = self.to_ary(self.agent_entry, "EnterTime") + self.exit_time = self.to_ary(self.agent_exit, "ExitTime") + self.exit_ids = self.to_ary(self.agent_exit, "AgentId") + self.source_id = self.find_ids(":cycamore:Source", self.agent_entry) + self.sink_id = self.find_ids(":cycamore:Sink", self.agent_entry) + + # Check transactions + self.sender_ids = self.to_ary(self.transactions, "SenderId") + self.receiver_ids = self.to_ary(self.transactions, "ReceiverId") + self.trans_time = self.to_ary(self.transactions, "Time") + self.trans_resource = self.to_ary(self.transactions, "ResourceId") + + # Track transacted resources + self.resource_ids = self.to_ary(self.resources, "ResourceId") + self.quantities = self.to_ary(self.resources, "Quantity") + + def test_source_deployment(self): + # test number of sources + assert_equal(len(self.source_id), 3) + # Test that source facilities are all deployed at time step 1 + for s in self.source_id: + assert_equal(self.depl_time[np.where(self.agent_ids == s)], 1) + + def test_sink_deployment(self): + # test number of sinks + assert_equal(len(self.sink_id), 4) + # Test that first 2 sink facilities are deployed at time step 1 + # and decommissioned at time step 2 + for i in [0, 1]: + assert_equal( + self.depl_time[np.where(self.agent_ids == self.sink_id[i])][0], + 1) + assert_equal( + self.exit_time[np.where(self.exit_ids == self.sink_id[i])][0], + 2) + # Test that second 2 sink facilities are deployed at time step 2 + # and decommissioned at time step 3 + for i in [2, 3]: + assert_equal( + self.depl_time[np.where(self.agent_ids == self.sink_id[i])][0], + 2) + assert_equal( + self.exit_time[np.where(self.exit_ids == self.sink_id[i])][0], + 3) + + def test_xaction_general(self): + # Check that transactions are between sources and sinks only + for s in self.sender_ids: + assert_equal(len(np.where(self.source_id == s)[0]), 1) + for r in self.receiver_ids: + assert_equal(len(np.where(self.sink_id == r)[0]), 1) + # Total expected number of transactions + assert_equal(len(self.trans_time), 7) + # Check that at time step 1, there are 2 transactions + assert_equal(len(np.where(self.trans_time == 1)[0]), 2) + # Check that at time step 2, there are 3 transactions + assert_equal(len(np.where(self.trans_time == 2)[0]), 3) + # Check that at time step 3, there are 2 transactions + assert_equal(len(np.where(self.trans_time == 3)[0]), 2) + + def test_xaction_specific(self): + # Check that at time step 1, there are 2 transactions with total + # amount of 2 + quantity = 0 + for t in np.where(self.trans_time == 1)[0]: + quantity += self.quantities[ + np.where(self.resource_ids == self.trans_resource[t])] + assert_equal(quantity, 2) + + # Check that at time step 2, there are 3 transactions with total + # amount of 3 + quantity = 0 + for t in np.where(self.trans_time == 2)[0]: + quantity += self.quantities[ + np.where(self.resource_ids == self.trans_resource[t])] + assert_equal(quantity, 3) + + # Check that at time step 3, there are 2 transactions with total + # amount of 2 + quantity = 0 + for t in np.where(self.trans_time == 3)[0]: + quantity += self.quantities[ + np.where(self.resource_ids == self.trans_resource[t])] + assert_equal(quantity, 2) + +class TestGrowth(TestRegression): + """Tests GrowthRegion, ManagerInst, and Source over a 4-time step + simulation. + + A linear growth demand (y = x + 2) is provided to the growth region. Two + Sources are allowed in the ManagerInst, with capacities of 2 and 1.1, + respectively. At t=1, a 2-capacity Source is expected to be built, and at + t=2 and t=3, 1-capacity Sources are expected to be built. + + A linear growth demand (y = 0x + 3) for a second commodity is provided at t=2 + to test the demand for multiple commodities. + """ + def __init__(self, *args, **kwargs): + super(TestGrowth, self).__init__(*args, **kwargs) + self.inf = "./input/growth.xml" + + def test_deployment(self): + agent_ids = self.to_ary(self.agent_entry, "AgentId") + proto = self.to_ary(self.agent_entry, "Prototype") + enter_time = self.to_ary(self.agent_entry, "EnterTime") - WARNING: the tests require cyclus executable to be included in PATH - """ - for root, dirs, files in os.walk(self.in_dir_): - for f in files: - if not f.endswith('.xml'): - continue - tmp_file = self.tmp_files_[f] - run_cyclus("cyclus", os.getcwd(), os.path.join(root, f), - tmp_file) - - if os.path.isfile(tmp_file): - if f not in sim_files: - continue # nada to do, just making sure it runs - if check_deterministic: - determ = compare_determ(sim_files[f], tmp_file, - verbose=True) - assert_true(determ) - else: - nondeterm = compare_nondeterm(sim_files[f], tmp_file) - assert_true(nondeterm) - - if os.path.isfile(tmp_file): - print("removing {0}".format(tmp_file)) - os.remove(tmp_file) - - tmp_file = tmp_file.split('.')[0] + '.sqlite' - - if os.path.isfile(tmp_file): - print("removing {0}".format(tmp_file)) - os.remove(tmp_file) + source1_id = self.find_ids("Source1", self.agent_entry, + spec_col="Prototype") + source2_id = self.find_ids("Source2", self.agent_entry, + spec_col="Prototype") + source3_id = self.find_ids("Source3", self.agent_entry, + spec_col="Prototype") + + assert_equal(len(source2_id), 1) + assert_equal(len(source1_id), 2) + assert_equal(len(source3_id), 3) + + assert_equal(enter_time[np.where(agent_ids == source2_id[0])], 1) + assert_equal(enter_time[np.where(agent_ids == source1_id[0])], 2) + assert_equal(enter_time[np.where(agent_ids == source1_id[1])], 3) + for x in source3_id: + yield assert_equal, enter_time[np.where(agent_ids == x)], 2 + +class TestRecycle(TestRegression): + """This class tests the input/recycle.xml file. + """ + def __init__(self, *args, **kwargs): + super(TestRecycle, self).__init__(*args, **kwargs) + + # this test requires separations which isn't supported by hdf5 + # so we force sqlite: + base, _ = os.path.splitext(self.outf) + self.ext = '.sqlite' + self.outf = base + self.ext + self.inf = "../input/recycle.xml" + self.sql = """ + SELECT t.time as time,SUM(c.massfrac*r.quantity) as qty FROM transactions as t + JOIN resources as r ON t.resourceid=r.resourceid AND r.simid=t.simid + JOIN agententry as send ON t.senderid=send.agentid AND send.simid=t.simid + JOIN agententry as recv ON t.receiverid=recv.agentid AND recv.simid=t.simid + JOIN compositions as c ON c.qualid=r.qualid AND c.simid=r.simid + WHERE send.prototype=? AND recv.prototype=? AND c.nucid=? + GROUP BY t.time;""" + + def do_compare(self, fromfac, tofac, nuclide, exp_invs): + conn = sqlite3.connect(self.outf) + c = conn.cursor() + eps = 1e-10 + simdur = len(exp_invs) + + invs = [0.0] * simdur + for i, row in enumerate(c.execute(self.sql, (fromfac, tofac, nuclide))): + t = row[0] + invs[t] = row[1] + + expfname = 'exp_recycle_{0}-{1}-{2}.dat'.format(fromfac, tofac, nuclide) + with open(expfname, 'w') as f: + for t, val in enumerate(exp_invs): + f.write('{0} {1}\n'.format(t, val)) + obsfname = 'obs_recycle_{0}-{1}-{2}.dat'.format(fromfac, tofac, nuclide) + with open(obsfname, 'w') as f: + for t, val in enumerate(invs): + f.write('{0} {1}\n'.format(t, val)) + + i = 0 + for exp, obs in zip(invs, exp_invs): + i += 1 + self.assertAlmostEquals(exp, obs, msg='mismatch at t={0}'.format(i)) + + os.remove(expfname) + os.remove(obsfname) + + def test_pu239_sep_repo(self): + simdur = 600 + exp = [0.0] * simdur + exp[18] = 1.70022267 + exp[37] = 1.70022267 + exp[56] = 1.70022267 + exp[75] = 1.70022267 + exp[94] = 1.70022267 + exp[113] = 1.70022267 + exp[132] = 1.70022267 + exp[151] = 1.70022267 + exp[170] = 1.70022267 + exp[189] = 1.70022267 + exp[208] = 1.70022267 + exp[246] = 1.70022267 + exp[265] = 1.70022267 + exp[284] = 1.70022267 + exp[303] = 1.70022267 + exp[322] = 1.70022267 + exp[341] = 1.70022267 + exp[360] = 1.70022267 + exp[379] = 1.70022267 + exp[417] = 1.70022267 + exp[436] = 1.70022267 + exp[455] = 1.70022267 + exp[474] = 1.70022267 + exp[493] = 1.70022267 + exp[512] = 1.70022267 + exp[531] = 1.70022267 + exp[569] = 1.70022267 + exp[588] = 1.70022267 + + self.do_compare('separations', 'repo', 942390000, exp) + + def test_pu239_reactor_repo(self): + simdur = 600 + exp = [0.0] * simdur + exp[226] = 420.42772559790944 + exp[397] = 420.42772559790944 + exp[549] = 420.42772559790944 + self.do_compare('reactor', 'repo', 942390000, exp) + diff --git a/tests/test_run_inputs.py b/tests/test_run_inputs.py new file mode 100644 index 0000000000..3957557648 --- /dev/null +++ b/tests/test_run_inputs.py @@ -0,0 +1,13 @@ +import os +import subprocess + +from nose.tools import assert_true + +import run_inputs as ri + +def test_inputs(): + files, _, _ = ri.get_files(ri.input_path) + for f in files: + testf = ri.TestFile(ri.cyclus_path, f, "-v0") + testf.run() + yield assert_true, testf.passed, "Failed running {}".format(f) diff --git a/tests/tools.py b/tests/tools.py deleted file mode 100644 index f9b0abc778..0000000000 --- a/tests/tools.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import print_function - -import os -import re -import sys -import imp -import shutil -import unittest -import subprocess -import tempfile -from contextlib import contextmanager - -from nose.tools import assert_true, assert_equal -from nose.plugins.attrib import attr -from nose.plugins.skip import SkipTest - -if sys.version_info[0] >= 3: - basestring = str - -unit = attr('unit') -integration = attr('integration') - -def cleanfs(paths): - """Removes the paths from the file system.""" - for p in paths: - p = os.path.join(*p) - if os.path.isfile(p): - os.remove(p) - elif os.path.isdir(p): - shutil.rmtree(p) - -def check_cmd(args, cwd, holdsrtn): - """Runs a command in a subprocess and verifies that it executed properly. - """ - if not isinstance(args, basestring): - args = " ".join(args) - print("TESTING: running command in {0}:\n\n{1}\n".format(cwd, args)) - env = dict(os.environ) - env['_'] = subprocess.check_output(['which', 'cyclus'], cwd=cwd).strip() - f = tempfile.NamedTemporaryFile() - rtn = subprocess.call(args, shell=True, cwd=cwd, stdout=f, stderr=f, env=env) - if rtn != 0: - f.seek(0) - print("STDOUT + STDERR:\n\n" + f.read().decode()) - f.close() - holdsrtn[0] = rtn - assert_equal(rtn, 0) - -@contextmanager -def clean_import(name, paths=None): - """Imports and returns a module context manager and then removes - all modules which didn't originally exist when exiting the block. - Be sure to delete any references to the returned module prior to - exiting the context. - """ - sys.path = paths + sys.path - origmods = set(sys.modules.keys()) - mod = imp.load_module(name, *imp.find_module(name, paths)) - yield mod - sys.path = sys.path[len(paths):] - del mod - newmods = set(sys.modules.keys()) - origmods - for newmod in newmods: - del sys.modules[newmod] - -TESTNAME_RE = re.compile('(?:^|[\\b_\\.-])[Tt]est') - -def modtests(mod): - """Finds all of the tests in a module.""" - tests = [] - for name in dir(mod): - if TESTNAME_RE.match(name) is None: - continue - test = getattr(mod, name) - if test is unittest.TestCase: - continue - tests.append(test) - return tests - -def dirtests(d): - """Finds all of the test files in a directory.""" - files = os.listdir(d) - filenames = [] - for file in files: - if not file.endswith('.py'): - continue - if TESTNAME_RE.match(file) is None: - - continue - filenames.append(file[:-3]) - return filenames - -def skip_then_continue(msg=""): - """A simple function to yield such that a test is marked as skipped - and we may continue on our merry way. A message may be optionally passed - to this function. - """ - raise SkipTest(msg) diff --git a/tests/visitors.py b/tests/visitors.py deleted file mode 100644 index 255f3a5ec2..0000000000 --- a/tests/visitors.py +++ /dev/null @@ -1,140 +0,0 @@ -from __future__ import print_function - -import re -from collections import defaultdict - -import numpy as np -import tables - -_invar_table_names = {"agents": "AgentEntry", - "rsrcs": "Resources"} - -_agent_key = "AgentId" -_agent_schema = ["Kind", "Spec", "Prototype", "ParentId", "EnterTime"] - -_simulation_time_info_schema = ["InitialYear", "InitialMonth", - "Duration"] - -_xaction_schema = ["SenderId", "ReceiverId", "ResourceId", "Commodity", - "Time"] - -_rsrc_key = "ResourceId" -_rsrc_schema = ["Type", "TimeCreated", "Quantity", "Units"] - -_agent_id_names = ["ParentId", "SenderId", "ReceiverId"] -_rsrc_id_names = ["ResourceId"] - -class HDF5RegressionVisitor(object): - """ An HDF5RegressionVisitor visits a number of Cyclus HDF5 tables, - returning objects that can be equality-comparable with other visitors. - """ - - def __init__(self, db_path): - """Parameters - ---------- - db_path : str - The path to an HDF5 database - """ - self._db = tables.open_file(db_path, mode = "r") - self.agent_invariants = self._populate_agent_invariants() - self.rsrc_invariants = self._populate_rsrc_invariants() - - def __del__(self): - self._db.close() - - def _populate_agent_invariants(self): - invars = {} - table = self._db.get_node(self._db.root, - name=_invar_table_names["agents"], - classname="Table") - for row in table.iterrows(): - a_id = row["AgentId"] - p_id = row["ParentId"] - p_invar = None - # print(p_id, a_id) - if p_id != -1: - if p_id not in invars: - raise KeyError("Parent with id " + str(a_id) +\ - " not previously registered.") - else: - p_invar = invars[p_id] - newrow = [] - for i in _agent_schema: - if i in _agent_id_names: - newrow.append(p_invar) - elif isinstance(row[i], np.ndarray): - newrow.append(tuple(row[i])) - else: - newrow.append(row[i]) - invars[a_id] = tuple(newrow) - return invars - - def _populate_rsrc_invariants(self): - table = self._db.get_node(self._db.root, - name = _invar_table_names["rsrcs"], - classname = "Table") - d = {} - for row in table.iterrows(): - entry = [] - for item in _rsrc_schema: - if isinstance(row[item], np.ndarray): - entry.append(tuple(row[item])) - else: - entry.append(row[item]) - d[row[_rsrc_key]] = tuple(entry) - return d - - def _xaction_entry(self, row): - entry = [] - for item in _xaction_schema: - if item in _agent_id_names: - entry.append(self.agent_invariants[row[item]]) - elif item in _rsrc_id_names: - entry.append(self.rsrc_invariants[row[item]]) - else: - entry.append(row[item]) - return tuple(tuple(i) if isinstance(i, np.ndarray) else i for i in entry) - - def walk(self): - """Visits all tables, populating an equality-comparable object - """ - ret = set() - for table in self._db.walk_nodes(classname = "Table"): - tblname = re.sub('([A-Z]+)', r'\1', table._v_name).lower() - methname = 'visit_' + tblname - if hasattr(self, methname): - print("visiting ", tblname) - meth = getattr(self, methname) - obj = meth(table) - ret.add(obj) - return ret - - def visit_agententry(self, table): - d = {} - for row in table.iterrows(): - entry = [] - for i in _agent_schema: - if i in _agent_id_names: - entry.append(self.agent_invariants[row[_agent_key]]) - elif isinstance(row[i], np.ndarray): - entry.append(tuple(row[i])) - else: - entry.append(row[i]) - d[self.agent_invariants[row[_agent_key]]] = tuple(entry) - return tuple((k, d[k]) for k in sorted(d.keys())) - - def visit_info(self, table): - return tuple(row[i] for i in _simulation_time_info_schema - for row in table.iterrows()) - - def visit_transactions(self, table): - xactions = [] - tmin = table[0]["Time"] - tmax = table[-1]["Time"] - xactions = [] - for i in range(tmin, tmax + 1): - entry = set() - for row in table.where('Time ==' + str(i)): - entry.add(self._xaction_entry(row)) - xactions.append(frozenset(entry)) - return tuple(xactions)