feos-campd 0.2.0

Computer-aided molecular and process design using the FeOs framework.
Documentation
use super::{Isobar, Process, ProcessState, ProcessStep, StatePoint};
use feos::core::si::*;
use feos::core::{Contributions, DensityInitialization, EosResult, IdealGas, Residual};

/// Representation of a single equipment in a [Process] graph.
pub struct Equipment {
    pub states: Vec<StatePoint>,
}

impl Equipment {
    /// Return the outlet of the equipment.
    pub fn out(&self) -> StatePoint {
        *self.states.last().unwrap()
    }
}

impl<E: Residual + IdealGas> Process<E> {
    pub fn total_condenser(
        &mut self,
        feed: StatePoint,
        isobar: &Isobar<E>,
        subcooling: Option<Temperature<f64>>,
    ) -> EosResult<Equipment> {
        let mass_flow_rate = self[feed].mass_flow_rate();
        let vle = isobar.phase_equilibrium()?;

        let vapor_state =
            if self[feed].molar_enthalpy() < vle.vapor().molar_enthalpy(Contributions::Total) {
                // Add a dummy step to make sure the number of pinch constraints is always the same
                self[feed].clone()
            } else {
                ProcessState::TwoPhase(vle.clone(), 1.0, mass_flow_rate)
            };
        let liquid_state =
            if self[feed].molar_enthalpy() < vle.liquid().molar_enthalpy(Contributions::Total) {
                // Add a dummy step to make sure the number of pinch constraints is always the same
                self[feed].clone()
            } else {
                ProcessState::TwoPhase(vle.clone(), 0.0, mass_flow_rate)
            };
        let subcooled_state = subcooling
            .map(|dt| liquid_state.isobaric_temperature_change(-dt, DensityInitialization::Liquid))
            .transpose()?;

        let vapor = self.add_step(feed, vapor_state, ProcessStep::Isobaric);
        let liquid = self.add_step(vapor, liquid_state, ProcessStep::PhaseChange);
        let subcooled = subcooled_state.map(|s| self.add_step(liquid, s, ProcessStep::Isobaric));

        let states = [feed, vapor, liquid].into_iter().chain(subcooled).collect();

        Ok(Equipment { states })
    }

    pub fn evaporator(
        &mut self,
        feed: StatePoint,
        isobar: &Isobar<E>,
        superheating: Option<Temperature<f64>>,
    ) -> EosResult<Equipment> {
        let mass_flow_rate = self[feed].mass_flow_rate();
        let vle = isobar.phase_equilibrium()?;

        let liquid_state =
            if self[feed].molar_enthalpy() > vle.liquid().molar_enthalpy(Contributions::Total) {
                // Add a dummy step to make sure the number of pinch constraints is always the same
                self[feed].clone()
            } else {
                ProcessState::TwoPhase(vle.clone(), 0.0, mass_flow_rate)
            };
        let vapor_state =
            if self[feed].molar_enthalpy() > vle.vapor().molar_enthalpy(Contributions::Total) {
                // Add a dummy step to make sure the number of pinch constraints is always the same
                self[feed].clone()
            } else {
                ProcessState::TwoPhase(vle.clone(), 1.0, mass_flow_rate)
            };
        let superheated_state = superheating
            .map(|dt| vapor_state.isobaric_temperature_change(dt, DensityInitialization::Vapor))
            .transpose()?;

        let liquid = self.add_step(feed, liquid_state, ProcessStep::Isobaric);
        let vapor = self.add_step(liquid, vapor_state, ProcessStep::PhaseChange);
        let superheated = superheated_state.map(|s| self.add_step(vapor, s, ProcessStep::Isobaric));

        let states = [feed, liquid, vapor]
            .into_iter()
            .chain(superheated)
            .collect();

        Ok(Equipment { states })
    }

    pub fn pump(
        &mut self,
        feed: StatePoint,
        isobar: &Isobar<E>,
        efficiency: f64,
    ) -> EosResult<Equipment> {
        self.pressure_changer(feed, isobar, Some(1.0 / efficiency))
    }

    pub fn turbine(
        &mut self,
        feed: StatePoint,
        isobar: &Isobar<E>,
        efficiency: f64,
    ) -> EosResult<Equipment> {
        self.pressure_changer(feed, isobar, Some(efficiency))
    }

    pub fn compressor(
        &mut self,
        feed: StatePoint,
        isobar: &Isobar<E>,
        efficiency: f64,
    ) -> EosResult<Equipment> {
        self.pressure_changer(feed, isobar, Some(1.0 / efficiency))
    }

    pub fn throttle(&mut self, feed: StatePoint, isobar: &Isobar<E>) -> EosResult<Equipment> {
        self.pressure_changer(feed, isobar, None)
    }

    fn pressure_changer(
        &mut self,
        feed: StatePoint,
        isobar: &Isobar<E>,
        efficiency: Option<f64>,
    ) -> EosResult<Equipment> {
        let mut initial_temperature = self[feed].temperature();
        let molar_work = if let Some(efficiency) = efficiency {
            let state2s = self[feed].isentropic_expansion(isobar, initial_temperature)?;
            initial_temperature = state2s.temperature();
            let h1 = self[feed].molar_enthalpy();
            let h2s = state2s.molar_enthalpy();
            efficiency * (h2s - h1)
        } else {
            0.0 * JOULE / MOL
        };
        let out = self[feed].polytropic_expansion(isobar, molar_work, initial_temperature)?;
        let out = self.add_step(feed, out, ProcessStep::Polytropic);

        Ok(Equipment {
            states: vec![feed, out],
        })
    }

    pub fn recuperator(
        &mut self,
        liquid: StatePoint,
        vapor: StatePoint,
        isobar_liquid: &Isobar<E>,
        isobar_vapor: &Isobar<E>,
        transferred_heat: Power<f64>,
    ) -> EosResult<[Equipment; 2]> {
        let liquid_out = self[liquid].isobaric_heat_transfer(transferred_heat, isobar_liquid)?;
        let vapor_out = self[vapor].isobaric_heat_transfer(-transferred_heat, isobar_vapor)?;

        let liquid_out = self.add_step(liquid, liquid_out, ProcessStep::Isobaric);
        let vapor_out = self.add_step(vapor, vapor_out, ProcessStep::Isobaric);
        Ok([
            Equipment {
                states: vec![liquid, liquid_out],
            },
            Equipment {
                states: vec![vapor, vapor_out],
            },
        ])
    }

    pub fn split(&mut self, feed: StatePoint, ratio: f64) -> [StatePoint; 2] {
        let [out1, out2] = self[feed].split(ratio);
        [self.add_state(out1), self.add_state(out2)]
    }

    pub fn isobaric_mixer(
        &mut self,
        feed1: StatePoint,
        feed2: StatePoint,
        isobar: &Isobar<E>,
    ) -> EosResult<StatePoint> {
        let mix = self[feed1].mix(&self[feed2], isobar)?;
        let out = self.add_step(feed1, mix, ProcessStep::Isobaric);
        self.add_connection(feed2, out, ProcessStep::Isobaric);
        Ok(out)
    }
}