# Licensed under a 3-clause BSD style license - see LICENSE
import numpy as np
from timer import Timer
import neutronics
import reactivity_insertion as ri
import th_system
from db import database
[docs]class SimInfo(object):
    """This class holds information about a reactor kinetics simulation"""
    def __init__(self,
                 timer=Timer(),
                 components={},
                 iso="u235",
                 e="thermal",
                 n_precursors=6,
                 n_decay=11,
                 n_fic=0,
                 kappa=0.0,
                 rho_ext=None,
                 feedback=False,
                 plotdir='images',
                 infile=None,
                 sim_id=None,
                 db=None):
        """This class holds information about a reactor kinetics simulation
        :param timer: the Timer object for the simulation
        :type timer: Timer
        :param components: the components making up the reactor
        :type components: dictionary
        :param iso: the main fissioning isotope, decides precursor data
        :type iso: only a few values are supported. see PrecursorData class
        :param e: spectrum ("fast", "thermal", etc.)
        :type e: string
        :param n_precursors: number of delayed precursor groups
        :type n_precursors: int
        :param n_decay: number of decay groups
        :type n_decay: int
        :param n_fic: number of fictitious neutron groups for multi
        point kinetics
        :type n_fic: int
        :param kappa: the value for kappa, a decay heat parameter
        :type kappa: float
        :param rho_ext: external reactivity
        :type rho_ext: a ReactivityInsertion object or None
        :param feedback: is reactivity feedback present in the simulation
        :type feedback: bool
        :param plotdir: the directory where the plots will be placed
        :type plotdir: string
        """
        self.timer = timer
        self.components = components
        self.iso = iso
        self.e = e
        self.n_pg = n_precursors + n_fic
        self.n_dg = n_decay
        self.rho_ext = self.init_rho_ext(rho_ext)
        self.feedback = feedback
        self.ne = self.init_ne()
        self.kappa = kappa
        self.th = th_system.THSystem(kappa=kappa, components=components)
        self.y = np.zeros(shape=(timer.timesteps(), self.n_entries()),
                          dtype=float)
        self.plotdir = plotdir
        self.infile = infile
        if sim_id is not None:
            self.sim_id = sim_id
        else:
            self.sim_id = self.generate_sim_id()
        if db is not None:
            self.db = db
        else:
            self.db = database.Database()
        self.register_recorders()
[docs]    def register_recorders(self):
        """Registers the function pointers that return database rows
        """
        self.db.register_recorder('metadata', 'sim_info', self.metadata,
                                  timeseries=False)
        self.db.register_recorder('metadata', 'sim_timeseries', self.record,
                                timeseries=True)
        self.db.register_recorder('neutronics', 'neutronics_params',
                                  self.ne.record,
                                  timeseries=True)
        for c in self.components:
            self.db.register_recorder('th', 'th_timeseries',
                                      c.record,
                                      timeseries=True)
            self.db.register_recorder('th', 'th_params',
                                      c.metadata,
                                      timeseries=False)
        # TODO: for all n_pg and n_dg, report zetas and omegas
 
[docs]    def init_rho_ext(self, rho_ext):
        """Initializes reactivity insertion object for the none case.
        :param rho_ext: external reactivity
        :type rho_ext: a ReactivityInsertion object or None
        """
        if rho_ext is None:
            rho_ext = ri.ReactivityInsertion(self.timer)
        return rho_ext
 
[docs]    def init_ne(self):
        """Initializes the neutronics object owned by the siminfo object
        """
        ne = neutronics.Neutronics(iso=self.iso, e=self.e,
                                   n_precursors=self.n_pg,
                                   n_decay=self.n_dg,
                                   timer=self.timer,
                                   rho_ext=self.rho_ext,
                                   feedback=self.feedback)
        return ne
 
[docs]    def n_components(self):
        """The number of components in the simulation.
        """
        to_ret = len(self.components)
        return to_ret
 
[docs]    def n_entries(self):
        """The number of entries in the pde to be solved
        """
        to_ret = 1 + self.n_pg + self.n_dg + len(self.components)
        return int(to_ret)
 
[docs]    def add_th_component(self, th_component):
        """Adds a thermal-hydralic component to this simulation.
        It should be fully initialized. A single simulation may have many
        thermal-hydralic components. They are held in a dictionary.
        :param th_component: the th_component to add to the system
        :type th_component: THComponent
        """
        if th_component.name in self.components:
            msg = "A component named "
            msg += th_component.name
            msg += " already exists in the simulation."
            raise ValueError(msg)
        else:
            self.components[th_component.name] = th_component
            return th_component
 
[docs]    def get_git_revision_hash(self):
        import subprocess
        return subprocess.check_output(['git', 'rev-parse', 'HEAD'])
 
[docs]    def get_git_revision_short_hash(self):
        import subprocess
        return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
 
[docs]    def get_timestamp(self):
        # time since epoch, a float
        import time
        ts = time.time()
        # human readable time, a string
        import datetime
        st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
        return ts, st
 
[docs]    def generate_sim_id(self):
        """generates sim id, unique to this simulation.
        :rtype: str"""
        import uuid
        sim_id = uuid.uuid4().hex
        return sim_id
 
[docs]    def record(self):
        """A recorder function for the metadata/sim_timeseries table
        TODO: reconsider the database structure.
        """
        t_idx = self.timer.current_timestep() - 1
        power = self.y[t_idx][0]
        rec = {'t_idx': t_idx,
               'power': power}
        return rec