def replace_missing_fpy(actinide, fpy_data, decay_data): """Replace missing fission product yields Parameters ---------- actinide : str Name of actinide missing FPY data fpy_data : dict Dictionary of FPY data decay_data : dict Dictionary of decay data Returns ------- str Actinide that can be used as replacement for FPY purposes """ # Check if metastable state has data (e.g., Am242m) Z, A, m = zam(actinide) if m == 0: metastable = gnd_name(Z, A, 1) if metastable in fpy_data: return metastable # Try increasing Z, holding N constant isotone = actinide while isotone in decay_data: Z += 1 A += 1 isotone = gnd_name(Z, A, 0) if isotone in fpy_data: return isotone # Try decreasing Z, holding N constant isotone = actinide while isotone in decay_data: Z -= 1 A -= 1 isotone = gnd_name(Z, A, 0) if isotone in fpy_data: return isotone # If all else fails, use U235 yields return 'U235'
def set_branch_ratios(self, branch_ratios, reaction="(n,gamma)", strict=True, tolerance=1e-5): """Set the branching ratios for a given reactions Parameters ---------- branch_ratios : dict of {str: {str: float}} Capture branching ratios to be inserted. First layer keys are names of parent nuclides, e.g. ``"Am241"``. The branching ratios for these parents will be modified. Corresponding values are dictionaries of ``{target: branching_ratio}`` reaction : str, optional Reaction name like ``"(n,gamma)"`` [default], or ``"(n, alpha)"``. strict : bool, optional Error control. If this evalutes to ``True``, then errors will be raised if inconsistencies are found. Otherwise, warnings will be raised for most issues. tolerance : float, optional Tolerance on the sum of all branching ratios for a single parent. Will be checked with:: 1 - tol < sum_br < 1 + tol Raises ------ IndexError If no isotopes were found on the chain that have the requested reaction KeyError If ``strict`` evaluates to ``False`` and a parent isotope in ``branch_ratios`` does not exist on the chain AttributeError If ``strict`` evaluates to ``False`` and a parent isotope in ``branch_ratios`` does not have the requested reaction ValueError If ``strict`` evalutes to ``False`` and the sum of one parents branch ratios is outside 1 +/- ``tolerance`` See Also -------- :meth:`get_branch_ratios` """ # Store some useful information through the validation stage sums = {} rxn_ix_map = {} grounds = {} tolerance = abs(tolerance) missing_parents = set() missing_products = {} missing_reaction = set() bad_sums = {} # Secondary products, like alpha particles, should not be modified secondary = _SECONDARY_PARTICLES.get(reaction, []) # Check for validity before manipulation for parent, sub in branch_ratios.items(): if parent not in self: if strict: raise KeyError(parent) missing_parents.add(parent) continue # Make sure all products are present in the chain prod_flag = False for product in sub: if product not in self: if strict: raise KeyError(product) missing_products[parent] = product prod_flag = True break if prod_flag: continue # Make sure this nuclide has the reaction indexes = [] for ix, rx in enumerate(self[parent].reactions): if rx.type == reaction and rx.target not in secondary: indexes.append(ix) if "_m" not in rx.target: grounds[parent] = rx.target if len(indexes) == 0: if strict: raise AttributeError( "Nuclide {} does not have {} reactions".format( parent, reaction)) missing_reaction.add(parent) continue this_sum = sum(sub.values()) # sum of branching ratios can be lower than 1 if no ground # target is given, but never greater if (this_sum >= 1 + tolerance or (grounds[parent] in sub and this_sum <= 1 - tolerance)): if strict: msg = ("Sum of {} branching ratios for {} " "({:7.3f}) outside tolerance of 1 +/- " "{:5.3e}".format(reaction, parent, this_sum, tolerance)) raise ValueError(msg) bad_sums[parent] = this_sum else: rxn_ix_map[parent] = indexes sums[parent] = this_sum if len(rxn_ix_map) == 0: raise IndexError("No {} reactions found in this {}".format( reaction, self.__class__.__name__)) if len(missing_parents) > 0: warn("The following nuclides were not found in {}: {}".format( self.__class__.__name__, ", ".join(sorted(missing_parents)))) if len(missing_reaction) > 0: warn("The following nuclides did not have {} reactions: " "{}".format(reaction, ", ".join(sorted(missing_reaction)))) if len(missing_products) > 0: tail = ("{} -> {}".format(k, v) for k, v in sorted(missing_products.items())) warn("The following products were not found in the {} and " "parents were unmodified: \n{}".format( self.__class__.__name__, ", ".join(tail))) if len(bad_sums) > 0: tail = ("{}: {:5.3f}".format(k, s) for k, s in sorted(bad_sums.items())) warn("The following parent nuclides were given {} branch ratios " "with a sum outside tolerance of 1 +/- {:5.3e}:\n{}".format( reaction, tolerance, "\n".join(tail))) # Insert new ReactionTuples with updated branch ratios for parent_name, rxn_index in rxn_ix_map.items(): parent = self[parent_name] new_ratios = branch_ratios[parent_name] rxn_index = rxn_ix_map[parent_name] # Assume Q value is independent of target state rxn_Q = parent.reactions[rxn_index[0]].Q # Remove existing reactions for ix in reversed(rxn_index): parent.reactions.pop(ix) all_meta = True for tgt, br in new_ratios.items(): all_meta = all_meta and ("_m" in tgt) parent.reactions.append(ReactionTuple(reaction, tgt, rxn_Q, br)) if all_meta and sums[parent_name] != 1.0: ground_br = 1.0 - sums[parent_name] ground_tgt = grounds.get(parent_name) if ground_tgt is None: pz, pa, pm = zam(parent_name) ground_tgt = gnd_name(pz, pa + 1, 0) new_ratios[ground_tgt] = ground_br parent.reactions.append( ReactionTuple(reaction, ground_tgt, rxn_Q, ground_br))