def test_init_params(self): # testing to see if parameters for generating spectra are defined correctly env = gym.make(ENV_NAME) # converting materials to class object representation material_classes = convert_to_class(materials=REACTANTS + PRODUCTS) # define parameters for generating spectra params = [] for material in material_classes: params.append(material().get_spectra_no_overlap()) self.assertEqual(params, env.reaction.params)
def _prepare_materials(materials=[]): """ Method to prepare a list of materials into a material dictionary. The provided materials are expected to be of the following form: materials = [ {"Material": INSERT MATERIAL NAME, "Initial": INSERT INITIAL AMOUNT} {"Material": INSERT MATERIAL NAME, "Initial": INSERT INITIAL AMOUNT} . . . ] Parameters --------------- `materials` : `list` (default=`None`) A list of dictionaries including initial material names and amounts. Returns --------------- `material_dict` : `dict` A dictionary containing all the inputted materials, class representations, and their molar amounts. Raises --------------- None """ # prepare lists to maintain the names and amounts of the materials material_names = [] material_amounts = [] # iterate through the provided list of materials and obtain the names and amounts for material_params in materials: material_names.append(material_params["Material"]) material_amounts.append(material_params["Initial"]) # acquire the material class representations from the material names material_classes = convert_to_class(materials=material_names) # create a dictionary to contain the material and its properties material_dict = {} # iterate through each of the material names, classes, and amounts lists; # it is assumed the materials are provided in units of mol for i, material in enumerate(material_names): material_dict[material] = [ material_classes[i](), material_amounts[i], 'mol' ] return material_dict
def _prepare_solutes(material_dict=None, solvents=None): """ Method to prepare a list of materials into a material dictionary. The provided materials are expected to be of the following form: materials = [ {"Material": INSERT MATERIAL NAME, "Initial": INSERT INITIAL AMOUNT} {"Material": INSERT MATERIAL NAME, "Initial": INSERT INITIAL AMOUNT} . . . ] Parameters --------------- `solvents` : `list` (default=`None`) A list of dictionaries including initial solvent names and amounts. Returns --------------- `solvent_dict` : `dict` A dictionary containing all the inputted solvents, class representations, and their molar amounts. Raises --------------- None """ solute_dict = {} for name, material in material_dict.items(): if name not in solute_dict and material[0]._solute: solute_dict[name] = {} for solvent in solvents: solvent_class = convert_to_class([solvent['Material']]) solute_dict[name][solvent['Material']] = [ solvent_class[0](), solvent["Initial"], 'mol' ] return solute_dict
def __init__(self, materials=None, solutes=None, desired="", overlap=False): ''' Constructor class module for the Reaction class. Parameters --------------- `materials` : `list` (default=`None`) A list of dictionaries containing initial material names, classes, and amounts. `solutes` : `list` (default=`None`) A list of dictionaries containing initial solute names, classes, and amounts. `desired` : `str` (default="") A string indicating the name of the desired material. `overlap` : `bool` (default=`False`) Indicate if the spectral plot includes overlapping plots. Returns --------------- None Raises --------------- None ''' self.name = "demo_reaction" # get the initial amounts of each reactant material initial_materials = np.zeros(len(REACTANTS)) for material in materials: if material["Material"] in REACTANTS: index = REACTANTS.index(material["Material"]) initial_materials[index] = material["Initial"] self.initial_in_hand = initial_materials # get the initial amount of each solute initial_solutes = np.zeros(len(SOLUTES)) for solute in solutes: if solute["Solute"] in SOLUTES: index = SOLUTES.index(solute["Solute"]) initial_solutes[index] = solute["Initial"] self.initial_solutes = initial_solutes self.solute_labels = SOLUTES # specify the desired material self.desired_material = desired # convert the reactants and products to their class object representations self.reactant_classes = convert_to_class(materials=REACTANTS) self.product_classes = convert_to_class(materials=PRODUCTS) self.material_classes = convert_to_class(materials=ALL_MATERIALS) self.solute_classes = convert_to_class(materials=SOLUTES) # define the maximum of each chemical allowed at one time (in mol) self.nmax = np.array([1.0 for __ in ALL_MATERIALS]) # create labels for each of the chemicals involved self.labels = ALL_MATERIALS # define a space to record all six reaction rates self.rate = np.zeros(6) # define the maximal number of moles available for any chemical self.max_mol = 2.0 # define parameters for generating spectra self.params = [] if overlap: self.params.append(spec.S_3_3) # spectra for NaCl self.params.append(spec.S_6) # spectra for the Na self.params.append(spec.S_7) # spectra for the Cl else: self.params.append(spec.S_8) # spectra for NaCl self.params.append(spec.S_1) # spectra for the Na self.params.append(spec.S_3) # spectra for the Cl
def reset(self, extraction_vessel): ''' Method to reset the environment. Parameters --------------- `extraction_vessel` : `vessel` (default=`None`) A vessel object containing state variables, materials, solutes, and spectral data. Returns --------------- `vessels` : `list` A list of all the vessel objects that contain materials and solutes. `external_vessels` : `list` A list of the external vessels, beakers, to be used in the extraction. `state` : `np.array` An array containing state variables, material concentrations, and spectral data. Raises --------------- None ''' # delete the extraction vessel's solute_dict and copy it into a list of vessels solute_dict = extraction_vessel._solute_dict extraction_vessel._solute_dict = {} vessels = [copy.deepcopy(extraction_vessel)] # create all the necessary beakers and add them to the list for i in range(self.n_empty_vessels): temp_vessel = vessel.Vessel(label='beaker_{}'.format(i + 1), v_max=self.max_vessel_volume, default_dt=0.05, n_pixels=self.n_vessel_pixels) vessels.append(temp_vessel) # generate a list of external vessels to contain solutes external_vessels = [] # generate a vessel to contain the main solute solute_vessel = vessel.Vessel( label='solute_vessel0', v_max=self.solute_volume, n_pixels=self.n_vessel_pixels, settling_switch=False, layer_switch=False, ) # create the material dictionary for the solute vessel solute_material_dict = {} solute_class = convert_to_class(materials=[self.solute])[0] solute_material_dict[self.solute] = [solute_class, self.solute_volume] # check for overflow solute_material_dict, _, _ = util.check_overflow( material_dict=solute_material_dict, solute_dict={}, v_max=solute_vessel.get_max_volume()) # instruct the vessel to update its material dictionary event = ['update material dict', solute_material_dict] solute_vessel.push_event_to_queue(feedback=[event], dt=0) # add the main solute vessel to the list of external vessels external_vessels.append(solute_vessel) # generate vessels for each solute in the extraction vessel for solute_name in solute_dict: # generate an empty vessel to be filled with a single solute solute_vessel = vessel.Vessel(label='solute_vessel{}'.format( len(external_vessels)), v_max=extraction_vessel.v_max, n_pixels=self.n_vessel_pixels, settling_switch=False, layer_switch=False) solute_material_dict = {} solute_material_dict[solute_name] = solute_dict[solute_name] # check for overflow solute_material_dict, _, _ = util.check_overflow( material_dict=solute_material_dict, solute_dict={}, v_max=solute_vessel.get_max_volume()) # instruct the vessel to update its material dictionary event = ['update material dict', solute_material_dict] solute_vessel.push_event_to_queue(feedback=[event], dt=0) # add this solute vessel to the list of external vessels external_vessels.append(solute_vessel) # generate the state state = util.generate_state(vessel_list=vessels, max_n_vessel=self.n_total_vessels) return vessels, external_vessels, state
def __init__(self, reaction_file_identifier="", dt=0.01, overlap=False, solver='RK45'): """ Constructor class module for the Reaction class. Parameters --------------- `reaction_file_identifier` : `str` (default=`""`) The basename of the reaction file that contains the parameters necessary to perform the desired reaction(s). `overlap` : `bool` (default=`False`) Indicate if the spectral plot includes overlapping plots. Returns --------------- None Raises --------------- None """ # define a name/label for this reaction base class self.name = "base_reaction" # ensure the requested reaction file is found and accessible reaction_filepath = self._find_reaction_file( reaction_file=reaction_file_identifier) # acquire the necessary parameters from the reaction file reaction_params = self._get_reaction_params( reaction_filepath=reaction_filepath) # UNPACK THE REACTION PARAMETERS: # materials used and the desired material self.reactants = reaction_params["REACTANTS"] self.products = reaction_params["PRODUCTS"] self.solvents = reaction_params["SOLVENTS"] self.desired = reaction_params["DESIRED"] # initial vessel properties (used in reseting the reaction vessel) self.Ti = reaction_params["Ti"] self.Vi = reaction_params["Vi"] # additional vessel properties (to be assigned during the reset function) self.Tmin = reaction_params["Tmin"] self.Tmax = reaction_params["Tmax"] self.Vmin = reaction_params["Vmin"] self.Vmax = reaction_params["Vmax"] # thermodynamic increment values (used when performing the reactions) self.dt = dt self.dT = reaction_params["dT"] self.dV = reaction_params["dV"] # the arrays used in dictating how reaction calculations are performed self.activ_energy_arr = reaction_params["activ_energy_arr"] self.stoich_coeff_arr = reaction_params["stoich_coeff_arr"] self.conc_coeff_arr = reaction_params["conc_coeff_arr"] self.de = De(self.stoich_coeff_arr, self.activ_energy_arr, self.conc_coeff_arr, len(self.reactants)) # specify the full list of materials self.materials = [] for mat in self.reactants + self.products + self.solvents: if mat not in self.materials: self.materials.append(mat) # set a threshold value for the minimum, non-negligible material molar amount self.threshold = 1e-8 # define these parameters initially as they are to be given properly in the reset function self.initial_in_hand = np.zeros(len(self.reactants)) self.cur_in_hand = np.zeros(len(self.reactants)) self.initial_materials = np.zeros(len(self.materials)) # convert the reactants and products to their class object representations self.reactant_classes = convert_to_class(materials=self.reactants) self.product_classes = convert_to_class(materials=self.products) self.material_classes = convert_to_class(materials=self.materials) # create the (empty) n array which will contain the molar amounts of materials in use self.n = np.zeros(len(self.materials)) # define the maximal number of moles available for any chemical self.max_mol = 2.0 # define the maximal number of moles of each chemical allowed at one time self.nmax = self.max_mol * np.ones(len(self.materials)) # include the available solvers self.solvers = {'RK45', 'RK23', 'DOP853', 'DBF', 'LSODA'} # select the intended solver or use the default if solver in self.solvers: self.solver = solver else: self.solver = 'newton' self._solver = solve_ivp # define parameters for generating spectra self.params = [] for material in self.material_classes: if overlap: self.params.append(material().get_spectra_overlap()) else: self.params.append(material().get_spectra_no_overlap())