Source code for biosteam.units.design_tools.batch

# -*- coding: utf-8 -*-
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
# Copyright (C) 2020-2023, Yoel Cortes-Pena <yoelcortes@gmail.com>
# 
# This module is under the UIUC open-source license. See 
# github.com/BioSTEAMDevelopmentGroup/biosteam/blob/master/LICENSE.txt
# for license details.
"""
General functional algorithms for batch design.

"""
from math import ceil
from flexsolve import wegstein

__all__ = ('size_batch',)

[docs] def size_batch(F_vol, tau_reaction, tau_cleaning, V_wf, V_max=None, N_reactors=None, loading_time=None) -> dict: r""" Solve for batch reactor volume, cycle time, and loading time. Parameters ---------- F_vol : float Volumetric flow rate. tau_reaction : float Reaction time. tau_cleaning : float Cleaning in place time. V_wf : float Fraction of working volume. V_max : int Maximum volume of a reactor. N_reactors : int Number of reactors. loading_time : Loading time of batch reactor. If not given, it will assume each vessel is constantly being filled. Returns ------- dict * 'Reactor volume': float * 'Batch time': float * 'Loading time': float Notes ----- By assuming no downtime, the total volume of all reactors is: .. math:: V_T = F_{vol}(\tau_{reaction} + \tau_{cleaning} + \tau_{loading}) where :math:`V_T` is the total volume required, :math:`F_{vol}` is the volumetric flow rate of the feed, :math:`\tau_{reaction}` is the reaction time, :math:`\tau_{cleaning}` is the cleaning and unloading time, and :math:`\tau_{loading}` is the time required to load a vessel. This equation makes the conservative assumption that no reaction takes place when the tank is being filled. The number of reactors is: .. math:: N_{reactors} = ceil(\frac{V_T}{V_{max} \cdot V_{wf}}) where :math:`N_{reactors}` is the number of reactor vessels, :math:`V_{max}` is the maximum reactor volume, and :math:`V_{wf}` is the fraction of working volume in a reactor. The working volume of an individual reactor is: .. math:: V_{i,working} = \frac{V_T}{N_{reactors}} If there is no upstream storage and a vessel is constantly being filled, the time required to load a reactor is: .. math:: \tau_{loading} = \frac{V_{i,working}}{F_{vol}} Note that the actual volume of a reactor is: .. math:: V_i = \frac{V_{i,working}}{f} Plugging in and solving for the total volume, :math:`V_{T}`, we have: .. math:: V_T = F_{vol}\frac{\tau_{reaction} + \tau_{cleaning}}{1 - \frac{1}{N_{reactors}}} If the number of reactors is specified but not the loading time, :math:`V_T` is first calculated, then :math:`V_{i, working}`, :math:`\tau_{loading}`, and :math:`V_i`. If neither the number of reactors nor the loading time are specified, we solve the equations iteratively until :math:`\tau_{loading}` converges. If the loading time is given but not the number of reactors, then the equation for :math:`tau_{loading}` does not apply and we first compute :math:`N_reactors` given :math:`V_{max}. Units of measure may vary so long as they are consistent. The loading time can be considered the cycle time in this scenario. Examples -------- Size batch given a maximum reactor volume of 1,000 m3 and zero loading time: >>> from biosteam.units.design_tools import size_batch >>> F_vol = 1e3; tau_reaction = 30; tau_cleaning = 3; V_wf = 0.95 >>> size_batch( ... F_vol, tau_reaction, tau_cleaning, V_wf, ... V_max=1e3, loading_time=0 ... ) {'Reactor volume': 992.48, 'Batch time': 33, 'Loading time': 0, 'Number of reactors': 35} Size batch given 35 reactors and zero loading time: >>> size_batch( ... F_vol, tau_reaction, tau_cleaning, V_wf, ... N_reactors=35, loading_time=0 ... ) {'Reactor volume': 992.48, 'Batch time': 33, 'Loading time': 0, 'Number of reactors': 35} Size batch given a maximum reactor volume of 1,000 m3 and assume the constant loading: >>> size_batch( ... F_vol, tau_reaction, tau_cleaning, V_wf, ... V_max=1000, ... ) {'Reactor volume': 992.6900584795799, 'Batch time': 33.95000000000163, 'Loading time': 0.950000000001627, 'Number of reactors': 36} Size batch given 36 reactors and assume the constant loading: >>> size_batch( ... F_vol, tau_reaction, tau_cleaning, V_wf, ... N_reactors=36 ... ) {'Reactor volume': 992.4812030075188, 'Batch time': 33.94285714285714, 'Loading time': 0.9428571428571428, 'Number of reactors': 36} """ if sum([i is None for i in [N_reactors, V_max]]) != 1: raise ValueError('must pass either `N_reactors` or `V_max`') if loading_time is None: if N_reactors is None: # Solve iteratively def f(tau_loading): N_reactors = F_vol * (tau_reaction + tau_cleaning + tau_loading) / (V_max * V_wf) V_T = F_vol * (tau_reaction + tau_cleaning) / (1 - 1 / N_reactors) V_i = V_T/N_reactors tau_loading = V_i/F_vol return tau_loading tau_loading = wegstein(f, 0.5, checkconvergence=False, checkiter=False) N_reactors = ceil(F_vol * (tau_reaction + tau_cleaning + tau_loading) / (V_max * V_wf)) V_T = F_vol * (tau_reaction + tau_cleaning + tau_loading) V_i = V_T / N_reactors else: # Total volume of all reactors, assuming no downtime V_T = F_vol * (tau_reaction + tau_cleaning) / (1 - 1 / N_reactors) # Volume of an individual reactor V_i = V_T / N_reactors # Time required to load a reactor tau_loading = V_i / F_vol else: tau_loading = loading_time if N_reactors is None: # Number of reactors N_reactors = ceil(F_vol * (tau_reaction + tau_cleaning + tau_loading) / (V_max * V_wf)) # Total volume of all reactors V_T = F_vol * (tau_reaction + tau_cleaning + tau_loading) # Volume of an individual reactor V_i = V_T / N_reactors # Total batch time tau_batch = tau_reaction + tau_cleaning + tau_loading # Account for excess volume V_i /= V_wf return {'Reactor volume': V_i, 'Batch time': tau_batch, 'Loading time': tau_loading, 'Number of reactors': N_reactors}