From 0a4e74e38b1b85ed4164b04d056f07807139a166 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Wed, 27 Nov 2024 12:30:27 -0600 Subject: [PATCH 01/49] added iced gui on new branch --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4617601..7ca18e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ +members = [ "oscps-gui", "oscps-lib", ] From d6f46b06d080c671dbfd3f0f65e2ee8531585676 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 2 Dec 2024 14:02:11 -0600 Subject: [PATCH 02/49] consolidated streams into 1 struct --- oscps-lib/src/connector.rs | 67 +++++++++++++------------------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/oscps-lib/src/connector.rs b/oscps-lib/src/connector.rs index 03db4ea..d7e2750 100644 --- a/oscps-lib/src/connector.rs +++ b/oscps-lib/src/connector.rs @@ -1,54 +1,31 @@ //! # Connector //! -//! Two types of connectors represent different energy types of streams -//! One is a mass connector, Mconnector, which represents mass flow rates -//! -//! The other is Econnector, which represents energy flow rates. -#[allow(dead_code)] -/// A connector for storing mass information. This includes an ID and a -/// total mass flow rate. -/// -/// TODO: These should be consolidated into a single connector. -pub struct Mconnector { - /// Mass connector ID - pub m_conn_id: String, - /// Total mass flow rate - pub m_flow_total: f64, -} +use crate::thermodynamics::ThermoState; -#[allow(dead_code)] -/// Functions implemented on Mconnectors. -impl Mconnector { - /// Constructor for a connector. - pub fn new(id: String) -> Mconnector { - return Mconnector { - m_conn_id: id, - m_flow_total: 0.0, - }; - } +/// # Stream +/// +/// Struct to hold stream information +pub struct Stream { + /// Stream id + pub s_id : String, + /// Instance of ThermoState struct that holds + pub thermo : Option, + /// block id for where the stream is coming from + pub from_block : String, + /// block id for where the stream is going to + pub to_block : String } -#[allow(dead_code)] -/// A connector for storing energy information. This includes an ID and a -/// total energy flow rate. -/// -/// TODO: These should be consolidated into a single connector. -pub struct Econnector { - /// Energy connector ID. - pub e_conn_id: String, - /// Total energy flow rate. - pub energy_flow_total: f64, -} -#[allow(dead_code)] -/// Functions implemented on Econnectors. -impl Econnector { - /// Constructor for a connector. - pub fn new(id: String) -> Econnector { - return Econnector { - e_conn_id: id, - energy_flow_total: 0.0, - }; +impl Stream { + /// Constructor for 'Stream' struct + pub fn new(id: String, from_blk_id : String, to_blk_id : String) -> Stream { + Stream { + s_id : id, + thermo : None, + from_block : from_blk_id, + to_block : to_blk_id + } } } From b53ff9b341c965191d9cc6d354244c8d07fd7f40 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 2 Dec 2024 14:15:07 -0600 Subject: [PATCH 03/49] updating blocks and thermodynamics modules with changes in the connector module --- oscps-lib/src/blocks.rs | 14 +++++++------- oscps-lib/src/thermodynamics.rs | 8 ++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/oscps-lib/src/blocks.rs b/oscps-lib/src/blocks.rs index 9949904..887feb5 100644 --- a/oscps-lib/src/blocks.rs +++ b/oscps-lib/src/blocks.rs @@ -6,7 +6,7 @@ //! For example, if a block is a simple mixer, then it will implement the //! MassBalance trait but not the EnergyBalance. -use crate::connector; +use crate::connector::Stream; use once_cell::sync::Lazy; use uom::si::energy::joule; use uom::si::f64::Energy; @@ -80,13 +80,13 @@ pub struct Mixer { /// The y-coordinate on a flowsheet of the block. pub y_cord: i32, /// All mass input streams for the block. - pub input_streams_mass: Vec, + pub input_streams_mass: Vec, /// All energy input streams for the block. - pub input_streams_energy: Vec, + pub input_streams_energy: Vec, /// All mass output streams for the block. - pub outlet_stream_mass: Option, + pub outlet_stream_mass: Option, /// All energy output streams for the block - pub outlet_stream_energy: Option, + pub outlet_stream_energy: Option, } /// Applying mass balance trait to Mixer Block @@ -103,8 +103,8 @@ impl Mixer { id: String, x_cord: i32, y_cord: i32, - in_streams_mass: Vec, - in_streams_energy: Vec, + in_streams_mass: Vec, + in_streams_energy: Vec, ) -> Mixer { return Mixer { block_id: id, diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index a1bd05b..38f15e0 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -123,6 +123,14 @@ impl ThermoState { const R: f64 = 8.314; // J/(mol·K) (n * R * t) / v } + + pub fn total_mass() { + + } + + pub fn enthalpy() { + + } } #[cfg(test)] From 415c8c2e8cd6894bb782477c5ba552b2c0df1131 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Wed, 4 Dec 2024 23:23:45 -0500 Subject: [PATCH 04/49] updating readme file --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5148539..6309cd0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # OSCPS (Open Sourced Chemical Engineering Process Simulator) -![Rust Tests](https://github.com/OSCPS-Project/OSCPS/actions/workflows/rust-tests.yml/badge.svg) -![Documentation](https://github.com/OSCPS-Project/OSCPS/actions/workflows/check-docs.yml/badge.svg) +![Rust Tests](https://github.com/OSCPS-Project/OSCPS/actions/workflows/rust-tests.yml/badge.svg?branch=develop) +![Documentation](https://github.com/OSCPS-Project/OSCPS/actions/workflows/check-docs.yml/badge.svg?branch=develop) Developing a dynamic & steady-state chemical process simulator using a Rust backend and a iced-rs frontend. This project aims to create a much better version of ASPEN that will also be open-sourced. From c5648b6364ad6319df7c80db48cc58c9fd48a693 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Thu, 5 Dec 2024 19:08:45 -0500 Subject: [PATCH 05/49] adding documentation comments and implemented total_mass function in thermodynamics.rs --- oscps-lib/src/thermodynamics.rs | 38 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 38f15e0..3e2fd68 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -58,9 +58,21 @@ impl ThermodynamicConstants { } } + +#[allow(dead_code)] +/// Species list +pub struct SpeciesListPair { + /// Chemical species + pub chemical_species: Chemical, + /// Mass quantity + pub mass_quantity: Mass, +} + #[allow(dead_code)] +/// # ThermoState /// Returns a thermodynamic state, including pressure, temperature, and /// mole fractions. +/// This struct will be used for streams in the flow diagram pub struct ThermoState { /// Pressure of the state. pub pressure: Pressure, // Pressure in Pascals @@ -70,19 +82,10 @@ pub struct ThermoState { pub mass_list: Vec, // Mole fractions, typically unitless } -#[allow(dead_code)] -/// Species list -pub struct SpeciesListPair { - /// Chemical species - pub chemical_species: Chemical, - /// Mass quantity - pub mass_quantity: Mass, -} #[allow(dead_code)] /// Implementation of ThermoState -/// This struct holds the functionality to perform thermodynamic calculations for a stream or for -/// an individual species +/// This struct holds the functionality to perform thermodynamic calculations for streams impl ThermoState { /// Constructor for creating a ThermoState pub fn new( @@ -123,16 +126,23 @@ impl ThermoState { const R: f64 = 8.314; // J/(mol·K) (n * R * t) / v } - - pub fn total_mass() { - + + /// this function will return the total mass for an individual stream + pub fn total_mass(&self) -> f64 { + let mut mass_sum = 0.0; + for chem in &self.mass_list { + mass_sum += chem.mass_quantity.get::(); + } + mass_sum } - + + /// This function will provide the enthalpy of an individual stream pub fn enthalpy() { } } + #[cfg(test)] mod thermo_tests { use super::*; From 205f01eb77a0f3e04097cfc35bcec5b1ea85fcb0 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Sat, 14 Dec 2024 15:47:25 -0600 Subject: [PATCH 06/49] copied bezier example from iced repo. Will modify. --- oscps-gui/Cargo.toml | 3 +- oscps-gui/src/bezier.rs | 249 ++++++++++++++++++++++++++++++++++++++++ oscps-gui/src/main.rs | 105 +++++++++++++---- 3 files changed, 334 insertions(+), 23 deletions(-) create mode 100644 oscps-gui/src/bezier.rs diff --git a/oscps-gui/Cargo.toml b/oscps-gui/Cargo.toml index 8eb9129..f92c409 100644 --- a/oscps-gui/Cargo.toml +++ b/oscps-gui/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "oscps-gui" version = "0.1.0" +authors = ["Nathaniel Thomas ", "Bhargav Akula "] edition = "2021" [dependencies] -iced = "0.13.1" +iced = {version = "0.13.1", features = ["canvas", "debug"]} diff --git a/oscps-gui/src/bezier.rs b/oscps-gui/src/bezier.rs new file mode 100644 index 0000000..59d44f2 --- /dev/null +++ b/oscps-gui/src/bezier.rs @@ -0,0 +1,249 @@ +// We are going to define a module here to define the bezier curves that we are +// going to use. +use iced::mouse; // We would like to be able to detect mouse movements. +use iced::widget::canvas::event::{self, Event}; // We can capture events in the canvas (clicks, + // for example) +use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; // We will be using a + // number of structs + // provided by the + // Canvas module. +use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; // Some tools thatcan we + +// Here we define the state of the screen. It will cache the state of the screen. +#[derive(Default)] +pub struct State { + cache: canvas::Cache, +} + +// We will implement some methods for State. Reminder on lifetime annotation, +// 'a is the lifetime that [Curve] must have. +impl State { + // The view method, which accepts a self reference and a curves vector and an array. + pub fn view<'a>(&'a self, curves: &'a [Curve]) -> Element<'a, Curve> { + // Create a new Bezier curve. It will have a state of itself. + Canvas::new(Bezier { + state: self, + curves, + }) + .width(Fill) + .height(Fill) + .into() + } + + // We request that we redraw the screen. This just clears the cache. + pub fn request_redraw(&mut self) { + self.cache.clear(); + } +} + +// A Bezier curve. It has a state, as well as a curves array. +struct Bezier<'a> { + state: &'a State, + curves: &'a [Curve], +} + +// A Program canvas. +impl<'a> canvas::Program for Bezier<'a> { + type State = Option; + + fn update( + &self, + state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> (event::Status, Option) { + // Capture the cursor position. Ignore if not over box. + let Some(cursor_position) = cursor.position_in(bounds) else { + return (event::Status::Ignored, None); + }; + + // Detect an event. + match event { + // If there is a mouse event. + Event::Mouse(mouse_event) => { + // Message will be determined by event type. + let message = match mouse_event { + // A mouse button was pressed. The left button specifically. + mouse::Event::ButtonPressed(mouse::Button::Left) => { + match *state { + // If there is no state, set the value of the + // first point to the cursor position. + None => { + println!("Started Bezier creation."); + *state = Some(Pending::One { + from: cursor_position, + }); + + // We create a pending but we do not return a message yet. + None + } + // If there is already one point, set the value + // of the second point. + Some(Pending::One { from }) => { + println!("Bezier destination chosen."); + *state = Some(Pending::Two { + from, + to: cursor_position, + }); + + // We update the state of the Pending, but we don't actually return + // a message for Curve creation yet. + None + } + + // If there is a second point, capture the third point and make a + // curve. + Some(Pending::Two { from, to }) => { + *state = None; // Set state to none so you can draw another Bezier + + println!("Bezier complete."); + Some(Curve { + from, + to, + control: cursor_position, + }) + } + } + } + // Ignore anything else + _ => None, + }; + + (event::Status::Captured, message) + } + _ => (event::Status::Ignored, None), + } + } + + fn draw( + &self, + state: &Self::State, + renderer: &Renderer, + theme: &Theme, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> Vec { + + println!("Bezier draw."); + // This creates content from cache for the drawing of a frame. The third argument is a lambda function + // that accepts a frame and uses draw_all() + let content = + self.state.cache.draw(renderer, bounds.size(), |frame| { + // Draws all curves on the frame. + Curve::draw_all(self.curves, frame, theme); + + // Draws border currounding the drawing box. + frame.stroke( + &Path::rectangle(Point::ORIGIN, frame.size()), + Stroke::default() + .with_width(20.0) + .with_color(theme.palette().text), + ); + }); + + // Draw the pending curve, if there is one. Otherwise, just draw the current content. + // The return value is decided here. + if let Some(pending) = state { + vec![content, pending.draw(renderer, theme, bounds, cursor)] + } else { + vec![content] + } + } + + // Control what the mouse cursor looks like. + fn mouse_interaction( + &self, + _state: &Self::State, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> mouse::Interaction { + print!("Mouse is over bounds."); + if cursor.is_over(bounds) { + println!(" Drawing crosshair."); + mouse::Interaction::Crosshair + } else { + println!(" Drawing pointer."); + mouse::Interaction::default() + } + } +} + +// Three points which define a Bezier curve. +#[derive(Debug, Clone, Copy)] +pub struct Curve { + from: Point, // The starting point + to: Point, // The ending point + control: Point, // The point that controls the curvature. +} + +// We will implement on Curve several functions. +impl Curve { + // This accepts a reference to the array of Curve vectors. + // It will accept a frame, as well as a theme. + fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { + println!("Drawing all curves."); + // We will define curves as Path with + let curves = Path::new(|p| { + // This calculates the path of the curves. + for curve in curves { + p.move_to(curve.from); + p.quadratic_curve_to(curve.control, curve.to); + } + }); + + // This will draw a stroke for the curve on the canvas. + frame.stroke( + &curves, + Stroke::default() + .with_width(2.0) + .with_color(theme.palette().text), + ); + } +} + +// A type which represents the intermediate phases of drawing a Bezier curve. +#[derive(Debug, Clone, Copy)] +enum Pending { + One { from: Point }, + Two { from: Point, to: Point }, +} + +// Functions that Pending implements. +impl Pending { + fn draw( + &self, + renderer: &Renderer, + theme: &Theme, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> Geometry { + println!("Pending draw."); + let mut frame = Frame::new(renderer, bounds.size()); + + if let Some(cursor_position) = cursor.position_in(bounds) { + match *self { + Pending::One { from } => { + let line = Path::line(from, cursor_position); + frame.stroke( + &line, + Stroke::default() + .with_width(0.5) + .with_color(theme.palette().text), + ); + } + Pending::Two { from, to } => { + let curve = Curve { + from, + to, + control: cursor_position, + }; + + Curve::draw_all(&[curve], &mut frame, theme); + } + }; + } + + frame.into_geometry() + } +} diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index 5f3a686..2ddee62 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -1,32 +1,93 @@ +//! This example showcases an interactive `Canvas` for drawing Bézier curves. +mod bezier; +use iced::widget::{button, container, horizontal_space, hover}; +use iced::{Element, Fill, Theme}; + +// Main function. Returns result from iced::application +pub fn main() -> iced::Result { + // Sets window title, update method, and view method. Sets the theme, enables + // antialiasing, centers the main window, and runs the program. + iced::application("Bezier Tool - Iced", Example::update, Example::view) + .theme(|_| Theme::Dark) + .antialiasing(true) + .centered() + .run() +} + +// Creates the Example struct, which has a bezier and and a vector of beziers. +// Presumably, the first bezier is the one actively being used #[derive(Default)] -struct Counter { - value: i8, +struct Example { + bezier: bezier::State, + curves: Vec, } + +// Messages that are processed. The first is a click to add a curve, the second +// is to clear the screen. #[derive(Debug, Clone, Copy)] -pub enum Message { - Increment, - Decrement, +enum Message { + AddCurve(bezier::Curve), + Clear, } -use iced::widget::{button, row, text, Row}; -impl Counter { - pub fn view(&self) -> Row { - row![ - button("+").on_press(Message::Increment), - text(self.value).size(50), - button("-").on_press(Message::Decrement), - ] - } - pub fn update(&mut self, message: Message) { + +// For example, we are going to implement the update and view functions. +impl Example { + // This function accepts the Example struct, as well as a message. + fn update(&mut self, message: Message) { + // Match the message to one of two actions match message { - Message::Increment => { - self.value += 1; + // If it is add curve, we will push a curve to the curves struct. + // The message will also contain the curve to add. We will then + // redraw the screen. + Message::AddCurve(curve) => { + self.curves.push(curve); + self.bezier.request_redraw(); } - Message::Decrement => { - self.value -= 1; + // If a clear message is sent, then the state of the bezier will + // be set to default, and the bezier curve vector will be cleared. + Message::Clear => { + self.bezier = bezier::State::default(); + self.curves.clear(); } } } -} -fn main() -> iced::Result { - iced::run("A cool counter", Counter::update, Counter::view) + // For this function, we are going to configure how the view is seen by the + // user. + fn view(&self) -> Element { + // A container (https://docs.rs/iced/latest/iced/widget/container/index.html) + // is essentially a box that allows a user to align the contents within. In + // this case, we are giving it a padding of 10 (presumably points) and will + // aligh_right(Fill), which will align the container to the right. This should + // not effect the appearance, as the container has even padding on all sides. + // Confirmed, there appears to be no effect. + container( + // Hover displays one widget on top of another one. This will be + // where the bezier curves are drawn. (I think) + hover( + // This askes the current bezier curve to display a view, and it + // will map it to the AddCurve message. + self.bezier.view(&self.curves).map(Message::AddCurve), + // If the curves vector is empty, just draw a container with a horizontal space + // inside. + if self.curves.is_empty() { + container(horizontal_space()) + } else { + // What we are doing to do is return a button which allows + // the user to clear the screen. In this case it will have the + // "danger" style, which makes it red. It will then clear the + // screen if it is pressed. It is again given padding and aligned right. + // In this case + container( + button("Clear") + .style(button::danger) + .on_press(Message::Clear), + ) + .padding(10) + .align_bottom(Fill) + }, + ) + ) + .padding(20) + .into() + } } From 74c3ea37e3b69913157c36be84551728a43935c0 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Sat, 14 Dec 2024 16:03:25 -0600 Subject: [PATCH 07/49] added more output for understanding --- oscps-gui/src/bezier.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/oscps-gui/src/bezier.rs b/oscps-gui/src/bezier.rs index 59d44f2..d87d920 100644 --- a/oscps-gui/src/bezier.rs +++ b/oscps-gui/src/bezier.rs @@ -36,7 +36,7 @@ impl State { } } -// A Bezier curve. It has a state, as well as a curves array. +// Stores Bezier curves. It has a state, as well as a curves array. struct Bezier<'a> { state: &'a State, curves: &'a [Curve], @@ -224,21 +224,25 @@ impl Pending { if let Some(cursor_position) = cursor.position_in(bounds) { match *self { Pending::One { from } => { + println!("Drawing a line."); + // Draw a line from the "from" point to the cursor position. let line = Path::line(from, cursor_position); frame.stroke( &line, Stroke::default() - .with_width(0.5) + .with_width(1.0) .with_color(theme.palette().text), ); } Pending::Two { from, to } => { + println!("Drawing an in-situ curve."); let curve = Curve { from, to, control: cursor_position, }; - + + // Draw this curve in this frame, using this theme. Curve::draw_all(&[curve], &mut frame, theme); } }; From a8c80f20da6433518cd6ccb513e207073463e2d0 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Sat, 14 Dec 2024 19:59:49 -0600 Subject: [PATCH 08/49] added a test button --- oscps-gui/src/bezier.rs | 10 ++++++++++ oscps-gui/src/main.rs | 21 ++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/oscps-gui/src/bezier.rs b/oscps-gui/src/bezier.rs index d87d920..fecbf7f 100644 --- a/oscps-gui/src/bezier.rs +++ b/oscps-gui/src/bezier.rs @@ -105,12 +105,22 @@ impl<'a> canvas::Program for Bezier<'a> { }) } } + }, + mouse::Event::ButtonPressed(mouse::Button::Right) => { + println!("You pressed the right mouse button!"); + None } // Ignore anything else _ => None, }; (event::Status::Captured, message) + }, + Event::Keyboard(_) => { + println!("You pressed a key!"); + + + (event::Status::Captured, None) } _ => (event::Status::Ignored, None), } diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index 2ddee62..cfcdeff 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -1,12 +1,13 @@ //! This example showcases an interactive `Canvas` for drawing Bézier curves. mod bezier; -use iced::widget::{button, container, horizontal_space, hover}; +use iced::widget::{button, container, horizontal_space, hover, column}; use iced::{Element, Fill, Theme}; // Main function. Returns result from iced::application pub fn main() -> iced::Result { // Sets window title, update method, and view method. Sets the theme, enables // antialiasing, centers the main window, and runs the program. + println!("Starting application..."); iced::application("Bezier Tool - Iced", Example::update, Example::view) .theme(|_| Theme::Dark) .antialiasing(true) @@ -18,8 +19,8 @@ pub fn main() -> iced::Result { // Presumably, the first bezier is the one actively being used #[derive(Default)] struct Example { - bezier: bezier::State, - curves: Vec, + bezier: bezier::State, // State variable, stores cache, which stores geometry. + curves: Vec, // Curves are just three points. } // Messages that are processed. The first is a click to add a curve, the second @@ -28,6 +29,7 @@ struct Example { enum Message { AddCurve(bezier::Curve), Clear, + Test, } // For example, we are going to implement the update and view functions. @@ -49,6 +51,9 @@ impl Example { self.bezier = bezier::State::default(); self.curves.clear(); } + Message::Test => { + println!("Test clicked!"); + } } } // For this function, we are going to configure how the view is seen by the @@ -63,6 +68,7 @@ impl Example { container( // Hover displays one widget on top of another one. This will be // where the bezier curves are drawn. (I think) + column![ hover( // This askes the current bezier curve to display a view, and it // will map it to the AddCurve message. @@ -82,10 +88,15 @@ impl Example { .style(button::danger) .on_press(Message::Clear), ) - .padding(10) - .align_bottom(Fill) + .padding(10) + .align_top(Fill) }, + ), + container( + button("Test text").on_press(Message::Test), ) + + ] ) .padding(20) .into() From 9cbc539d78f0e826c1b2cb177e64a82a6f8c12d2 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Tue, 17 Dec 2024 08:46:03 -0600 Subject: [PATCH 09/49] primitive example --- oscps-gui/Cargo.toml | 2 +- oscps-gui/src/{bezier.rs => flowsheet.rs} | 103 +----- oscps-gui/src/main.rs | 400 +++++++++++++++++++--- oscps-gui/src/style.rs | 50 +++ 4 files changed, 409 insertions(+), 146 deletions(-) rename oscps-gui/src/{bezier.rs => flowsheet.rs} (53%) create mode 100644 oscps-gui/src/style.rs diff --git a/oscps-gui/Cargo.toml b/oscps-gui/Cargo.toml index f92c409..7727ca5 100644 --- a/oscps-gui/Cargo.toml +++ b/oscps-gui/Cargo.toml @@ -5,4 +5,4 @@ authors = ["Nathaniel Thomas ", "Bhargav Akula (&'a self, curves: &'a [Curve]) -> Element<'a, Curve> { - // Create a new Bezier curve. It will have a state of itself. - Canvas::new(Bezier { + Canvas::new(Flowsheet { state: self, curves, }) .width(Fill) - .height(Fill) - .into() + .height(Fill) + .height(Fill) + .into() } - - // We request that we redraw the screen. This just clears the cache. pub fn request_redraw(&mut self) { self.cache.clear(); } } -// Stores Bezier curves. It has a state, as well as a curves array. -struct Bezier<'a> { +struct Flowsheet<'a> { state: &'a State, curves: &'a [Curve], } -// A Program canvas. -impl<'a> canvas::Program for Bezier<'a> { +impl<'a> canvas::Program for Flowsheet<'a> { type State = Option; - fn update( &self, state: &mut Self::State, @@ -53,51 +38,29 @@ impl<'a> canvas::Program for Bezier<'a> { bounds: Rectangle, cursor: mouse::Cursor, ) -> (event::Status, Option) { - // Capture the cursor position. Ignore if not over box. let Some(cursor_position) = cursor.position_in(bounds) else { return (event::Status::Ignored, None); }; - - // Detect an event. match event { - // If there is a mouse event. Event::Mouse(mouse_event) => { - // Message will be determined by event type. let message = match mouse_event { - // A mouse button was pressed. The left button specifically. mouse::Event::ButtonPressed(mouse::Button::Left) => { match *state { - // If there is no state, set the value of the - // first point to the cursor position. None => { - println!("Started Bezier creation."); *state = Some(Pending::One { from: cursor_position, }); - - // We create a pending but we do not return a message yet. None } - // If there is already one point, set the value - // of the second point. Some(Pending::One { from }) => { - println!("Bezier destination chosen."); *state = Some(Pending::Two { from, to: cursor_position, }); - - // We update the state of the Pending, but we don't actually return - // a message for Curve creation yet. None } - - // If there is a second point, capture the third point and make a - // curve. Some(Pending::Two { from, to }) => { - *state = None; // Set state to none so you can draw another Bezier - - println!("Bezier complete."); + *state = None; Some(Curve { from, to, @@ -107,19 +70,13 @@ impl<'a> canvas::Program for Bezier<'a> { } }, mouse::Event::ButtonPressed(mouse::Button::Right) => { - println!("You pressed the right mouse button!"); None } - // Ignore anything else _ => None, }; - (event::Status::Captured, message) }, Event::Keyboard(_) => { - println!("You pressed a key!"); - - (event::Status::Captured, None) } _ => (event::Status::Ignored, None), @@ -134,16 +91,9 @@ impl<'a> canvas::Program for Bezier<'a> { bounds: Rectangle, cursor: mouse::Cursor, ) -> Vec { - - println!("Bezier draw."); - // This creates content from cache for the drawing of a frame. The third argument is a lambda function - // that accepts a frame and uses draw_all() let content = self.state.cache.draw(renderer, bounds.size(), |frame| { - // Draws all curves on the frame. Curve::draw_all(self.curves, frame, theme); - - // Draws border currounding the drawing box. frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), Stroke::default() @@ -151,58 +101,42 @@ impl<'a> canvas::Program for Bezier<'a> { .with_color(theme.palette().text), ); }); - - // Draw the pending curve, if there is one. Otherwise, just draw the current content. - // The return value is decided here. if let Some(pending) = state { vec![content, pending.draw(renderer, theme, bounds, cursor)] } else { vec![content] } } - - // Control what the mouse cursor looks like. fn mouse_interaction( &self, _state: &Self::State, bounds: Rectangle, cursor: mouse::Cursor, ) -> mouse::Interaction { - print!("Mouse is over bounds."); if cursor.is_over(bounds) { - println!(" Drawing crosshair."); mouse::Interaction::Crosshair } else { - println!(" Drawing pointer."); mouse::Interaction::default() } } } -// Three points which define a Bezier curve. #[derive(Debug, Clone, Copy)] pub struct Curve { - from: Point, // The starting point - to: Point, // The ending point - control: Point, // The point that controls the curvature. + from: Point, + to: Point, + control: Point, } -// We will implement on Curve several functions. impl Curve { - // This accepts a reference to the array of Curve vectors. - // It will accept a frame, as well as a theme. fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { - println!("Drawing all curves."); - // We will define curves as Path with let curves = Path::new(|p| { - // This calculates the path of the curves. for curve in curves { p.move_to(curve.from); p.quadratic_curve_to(curve.control, curve.to); } }); - // This will draw a stroke for the curve on the canvas. frame.stroke( &curves, Stroke::default() @@ -212,14 +146,12 @@ impl Curve { } } -// A type which represents the intermediate phases of drawing a Bezier curve. #[derive(Debug, Clone, Copy)] enum Pending { One { from: Point }, Two { from: Point, to: Point }, } -// Functions that Pending implements. impl Pending { fn draw( &self, @@ -228,31 +160,26 @@ impl Pending { bounds: Rectangle, cursor: mouse::Cursor, ) -> Geometry { - println!("Pending draw."); let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(bounds) { match *self { Pending::One { from } => { - println!("Drawing a line."); - // Draw a line from the "from" point to the cursor position. let line = Path::line(from, cursor_position); frame.stroke( &line, Stroke::default() - .with_width(1.0) + .with_width(2.0) .with_color(theme.palette().text), ); } Pending::Two { from, to } => { - println!("Drawing an in-situ curve."); let curve = Curve { from, to, control: cursor_position, }; - // Draw this curve in this frame, using this theme. Curve::draw_all(&[curve], &mut frame, theme); } }; diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index cfcdeff..f895318 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -1,88 +1,202 @@ -//! This example showcases an interactive `Canvas` for drawing Bézier curves. -mod bezier; -use iced::widget::{button, container, horizontal_space, hover, column}; -use iced::{Element, Fill, Theme}; +mod flowsheet; +mod style; + +use iced::keyboard; +use iced::widget::pane_grid::{self, PaneGrid}; +use iced::widget::{button, column, container, horizontal_space, hover, responsive, row, scrollable, text}; +use iced::{Center, Color, Element, Fill, Size, Subscription, Theme}; -// Main function. Returns result from iced::application pub fn main() -> iced::Result { - // Sets window title, update method, and view method. Sets the theme, enables - // antialiasing, centers the main window, and runs the program. - println!("Starting application..."); - iced::application("Bezier Tool - Iced", Example::update, Example::view) + iced::application("Open Source Chemical Process Simulator", MainWindow::update, MainWindow::view) .theme(|_| Theme::Dark) .antialiasing(true) .centered() .run() } -// Creates the Example struct, which has a bezier and and a vector of beziers. -// Presumably, the first bezier is the one actively being used -#[derive(Default)] -struct Example { - bezier: bezier::State, // State variable, stores cache, which stores geometry. - curves: Vec, // Curves are just three points. +struct MainWindow { + panes: pane_grid::State, + panes_created: usize, + focus: Option, + flowsheet: flowsheet::State, + curves: Vec, } -// Messages that are processed. The first is a click to add a curve, the second -// is to clear the screen. #[derive(Debug, Clone, Copy)] enum Message { - AddCurve(bezier::Curve), + AddCurve(flowsheet::Curve), Clear, - Test, + PlaceMixer, + Split(pane_grid::Axis, pane_grid::Pane), + SplitFocused(pane_grid::Axis), + FocusAdjacent(pane_grid::Direction), + Clicked(pane_grid::Pane), + Dragged(pane_grid::DragEvent), + Resized(pane_grid::ResizeEvent), + TogglePin(pane_grid::Pane), + Maximize(pane_grid::Pane), + Restore, + Close(pane_grid::Pane), + CloseFocused, } -// For example, we are going to implement the update and view functions. -impl Example { - // This function accepts the Example struct, as well as a message. +impl MainWindow { + + fn new() -> Self { + let (mut panes, pane) = pane_grid::State::new(Pane::new_canvas(0)); + panes.split(pane_grid::Axis::Horizontal, pane, Pane::new_selection()); + + MainWindow { + panes, + panes_created: 1, + focus: None, + flowsheet: flowsheet::State::default(), + curves: Vec::default(), + } + } + fn update(&mut self, message: Message) { - // Match the message to one of two actions match message { - // If it is add curve, we will push a curve to the curves struct. - // The message will also contain the curve to add. We will then - // redraw the screen. Message::AddCurve(curve) => { self.curves.push(curve); - self.bezier.request_redraw(); + self.flowsheet.request_redraw(); } - // If a clear message is sent, then the state of the bezier will - // be set to default, and the bezier curve vector will be cleared. Message::Clear => { - self.bezier = bezier::State::default(); + self.flowsheet = flowsheet::State::default(); self.curves.clear(); } - Message::Test => { - println!("Test clicked!"); + Message::PlaceMixer => { + println!("Placing Mixer!"); } + Message::Split(_axis, _pane) => { + println!("You split a pane!") + }, + Message::SplitFocused(_axis) => { + println!("You split a focused pane!") + }, + Message::FocusAdjacent(_direction) => (), + Message::Clicked(pane) => { + self.focus = Some(pane); + println!("You clicked on a pane!") + }, + Message::Dragged(pane_grid::DragEvent::Dropped{ pane, target }) => { // pane, target + self.panes.drop(pane, target); + println!("You dragged a pane!") + }, + Message::Dragged(_) => { + println!("You dragged, but did not drop a pane!") + }, + Message::Resized(pane_grid::ResizeEvent { split, ratio } ) => { + self.panes.resize(split, ratio); + println!("You resized a pane!") + }, + Message::TogglePin(_pane) => { + println!("You pinned a pane!") + }, + Message::Maximize(_pane) => { + println!("You maximized a pane!") + }, + Message::Restore => { + println!("You restored a window!") + }, + Message::Close(_pane) => { + println!("You closed a pane!") + }, + Message::CloseFocused => { + println!("You closed a focused pane!") + }, + _ => () } } - // For this function, we are going to configure how the view is seen by the - // user. + + fn subscription(&self) -> Subscription { + keyboard::on_key_press(|key_code, modifiers| { + if !modifiers.command() { + return None; + } + + handle_hotkey(key_code) + }) + } + fn view(&self) -> Element { - // A container (https://docs.rs/iced/latest/iced/widget/container/index.html) - // is essentially a box that allows a user to align the contents within. In - // this case, we are giving it a padding of 10 (presumably points) and will - // aligh_right(Fill), which will align the container to the right. This should - // not effect the appearance, as the container has even padding on all sides. - // Confirmed, there appears to be no effect. - container( - // Hover displays one widget on top of another one. This will be - // where the bezier curves are drawn. (I think) - column![ + let focus = self.focus; + let total_panes = self.panes.len(); + + let pane_grid = PaneGrid::new(&self.panes, |id, pane, _is_maximized| { + match pane { + Pane::Canvas{ id: _, is_pinned: _} => { + println!("Found canvas!"); + } + Pane::UnitSelection => { + println!("Found Selection!"); + return row![ + container( + button("Place Mixer").on_press(Message::PlaceMixer), + ), + container( + button("Place Other Component"), + ), + ].into() + } + } + let is_focused = focus == Some(id); + + // let pin_button = button( + // text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), + // ) + // .on_press(Message::TogglePin(id)) + // .padding(3); + + let title = row![ + // pin_button, + "Pane", + // text(pane.id.to_string()).color(if is_focused{ + // PANE_ID_COLOR_FOCUSED + // } else { + // PANE_ID_COLOR_UNFOCUSED + // }), + ] + .spacing(5); + + let title_bar = pane_grid::TitleBar::new(title) + // .controls(pane_grid::Controls::dynamic( + // view_controls( + // id, + // total_panes, + // pane.is_pinned, + // is_maximized, + // ), + // button(text("X").size(14)) + // .style(button::danger) + // .padding(3) + // .on_press_maybe( + // if total_panes > 1 && !pane.is_pinned { + // Some(Message::Close(id)) + // } else { + // None + // }, + // ), + // )) + .padding(10) + .style(if is_focused { + style::title_bar_focused + } else { + style::title_bar_active + }); + + pane_grid::Content::new(responsive(move |size| { + view_content( + id, + total_panes, + false, + // pane.is_pinned, + size, hover( - // This askes the current bezier curve to display a view, and it - // will map it to the AddCurve message. - self.bezier.view(&self.curves).map(Message::AddCurve), - // If the curves vector is empty, just draw a container with a horizontal space - // inside. + self.flowsheet.view(&self.curves).map(Message::AddCurve), if self.curves.is_empty() { container(horizontal_space()) } else { - // What we are doing to do is return a button which allows - // the user to clear the screen. In this case it will have the - // "danger" style, which makes it red. It will then clear the - // screen if it is pressed. It is again given padding and aligned right. - // In this case container( button("Clear") .style(button::danger) @@ -92,13 +206,185 @@ impl Example { .align_top(Fill) }, ), - container( - button("Test text").on_press(Message::Test), - ) + ) + })) + .title_bar(title_bar) + .style(if is_focused { + style::pane_focused + } else { + style::pane_active + }) + }) + .width(Fill) + .height(Fill) + .spacing(10) + .on_click(Message::Clicked) + .on_drag(Message::Dragged) + .on_resize(10, Message::Resized); + + container( + column![ + pane_grid, ] ) .padding(20) .into() } } + +impl Default for MainWindow { + fn default() -> Self { + MainWindow::new() + } +} + +const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC7 as f32 / 255.0, + 0xC7 as f32 / 255.0, +); + +const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb( + 0xFF as f32 / 255.0, + 0x47 as f32 / 255.0, + 0x47 as f32 / 255.0, +); + +fn handle_hotkey(key: keyboard::Key) -> Option { + use keyboard::key::{self, Key}; + use pane_grid::{Axis, Direction}; + + match key.as_ref() { + Key::Character("v") => Some(Message::SplitFocused(Axis::Vertical)), + Key::Character("h") => Some(Message::SplitFocused(Axis::Horizontal)), + Key::Character("w") => Some(Message::CloseFocused), + Key::Named(key) => { + let direction = match key { + key::Named::ArrowUp => Some(Direction::Up), + key::Named::ArrowDown => Some(Direction::Down), + key::Named::ArrowLeft => Some(Direction::Left), + key::Named::ArrowRight => Some(Direction::Right), + _ => None, + }; + + direction.map(Message::FocusAdjacent) + } + _ => None, + } +} + +// #[derive(Clone,Copy,Default)] +// struct Pane { +// id: usize, +// pub is_pinned: bool, +// } +#[derive(Clone,Copy,Default)] +enum Pane { + Canvas{ + id: usize, + is_pinned: bool, + }, + #[default] + UnitSelection, +} + +impl Pane { + fn new_canvas(id: usize) -> Self { + Pane::Canvas { + id, + is_pinned: false, + } + } + + fn new_selection() -> Self { + Pane::UnitSelection + } + + // fn default() -> Self { + // Pane::Canvas { + // id: 0, + // is_pinned: false, + // } + // } +} + + +fn view_content<'a>( + _pane: pane_grid::Pane, + _total_panes: usize, + _is_pinned: bool, + size: Size, + flowsheet: Element<'a, Message>, +) -> Element<'a, Message> { + // let button = |label, message| { + // button(text(label).width(Fill).align_x(Center).size(16)) + // .width(Fill) + // .padding(8) + // .on_press(message) + // }; + + // let controls = column![ + // button( + // "Split horizontally", + // Message::Split(pane_grid::Axis::Horizontal, pane), + // ), + // button( + // "Split vertically", + // Message::Split(pane_grid::Axis::Vertical, pane), + // ) + // ] + // .push_maybe(if total_panes > 1 && !is_pinned { + // Some(button("Close", Message::Close(pane)).style(button::danger)) + // } else { + // None + // }) + // .spacing(5) + // .max_width(160); + + let content = + column![flowsheet, text!("{}x{}", size.width, size.height).size(24), ] // controls, + .spacing(10) + .align_x(Center); + + // container(scrollable(content)) + container(content) + .center_y(Fill) + .padding(5) + .into() +} + +fn view_controls<'a>( + pane: pane_grid::Pane, + total_panes: usize, + is_pinned: bool, + is_maximized: bool, +) -> Element<'a, Message> { + let row = row![].spacing(5).push_maybe(if total_panes > 1 { + let (content, message) = if is_maximized { + ("Restore", Message::Restore) + } else { + ("Maximize", Message::Maximize(pane)) + }; + + Some( + button(text(content).size(14)) + .style(button::secondary) + .padding(3) + .on_press(message), + ) + } else { + None + }); + + let close = button(text("Close").size(14)) + .style(button::danger) + .padding(3) + .on_press_maybe(if total_panes > 1 && !is_pinned { + Some(Message::Close(pane)) + } else { + None + }); + + row.push(close).into() +} diff --git a/oscps-gui/src/style.rs b/oscps-gui/src/style.rs new file mode 100644 index 0000000..3cbdb64 --- /dev/null +++ b/oscps-gui/src/style.rs @@ -0,0 +1,50 @@ +use iced::widget::container; +use iced::{Border, Theme}; + +pub fn title_bar_active(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + + container::Style { + text_color: Some(palette.background.strong.text), + background: Some(palette.background.strong.color.into()), + ..Default::default() + } +} + +pub fn title_bar_focused(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + + container::Style { + text_color: Some(palette.primary.strong.text), + background: Some(palette.primary.strong.color.into()), + ..Default::default() + } +} + +pub fn pane_active(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + + container::Style { + background: Some(palette.background.weak.color.into()), + border: Border { + width: 2.0, + color: palette.background.strong.color, + ..Border::default() + }, + ..Default::default() + } +} + +pub fn pane_focused(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + + container::Style { + background: Some(palette.background.weak.color.into()), + border: Border { + width: 2.0, + color: palette.primary.strong.color, + ..Border::default() + }, + ..Default::default() + } +} From b7a17f8f9a67e318076d824f5310f03afc9b8a09 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Tue, 17 Dec 2024 19:35:26 -0600 Subject: [PATCH 10/49] added squares partially --- oscps-gui/src/flowsheet.rs | 123 ++++++++++++++++++++++++++++++++++++- oscps-gui/src/main.rs | 7 ++- 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/oscps-gui/src/flowsheet.rs b/oscps-gui/src/flowsheet.rs index c522a1e..aafe01d 100644 --- a/oscps-gui/src/flowsheet.rs +++ b/oscps-gui/src/flowsheet.rs @@ -9,10 +9,11 @@ pub struct State { } impl State { - pub fn view<'a>(&'a self, curves: &'a [Curve]) -> Element<'a, Curve> { + pub fn view<'a>(&'a self, curves: &'a [Curve], squares: &'a [Square]) -> Element<'a, Curve> { Canvas::new(Flowsheet { state: self, curves, + squares, }) .width(Fill) .height(Fill) @@ -27,6 +28,7 @@ impl State { struct Flowsheet<'a> { state: &'a State, curves: &'a [Curve], + squares: &'a [Square], } impl<'a> canvas::Program for Flowsheet<'a> { @@ -121,6 +123,100 @@ impl<'a> canvas::Program for Flowsheet<'a> { } } +// impl<'a> canvas::Program for Flowsheet<'a> { +// type State = Option; +// fn update( +// &self, +// state: &mut Self::State, +// event: Event, +// bounds: Rectangle, +// cursor: mouse::Cursor, +// ) -> (event::Status, Option) { +// let Some(cursor_position) = cursor.position_in(bounds) else { +// return (event::Status::Ignored, None); +// }; +// match event { +// Event::Mouse(mouse_event) => { +// let message = match mouse_event { +// mouse::Event::ButtonPressed(mouse::Button::Left) => { +// match *state { +// None => { +// *state = Some(Pending::One { +// from: cursor_position, +// }); +// None +// } +// Some(Pending::One { from }) => { +// *state = Some(Pending::Two { +// from, +// to: cursor_position, +// }); +// None +// } +// Some(Pending::Two { from, to }) => { +// *state = None; +// Some(Curve { +// from, +// to, +// control: cursor_position, +// }) +// } +// } +// }, +// mouse::Event::ButtonPressed(mouse::Button::Right) => { +// None +// } +// _ => None, +// }; +// (event::Status::Captured, message) +// }, +// Event::Keyboard(_) => { +// (event::Status::Captured, None) +// } +// _ => (event::Status::Ignored, None), +// } +// } + +// fn draw( +// &self, +// state: &Self::State, +// renderer: &Renderer, +// theme: &Theme, +// bounds: Rectangle, +// cursor: mouse::Cursor, +// ) -> Vec { +// // let content = +// // self.state.cache.draw(renderer, bounds.size(), |frame| { +// // Curve::draw_all(self.curves, frame, theme); +// // frame.stroke( +// // &Path::rectangle(Point::ORIGIN, frame.size()), +// // Stroke::default() +// // .with_width(20.0) +// // .with_color(theme.palette().text), +// // ); +// // }); +// // if let Some(pending) = state { +// // vec![content, pending.draw(renderer, theme, bounds, cursor)] +// // } else { +// // vec![content] +// // } +// vec![] +// } + +// fn mouse_interaction( +// &self, +// _state: &Self::State, +// bounds: Rectangle, +// cursor: mouse::Cursor, +// ) -> mouse::Interaction { +// // if cursor.is_over(bounds) { +// // mouse::Interaction::Crosshair +// // } else { +// mouse::Interaction::default() +// // } +// } +// } + #[derive(Debug, Clone, Copy)] pub struct Curve { from: Point, @@ -146,6 +242,31 @@ impl Curve { } } +#[derive(Debug, Clone, Copy)] +pub struct Square { + from: Point, + to: Point, + control: Point, +} + +impl Square { + fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { + let curves = Path::new(|p| { + for curve in curves { + p.move_to(curve.from); + p.quadratic_curve_to(curve.control, curve.to); + } + }); + + frame.stroke( + &curves, + Stroke::default() + .with_width(2.0) + .with_color(theme.palette().text), + ); + } +} + #[derive(Debug, Clone, Copy)] enum Pending { One { from: Point }, diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index f895318..7f5c02a 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -3,7 +3,7 @@ mod style; use iced::keyboard; use iced::widget::pane_grid::{self, PaneGrid}; -use iced::widget::{button, column, container, horizontal_space, hover, responsive, row, scrollable, text}; +use iced::widget::{button, column, container, horizontal_space, hover, responsive, row, text}; use iced::{Center, Color, Element, Fill, Size, Subscription, Theme}; pub fn main() -> iced::Result { @@ -20,6 +20,7 @@ struct MainWindow { focus: Option, flowsheet: flowsheet::State, curves: Vec, + squares: Vec, } #[derive(Debug, Clone, Copy)] @@ -52,6 +53,7 @@ impl MainWindow { focus: None, flowsheet: flowsheet::State::default(), curves: Vec::default(), + squares: Vec::default(), } } @@ -105,7 +107,6 @@ impl MainWindow { Message::CloseFocused => { println!("You closed a focused pane!") }, - _ => () } } @@ -193,7 +194,7 @@ impl MainWindow { // pane.is_pinned, size, hover( - self.flowsheet.view(&self.curves).map(Message::AddCurve), + self.flowsheet.view(&self.curves, &self.squares).map(Message::AddCurve), if self.curves.is_empty() { container(horizontal_space()) } else { From 4ae142b21b1ffe481604acd91fce0e4fbc0e3631 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Tue, 17 Dec 2024 20:32:15 -0600 Subject: [PATCH 11/49] working square placement --- oscps-gui/src/flowsheet.rs | 173 +++++++++---------------------------- oscps-gui/src/main.rs | 4 +- 2 files changed, 42 insertions(+), 135 deletions(-) diff --git a/oscps-gui/src/flowsheet.rs b/oscps-gui/src/flowsheet.rs index aafe01d..397b72a 100644 --- a/oscps-gui/src/flowsheet.rs +++ b/oscps-gui/src/flowsheet.rs @@ -1,7 +1,7 @@ use iced::mouse; use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; -use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; +use iced::{Element, Fill, Point, Rectangle, Renderer, Theme, Size}; #[derive(Default)] pub struct State { @@ -9,11 +9,10 @@ pub struct State { } impl State { - pub fn view<'a>(&'a self, curves: &'a [Curve], squares: &'a [Square]) -> Element<'a, Curve> { + pub fn view<'a>(&'a self, curves: &'a [Curve]) -> Element<'a, Curve> { Canvas::new(Flowsheet { state: self, curves, - squares, }) .width(Fill) .height(Fill) @@ -28,7 +27,6 @@ impl State { struct Flowsheet<'a> { state: &'a State, curves: &'a [Curve], - squares: &'a [Square], } impl<'a> canvas::Program for Flowsheet<'a> { @@ -63,7 +61,7 @@ impl<'a> canvas::Program for Flowsheet<'a> { } Some(Pending::Two { from, to }) => { *state = None; - Some(Curve { + Some(Curve::Bezier { from, to, control: cursor_position, @@ -72,7 +70,22 @@ impl<'a> canvas::Program for Flowsheet<'a> { } }, mouse::Event::ButtonPressed(mouse::Button::Right) => { - None + match *state { + None => { + *state = Some(Pending::One { + from: cursor_position, + }); + None + } + Some(Pending::One { from }) => { + *state = None; + Some(Curve::Square { + from, + to: cursor_position, + }) + } + _ => None + } } _ => None, }; @@ -123,138 +136,34 @@ impl<'a> canvas::Program for Flowsheet<'a> { } } -// impl<'a> canvas::Program for Flowsheet<'a> { -// type State = Option; -// fn update( -// &self, -// state: &mut Self::State, -// event: Event, -// bounds: Rectangle, -// cursor: mouse::Cursor, -// ) -> (event::Status, Option) { -// let Some(cursor_position) = cursor.position_in(bounds) else { -// return (event::Status::Ignored, None); -// }; -// match event { -// Event::Mouse(mouse_event) => { -// let message = match mouse_event { -// mouse::Event::ButtonPressed(mouse::Button::Left) => { -// match *state { -// None => { -// *state = Some(Pending::One { -// from: cursor_position, -// }); -// None -// } -// Some(Pending::One { from }) => { -// *state = Some(Pending::Two { -// from, -// to: cursor_position, -// }); -// None -// } -// Some(Pending::Two { from, to }) => { -// *state = None; -// Some(Curve { -// from, -// to, -// control: cursor_position, -// }) -// } -// } -// }, -// mouse::Event::ButtonPressed(mouse::Button::Right) => { -// None -// } -// _ => None, -// }; -// (event::Status::Captured, message) -// }, -// Event::Keyboard(_) => { -// (event::Status::Captured, None) -// } -// _ => (event::Status::Ignored, None), -// } -// } - -// fn draw( -// &self, -// state: &Self::State, -// renderer: &Renderer, -// theme: &Theme, -// bounds: Rectangle, -// cursor: mouse::Cursor, -// ) -> Vec { -// // let content = -// // self.state.cache.draw(renderer, bounds.size(), |frame| { -// // Curve::draw_all(self.curves, frame, theme); -// // frame.stroke( -// // &Path::rectangle(Point::ORIGIN, frame.size()), -// // Stroke::default() -// // .with_width(20.0) -// // .with_color(theme.palette().text), -// // ); -// // }); -// // if let Some(pending) = state { -// // vec![content, pending.draw(renderer, theme, bounds, cursor)] -// // } else { -// // vec![content] -// // } -// vec![] -// } - -// fn mouse_interaction( -// &self, -// _state: &Self::State, -// bounds: Rectangle, -// cursor: mouse::Cursor, -// ) -> mouse::Interaction { -// // if cursor.is_over(bounds) { -// // mouse::Interaction::Crosshair -// // } else { -// mouse::Interaction::default() -// // } -// } -// } - #[derive(Debug, Clone, Copy)] -pub struct Curve { - from: Point, - to: Point, - control: Point, -} - -impl Curve { - fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { - let curves = Path::new(|p| { - for curve in curves { - p.move_to(curve.from); - p.quadratic_curve_to(curve.control, curve.to); - } - }); - - frame.stroke( - &curves, - Stroke::default() - .with_width(2.0) - .with_color(theme.palette().text), - ); +pub enum Curve { + Bezier { + from: Point, + to: Point, + control: Point + }, + Square { + from: Point, + to: Point, } } -#[derive(Debug, Clone, Copy)] -pub struct Square { - from: Point, - to: Point, - control: Point, -} - -impl Square { +impl Curve { fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { let curves = Path::new(|p| { for curve in curves { - p.move_to(curve.from); - p.quadratic_curve_to(curve.control, curve.to); + match curve { + Curve::Bezier{ from, to, control } => { + p.move_to(*from); + p.quadratic_curve_to(*control, *to); + } + Curve::Square{ from, to } => { + p.move_to(*from); + p.rectangle(*from, Size::new((to.x - from.x), (to.y - from.y))); + println!("Lol sqr"); + } + } } }); @@ -295,7 +204,7 @@ impl Pending { ); } Pending::Two { from, to } => { - let curve = Curve { + let curve = Curve::Bezier { from, to, control: cursor_position, diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index 7f5c02a..f860ae8 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -20,7 +20,6 @@ struct MainWindow { focus: Option, flowsheet: flowsheet::State, curves: Vec, - squares: Vec, } #[derive(Debug, Clone, Copy)] @@ -53,7 +52,6 @@ impl MainWindow { focus: None, flowsheet: flowsheet::State::default(), curves: Vec::default(), - squares: Vec::default(), } } @@ -194,7 +192,7 @@ impl MainWindow { // pane.is_pinned, size, hover( - self.flowsheet.view(&self.curves, &self.squares).map(Message::AddCurve), + self.flowsheet.view(&self.curves).map(Message::AddCurve), if self.curves.is_empty() { container(horizontal_space()) } else { From f92cfe506ee455d1e05c2ad36a57b53b082a6ea1 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Sun, 22 Dec 2024 21:08:52 -0600 Subject: [PATCH 12/49] functioning connectors --- oscps-gui/Cargo.toml | 2 + oscps-gui/src/flowsheet.rs | 257 ++++++++++++++++++++++++++++--------- oscps-gui/src/main.rs | 74 +++++++++-- 3 files changed, 261 insertions(+), 72 deletions(-) diff --git a/oscps-gui/Cargo.toml b/oscps-gui/Cargo.toml index 7727ca5..0822647 100644 --- a/oscps-gui/Cargo.toml +++ b/oscps-gui/Cargo.toml @@ -5,4 +5,6 @@ authors = ["Nathaniel Thomas ", "Bhargav Akula Self { + BlockPlacement::Default + } +} + struct Flowsheet<'a> { state: &'a State, curves: &'a [Curve], @@ -45,47 +62,77 @@ impl<'a> canvas::Program for Flowsheet<'a> { Event::Mouse(mouse_event) => { let message = match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { - match *state { - None => { - *state = Some(Pending::One { - from: cursor_position, - }); - None - } - Some(Pending::One { from }) => { - *state = Some(Pending::Two { - from, - to: cursor_position, - }); - None - } - Some(Pending::Two { from, to }) => { - *state = None; - Some(Curve::Bezier { - from, - to, - control: cursor_position, + info!("Click detected at ({})", cursor_position); + match self.state.placement_mode { + BlockPlacement::Connector => { + match *state { + None => { + info!("Beginning creation of connector..."); + let mut result = Some(Pending::One { + from: cursor_position, + }); + for curve in self.curves { + if !matches!(curve, Curve::Connector{..}) && curve.on_output_connector(cursor_position) { + info!("Connected to input!"); + result = Some(Pending::One { + from: curve.get_output_point() + }); + break; + } + } + *state = result; + None + } + Some(Pending::One { from }) => { + info!("Created connector."); + *state = None; + let mut result = Some(Curve::Connector { + from, + to: cursor_position + }); + for curve in self.curves { + if !matches!(curve, Curve::Connector{..}) && curve.on_input_connector(cursor_position) { + info!("Connected to input!"); + result = Some(Curve::Connector { + from, + to: curve.get_input_point() + }); + break; + } + } + result + } + // Some(Pending::Two { from, to }) => { + // *state = None; + // Some(Curve::Connector { + // from, + // to, + // }) + // } + } + }, + BlockPlacement::Mixer => { + let input_point = Point::new(cursor_position.x - 5.0, cursor_position.y + 50.0); + let output_point = Point::new(cursor_position.x + 105.0, cursor_position.y + 50.0); + info!("Creating mixer at ({}, {}) with input at ({}, {}) and output at ({}, {})", cursor_position.x, cursor_position.y, input_point.x, input_point.y, output_point.x, output_point.y); + Some(Curve::Mixer { + at: cursor_position, + input_point, + output_point, }) + }, + BlockPlacement::Default => { + // TODO: Add code for selecting stuff + None } } + }, + // Right click should cancel placement. mouse::Event::ButtonPressed(mouse::Button::Right) => { - match *state { - None => { - *state = Some(Pending::One { - from: cursor_position, - }); - None - } - Some(Pending::One { from }) => { - *state = None; - Some(Curve::Square { - from, - to: cursor_position, - }) - } - _ => None - } + info!("Right mouse button clicked"); + *state = None; + None } _ => None, }; @@ -138,30 +185,108 @@ impl<'a> canvas::Program for Flowsheet<'a> { #[derive(Debug, Clone, Copy)] pub enum Curve { - Bezier { + Connector { from: Point, to: Point, - control: Point }, - Square { - from: Point, - to: Point, + Mixer { + at: Point, + input_point: Point, + output_point: Point, } } impl Curve { + fn on_input_connector( + &self, + cursor_position: Point + ) -> bool { + // TODO: Fix arbitrary 5-pixel bounding box. Ideally use a circular bound. + let input = self.get_input_point(); + info!("Checking input bounds with cursor at ({}, {})", cursor_position.x, cursor_position.y); + if cursor_position.x > input.x - 5.0 && cursor_position.x < input.x + 5.0 { + debug!("Bound x match!"); + if cursor_position.y > input.y - 5.0 && cursor_position.y < input.y + 5.0 { + info!("Bounds match!"); + return true + } + } + false + } + + fn get_input_point(&self) -> Point { + return match self { + Curve::Connector{from, ..} => { + *from + }, + Curve::Mixer{input_point, ..} => { + *input_point + }, + } + } + + fn on_output_connector( + &self, + cursor_position: Point + ) -> bool { + let output = self.get_output_point(); + info!("Checking output bounds with cursor at ({}, {})", cursor_position.x, cursor_position.y); + if cursor_position.x > output.x - 5.0 && cursor_position.x < output.x + 5.0 { + debug!("Bound x match!"); + if cursor_position.y > output.y - 5.0 && cursor_position.y < output.y + 5.0 { + info!("Bounds match!"); + return true + } + } + false + } + + fn get_output_point(&self) -> Point { + return match self { + Curve::Connector{to, ..} => { + *to + }, + Curve::Mixer{output_point, ..} => { + *output_point + }, + } + } fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) { let curves = Path::new(|p| { for curve in curves { match curve { - Curve::Bezier{ from, to, control } => { + Curve::Connector{ from, to } => { + debug!("Drawing connector"); p.move_to(*from); - p.quadratic_curve_to(*control, *to); + // p.quadratic_curve_to(*control, *to); + let half_x_coord = from.x + (to.x - from.x)/2.0; + p.line_to(Point::new(half_x_coord, from.y)); + p.line_to(Point::new(half_x_coord, to.y)); + p.line_to(Point::new(to.x, to.y)); + let mut arrow_offset_x = -10.0; + let arrow_offset_y = 5.0; + if to.x < from.x { + arrow_offset_x *= -1.0; + } + p.line_to(Point::new(to.x + arrow_offset_x, to.y + arrow_offset_y)); + p.line_to(Point::new(to.x + arrow_offset_x, to.y - arrow_offset_y)); + p.line_to(Point::new(to.x, to.y)); } - Curve::Square{ from, to } => { - p.move_to(*from); - p.rectangle(*from, Size::new((to.x - from.x), (to.y - from.y))); - println!("Lol sqr"); + Curve::Mixer{at, input_point, output_point} => { + debug!("Drawing mixer."); + p.move_to(*at); + // p.rectangle(*at, Size::new(200.0, 200.0)); + let bottom_point = Point::new(at.x, at.y + 100.0); + let middle_point = Point::new(at.x + 100.0, at.y + 50.0); + p.line_to(bottom_point); + p.line_to(middle_point); + p.line_to(*at); + // Draw a circle for input connectors + p.move_to(*at); + p.circle(*input_point, 5.0); + // Another circle for output connectors + p.move_to(*at); + p.circle(*output_point, 5.0); } } } @@ -179,7 +304,7 @@ impl Curve { #[derive(Debug, Clone, Copy)] enum Pending { One { from: Point }, - Two { from: Point, to: Point }, + // Two { from: Point, to: Point }, } impl Pending { @@ -195,7 +320,24 @@ impl Pending { if let Some(cursor_position) = cursor.position_in(bounds) { match *self { Pending::One { from } => { - let line = Path::line(from, cursor_position); + let to = cursor_position; + let line = Path::new(|p| { + p.move_to(from); + // p.quadratic_curve_to(*control, *to); + let half_x_coord = from.x + (to.x - from.x)/2.0; + p.line_to(Point::new(half_x_coord, from.y)); + p.line_to(Point::new(half_x_coord, to.y)); + p.line_to(Point::new(to.x, to.y)); + + let mut arrow_offset_x = -10.0; + let arrow_offset_y = 5.0; + if to.x < from.x { + arrow_offset_x *= -1.0; + } + p.line_to(Point::new(to.x + arrow_offset_x, to.y + arrow_offset_y)); + p.line_to(Point::new(to.x + arrow_offset_x, to.y - arrow_offset_y)); + p.line_to(Point::new(to.x, to.y)); + }); frame.stroke( &line, Stroke::default() @@ -203,15 +345,14 @@ impl Pending { .with_color(theme.palette().text), ); } - Pending::Two { from, to } => { - let curve = Curve::Bezier { - from, - to, - control: cursor_position, - }; - - Curve::draw_all(&[curve], &mut frame, theme); - } + // Pending::Two { from, to } => { + // let curve = Curve::Connector { + // from, + // to, + // }; + + // Curve::draw_all(&[curve], &mut frame, theme); + // } }; } diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index f860ae8..6979f9f 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -3,18 +3,24 @@ mod style; use iced::keyboard; use iced::widget::pane_grid::{self, PaneGrid}; -use iced::widget::{button, column, container, horizontal_space, hover, responsive, row, text}; +use iced::widget::{toggler, button, column, container, horizontal_space, hover, responsive, row, text}; use iced::{Center, Color, Element, Fill, Size, Subscription, Theme}; +use log::{error, warn, info, debug, trace}; + + pub fn main() -> iced::Result { + env_logger::init(); + info!("Starting application"); iced::application("Open Source Chemical Process Simulator", MainWindow::update, MainWindow::view) - .theme(|_| Theme::Dark) + .theme(|_| Theme::CatppuccinMocha) .antialiasing(true) .centered() .run() } struct MainWindow { + theme: Theme, panes: pane_grid::State, panes_created: usize, focus: Option, @@ -26,7 +32,7 @@ struct MainWindow { enum Message { AddCurve(flowsheet::Curve), Clear, - PlaceMixer, + PlaceComponent(flowsheet::BlockPlacement), Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), FocusAdjacent(pane_grid::Direction), @@ -47,6 +53,7 @@ impl MainWindow { panes.split(pane_grid::Axis::Horizontal, pane, Pane::new_selection()); MainWindow { + theme: Theme::default(), panes, panes_created: 1, focus: None, @@ -58,6 +65,7 @@ impl MainWindow { fn update(&mut self, message: Message) { match message { Message::AddCurve(curve) => { + info!("Adding curve"); self.curves.push(curve); self.flowsheet.request_redraw(); } @@ -65,19 +73,33 @@ impl MainWindow { self.flowsheet = flowsheet::State::default(); self.curves.clear(); } - Message::PlaceMixer => { - println!("Placing Mixer!"); - } + // Default placement mode should be 'None' + Message::PlaceComponent(component) => { + match component { // TODO: Modify to do more work other than a simple assignment. + flowsheet::BlockPlacement::Default => { + info!("Setting to default placement mode."); + self.flowsheet.placement_mode = flowsheet::BlockPlacement::default(); + }, + flowsheet::BlockPlacement::Connector => { + info!("Setting to connector placement mode."); + self.flowsheet.placement_mode = flowsheet::BlockPlacement::Connector; + }, + flowsheet::BlockPlacement::Mixer => { + info!("Setting to mixer placement mode."); + self.flowsheet.placement_mode = flowsheet::BlockPlacement::Mixer; + }, + } + }, Message::Split(_axis, _pane) => { - println!("You split a pane!") + info!("You split a pane!") }, Message::SplitFocused(_axis) => { - println!("You split a focused pane!") + info!("You split a focused pane!") }, Message::FocusAdjacent(_direction) => (), Message::Clicked(pane) => { self.focus = Some(pane); - println!("You clicked on a pane!") + info!("You clicked on a pane!") }, Message::Dragged(pane_grid::DragEvent::Dropped{ pane, target }) => { // pane, target self.panes.drop(pane, target); @@ -125,16 +147,40 @@ impl MainWindow { let pane_grid = PaneGrid::new(&self.panes, |id, pane, _is_maximized| { match pane { Pane::Canvas{ id: _, is_pinned: _} => { - println!("Found canvas!"); + debug!("Found canvas!"); } Pane::UnitSelection => { - println!("Found Selection!"); + debug!("Found Selection!"); return row![ container( - button("Place Mixer").on_press(Message::PlaceMixer), + button("Place Connector") + .style( + match self.flowsheet.placement_mode { + flowsheet::BlockPlacement::Connector => button::danger, + _ => button::secondary, + } + ) + .on_press( + match self.flowsheet.placement_mode { + flowsheet::BlockPlacement::Connector => Message::PlaceComponent(flowsheet::BlockPlacement::Default), + _ => Message::PlaceComponent(flowsheet::BlockPlacement::Connector) + } + ) ), container( - button("Place Other Component"), + button("Place Mixer") + .style( + match self.flowsheet.placement_mode { + flowsheet::BlockPlacement::Mixer => button::danger, + _ => button::secondary, + } + ) + .on_press( + match self.flowsheet.placement_mode { + flowsheet::BlockPlacement::Mixer => Message::PlaceComponent(flowsheet::BlockPlacement::Default), + _ => Message::PlaceComponent(flowsheet::BlockPlacement::Mixer) + } + ) ), ].into() } @@ -149,7 +195,7 @@ impl MainWindow { let title = row![ // pin_button, - "Pane", + "Flowsheet", // text(pane.id.to_string()).color(if is_focused{ // PANE_ID_COLOR_FOCUSED // } else { From 8c0f5a96c7beac6e203cbe1159899b528c25e35b Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Mon, 23 Dec 2024 10:24:52 -0600 Subject: [PATCH 13/49] removed warnings --- oscps-gui/src/flowsheet.rs | 12 +- oscps-gui/src/main.rs | 222 ++++--------------------------------- 2 files changed, 25 insertions(+), 209 deletions(-) diff --git a/oscps-gui/src/flowsheet.rs b/oscps-gui/src/flowsheet.rs index 0f1a644..6a18a06 100644 --- a/oscps-gui/src/flowsheet.rs +++ b/oscps-gui/src/flowsheet.rs @@ -1,9 +1,9 @@ use iced::mouse; use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; -use iced::{Element, Fill, Point, Rectangle, Renderer, Theme, Size}; +use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; -use log::{error, warn, info, debug, trace}; +use log::{info, debug}; #[derive(Default)] pub struct State { @@ -345,14 +345,6 @@ impl Pending { .with_color(theme.palette().text), ); } - // Pending::Two { from, to } => { - // let curve = Curve::Connector { - // from, - // to, - // }; - - // Curve::draw_all(&[curve], &mut frame, theme); - // } }; } diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs index 6979f9f..5fc3d3c 100644 --- a/oscps-gui/src/main.rs +++ b/oscps-gui/src/main.rs @@ -1,13 +1,11 @@ mod flowsheet; mod style; -use iced::keyboard; use iced::widget::pane_grid::{self, PaneGrid}; -use iced::widget::{toggler, button, column, container, horizontal_space, hover, responsive, row, text}; -use iced::{Center, Color, Element, Fill, Size, Subscription, Theme}; - -use log::{error, warn, info, debug, trace}; +use iced::widget::{button, column, container, horizontal_space, hover, responsive, row, text}; +use iced::{Center, Element, Fill, Size, Theme}; +use log::{info, debug}; pub fn main() -> iced::Result { env_logger::init(); @@ -20,9 +18,8 @@ pub fn main() -> iced::Result { } struct MainWindow { - theme: Theme, + // theme: Theme, panes: pane_grid::State, - panes_created: usize, focus: Option, flowsheet: flowsheet::State, curves: Vec, @@ -33,29 +30,20 @@ enum Message { AddCurve(flowsheet::Curve), Clear, PlaceComponent(flowsheet::BlockPlacement), - Split(pane_grid::Axis, pane_grid::Pane), - SplitFocused(pane_grid::Axis), - FocusAdjacent(pane_grid::Direction), Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), - TogglePin(pane_grid::Pane), - Maximize(pane_grid::Pane), - Restore, - Close(pane_grid::Pane), - CloseFocused, } impl MainWindow { fn new() -> Self { - let (mut panes, pane) = pane_grid::State::new(Pane::new_canvas(0)); + let (mut panes, pane) = pane_grid::State::new(Pane::new_canvas()); panes.split(pane_grid::Axis::Horizontal, pane, Pane::new_selection()); MainWindow { - theme: Theme::default(), + // theme: Theme::default(), panes, - panes_created: 1, focus: None, flowsheet: flowsheet::State::default(), curves: Vec::default(), @@ -90,13 +78,6 @@ impl MainWindow { }, } }, - Message::Split(_axis, _pane) => { - info!("You split a pane!") - }, - Message::SplitFocused(_axis) => { - info!("You split a focused pane!") - }, - Message::FocusAdjacent(_direction) => (), Message::Clicked(pane) => { self.focus = Some(pane); info!("You clicked on a pane!") @@ -112,41 +93,18 @@ impl MainWindow { self.panes.resize(split, ratio); println!("You resized a pane!") }, - Message::TogglePin(_pane) => { - println!("You pinned a pane!") - }, - Message::Maximize(_pane) => { - println!("You maximized a pane!") - }, - Message::Restore => { - println!("You restored a window!") - }, - Message::Close(_pane) => { - println!("You closed a pane!") - }, - Message::CloseFocused => { - println!("You closed a focused pane!") - }, } } - fn subscription(&self) -> Subscription { - keyboard::on_key_press(|key_code, modifiers| { - if !modifiers.command() { - return None; - } - - handle_hotkey(key_code) - }) - } - fn view(&self) -> Element { let focus = self.focus; let total_panes = self.panes.len(); let pane_grid = PaneGrid::new(&self.panes, |id, pane, _is_maximized| { match pane { - Pane::Canvas{ id: _, is_pinned: _} => { + Pane::Canvas + // { id: _, is_pinned: _} + => { debug!("Found canvas!"); } Pane::UnitSelection => { @@ -187,42 +145,12 @@ impl MainWindow { } let is_focused = focus == Some(id); - // let pin_button = button( - // text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), - // ) - // .on_press(Message::TogglePin(id)) - // .padding(3); - let title = row![ - // pin_button, "Flowsheet", - // text(pane.id.to_string()).color(if is_focused{ - // PANE_ID_COLOR_FOCUSED - // } else { - // PANE_ID_COLOR_UNFOCUSED - // }), ] .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - // .controls(pane_grid::Controls::dynamic( - // view_controls( - // id, - // total_panes, - // pane.is_pinned, - // is_maximized, - // ), - // button(text("X").size(14)) - // .style(button::danger) - // .padding(3) - // .on_press_maybe( - // if total_panes > 1 && !pane.is_pinned { - // Some(Message::Close(id)) - // } else { - // None - // }, - // ), - // )) .padding(10) .style(if is_focused { style::title_bar_focused @@ -235,7 +163,6 @@ impl MainWindow { id, total_panes, false, - // pane.is_pinned, size, hover( self.flowsheet.view(&self.curves).map(Message::AddCurve), @@ -284,77 +211,35 @@ impl Default for MainWindow { } } -const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb( - 0xFF as f32 / 255.0, - 0xC7 as f32 / 255.0, - 0xC7 as f32 / 255.0, -); - -const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb( - 0xFF as f32 / 255.0, - 0x47 as f32 / 255.0, - 0x47 as f32 / 255.0, -); - -fn handle_hotkey(key: keyboard::Key) -> Option { - use keyboard::key::{self, Key}; - use pane_grid::{Axis, Direction}; - - match key.as_ref() { - Key::Character("v") => Some(Message::SplitFocused(Axis::Vertical)), - Key::Character("h") => Some(Message::SplitFocused(Axis::Horizontal)), - Key::Character("w") => Some(Message::CloseFocused), - Key::Named(key) => { - let direction = match key { - key::Named::ArrowUp => Some(Direction::Up), - key::Named::ArrowDown => Some(Direction::Down), - key::Named::ArrowLeft => Some(Direction::Left), - key::Named::ArrowRight => Some(Direction::Right), - _ => None, - }; - - direction.map(Message::FocusAdjacent) - } - _ => None, - } -} - -// #[derive(Clone,Copy,Default)] -// struct Pane { -// id: usize, -// pub is_pinned: bool, -// } #[derive(Clone,Copy,Default)] enum Pane { - Canvas{ - id: usize, - is_pinned: bool, - }, + Canvas + // { + // id: usize, + // is_pinned: bool, + // } + , #[default] UnitSelection, } impl Pane { - fn new_canvas(id: usize) -> Self { - Pane::Canvas { - id, - is_pinned: false, - } + fn new_canvas( + // id: usize + ) -> Self { + Pane::Canvas + // { + // id, + // is_pinned: false, + // } } fn new_selection() -> Self { Pane::UnitSelection } - // fn default() -> Self { - // Pane::Canvas { - // id: 0, - // is_pinned: false, - // } - // } } - fn view_content<'a>( _pane: pane_grid::Pane, _total_panes: usize, @@ -362,74 +247,13 @@ fn view_content<'a>( size: Size, flowsheet: Element<'a, Message>, ) -> Element<'a, Message> { - // let button = |label, message| { - // button(text(label).width(Fill).align_x(Center).size(16)) - // .width(Fill) - // .padding(8) - // .on_press(message) - // }; - - // let controls = column![ - // button( - // "Split horizontally", - // Message::Split(pane_grid::Axis::Horizontal, pane), - // ), - // button( - // "Split vertically", - // Message::Split(pane_grid::Axis::Vertical, pane), - // ) - // ] - // .push_maybe(if total_panes > 1 && !is_pinned { - // Some(button("Close", Message::Close(pane)).style(button::danger)) - // } else { - // None - // }) - // .spacing(5) - // .max_width(160); - let content = column![flowsheet, text!("{}x{}", size.width, size.height).size(24), ] // controls, .spacing(10) .align_x(Center); - // container(scrollable(content)) container(content) .center_y(Fill) .padding(5) .into() } - -fn view_controls<'a>( - pane: pane_grid::Pane, - total_panes: usize, - is_pinned: bool, - is_maximized: bool, -) -> Element<'a, Message> { - let row = row![].spacing(5).push_maybe(if total_panes > 1 { - let (content, message) = if is_maximized { - ("Restore", Message::Restore) - } else { - ("Maximize", Message::Maximize(pane)) - }; - - Some( - button(text(content).size(14)) - .style(button::secondary) - .padding(3) - .on_press(message), - ) - } else { - None - }); - - let close = button(text("Close").size(14)) - .style(button::danger) - .padding(3) - .on_press_maybe(if total_panes > 1 && !is_pinned { - Some(Message::Close(pane)) - } else { - None - }); - - row.push(close).into() -} From 103197fca8e3656b7c131bfb20add511658c7e01 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 11 Jan 2025 16:58:36 -0500 Subject: [PATCH 14/49] fixed bugs in blocks.rs after consolidating the mass and energy streams into a single struct --- oscps-lib/src/blocks.rs | 138 +++++++++++++++----------------- oscps-lib/src/thermodynamics.rs | 7 +- 2 files changed, 70 insertions(+), 75 deletions(-) diff --git a/oscps-lib/src/blocks.rs b/oscps-lib/src/blocks.rs index f54846c..07933ea 100644 --- a/oscps-lib/src/blocks.rs +++ b/oscps-lib/src/blocks.rs @@ -6,6 +6,7 @@ //! For example, if a block is a simple mixer, then it will implement the //! MassBalance trait but not the EnergyBalance. +use crate::connector; use crate::connector::Stream; use once_cell::sync::Lazy; use uom::si::energy::joule; @@ -77,14 +78,10 @@ pub struct Mixer { pub x_cord: i32, /// The y-coordinate on a flowsheet of the block. pub y_cord: i32, - /// All mass input streams for the block. - pub input_streams_mass: Vec, - /// All energy input streams for the block. - pub input_streams_energy: Vec, - /// All mass output streams for the block. - pub outlet_stream_mass: Option, - /// All energy output streams for the block - pub outlet_stream_energy: Option, + /// Set of inlet streams for the mixer + pub inlet_streams : Vec, + /// outlet stream for the mixer block + pub outlet_stream : Option } /// Applying mass balance trait to Mixer Block @@ -101,31 +98,32 @@ impl Mixer { id: String, x_cord: i32, y_cord: i32, - in_streams_mass: Vec, - in_streams_energy: Vec, + in_streams: Vec, ) -> Mixer { Mixer { block_id: id, - x_cord, - y_cord, - input_streams_mass: in_streams_mass, - input_streams_energy: in_streams_energy, - outlet_stream_mass: None, - outlet_stream_energy: None, + x_cord: x_cord, + y_cord: y_cord, + inlet_streams: in_streams, + outlet_stream: None + } } /// Execute the mixer block (calculate balances, output streams, etc) - /// TODO: This block should calculate the new state of external connectors. + /// This function still needs to be implemented pub fn execute_block(&mut self) { - self.outlet_stream_mass = Some(connector::Mconnector { - m_conn_id: String::from("Mass_Outlet"), - m_flow_total: self.compute_total_outlet_mass_flow().unwrap(), - }); - self.outlet_stream_energy = Some(connector::Econnector { - e_conn_id: String::from("Energy Outlet"), - energy_flow_total: self.compute_outlet_energy_flows().unwrap(), + self.outlet_stream = Some(connector::Stream { + s_id: String::from("Mass_Outlet"), + thermo: None, + from_block: String::from("M1"), + to_block: String::from("M2") + // m_flow_total: self.compute_total_outlet_mass_flow().unwrap(), }); + // self.outlet_stream_energy = Some(connector::Econnector { + // e_conn_id: String::from("Energy Outlet"), + // energy_flow_total: self.compute_outlet_energy_flows().unwrap(), + // }); } /// This private method will compute the outlet mass flows for the mixer block @@ -140,8 +138,8 @@ impl Mixer { // Use the UOM package to help with this part... let mut mass_flow_sum: f64 = 0.0; - for stream in &self.input_streams_mass { - mass_flow_sum += stream.m_flow_total; + for s in self.inlet_streams.iter() { + mass_flow_sum += s.thermo.as_ref().unwrap().total_mass(); } Some(mass_flow_sum) } @@ -150,8 +148,8 @@ impl Mixer { fn compute_outlet_energy_flows(&self) -> Option { let mut energy_flow_sum: f64 = 0.0; - for stream in &self.input_streams_energy { - energy_flow_sum += stream.energy_flow_total; + for s in self.inlet_streams.iter() { + energy_flow_sum += s.thermo.as_ref().unwrap().enthalpy(); } Some(energy_flow_sum) } @@ -174,7 +172,7 @@ impl Mixer { /// The following module holds all the unit test cases for the blocks module #[cfg(test)] mod block_tests { - use crate::connector::{Econnector, Mconnector}; + use crate::connector::Stream; use super::*; use uom::si::energy::kilojoule; @@ -190,10 +188,8 @@ mod block_tests { block_id: String::from("Test Mixer"), x_cord: 0, y_cord: 0, - input_streams_mass: Vec::new(), - input_streams_energy: Vec::new(), - outlet_stream_mass: None, - outlet_stream_energy: None, + inlet_streams: Vec::new(), + outlet_stream: None, }; let mass_in = Mass::new::(100.0); let mass_out = Mass::new::(95.0); @@ -208,49 +204,47 @@ mod block_tests { block_id: String::from("Test Mixer"), x_cord: 0, y_cord: 0, - input_streams_mass: Vec::new(), - input_streams_energy: Vec::new(), - outlet_stream_mass: None, - outlet_stream_energy: None, + inlet_streams: Vec::new(), + outlet_stream: None, }; let energy_in = Energy::new::(10.0); let energy_out = Energy::new::(95.0); assert!(mixer_test_obj.energy_balance_check(energy_in, energy_out)); } - #[test] - /// checking functionality of 'compute_total_outlet_mass_flow' - fn test_compute_total_outlet_mass_flow() { - let in_streams_mass = vec![ - Mconnector { - m_conn_id: String::from("Mass1"), - m_flow_total: 3.0, - }, - Mconnector { - m_conn_id: String::from("Mass2"), - m_flow_total: 7.0, - }, - ]; - let mixer = Mixer::new(String::from("Mixer3"), 0, 0, in_streams_mass, vec![]); - - assert_eq!(mixer.compute_total_outlet_mass_flow(), Some(10.0)); - } - - #[test] - /// checking functionality of 'compute_outlet_energy_flows' - fn test_compute_outlet_energy_flows() { - let in_streams_energy = vec![ - Econnector { - e_conn_id: String::from("Energy1"), - energy_flow_total: 100.0, - }, - Econnector { - e_conn_id: String::from("Energy2"), - energy_flow_total: 200.0, - }, - ]; - let mixer = Mixer::new(String::from("Mixer5"), 0, 0, vec![], in_streams_energy); - - assert_eq!(mixer.compute_outlet_energy_flows(), Some(300.0)); - } + // #[test] + // checking functionality of 'compute_total_outlet_mass_flow' + // fn test_compute_total_outlet_mass_flow() { + // let in_streams_mass = vec![ + // Mconnector { + // m_conn_id: String::from("Mass1"), + // m_flow_total: 3.0, + // }, + // Mconnector { + // m_conn_id: String::from("Mass2"), + // m_flow_total: 7.0, + // }, + // ]; + // let mixer = Mixer::new(String::from("Mixer3"), 0, 0, in_streams_mass, vec![]); + + // assert_eq!(mixer.compute_total_outlet_mass_flow(), Some(10.0)); + // } + + // #[test] + // /// checking functionality of 'compute_outlet_energy_flows' + // fn test_compute_outlet_energy_flows() { + // let in_streams_energy = vec![ + // Econnector { + // e_conn_id: String::from("Energy1"), + // energy_flow_total: 100.0, + // }, + // Econnector { + // e_conn_id: String::from("Energy2"), + // energy_flow_total: 200.0, + // }, + // ]; + // let mixer = Mixer::new(String::from("Mixer5"), 0, 0, vec![], in_streams_energy); + + // assert_eq!(mixer.compute_outlet_energy_flows(), Some(300.0)); + // } } diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 3e2fd68..438ac43 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -128,7 +128,7 @@ impl ThermoState { } /// this function will return the total mass for an individual stream - pub fn total_mass(&self) -> f64 { + pub fn total_mass(& self) -> f64 { let mut mass_sum = 0.0; for chem in &self.mass_list { mass_sum += chem.mass_quantity.get::(); @@ -137,8 +137,9 @@ impl ThermoState { } /// This function will provide the enthalpy of an individual stream - pub fn enthalpy() { - + pub fn enthalpy(& self) -> f64 { + let mut energy_num = 0.0; + energy_num } } From c28fb6f6e5902c4a7b7b393260a28fe71697d524 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 11 Jan 2025 17:26:14 -0500 Subject: [PATCH 15/49] updating branch with minor modification with naming convention in thermodynamics.rs --- oscps-lib/src/thermodynamics.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 438ac43..e2b9677 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -61,7 +61,7 @@ impl ThermodynamicConstants { #[allow(dead_code)] /// Species list -pub struct SpeciesListPair { +pub struct SpeciesQuantityPair { /// Chemical species pub chemical_species: Chemical, /// Mass quantity @@ -79,7 +79,7 @@ pub struct ThermoState { /// Temperature of the state. pub temperature: ThermodynamicTemperature, // Temperature in Kelvin /// List of mole fractions. - pub mass_list: Vec, // Mole fractions, typically unitless + pub mass_list: Vec, // Mole fractions, typically unitless } @@ -91,7 +91,7 @@ impl ThermoState { pub fn new( pressure: f64, // in Pascals temperature: f64, // in Kelvin - mass_list: Vec, + mass_list: Vec, ) -> Self { ThermoState { pressure: Pressure::new::(pressure), @@ -168,7 +168,7 @@ mod thermo_tests { }; thread::sleep(Duration::from_secs(10)); let water_mass = Mass::new::(2.0); - let water_species_pair = SpeciesListPair { + let water_species_pair = SpeciesQuantityPair { chemical_species: water, mass_quantity: water_mass, }; @@ -224,13 +224,13 @@ mod thermo_tests { thread::sleep(Duration::from_secs(10)); let water_mass = Mass::new::(2.0); - let water_species_pair = SpeciesListPair { + let water_species_pair = SpeciesQuantityPair { chemical_species: water, mass_quantity: water_mass, }; let anisidine_mass = Mass::new::(8.0); - let anisidine_species_pair = SpeciesListPair { + let anisidine_species_pair = SpeciesQuantityPair { chemical_species: anisdine, mass_quantity: anisidine_mass, }; From dac5151c985611d9cb2c16a98dc732db5d30dba4 Mon Sep 17 00:00:00 2001 From: Nathaniel Thomas Date: Sun, 12 Jan 2025 09:53:19 -0600 Subject: [PATCH 16/49] modified comments --- oscps-lib/src/connector.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/oscps-lib/src/connector.rs b/oscps-lib/src/connector.rs index d7e2750..fa67621 100644 --- a/oscps-lib/src/connector.rs +++ b/oscps-lib/src/connector.rs @@ -7,13 +7,19 @@ use crate::thermodynamics::ThermoState; /// /// Struct to hold stream information pub struct Stream { - /// Stream id + // TODO: IDs must be unique within the flowsheet. Consider using integers + // as IDs and having a separate field for the name of a connector. Adopt + // a similar scheme for blocks. + /// ID of the stream. pub s_id : String, - /// Instance of ThermoState struct that holds + /// Instance of ThermoState struct that holds thermodynamic information. pub thermo : Option, - /// block id for where the stream is coming from + // TODO: Change these from strings to integers, or better yet, + // references to the source and destination blocks, to minimize + // computation time spent on looking for sources and destinations. + /// ID of source block pub from_block : String, - /// block id for where the stream is going to + /// ID of destination block pub to_block : String } From c213ed6a5743e2d61e3992e3d93d9f6c367d8bcb Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 13 Jan 2025 19:55:09 -0500 Subject: [PATCH 17/49] updating branch --- oscps-lib/src/blocks.rs | 6 +++--- oscps-lib/src/thermodynamics.rs | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/oscps-lib/src/blocks.rs b/oscps-lib/src/blocks.rs index 07933ea..9b3f360 100644 --- a/oscps-lib/src/blocks.rs +++ b/oscps-lib/src/blocks.rs @@ -102,8 +102,8 @@ impl Mixer { ) -> Mixer { Mixer { block_id: id, - x_cord: x_cord, - y_cord: y_cord, + x_cord, + y_cord, inlet_streams: in_streams, outlet_stream: None @@ -172,7 +172,7 @@ impl Mixer { /// The following module holds all the unit test cases for the blocks module #[cfg(test)] mod block_tests { - use crate::connector::Stream; + // use crate::connector::Stream; use super::*; use uom::si::energy::kilojoule; diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index e2b9677..7f909dc 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -135,11 +135,26 @@ impl ThermoState { } mass_sum } + + /// This function will provide the enthalpy of an individual stream - pub fn enthalpy(& self) -> f64 { - let mut energy_num = 0.0; - energy_num + pub fn enthalpy(&self) -> f64 { + let mut total_enthalpy = 0.0; + + // Need to run a for loop where I calculate the enthalpy of each species and then add it to + // the variable 'total_enthalpy' + // ASSUMPTIONS CURRENTLY MADE: + // No enthalpy from phase change or pressure changes + // when working with gases, assume that they are ideal gases + // Tref = 298 K & Pref = 101.325 kPa + // Href = 0 + + for chem in &self.mass_list { + // let species_enthalpy = + } + + total_enthalpy } } From 8acbae2f2c497112f95c674293d9e87a8e905d71 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 22 Feb 2025 17:42:44 -0500 Subject: [PATCH 18/49] added enthalpy function into the thermodynamics module --- oscps-lib/src/thermodynamics.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 7f909dc..0ea0306 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -8,9 +8,10 @@ //! implemented in the future. use crate::component::Chemical; -use uom::si::f64::*; +use uom::si::{f64::*, Quantity}; use uom::si::mass::kilogram; use uom::si::pressure::pascal; +// use uom::si::temperature_interval::kelvin; use uom::si::thermodynamic_temperature::kelvin; #[allow(dead_code)] @@ -66,6 +67,12 @@ pub struct SpeciesQuantityPair { pub chemical_species: Chemical, /// Mass quantity pub mass_quantity: Mass, + + ///Heat capacity constants (for enthalpy calculations) + pub const_a: f64, + pub const_b: f64, + pub const_c: f64, + pub const_d: f64 } #[allow(dead_code)] @@ -136,11 +143,12 @@ impl ThermoState { mass_sum } - - /// This function will provide the enthalpy of an individual stream pub fn enthalpy(&self) -> f64 { let mut total_enthalpy = 0.0; + let t_ref = 298.15; //reference temperature + let h_ref = 0.0; //Reference enthalpy + // Need to run a for loop where I calculate the enthalpy of each species and then add it to // the variable 'total_enthalpy' @@ -151,7 +159,18 @@ impl ThermoState { // Href = 0 for chem in &self.mass_list { - // let species_enthalpy = + let mut cp_ref = 0.0; + let mut cp_t = 0.0; + if(chem.const_c != 0.0){ + cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); + } + else{ + cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); + } + let species_enthalpy = h_ref + (cp_t - cp_ref); + total_enthalpy += species_enthalpy; } total_enthalpy @@ -186,6 +205,10 @@ mod thermo_tests { let water_species_pair = SpeciesQuantityPair { chemical_species: water, mass_quantity: water_mass, + const_a: 1.0, + const_b: 1.0, + const_c: 1.0, + const_d: 0.0 }; // Create ThermoState From 5c12001f97f8d589fde7448a754574d0f8952a99 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 22 Feb 2025 19:15:08 -0500 Subject: [PATCH 19/49] minor changes to the enthalpy function --- oscps-lib/src/thermodynamics.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 0ea0306..7d19d16 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -11,7 +11,6 @@ use crate::component::Chemical; use uom::si::{f64::*, Quantity}; use uom::si::mass::kilogram; use uom::si::pressure::pascal; -// use uom::si::temperature_interval::kelvin; use uom::si::thermodynamic_temperature::kelvin; #[allow(dead_code)] @@ -144,24 +143,24 @@ impl ThermoState { } /// This function will provide the enthalpy of an individual stream + #[warn(unused_assignments)] pub fn enthalpy(&self) -> f64 { let mut total_enthalpy = 0.0; let t_ref = 298.15; //reference temperature let h_ref = 0.0; //Reference enthalpy - + let mut cp_ref; + let mut cp_t; // Need to run a for loop where I calculate the enthalpy of each species and then add it to // the variable 'total_enthalpy' // ASSUMPTIONS CURRENTLY MADE: - // No enthalpy from phase change or pressure changes + // No enthalpy from phase change // when working with gases, assume that they are ideal gases // Tref = 298 K & Pref = 101.325 kPa // Href = 0 for chem in &self.mass_list { - let mut cp_ref = 0.0; - let mut cp_t = 0.0; - if(chem.const_c != 0.0){ + if chem.const_c != 0.0 { cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); } From cc7be316fde595d1da692a33375fd3c45b41508d Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 15 Mar 2025 11:50:11 -0400 Subject: [PATCH 20/49] Added ThermoPackage trait This trait will be implemented in each thermodynamic package (the thermodynamic packages will be structs). This will allow the user to switch between thermodynamic packages. --- oscps-lib/src/thermodynamics.rs | 71 +++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 7d19d16..0a751fc 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -8,10 +8,12 @@ //! implemented in the future. use crate::component::Chemical; -use uom::si::{f64::*, Quantity}; -use uom::si::mass::kilogram; -use uom::si::pressure::pascal; -use uom::si::thermodynamic_temperature::kelvin; +use uom::si::f64::*; +use uom::si::mass; +use uom::si::pressure; +use uom::si::thermodynamic_temperature; +use uom::si::energy; +use uom::si::amount_of_substance::mole; #[allow(dead_code)] /// Struct for storing physical constants for thermodynamics. @@ -45,13 +47,13 @@ impl ThermodynamicConstants { pub fn value(&self) -> ConstantValue { match self { ThermodynamicConstants::UniversalGasConstant => { - ConstantValue::Pressure(Pressure::new::(8.314462618)) + ConstantValue::Pressure(Pressure::new::(8.314462618)) } ThermodynamicConstants::StandardTemperature => { - ConstantValue::Temperature(ThermodynamicTemperature::new::(273.15)) + ConstantValue::Temperature(ThermodynamicTemperature::new::(273.15)) } ThermodynamicConstants::StandardPressure => { - ConstantValue::Pressure(Pressure::new::(101_325.0)) + ConstantValue::Pressure(Pressure::new::(101_325.0)) } ThermodynamicConstants::AvogadroNumber => ConstantValue::Dimensionless(6.02214076e23), } @@ -66,11 +68,13 @@ pub struct SpeciesQuantityPair { pub chemical_species: Chemical, /// Mass quantity pub mass_quantity: Mass, - - ///Heat capacity constants (for enthalpy calculations) + ///Heat capacity Coefficient A pub const_a: f64, + ///Heat capacity Coefficient B pub const_b: f64, + ///Heat capacity Coefficient C pub const_c: f64, + ///Heat capacity Coefficient D pub const_d: f64 } @@ -100,8 +104,8 @@ impl ThermoState { mass_list: Vec, ) -> Self { ThermoState { - pressure: Pressure::new::(pressure), - temperature: ThermodynamicTemperature::new::(temperature), + pressure: Pressure::new::(pressure), + temperature: ThermodynamicTemperature::new::(temperature), mass_list, } } @@ -112,11 +116,11 @@ impl ThermoState { let mut component_mass = 0.0; for chem in &self.mass_list { - total_mass += chem.mass_quantity.get::(); + total_mass += chem.mass_quantity.get::(); if let Some(cids) = Some(chem.chemical_species.pubchem_obj.cids().unwrap()[0]) { if cids == species.pubchem_obj.cids().unwrap_or_default()[0] { - component_mass = chem.mass_quantity.get::(); + component_mass = chem.mass_quantity.get::(); } } } @@ -137,7 +141,7 @@ impl ThermoState { pub fn total_mass(& self) -> f64 { let mut mass_sum = 0.0; for chem in &self.mass_list { - mass_sum += chem.mass_quantity.get::(); + mass_sum += chem.mass_quantity.get::(); } mass_sum } @@ -162,11 +166,11 @@ impl ThermoState { for chem in &self.mass_list { if chem.const_c != 0.0 { cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); - cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); } else{ cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); - cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); } let species_enthalpy = h_ref + (cp_t - cp_ref); total_enthalpy += species_enthalpy; @@ -176,6 +180,41 @@ impl ThermoState { } } +///Thermodynamic Packages. +/// +///#ThermoPackage +///Will be a common trait for all the thermodynamic packages +///Will include functions common to thermodynamic packages +///Will also enable to user to switch between thermodynamic packages within the ThermoState struct +///(the thermodynamic packages will be structs) + +pub trait ThermoPackage{ + ///Calculating the Enthalpy + fn enthalpy(&self) -> Energy; + ///Calculating the Entropy + fn entropy(&self) -> Energy; + /// Calculate amount of moles + fn calculate_moles(&self) -> mole; + ///Calculate pressure + fn pressure(&self) -> Pressure; + ///Calculate temperature + fn temperature(&self) -> ThermodynamicTemperature; + ///Calculate volume + fn volume(&self) -> Volume; + ///Calculate heat capacity + fn heat_capacity(&self) -> HeatCapacity; + ///Calculate internal temperature + fn internal_energy(&self) -> Energy; + ///Calculate gibbs free energy + fn gibbs_free_energy(&self) -> Energy; +} + +///#IdealThermoPackage +/// +///Will contain the ideal thermodynamic equations for very basic thermodynamic modelling + + + #[cfg(test)] mod thermo_tests { From 558c5bfaaa4393c85abc03532b6e38a50b87b891 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 15 Mar 2025 13:58:47 -0400 Subject: [PATCH 21/49] Added enthalpy and pressure calculation methods to the IdealGasPackage --- oscps-lib/src/thermodynamics.rs | 145 ++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 62 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 0a751fc..f86abe5 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -10,10 +10,13 @@ use crate::component::Chemical; use uom::si::f64::*; use uom::si::mass; +use uom::si::molar_heat_capacity; use uom::si::pressure; use uom::si::thermodynamic_temperature; use uom::si::energy; -use uom::si::amount_of_substance::mole; +use uom::si::amount_of_substance; +use uom::si::volume; + #[allow(dead_code)] /// Struct for storing physical constants for thermodynamics. @@ -29,33 +32,24 @@ pub enum ThermodynamicConstants { AvogadroNumber, // N_A } -#[allow(dead_code)] -/// Enum for representing different types of thermodynamic constant values -pub enum ConstantValue { - /// Pressure value - Pressure(Pressure), - /// Temperature value - Temperature(ThermodynamicTemperature), - /// Dimensionless value - Dimensionless(f64), -} - #[allow(dead_code)] /// Implements values of thermodynamic constants. impl ThermodynamicConstants { /// Returns the value of the thermodynamic constant with its appropriate type. - pub fn value(&self) -> ConstantValue { + pub fn value(&self) -> Box { match self { ThermodynamicConstants::UniversalGasConstant => { - ConstantValue::Pressure(Pressure::new::(8.314462618)) - } + let r = 8.314462618; + let constant = Energy::new::(r) / (ThermodynamicTemperature::new::(1.0)* AmountOfSubstance::new::(1.0)); + Box::new(constant) + }, ThermodynamicConstants::StandardTemperature => { - ConstantValue::Temperature(ThermodynamicTemperature::new::(273.15)) + Box::new(ThermodynamicTemperature::new::(273.15)) } ThermodynamicConstants::StandardPressure => { - ConstantValue::Pressure(Pressure::new::(101_325.0)) - } - ThermodynamicConstants::AvogadroNumber => ConstantValue::Dimensionless(6.02214076e23), + Box::new(Pressure::new::(101_325.0)) + }, + ThermodynamicConstants::AvogadroNumber => Box::new(6.02214076e23), //Units: particles/mole } } } @@ -68,6 +62,10 @@ pub struct SpeciesQuantityPair { pub chemical_species: Chemical, /// Mass quantity pub mass_quantity: Mass, + /// Molar quantity + pub molar_quantity: AmountOfSubstance, + ///volumetric quantity + pub vol_quantity: Volume, ///Heat capacity Coefficient A pub const_a: f64, ///Heat capacity Coefficient B @@ -130,13 +128,6 @@ impl ThermoState { _ => Some(component_mass / total_mass), } } - - /// Determine ideal gas pressure - fn ideal_gas_pressure(&self, n: f64, t: f64, v: f64) -> f64 { - const R: f64 = 8.314; // J/(mol·K) - (n * R * t) / v - } - /// this function will return the total mass for an individual stream pub fn total_mass(& self) -> f64 { let mut mass_sum = 0.0; @@ -145,39 +136,6 @@ impl ThermoState { } mass_sum } - - /// This function will provide the enthalpy of an individual stream - #[warn(unused_assignments)] - pub fn enthalpy(&self) -> f64 { - let mut total_enthalpy = 0.0; - let t_ref = 298.15; //reference temperature - let h_ref = 0.0; //Reference enthalpy - let mut cp_ref; - let mut cp_t; - - // Need to run a for loop where I calculate the enthalpy of each species and then add it to - // the variable 'total_enthalpy' - // ASSUMPTIONS CURRENTLY MADE: - // No enthalpy from phase change - // when working with gases, assume that they are ideal gases - // Tref = 298 K & Pref = 101.325 kPa - // Href = 0 - - for chem in &self.mass_list { - if chem.const_c != 0.0 { - cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); - cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); - } - else{ - cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); - cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); - } - let species_enthalpy = h_ref + (cp_t - cp_ref); - total_enthalpy += species_enthalpy; - } - - total_enthalpy - } } ///Thermodynamic Packages. @@ -194,7 +152,7 @@ pub trait ThermoPackage{ ///Calculating the Entropy fn entropy(&self) -> Energy; /// Calculate amount of moles - fn calculate_moles(&self) -> mole; + fn calculate_moles(&self) -> AmountOfSubstance; ///Calculate pressure fn pressure(&self) -> Pressure; ///Calculate temperature @@ -209,9 +167,72 @@ pub trait ThermoPackage{ fn gibbs_free_energy(&self) -> Energy; } -///#IdealThermoPackage +///#IdealGasPackage /// -///Will contain the ideal thermodynamic equations for very basic thermodynamic modelling +///Will contain equations related to ideal gases + +pub struct IdealGasPackage { + pub temperature : ThermodynamicTemperature, + pub pressure : Pressure, + pub species_list : Vec, + pub total_mass : Mass, + pub total_vol : Volume, + pub total_mol : AmountOfSubstance +} +///Implementing functions specific to the IdealGasPackage +impl IdealGasPackage { + ///Constructor + pub fn new(temperature: ThermodynamicTemperature, pressure : Pressure, species_list : Vec, total_mass : Mass, total_vol : Volume, total_mol : AmountOfSubstance) -> IdealGasPackage { + IdealGasPackage { + temperature, + pressure, + species_list, + total_mass, + total_vol, + total_mol + } + } +} +/// Implementing the ThermoPackage trait for the IdealGasPackage +impl ThermoPackage for IdealGasPackage { + ///Calculating enthalpy + fn enthalpy(&self) -> Energy { + let mut total_enthalpy = 0.0; + let t_ref = 298.15; //reference temperature + let h_ref = 0.0; //Reference enthalpy + let mut cp_ref; + let mut cp_t; + + // Need to run a for loop where I calculate the enthalpy of each species and then add it to + // the variable 'total_enthalpy' + // ASSUMPTIONS CURRENTLY MADE: + // No enthalpy from phase change + // when working with gases, assume that they are ideal gases + // Tref = 298 K & Pref = 101.325 kPa + // Href = 0 + + for chem in &self.species_list { + if chem.const_c != 0.0 { + cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); + } + else{ + cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); + } + let species_enthalpy = h_ref + (cp_t - cp_ref); + total_enthalpy += species_enthalpy; + } + + Energy::new::(total_enthalpy) + } + /// Determine ideal gas pressure + fn pressure(&self) -> Pressure { + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); + let ideal_pressure = (self.total_mol.get::() * r.get::() * self.temperature.get::()) / (self.total_vol.get::()); + Pressure::new::(ideal_pressure) + } +} From d51e5eacdbc50e4bde9412025fdd1afce6300dd2 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 15 Mar 2025 20:51:46 -0400 Subject: [PATCH 22/49] Added property within 'ThermoState' struct to hold the thermodynamics package This will allow users to change thermodynamics package for blocks and streams --- oscps-lib/src/thermodynamics.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index f86abe5..9deb3d4 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -88,6 +88,8 @@ pub struct ThermoState { pub temperature: ThermodynamicTemperature, // Temperature in Kelvin /// List of mole fractions. pub mass_list: Vec, // Mole fractions, typically unitless + ///Thermo Package + pub thermodynamic_package : Box } @@ -100,11 +102,13 @@ impl ThermoState { pressure: f64, // in Pascals temperature: f64, // in Kelvin mass_list: Vec, + thermo_package : Box ) -> Self { ThermoState { pressure: Pressure::new::(pressure), temperature: ThermodynamicTemperature::new::(temperature), mass_list, + thermodynamic_package : thermo_package } } From 8d6286e38e775e1a1cd6c6e85551c3c8c8288d6f Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sun, 16 Mar 2025 07:23:45 -0400 Subject: [PATCH 23/49] Fixed errors within blocks.rs file --- oscps-lib/src/blocks.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/oscps-lib/src/blocks.rs b/oscps-lib/src/blocks.rs index 9b3f360..1b1908e 100644 --- a/oscps-lib/src/blocks.rs +++ b/oscps-lib/src/blocks.rs @@ -9,22 +9,26 @@ use crate::connector; use crate::connector::Stream; use once_cell::sync::Lazy; -use uom::si::energy::joule; -use uom::si::f64::Energy; -use uom::si::f64::Mass; -use uom::si::mass::kilogram; +use uom::si::f64::*; +use uom::si::mass; +use uom::si::molar_heat_capacity; +use uom::si::pressure; +use uom::si::thermodynamic_temperature; +use uom::si::energy; +use uom::si::amount_of_substance; +use uom::si::volume; #[allow(dead_code)] /// Minimum error allowed for energy difference. /// TODO: Change this to a relative scale instead of an absolute scale. pub static TOLERENCE_ENERGY: Lazy = - Lazy::new(|| Energy::new::(5.0)); + Lazy::new(|| Energy::new::(5.0)); #[allow(dead_code)] /// Minimum error allowed for mass difference. /// TODO: Change this to a relative scale instead of an absolute scale. pub static TOLERENCE_MASS: Lazy = - Lazy::new(|| Mass::new::(5.0)); + Lazy::new(|| Mass::new::(5.0)); #[allow(dead_code)] /// # MassBalance @@ -40,10 +44,10 @@ pub trait MassBalance { /// fraction of the total inlet mass. This can be an adjustable parameter. /// Smaller takes longer to converge, but is more fn mass_balance_check(&self, mass_in: Mass, mass_out: Mass) -> bool { - let mass_in_kg = mass_in.get::(); - let mass_out_kg = mass_out.get::(); + let mass_in_kg = mass_in.get::(); + let mass_out_kg = mass_out.get::(); let mass_difference = mass_in_kg - mass_out_kg; - mass_difference <= TOLERENCE_MASS.get::() + mass_difference <= TOLERENCE_MASS.get::() } } @@ -60,10 +64,10 @@ pub trait EnergyBalance { /// Also implement changes in issue #19. fn energy_balance_check(&self, energy_in: Energy, energy_out: Energy) -> bool { - let energy_in_joules = energy_in.get::(); - let energy_out_joules = energy_out.get::(); + let energy_in_joules = energy_in.get::(); + let energy_out_joules = energy_out.get::(); let energy_difference = energy_in_joules - energy_out_joules; - energy_difference <= TOLERENCE_ENERGY.get::() + energy_difference <= TOLERENCE_ENERGY.get::() } } @@ -149,7 +153,7 @@ impl Mixer { let mut energy_flow_sum: f64 = 0.0; for s in self.inlet_streams.iter() { - energy_flow_sum += s.thermo.as_ref().unwrap().enthalpy(); + energy_flow_sum += (*(s.thermo.as_ref().unwrap().thermodynamic_package)).enthalpy().get::(); } Some(energy_flow_sum) } From c7c1d90d8e94398a06bbf79805ceb3b981b4c4f9 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sun, 16 Mar 2025 07:29:49 -0400 Subject: [PATCH 24/49] updated readme document --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6309cd0..cf35226 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Rust Tests](https://github.com/OSCPS-Project/OSCPS/actions/workflows/rust-tests.yml/badge.svg?branch=develop) ![Documentation](https://github.com/OSCPS-Project/OSCPS/actions/workflows/check-docs.yml/badge.svg?branch=develop) -Developing a dynamic & steady-state chemical process simulator using a Rust backend and a iced-rs frontend. This project aims to create a much better version of ASPEN that will also be open-sourced. +Developing a dynamic & steady-state chemical process simulator using a Rust-based backend and frontend. This project aims to create a much better version of ASPEN that will also be open-sourced. ## Authors From f233ed29f503f00182024227413c9877b26b6bbc Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sun, 16 Mar 2025 10:30:46 -0400 Subject: [PATCH 25/49] fixing github workflow for rust-tests --- .github/workflows/rust-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index bd4aeb0..a90fa14 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -45,7 +45,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - libwebkit2gtk-4.0-dev \ + # libwebkit2gtk-4.0-dev \ build-essential \ curl \ wget \ From 84952d188b4bdeebe625d4fad2a0395989ee9ac0 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sun, 16 Mar 2025 10:32:28 -0400 Subject: [PATCH 26/49] fixing github workflow for rust-tests --- .github/workflows/rust-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index a90fa14..edc52b2 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -45,7 +45,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - # libwebkit2gtk-4.0-dev \ build-essential \ curl \ wget \ From e4f159ae6f8238bb14d9fe50512a8674bcec2212 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sun, 16 Mar 2025 10:39:16 -0400 Subject: [PATCH 27/49] removing 'libwebkit2gtk-4.0-dev/' the check-docs workflow to help it properly run --- .github/workflows/check-docs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index a10f942..f514629 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -40,7 +40,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - libwebkit2gtk-4.0-dev \ build-essential \ curl \ wget \ From eb931ffe63d820ec4a112fe4683e52570c3add85 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 17 Mar 2025 18:14:06 -0400 Subject: [PATCH 28/49] Reorganizing library files for thermo package moved the IdealGasPackage struct into its own file and made it into a submodule of the thermodynamics module. In the future, as more packages are implemented, one simply needs to create another sub-module when implementing a new thermodynamics package --- oscps-lib/src/thermodynamics.rs | 77 +++---------------- .../src/thermodynamics/ideal_gas_package.rs | 77 +++++++++++++++++++ 2 files changed, 86 insertions(+), 68 deletions(-) create mode 100644 oscps-lib/src/thermodynamics/ideal_gas_package.rs diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 9deb3d4..3a73d37 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -7,7 +7,9 @@ //! documentation is in place, but more descriptive documentation should be //! implemented in the future. +pub mod ideal_gas_package; use crate::component::Chemical; + use uom::si::f64::*; use uom::si::mass; use uom::si::molar_heat_capacity; @@ -87,7 +89,13 @@ pub struct ThermoState { /// Temperature of the state. pub temperature: ThermodynamicTemperature, // Temperature in Kelvin /// List of mole fractions. - pub mass_list: Vec, // Mole fractions, typically unitless + pub mass_list: Vec, // Mole fractions, typically unitless + // Total Mass + pub total_mass : Mass, + // Total Moles + pub total_mol : AmountOfSubstance, + // Total Volume + pub total_volume : Volume, ///Thermo Package pub thermodynamic_package : Box } @@ -171,73 +179,6 @@ pub trait ThermoPackage{ fn gibbs_free_energy(&self) -> Energy; } -///#IdealGasPackage -/// -///Will contain equations related to ideal gases - -pub struct IdealGasPackage { - pub temperature : ThermodynamicTemperature, - pub pressure : Pressure, - pub species_list : Vec, - pub total_mass : Mass, - pub total_vol : Volume, - pub total_mol : AmountOfSubstance -} -///Implementing functions specific to the IdealGasPackage -impl IdealGasPackage { - ///Constructor - pub fn new(temperature: ThermodynamicTemperature, pressure : Pressure, species_list : Vec, total_mass : Mass, total_vol : Volume, total_mol : AmountOfSubstance) -> IdealGasPackage { - IdealGasPackage { - temperature, - pressure, - species_list, - total_mass, - total_vol, - total_mol - } - } -} -/// Implementing the ThermoPackage trait for the IdealGasPackage -impl ThermoPackage for IdealGasPackage { - ///Calculating enthalpy - fn enthalpy(&self) -> Energy { - let mut total_enthalpy = 0.0; - let t_ref = 298.15; //reference temperature - let h_ref = 0.0; //Reference enthalpy - let mut cp_ref; - let mut cp_t; - - // Need to run a for loop where I calculate the enthalpy of each species and then add it to - // the variable 'total_enthalpy' - // ASSUMPTIONS CURRENTLY MADE: - // No enthalpy from phase change - // when working with gases, assume that they are ideal gases - // Tref = 298 K & Pref = 101.325 kPa - // Href = 0 - - for chem in &self.species_list { - if chem.const_c != 0.0 { - cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); - cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); - } - else{ - cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); - cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); - } - let species_enthalpy = h_ref + (cp_t - cp_ref); - total_enthalpy += species_enthalpy; - } - - Energy::new::(total_enthalpy) - } - /// Determine ideal gas pressure - fn pressure(&self) -> Pressure { - let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); - let ideal_pressure = (self.total_mol.get::() * r.get::() * self.temperature.get::()) / (self.total_vol.get::()); - Pressure::new::(ideal_pressure) - } -} - diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs new file mode 100644 index 0000000..6205fbc --- /dev/null +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -0,0 +1,77 @@ +///#IdealGasPackage +/// +///Will contain equations related to ideal gases +use crate::thermodynamics::*; +use uom::si::f64::*; +use uom::si::mass; +use uom::si::molar_heat_capacity; +use uom::si::pressure; +use uom::si::thermodynamic_temperature; +use uom::si::energy; +use uom::si::amount_of_substance; +use uom::si::volume; + + +pub struct IdealGasPackage { + pub temperature : ThermodynamicTemperature, + pub pressure : Pressure, + pub species_list : Vec, + pub total_mass : Mass, + pub total_vol : Volume, + pub total_mol : AmountOfSubstance +} +///Implementing functions specific to the IdealGasPackage +impl IdealGasPackage { + ///Constructor + pub fn new(temperature: ThermodynamicTemperature, pressure : Pressure, species_list : Vec, total_mass : Mass, total_vol : Volume, total_mol : AmountOfSubstance) -> IdealGasPackage { + IdealGasPackage { + temperature, + pressure, + species_list, + total_mass, + total_vol, + total_mol + } + } +} +/// Implementing the ThermoPackage trait for the IdealGasPackage +impl ThermoPackage for IdealGasPackage { + ///Calculating enthalpy + fn enthalpy(&self) -> Energy { + let mut total_enthalpy = 0.0; + let t_ref = 298.15; //reference temperature + let h_ref = 0.0; //Reference enthalpy + let mut cp_ref; + let mut cp_t; + + // Need to run a for loop where I calculate the enthalpy of each species and then add it to + // the variable 'total_enthalpy' + // ASSUMPTIONS CURRENTLY MADE: + // No enthalpy from phase change + // when working with gases, assume that they are ideal gases + // Tref = 298 K & Pref = 101.325 kPa + // Href = 0 + + for chem in &self.species_list { + if chem.const_c != 0.0 { + cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); + } + else{ + cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); + cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); + } + let species_enthalpy = h_ref + (cp_t - cp_ref); + total_enthalpy += species_enthalpy; + } + + Energy::new::(total_enthalpy) + } + /// Determine ideal gas pressure + fn pressure(&self) -> Pressure { + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); + let ideal_pressure = (self.total_mol.get::() * r.get::() * self.temperature.get::()) / (self.total_vol.get::()); + Pressure::new::(ideal_pressure) + } +} + From d6cfd75a3f4bfa7f090e9310e187479e41089362 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 17 Mar 2025 21:11:02 -0400 Subject: [PATCH 29/49] Added entropy equation for ideal gas package implemented function for entropy calculation for the ideal gas package. Need to look into how to incorporate the mass and mole fraction functions within the thermo packages and ensure that the thermopackage structs and the 'ThermoState' package are working together cohesively --- oscps-lib/src/thermodynamics.rs | 2 + .../src/thermodynamics/ideal_gas_package.rs | 48 +++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 3a73d37..e976fe7 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -68,6 +68,8 @@ pub struct SpeciesQuantityPair { pub molar_quantity: AmountOfSubstance, ///volumetric quantity pub vol_quantity: Volume, + /// partial pressure + pub partial_pressure : Pressure, ///Heat capacity Coefficient A pub const_a: f64, ///Heat capacity Coefficient B diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index 6205fbc..bf240f7 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -37,13 +37,6 @@ impl IdealGasPackage { /// Implementing the ThermoPackage trait for the IdealGasPackage impl ThermoPackage for IdealGasPackage { ///Calculating enthalpy - fn enthalpy(&self) -> Energy { - let mut total_enthalpy = 0.0; - let t_ref = 298.15; //reference temperature - let h_ref = 0.0; //Reference enthalpy - let mut cp_ref; - let mut cp_t; - // Need to run a for loop where I calculate the enthalpy of each species and then add it to // the variable 'total_enthalpy' // ASSUMPTIONS CURRENTLY MADE: @@ -51,6 +44,13 @@ impl ThermoPackage for IdealGasPackage { // when working with gases, assume that they are ideal gases // Tref = 298 K & Pref = 101.325 kPa // Href = 0 + fn enthalpy(&self) -> Energy { + let mut total_enthalpy = 0.0; + let t_ref = 298.15; //reference temperature + let h_ref = 0.0; //Reference enthalpy + let mut cp_ref; + let mut cp_t; + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); for chem in &self.species_list { if chem.const_c != 0.0 { @@ -61,17 +61,47 @@ impl ThermoPackage for IdealGasPackage { cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); } - let species_enthalpy = h_ref + (cp_t - cp_ref); - total_enthalpy += species_enthalpy; + let species_enthalpy = h_ref + (cp_t - cp_ref)* r.get::(); + total_enthalpy += species_enthalpy; } Energy::new::(total_enthalpy) } + /// Determine ideal gas pressure fn pressure(&self) -> Pressure { let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); let ideal_pressure = (self.total_mol.get::() * r.get::() * self.temperature.get::()) / (self.total_vol.get::()); Pressure::new::(ideal_pressure) } + + ///Deterrmine entropy + // Will need to use equation (5.10) from the 'Introduction to Chemical Engineering + // Thermodynamics' + fn entropy(&self) -> Energy { + let mut entropy_total = 0.0; + let t_ref = 298.15_f64; //reference temperature + let mut cp_ref; + let mut cp_t; + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); + let p_o = 1.0_f64; // units atm + + for chem in &self.species_list { + if chem.const_c != 0.0 { + cp_ref = chem.const_a * t_ref.ln() + (chem.const_b / (10.0f64.powf(3.0))) * t_ref; + cp_t = chem.const_a * self.temperature.get::().ln() + (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::() + (1.0 / 2.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(2.0); + } + else{ + cp_ref = chem.const_a * t_ref.ln() + (chem.const_b / (10.0f64.powf(3.0))) * t_ref + (-1.0/2.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-2); + cp_t = chem.const_a * self.temperature.get::().ln() + (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::() + (-1.0/2.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-2.0); + } + let integral_solve_species = cp_t - cp_ref; + let pressure_ratio = chem.partial_pressure.get::() / p_o; + + entropy_total += r.get::()*(integral_solve_species - pressure_ratio); + } + + Energy::new::(entropy_total) + } } From 9fbb08199bceb84e7f66872b137dfe29037b33fc Mon Sep 17 00:00:00 2001 From: Bhargav Date: Wed, 19 Mar 2025 22:01:13 -0400 Subject: [PATCH 30/49] updated chemical properties struct and the thermostate struct to start matching the uml design --- oscps-lib/src/component.rs | 8 + oscps-lib/src/thermodynamics.rs | 281 ++++++++++++++++---------------- 2 files changed, 144 insertions(+), 145 deletions(-) diff --git a/oscps-lib/src/component.rs b/oscps-lib/src/component.rs index 48e6ecf..bc50972 100644 --- a/oscps-lib/src/component.rs +++ b/oscps-lib/src/component.rs @@ -90,6 +90,14 @@ pub struct ChemicalProperties { pub critical_pressure: f64, // Pa /// Acentric factor of a compound. pub acentric_factor: f64, + ///Heat capacity Coefficient A + pub const_a: f64, + ///Heat capacity Coefficient B + pub const_b: f64, + ///Heat capacity Coefficient C + pub const_c: f64, + ///Heat capacity Coefficient D + pub const_d: f64 } /// Implementation of the ChemicalProperties struct. diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index e976fe7..1a3a716 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -59,7 +59,7 @@ impl ThermodynamicConstants { #[allow(dead_code)] /// Species list -pub struct SpeciesQuantityPair { +pub struct ComponentData { /// Chemical species pub chemical_species: Chemical, /// Mass quantity @@ -70,14 +70,6 @@ pub struct SpeciesQuantityPair { pub vol_quantity: Volume, /// partial pressure pub partial_pressure : Pressure, - ///Heat capacity Coefficient A - pub const_a: f64, - ///Heat capacity Coefficient B - pub const_b: f64, - ///Heat capacity Coefficient C - pub const_c: f64, - ///Heat capacity Coefficient D - pub const_d: f64 } #[allow(dead_code)] @@ -87,19 +79,19 @@ pub struct SpeciesQuantityPair { /// This struct will be used for streams in the flow diagram pub struct ThermoState { /// Pressure of the state. - pub pressure: Pressure, // Pressure in Pascals + pub pressure: Option, // pressure /// Temperature of the state. - pub temperature: ThermodynamicTemperature, // Temperature in Kelvin + pub temperature: Option, // temperature /// List of mole fractions. - pub mass_list: Vec, // Mole fractions, typically unitless + pub mass_list: Vec,//Information about each component within stream // Total Mass - pub total_mass : Mass, + pub total_mass : Option, // total mass in stream // Total Moles - pub total_mol : AmountOfSubstance, + pub total_mol : Option, // total moles in stream // Total Volume - pub total_volume : Volume, + pub total_volume : Option, // total volume in stream ///Thermo Package - pub thermodynamic_package : Box + pub thermodynamic_package : Option> // thermodynamics package } @@ -108,47 +100,46 @@ pub struct ThermoState { /// This struct holds the functionality to perform thermodynamic calculations for streams impl ThermoState { /// Constructor for creating a ThermoState - pub fn new( - pressure: f64, // in Pascals - temperature: f64, // in Kelvin - mass_list: Vec, - thermo_package : Box - ) -> Self { + pub fn new() -> Self { ThermoState { - pressure: Pressure::new::(pressure), - temperature: ThermodynamicTemperature::new::(temperature), - mass_list, - thermodynamic_package : thermo_package + pressure : None, + temperature : None, + mass_list : vec![], + total_mass : None, + total_mol : None, + total_volume : None, + thermodynamic_package : None } } - - /// Determine mass fraction - pub fn mass_frac(&self, species: &Chemical) -> Option { - let mut total_mass = 0.0; - let mut component_mass = 0.0; - + /// this function will return the total mass for an individual stream + fn calc_total_mass(&mut self) -> Mass { + let mut mass_sum = 0.0; for chem in &self.mass_list { - total_mass += chem.mass_quantity.get::(); - - if let Some(cids) = Some(chem.chemical_species.pubchem_obj.cids().unwrap()[0]) { - if cids == species.pubchem_obj.cids().unwrap_or_default()[0] { - component_mass = chem.mass_quantity.get::(); - } - } + mass_sum += chem.mass_quantity.get::(); } - - match component_mass { - 0.0 => None, - _ => Some(component_mass / total_mass), + self.total_mass = Some(Mass::new::(mass_sum)); + + self.total_mass.unwrap() + } + /// this function will return the total moles for an individual stream + fn calc_total_moles(&mut self) -> AmountOfSubstance { + let mut mole_sum = 0.0; + for chem in &self.mass_list { + mole_sum += chem.molar_quantity.get::(); } + self.total_mol = Some(AmountOfSubstance::new::(mole_sum)); + + self.total_mol.unwrap() } - /// this function will return the total mass for an individual stream - pub fn total_mass(& self) -> f64 { - let mut mass_sum = 0.0; + /// this function will return the total volume for an individual stream + fn calc_total_volume(&mut self) -> Volume { + let mut vol_sum = 0.0; for chem in &self.mass_list { - mass_sum += chem.mass_quantity.get::(); + vol_sum += chem.vol_quantity.get::(); } - mass_sum + self.total_volume = Some(Volume::new::(vol_sum)); + + self.total_volume.unwrap() } } @@ -186,112 +177,112 @@ pub trait ThermoPackage{ #[cfg(test)] mod thermo_tests { - use super::*; - use crate::component::{Chemical, ChemicalProperties}; - use uom::si::mass::kilogram; - use uom::si::pressure::pascal; - use uom::si::thermodynamic_temperature::kelvin; - use std::{thread,time::Duration}; + // use super::*; + // use crate::component::{Chemical, ChemicalProperties}; + // use uom::si::mass::kilogram; + // use uom::si::pressure::pascal; + // use uom::si::thermodynamic_temperature::kelvin; + // use std::{thread,time::Duration}; - #[test] - ///Test case generates an instance of the 'ThermoState' struct - fn test_create_thermo_state() { - // Create some test data for ThermoMoleFrac (mole fractions) - let water = Chemical { - pubchem_obj: pubchem::Compound::new(962), - properties: ChemicalProperties { - molar_mass: 0.01801528, // kg/mol for water - critical_temp: 647.1, // K - critical_pressure: 2206.0, // Pa - acentric_factor: 0.344, // example - }, - }; - thread::sleep(Duration::from_secs(10)); - let water_mass = Mass::new::(2.0); - let water_species_pair = SpeciesQuantityPair { - chemical_species: water, - mass_quantity: water_mass, - const_a: 1.0, - const_b: 1.0, - const_c: 1.0, - const_d: 0.0 - }; + // #[test] + // ///Test case generates an instance of the 'ThermoState' struct + // fn test_create_thermo_state() { + // // Create some test data for ThermoMoleFrac (mole fractions) + // let water = Chemical { + // pubchem_obj: pubchem::Compound::new(962), + // properties: ChemicalProperties { + // molar_mass: 0.01801528, // kg/mol for water + // critical_temp: 647.1, // K + // critical_pressure: 2206.0, // Pa + // acentric_factor: 0.344, // example + // }, + // }; + // thread::sleep(Duration::from_secs(10)); + // let water_mass = Mass::new::(2.0); + // let water_species_pair = SpeciesQuantityPair { + // chemical_species: water, + // mass_quantity: water_mass, + // const_a: 1.0, + // const_b: 1.0, + // const_c: 1.0, + // const_d: 0.0 + // }; - // Create ThermoState - let thermo_state = ThermoState::new( - 101325.0, // pressure in Pascals (1 atm) - 298.15, // temperature in Kelvin (25°C) - vec![water_species_pair], // Example with one chemical - ); + // // Create ThermoState + // let thermo_state = ThermoState::new( + // 101325.0, // pressure in Pascals (1 atm) + // 298.15, // temperature in Kelvin (25°C) + // vec![water_species_pair], // Example with one chemical + // ); - // Validate ThermoState - assert_eq!(thermo_state.pressure.get::(), 101325.0); - assert_eq!(thermo_state.temperature.get::(), 298.15); - assert_eq!(thermo_state.mass_list.len(), 1); // Should contain one mole fraction entry + // // Validate ThermoState + // assert_eq!(thermo_state.pressure.get::(), 101325.0); + // assert_eq!(thermo_state.temperature.get::(), 298.15); + // assert_eq!(thermo_state.mass_list.len(), 1); // Should contain one mole fraction entry - + // - // Check that the mole fraction's chemical is correctly set - assert_eq!( - thermo_state.mass_list[0] - .chemical_species - .get_pubchem_obj() - .cids() - .unwrap()[0], - 962 - ); - } + // // Check that the mole fraction's chemical is correctly set + // assert_eq!( + // thermo_state.mass_list[0] + // .chemical_species + // .get_pubchem_obj() + // .cids() + // .unwrap()[0], + // 962 + // ); + // } - #[test] - ///Tests the mass fraction function within the 'ThermoState struct' - fn test_mass_fraction_calculation() { - let water = Chemical { - pubchem_obj: pubchem::Compound::new(962), - properties: ChemicalProperties { - molar_mass: 0.01801528, // kg/mol for water - critical_temp: 647.1, // K - critical_pressure: 2206.0, // Pa - acentric_factor: 0.344, // example - }, - }; - thread::sleep(Duration::from_secs(10)); + // #[test] + // ///Tests the mass fraction function within the 'ThermoState struct' + // fn test_mass_fraction_calculation() { + // let water = Chemical { + // pubchem_obj: pubchem::Compound::new(962), + // properties: ChemicalProperties { + // molar_mass: 0.01801528, // kg/mol for water + // critical_temp: 647.1, // K + // critical_pressure: 2206.0, // Pa + // acentric_factor: 0.344, // example + // }, + // }; + // thread::sleep(Duration::from_secs(10)); - let anisdine = Chemical { - pubchem_obj: pubchem::Compound::new(7732), - properties: ChemicalProperties { - molar_mass: 123.155, // g/mol, converting to kg/mol = 123.155 / 1000 - critical_temp: 592.0, // K (approximated) - critical_pressure: 2.6e6, // Pa (approximated) - acentric_factor: 0.24, // (approximated) - }, - }; - thread::sleep(Duration::from_secs(10)); - - let water_mass = Mass::new::(2.0); - let water_species_pair = SpeciesQuantityPair { - chemical_species: water, - mass_quantity: water_mass, - }; + // let anisdine = Chemical { + // pubchem_obj: pubchem::Compound::new(7732), + // properties: ChemicalProperties { + // molar_mass: 123.155, // g/mol, converting to kg/mol = 123.155 / 1000 + // critical_temp: 592.0, // K (approximated) + // critical_pressure: 2.6e6, // Pa (approximated) + // acentric_factor: 0.24, // (approximated) + // }, + // }; + // thread::sleep(Duration::from_secs(10)); + // + // let water_mass = Mass::new::(2.0); + // let water_species_pair = SpeciesQuantityPair { + // chemical_species: water, + // mass_quantity: water_mass, + // }; - let anisidine_mass = Mass::new::(8.0); - let anisidine_species_pair = SpeciesQuantityPair { - chemical_species: anisdine, - mass_quantity: anisidine_mass, - }; + // let anisidine_mass = Mass::new::(8.0); + // let anisidine_species_pair = SpeciesQuantityPair { + // chemical_species: anisdine, + // mass_quantity: anisidine_mass, + // }; - let therm_obj = ThermoState::new( - 101325.0, - 298.15, - vec![water_species_pair, anisidine_species_pair], - ); + // let therm_obj = ThermoState::new( + // 101325.0, + // 298.15, + // vec![water_species_pair, anisidine_species_pair], + // ); - let mass_fraction = therm_obj - .mass_frac(&therm_obj.mass_list[0].chemical_species) - .unwrap(); + // let mass_fraction = therm_obj + // .mass_frac(&therm_obj.mass_list[0].chemical_species) + // .unwrap(); - assert!( - (mass_fraction - 0.2).abs() < 1e-6, - "Mole fraction calculation failed" - ); // Should be 0.2 - } + // assert!( + // (mass_fraction - 0.2).abs() < 1e-6, + // "Mole fraction calculation failed" + // ); // Should be 0.2 + // } } From b04bf9c6a1f3b693b34453ebd441b24e852f7cb9 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Thu, 20 Mar 2025 19:29:45 -0400 Subject: [PATCH 31/49] made some changes to the IdealGasPackage struct --- .../src/thermodynamics/ideal_gas_package.rs | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index bf240f7..d92ac70 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -2,6 +2,7 @@ /// ///Will contain equations related to ideal gases use crate::thermodynamics::*; +use std::sync::Arc; use uom::si::f64::*; use uom::si::mass; use uom::si::molar_heat_capacity; @@ -13,17 +14,23 @@ use uom::si::volume; pub struct IdealGasPackage { - pub temperature : ThermodynamicTemperature, - pub pressure : Pressure, - pub species_list : Vec, - pub total_mass : Mass, - pub total_vol : Volume, - pub total_mol : AmountOfSubstance + pub temperature : Arc, + pub pressure : Arc, + pub species_list : Vec>, + pub total_mass : Arc, + pub total_vol : Arc, + pub total_mol : Arc } ///Implementing functions specific to the IdealGasPackage impl IdealGasPackage { ///Constructor - pub fn new(temperature: ThermodynamicTemperature, pressure : Pressure, species_list : Vec, total_mass : Mass, total_vol : Volume, total_mol : AmountOfSubstance) -> IdealGasPackage { + pub fn new( + temperature: Arc, + pressure : Arc, + species_list : Vec>, + total_mass : Arc, + total_vol : Arc, + total_mol : Arc) -> Self { IdealGasPackage { temperature, pressure, @@ -52,7 +59,8 @@ impl ThermoPackage for IdealGasPackage { let mut cp_t; let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); - for chem in &self.species_list { + for chem_object in &self.species_list { + let chem = &(*chem_object).chemical_species.properties; if chem.const_c != 0.0 { cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2); cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (1.0 / 3.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(3.0); @@ -86,7 +94,8 @@ impl ThermoPackage for IdealGasPackage { let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); let p_o = 1.0_f64; // units atm - for chem in &self.species_list { + for chem_object in &self.species_list { + let chem = &(*chem_object).chemical_species.properties; if chem.const_c != 0.0 { cp_ref = chem.const_a * t_ref.ln() + (chem.const_b / (10.0f64.powf(3.0))) * t_ref; cp_t = chem.const_a * self.temperature.get::().ln() + (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::() + (1.0 / 2.0) * (chem.const_c / (10.0f64.powf(6.0))) * self.temperature.get::().powf(2.0); @@ -96,7 +105,7 @@ impl ThermoPackage for IdealGasPackage { cp_t = chem.const_a * self.temperature.get::().ln() + (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::() + (-1.0/2.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-2.0); } let integral_solve_species = cp_t - cp_ref; - let pressure_ratio = chem.partial_pressure.get::() / p_o; + let pressure_ratio = (*chem_object).partial_pressure.get::() / p_o; entropy_total += r.get::()*(integral_solve_species - pressure_ratio); } From d6649ca84b9322fe30dc09ec7a4b9fc0b44c50b9 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 24 Mar 2025 07:19:03 -0400 Subject: [PATCH 32/49] removed 'volume' and 'calculate_moles' functions from ThermoPackage trait Felt those functions were unncessary as the changes in volume and moles would be attributed to reactions. This would be something implmeneted within the block structs rather than the thermoState struct, which should mostly be about holding relevant thermodynamic equations. --- oscps-lib/src/thermodynamics.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 1a3a716..a3402bd 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -156,14 +156,10 @@ pub trait ThermoPackage{ fn enthalpy(&self) -> Energy; ///Calculating the Entropy fn entropy(&self) -> Energy; - /// Calculate amount of moles - fn calculate_moles(&self) -> AmountOfSubstance; ///Calculate pressure fn pressure(&self) -> Pressure; ///Calculate temperature fn temperature(&self) -> ThermodynamicTemperature; - ///Calculate volume - fn volume(&self) -> Volume; ///Calculate heat capacity fn heat_capacity(&self) -> HeatCapacity; ///Calculate internal temperature From de4da4613f3760427530c701526a2627cb81d0f7 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 24 Mar 2025 07:30:29 -0400 Subject: [PATCH 33/49] updated the enthalpy and entropy equations --- oscps-lib/src/thermodynamics.rs | 1 - oscps-lib/src/thermodynamics/ideal_gas_package.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index a3402bd..7480241 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -150,7 +150,6 @@ impl ThermoState { ///Will include functions common to thermodynamic packages ///Will also enable to user to switch between thermodynamic packages within the ThermoState struct ///(the thermodynamic packages will be structs) - pub trait ThermoPackage{ ///Calculating the Enthalpy fn enthalpy(&self) -> Energy; diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index d92ac70..e338e20 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -69,7 +69,7 @@ impl ThermoPackage for IdealGasPackage { cp_ref = chem.const_a * t_ref + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * t_ref.powi(2) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * t_ref.powi(-1); cp_t = chem.const_a * self.temperature.get::() + (1.0 / 2.0) * (chem.const_b / (10.0f64.powf(3.0))) * self.temperature.get::().powf(2.0) + (-1.0) * (chem.const_d / (10.0f64.powf(-5.0))) * self.temperature.get::().powf(-1.0); } - let species_enthalpy = h_ref + (cp_t - cp_ref)* r.get::(); + let species_enthalpy = (chem_object.molar_quantity.get::()/self.total_mol.get::())*(h_ref + (cp_t - cp_ref)* r.get::()); total_enthalpy += species_enthalpy; } @@ -107,7 +107,7 @@ impl ThermoPackage for IdealGasPackage { let integral_solve_species = cp_t - cp_ref; let pressure_ratio = (*chem_object).partial_pressure.get::() / p_o; - entropy_total += r.get::()*(integral_solve_species - pressure_ratio); + entropy_total += (chem_object.molar_quantity.get::()/self.total_mol.get::())*r.get::()*(integral_solve_species - pressure_ratio); } Energy::new::(entropy_total) From ff32e7d40824687eb3ae5d179f07393528d8937e Mon Sep 17 00:00:00 2001 From: Bhargav Date: Wed, 26 Mar 2025 07:56:21 -0400 Subject: [PATCH 34/49] moved total volume calculation to thermoPackage due to differences in volume calculation under different EOS and based on vapor and liquid fractions --- oscps-lib/src/thermodynamics.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 7480241..07393e5 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -61,7 +61,7 @@ impl ThermodynamicConstants { /// Species list pub struct ComponentData { /// Chemical species - pub chemical_species: Chemical, + pub chemical_species: Chemical, // will contain intrinsic properties of species /// Mass quantity pub mass_quantity: Mass, /// Molar quantity @@ -131,16 +131,6 @@ impl ThermoState { self.total_mol.unwrap() } - /// this function will return the total volume for an individual stream - fn calc_total_volume(&mut self) -> Volume { - let mut vol_sum = 0.0; - for chem in &self.mass_list { - vol_sum += chem.vol_quantity.get::(); - } - self.total_volume = Some(Volume::new::(vol_sum)); - - self.total_volume.unwrap() - } } ///Thermodynamic Packages. @@ -157,6 +147,8 @@ pub trait ThermoPackage{ fn entropy(&self) -> Energy; ///Calculate pressure fn pressure(&self) -> Pressure; + ///Calculate volume + fn volume(&self) -> Volume; ///Calculate temperature fn temperature(&self) -> ThermodynamicTemperature; ///Calculate heat capacity From a84e94302e18855439d6bc1de300dd444be61669 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Wed, 26 Mar 2025 08:03:37 -0400 Subject: [PATCH 35/49] added vapor fraction function for ideal gas package basically made it so that it will always return 1 for the vapor fraction function as one would only use the ideal gas package when working with only gaseous components and no liquid components. --- oscps-lib/src/thermodynamics.rs | 2 ++ oscps-lib/src/thermodynamics/ideal_gas_package.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 07393e5..86ed754 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -151,6 +151,8 @@ pub trait ThermoPackage{ fn volume(&self) -> Volume; ///Calculate temperature fn temperature(&self) -> ThermodynamicTemperature; + ///Calculate vapor fractions + fn vapor_fraction(&self) -> Dimensionless; ///Calculate heat capacity fn heat_capacity(&self) -> HeatCapacity; ///Calculate internal temperature diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index e338e20..7e034a0 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -112,5 +112,11 @@ impl ThermoPackage for IdealGasPackage { Energy::new::(entropy_total) } + /// Determining vapor fraction + // In Ideal gas package, only will be used when components are all in gaseous state so + // vapor fraction will always be equal to 1 + fn vapor_fraction(&self) -> Ratio { + Ratio::new(1) + } } From ea87c953673ec72f16e8a0bbdae26e76f2138e70 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Wed, 26 Mar 2025 08:04:37 -0400 Subject: [PATCH 36/49] added the vapor_fraction function to the thermoPackage trait --- oscps-lib/src/thermodynamics.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 86ed754..4760e01 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -10,6 +10,7 @@ pub mod ideal_gas_package; use crate::component::Chemical; +use uom::si::f32::Ratio; use uom::si::f64::*; use uom::si::mass; use uom::si::molar_heat_capacity; @@ -152,7 +153,7 @@ pub trait ThermoPackage{ ///Calculate temperature fn temperature(&self) -> ThermodynamicTemperature; ///Calculate vapor fractions - fn vapor_fraction(&self) -> Dimensionless; + fn vapor_fraction(&self) -> Ratio; ///Calculate heat capacity fn heat_capacity(&self) -> HeatCapacity; ///Calculate internal temperature From 35c6c236f07f1b4c3293334223cf26aaa8e95ed1 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Thu, 27 Mar 2025 21:57:55 -0400 Subject: [PATCH 37/49] fixed units within ideal_gas_package --- oscps-lib/src/thermodynamics.rs | 8 +++-- .../src/thermodynamics/ideal_gas_package.rs | 33 ++++++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 4760e01..d3b25d4 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -10,6 +10,8 @@ pub mod ideal_gas_package; use crate::component::Chemical; +use uom::si::f32::MolarEnergy; +use uom::si::f32::MolarHeatCapacity; use uom::si::f32::Ratio; use uom::si::f64::*; use uom::si::mass; @@ -143,9 +145,9 @@ impl ThermoState { ///(the thermodynamic packages will be structs) pub trait ThermoPackage{ ///Calculating the Enthalpy - fn enthalpy(&self) -> Energy; + fn enthalpy(&self) -> MolarEnergy; ///Calculating the Entropy - fn entropy(&self) -> Energy; + fn entropy(&self) -> MolarHeatCapacity; ///Calculate pressure fn pressure(&self) -> Pressure; ///Calculate volume @@ -155,7 +157,7 @@ pub trait ThermoPackage{ ///Calculate vapor fractions fn vapor_fraction(&self) -> Ratio; ///Calculate heat capacity - fn heat_capacity(&self) -> HeatCapacity; + fn heat_capacity_const_pressure(&self) -> HeatCapacity; ///Calculate internal temperature fn internal_energy(&self) -> Energy; ///Calculate gibbs free energy diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index 7e034a0..cd77811 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -3,10 +3,17 @@ ///Will contain equations related to ideal gases use crate::thermodynamics::*; use std::sync::Arc; +use uom::si::f32::HeatCapacity; +use uom::si::f32::MolarEnergy; +use uom::si::f32::MolarHeatCapacity; +use uom::si::f64::MolarEnergy; +use uom::si::f64::MolarHeatCapacity; use uom::si::f64::*; use uom::si::mass; +use uom::si::molar_energy; use uom::si::molar_heat_capacity; use uom::si::pressure; +use uom::si::specific_heat_capacity; use uom::si::thermodynamic_temperature; use uom::si::energy; use uom::si::amount_of_substance; @@ -51,7 +58,7 @@ impl ThermoPackage for IdealGasPackage { // when working with gases, assume that they are ideal gases // Tref = 298 K & Pref = 101.325 kPa // Href = 0 - fn enthalpy(&self) -> Energy { + fn enthalpy(&self) -> MolarEnergy { let mut total_enthalpy = 0.0; let t_ref = 298.15; //reference temperature let h_ref = 0.0; //Reference enthalpy @@ -73,7 +80,7 @@ impl ThermoPackage for IdealGasPackage { total_enthalpy += species_enthalpy; } - Energy::new::(total_enthalpy) + MolarEnergy::new::(total_enthalpy) } /// Determine ideal gas pressure @@ -86,7 +93,7 @@ impl ThermoPackage for IdealGasPackage { ///Deterrmine entropy // Will need to use equation (5.10) from the 'Introduction to Chemical Engineering // Thermodynamics' - fn entropy(&self) -> Energy { + fn entropy(&self) -> MolarHeatCapacity { let mut entropy_total = 0.0; let t_ref = 298.15_f64; //reference temperature let mut cp_ref; @@ -110,7 +117,7 @@ impl ThermoPackage for IdealGasPackage { entropy_total += (chem_object.molar_quantity.get::()/self.total_mol.get::())*r.get::()*(integral_solve_species - pressure_ratio); } - Energy::new::(entropy_total) + MolarHeatCapacity::new::(entropy_total) } /// Determining vapor fraction // In Ideal gas package, only will be used when components are all in gaseous state so @@ -118,5 +125,23 @@ impl ThermoPackage for IdealGasPackage { fn vapor_fraction(&self) -> Ratio { Ratio::new(1) } + + /// Determining Cp (Heat capacity under constant pressure conditions) + fn heat_capacity_const_pressure(&self) -> HeatCapacity { + let mut total_heat_capacity_const_pressure : f64 = 0; + let mut cp_t; + let t = self.temperature.get::(); + for chem_object in &self.species_list { + let chem = &(*chem_object).chemical_species.properties; + if chem.const_c != 0.0 { + cp_t = chem.const_a + (chem.const_b / (10.0f64.powf(3.0)))*t + (chem.const_c / (10.0f64.powf(6.0)))*t.powf(2.0); + } + else { + cp_t = chem.const_a + (chem.const_b / (10.0f64.powf(3.0)))*t + (chem.const_d / (10.0f64.powf(-5.0)))*t.powf(-2.0); + } + total_heat_capacity_const_pressure += cp_t* (chem_object.molar_quantity.get::()/self.total_mol.get::())*r.get::(); + } + HeatCapacity::new::(total_heat_capacity_const_pressure) + } } From 0e27b7f4f67f24e08da0580029823b375fca7899 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Thu, 27 Mar 2025 21:59:41 -0400 Subject: [PATCH 38/49] updated import statements within ideal_gas_package --- oscps-lib/src/thermodynamics/ideal_gas_package.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index cd77811..ecde2ee 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -3,11 +3,6 @@ ///Will contain equations related to ideal gases use crate::thermodynamics::*; use std::sync::Arc; -use uom::si::f32::HeatCapacity; -use uom::si::f32::MolarEnergy; -use uom::si::f32::MolarHeatCapacity; -use uom::si::f64::MolarEnergy; -use uom::si::f64::MolarHeatCapacity; use uom::si::f64::*; use uom::si::mass; use uom::si::molar_energy; From 8c83cd14716d07aee51a91838154c96be2f5c62e Mon Sep 17 00:00:00 2001 From: Bhargav Date: Thu, 27 Mar 2025 22:00:14 -0400 Subject: [PATCH 39/49] updated whitespace --- oscps-lib/src/thermodynamics/ideal_gas_package.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index ecde2ee..a27aea2 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -120,7 +120,6 @@ impl ThermoPackage for IdealGasPackage { fn vapor_fraction(&self) -> Ratio { Ratio::new(1) } - /// Determining Cp (Heat capacity under constant pressure conditions) fn heat_capacity_const_pressure(&self) -> HeatCapacity { let mut total_heat_capacity_const_pressure : f64 = 0; From f12d3aa0c82fb75e95584e9e575056d82085f813 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Fri, 28 Mar 2025 17:49:15 -0400 Subject: [PATCH 40/49] updating the functions for the ideal_gas_package need to figure out a way to calculate Cv for internal energy function --- oscps-lib/src/thermodynamics.rs | 3 ++- .../src/thermodynamics/ideal_gas_package.rs | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index d3b25d4..5ba3b0a 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -13,6 +13,7 @@ use crate::component::Chemical; use uom::si::f32::MolarEnergy; use uom::si::f32::MolarHeatCapacity; use uom::si::f32::Ratio; +use uom::si::f64::MolarEnergy; use uom::si::f64::*; use uom::si::mass; use uom::si::molar_heat_capacity; @@ -159,7 +160,7 @@ pub trait ThermoPackage{ ///Calculate heat capacity fn heat_capacity_const_pressure(&self) -> HeatCapacity; ///Calculate internal temperature - fn internal_energy(&self) -> Energy; + fn internal_energy(&self) -> MolarEnergy; ///Calculate gibbs free energy fn gibbs_free_energy(&self) -> Energy; } diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index a27aea2..2515fc9 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -3,6 +3,7 @@ ///Will contain equations related to ideal gases use crate::thermodynamics::*; use std::sync::Arc; +use uom::si::f32::MolarEnergy; use uom::si::f64::*; use uom::si::mass; use uom::si::molar_energy; @@ -77,14 +78,12 @@ impl ThermoPackage for IdealGasPackage { MolarEnergy::new::(total_enthalpy) } - /// Determine ideal gas pressure fn pressure(&self) -> Pressure { let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); let ideal_pressure = (self.total_mol.get::() * r.get::() * self.temperature.get::()) / (self.total_vol.get::()); Pressure::new::(ideal_pressure) } - ///Deterrmine entropy // Will need to use equation (5.10) from the 'Introduction to Chemical Engineering // Thermodynamics' @@ -137,5 +136,22 @@ impl ThermoPackage for IdealGasPackage { } HeatCapacity::new::(total_heat_capacity_const_pressure) } + ///Determining internal energy + //Need to figure out way to calculate Cv + fn internal_energy(&self) -> MolarEnergy { + MolarEnergy::new::(0) + } + ///Determining temperature + fn temperature(&self) -> ThermodynamicTemperature { + + } + ///Determining volume + fn volume(&self) -> Volume { + + } + ///Determining the Gibbs free energy + fn gibbs_free_energy(&self) -> Energy { + + } } From 77ea14e3350b332790f390309144530c8eca7e63 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Fri, 28 Mar 2025 19:55:32 -0400 Subject: [PATCH 41/49] completed implementation of the ideal_gas_package --- oscps-lib/src/thermodynamics.rs | 8 +---- .../src/thermodynamics/ideal_gas_package.rs | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 5ba3b0a..35bda59 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -10,18 +10,12 @@ pub mod ideal_gas_package; use crate::component::Chemical; -use uom::si::f32::MolarEnergy; -use uom::si::f32::MolarHeatCapacity; -use uom::si::f32::Ratio; -use uom::si::f64::MolarEnergy; use uom::si::f64::*; use uom::si::mass; -use uom::si::molar_heat_capacity; use uom::si::pressure; use uom::si::thermodynamic_temperature; use uom::si::energy; use uom::si::amount_of_substance; -use uom::si::volume; #[allow(dead_code)] @@ -158,7 +152,7 @@ pub trait ThermoPackage{ ///Calculate vapor fractions fn vapor_fraction(&self) -> Ratio; ///Calculate heat capacity - fn heat_capacity_const_pressure(&self) -> HeatCapacity; + fn heat_capacity_const_pressure(&self) -> MolarHeatCapacity; ///Calculate internal temperature fn internal_energy(&self) -> MolarEnergy; ///Calculate gibbs free energy diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index 2515fc9..e55ac2f 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -3,17 +3,15 @@ ///Will contain equations related to ideal gases use crate::thermodynamics::*; use std::sync::Arc; -use uom::si::f32::MolarEnergy; use uom::si::f64::*; -use uom::si::mass; use uom::si::molar_energy; use uom::si::molar_heat_capacity; use uom::si::pressure; -use uom::si::specific_heat_capacity; use uom::si::thermodynamic_temperature; use uom::si::energy; use uom::si::amount_of_substance; use uom::si::volume; +use uom::si::ratio; pub struct IdealGasPackage { @@ -117,11 +115,12 @@ impl ThermoPackage for IdealGasPackage { // In Ideal gas package, only will be used when components are all in gaseous state so // vapor fraction will always be equal to 1 fn vapor_fraction(&self) -> Ratio { - Ratio::new(1) + Ratio::new::(1.0) } /// Determining Cp (Heat capacity under constant pressure conditions) - fn heat_capacity_const_pressure(&self) -> HeatCapacity { - let mut total_heat_capacity_const_pressure : f64 = 0; + fn heat_capacity_const_pressure(&self) -> MolarHeatCapacity { + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap(); + let mut total_heat_capacity_const_pressure : f64 = 0.0; let mut cp_t; let t = self.temperature.get::(); for chem_object in &self.species_list { @@ -134,24 +133,39 @@ impl ThermoPackage for IdealGasPackage { } total_heat_capacity_const_pressure += cp_t* (chem_object.molar_quantity.get::()/self.total_mol.get::())*r.get::(); } - HeatCapacity::new::(total_heat_capacity_const_pressure) + MolarHeatCapacity::new::(total_heat_capacity_const_pressure) } ///Determining internal energy //Need to figure out way to calculate Cv fn internal_energy(&self) -> MolarEnergy { - MolarEnergy::new::(0) + MolarEnergy::new::(0.0) } ///Determining temperature fn temperature(&self) -> ThermodynamicTemperature { - + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap().get::(); + //T = PV/nR + let p = self.pressure.get::(); + let v = self.total_vol.get::(); + let n = self.total_mol.get::(); + let ideal_temperature = (p*v)/(n*r); + ThermodynamicTemperature::new::(ideal_temperature) } ///Determining volume fn volume(&self) -> Volume { - + let r = ThermodynamicConstants::UniversalGasConstant.value().downcast::().unwrap().get::(); + // V = (nRT)/P + let n = self.total_mol.get::(); + let p = self.pressure.get::(); + let t = self.temperature.get::(); + let ideal_volume = (n*r*t)/(p); + Volume::new::(ideal_volume) } ///Determining the Gibbs free energy fn gibbs_free_energy(&self) -> Energy { - + let enthalpy = self.enthalpy().get::()*self.total_mol.get::(); + let entropy = self.entropy().get::()*self.total_mol.get::(); + let gibbs_free_energy_value = enthalpy - self.temperature.get::()*entropy; + Energy::new::(gibbs_free_energy_value) } } From 42e29a28680a39e6c30344b13b28e84ec77f3984 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Fri, 28 Mar 2025 20:03:56 -0400 Subject: [PATCH 42/49] imported changes to blocks.rs from 'add-simulation' branch --- oscps-lib/src/blocks.rs | 368 +++++++++++++++++++++++----------------- 1 file changed, 211 insertions(+), 157 deletions(-) diff --git a/oscps-lib/src/blocks.rs b/oscps-lib/src/blocks.rs index 1b1908e..ff99f2c 100644 --- a/oscps-lib/src/blocks.rs +++ b/oscps-lib/src/blocks.rs @@ -1,220 +1,274 @@ //! # Blocks //! -//! This file contains traits implemented by various structs to represent +//! This file contains traits implemented by various structs to represent //! different unit operations. //! //! For example, if a block is a simple mixer, then it will implement the //! MassBalance trait but not the EnergyBalance. -use crate::connector; use crate::connector::Stream; use once_cell::sync::Lazy; -use uom::si::f64::*; -use uom::si::mass; -use uom::si::molar_heat_capacity; -use uom::si::pressure; -use uom::si::thermodynamic_temperature; -use uom::si::energy; -use uom::si::amount_of_substance; -use uom::si::volume; +use uom::si::energy::joule; +use uom::si::f64::Energy; +use uom::si::f64::Mass; +use uom::si::mass::kilogram; + +/// # Block +/// +/// A trait that all blocks must implement. +/// TODO: In ASPEN, streams can be used to specify process inputs and outputs. +/// Instead, have special blocks that are 'source' and 'sink' blocks for +/// material entering and exiting the simulation. To make it more user friendly, +/// if a user attempts to run a simulation with stream that are not connected to +/// inputs or outputs, offer to automatically insert sources/sinks where the loose +/// ends are. While these special blocks will still have to implement this trait +/// (and thus implement unnecessary functions, such as the "connect_input" function +/// for a souce block, these functions can simply be dummy functions for this special case. +/// For safety, they can throw errors if called, but they should never be used. +pub trait Block { + /// Connect an input to a block. TODO: Have this function create the input stream and return a + /// reference to it. Then use that stream reference to connect an output. + fn connect_input(&mut self, stream: &mut Stream) -> Result<(), &str>; + /// Disconnect an input to a block + fn disconnect_input(&mut self, stream: &mut Stream) -> Result<(), &str>; + /// Connect an output to a block + fn connect_output(&mut self, stream: &mut Stream) -> Result<(), &str>; + /// Disconnect an output to a block + fn disconnect_output(&mut self, stream: &mut Stream) -> Result<(), &str>; + // TODO: Add additional functions that all Blocks should implement +} + +/// # Separator +/// +/// A Separator block that allows components of a stream to be separated. +/// Allows for a single input and an arbitrary number of outputs. +struct Separator { + id: u64, + input: Option, // An option is used in case there is no input stream + outputs: Vec, // An empty vec can represent no outputs, no need for Option>> + // TODO: Add additional fields that controls how components are separated +} + +impl Separator { + fn new(id: u64) -> Self { + Separator { + id, + input: None, + outputs: Vec::new(), + } + } +} #[allow(dead_code)] -/// Minimum error allowed for energy difference. +/// # Mixer +/// +/// A block used for simple stream mixing operations. Spacial information +/// is not stored in the case that non-gui applications use this backend. +pub struct Mixer { + /// The ID of the block. + pub id: u64, + /// Set of inlet streams for the mixer + pub inputs: Vec>, + /// outlet stream for the mixer block + pub output: Option>, +} + +#[allow(dead_code)] +/// Implementations of the mixer block. +impl Mixer { + /// Create a new mixer block. TODO: Figure out importance of lifetimes + pub fn new<'a>(id: u64) -> Mixer { + Mixer { + id, + inputs: Vec::new(), + output: None, + } + } + + // TODO: Uncomment once desired base functionality is achieved + // /// Execute the mixer block (calculate balances, output streams, etc) + // /// This function still needs to be implemented + // pub fn execute_block(&mut self) { + // self.outlet_stream = Some(connector::Stream { + // s_id: String::from("Mass_Outlet"), + // thermo: None, + // from_block: String::from("M1"), + // to_block: String::from("M2") + // // m_flow_total: self.compute_total_outlet_mass_flow().unwrap(), + // }); + // // self.outlet_stream_energy = Some(connector::Econnector { + // // e_conn_id: String::from("Energy Outlet"), + // // energy_flow_total: self.compute_outlet_energy_flows().unwrap(), + // // }); + // } + + // /// This private method will compute the outlet mass flows for the mixer block + // /// + // /// # Returns + // /// + // /// A Mass quantity (uom object) that holds the outlet mass flow + // fn compute_total_outlet_mass_flow(&self) -> Option { + // // TODO: steps to implement function: + // // Need to loop through each of the connector structures and add up the mass flows + // // During this process, need to make sure that all the mass flows are in the same units + // // Use the UOM package to help with this part... + // let mut mass_flow_sum: f64 = 0.0; + + // for s in self.inlet_streams.iter() { + // mass_flow_sum += s.thermo.as_ref().unwrap().total_mass(); + // } + // Some(mass_flow_sum) + // } + + // /// Determines the total energy flowing through the block + // fn compute_outlet_energy_flows(&self) -> Option { + // let mut energy_flow_sum: f64 = 0.0; + + // for s in self.inlet_streams.iter() { + // energy_flow_sum += s.thermo.as_ref().unwrap().enthalpy(); + // } + // Some(energy_flow_sum) + // } + + // /// Determines the phase fractions of the output using thermodynamics. + // /// TODO: Implement this function + // fn compute_outlet_phase_fractions(&self) {} + + // /// Computes the outlet temperature of the mixer (assumes no chemical + // /// reactions) TODO: Implement this function + // fn compute_outlet_temperature(&self) {} + + // /// Computes the mixer outlet pressure. + // /// TODO: Implement this function + // fn compute_outlet_pressure(&self) {} +} + +impl Block for Mixer { + fn connect_input<'a>(&mut self, _stream: &mut Stream) -> Result<(), &'static str> { + // TODO: Figure out how to store references to streams + // self.inputs.push(stream); + Ok(()) + } + + fn disconnect_input(&mut self, _stream: &mut Stream) -> Result<(), &'static str> { + Ok(()) + } + + fn connect_output(&mut self, _stream: &mut Stream) -> Result<(), &'static str> { + Ok(()) + } + + fn disconnect_output(&mut self, _stream: &mut Stream) -> Result<(), &'static str> { + Ok(()) + } +} + +#[allow(dead_code)] +/// Minimum error allowed for energy difference. /// TODO: Change this to a relative scale instead of an absolute scale. -pub static TOLERENCE_ENERGY: Lazy = - Lazy::new(|| Energy::new::(5.0)); +pub static TOLERENCE_ENERGY: Lazy = Lazy::new(|| Energy::new::(5.0)); #[allow(dead_code)] -/// Minimum error allowed for mass difference. +/// Minimum error allowed for mass difference. /// TODO: Change this to a relative scale instead of an absolute scale. -pub static TOLERENCE_MASS: Lazy = - Lazy::new(|| Mass::new::(5.0)); +pub static TOLERENCE_MASS: Lazy = Lazy::new(|| Mass::new::(5.0)); #[allow(dead_code)] /// # MassBalance /// /// Trait for ensuring the overall mass balance is maintained in a flowsheet. /// -/// This trait can be implemented by any block that needs to ensure mass +/// This trait can be implemented by any block that needs to ensure mass /// conservation. pub trait MassBalance { - /// Perform a mass balance check on object by comparing inlet and outlet + /// Perform a mass balance check on object by comparing inlet and outlet /// mass. TODO: Compare mass flow rates, not mass and check for relative /// error instead of absolute, perhaps error should be less than 1e-6 /// fraction of the total inlet mass. This can be an adjustable parameter. /// Smaller takes longer to converge, but is more fn mass_balance_check(&self, mass_in: Mass, mass_out: Mass) -> bool { - let mass_in_kg = mass_in.get::(); - let mass_out_kg = mass_out.get::(); + let mass_in_kg = mass_in.get::(); + let mass_out_kg = mass_out.get::(); let mass_difference = mass_in_kg - mass_out_kg; - mass_difference <= TOLERENCE_MASS.get::() + mass_difference <= TOLERENCE_MASS.get::() } } #[allow(dead_code)] /// # EnergyBalance /// -/// This trait ensures that blocks in the flowsheet adhere to energy +/// This trait ensures that blocks in the flowsheet adhere to energy /// conservation principles. pub trait EnergyBalance { /// Perform an energy balance on a block. Checks all input and output - /// streams and ensures that energy stays the same. TODO: Ensure that + /// streams and ensures that energy stays the same. TODO: Ensure that /// energy loss is accounted for. For example, a mixer may not be entirely - /// adiabatic, and therefor some energy will be lost to the environment. + /// adiabatic, and therefor some energy will be lost to the environment. /// Also implement changes in issue #19. - fn energy_balance_check(&self, energy_in: Energy, energy_out: Energy) -> - bool { - let energy_in_joules = energy_in.get::(); - let energy_out_joules = energy_out.get::(); + fn energy_balance_check(&self, energy_in: Energy, energy_out: Energy) -> bool { + let energy_in_joules = energy_in.get::(); + let energy_out_joules = energy_out.get::(); let energy_difference = energy_in_joules - energy_out_joules; - energy_difference <= TOLERENCE_ENERGY.get::() + energy_difference <= TOLERENCE_ENERGY.get::() } } -#[allow(dead_code)] -/// # Mixer -/// -/// A block used for simple stream mixing operations. -pub struct Mixer { - /// The ID of the block. - pub block_id: String, - /// The x-coordiante on a flowsheet of the block. - pub x_cord: i32, - /// The y-coordinate on a flowsheet of the block. - pub y_cord: i32, - /// Set of inlet streams for the mixer - pub inlet_streams : Vec, - /// outlet stream for the mixer block - pub outlet_stream : Option -} - /// Applying mass balance trait to Mixer Block impl MassBalance for Mixer {} /// Applying the energy balance trait to the Mixer Block impl EnergyBalance for Mixer {} -#[allow(dead_code)] -/// Implementations of the mixer block. -impl Mixer { - /// Create a new mixer block. - pub fn new( - id: String, - x_cord: i32, - y_cord: i32, - in_streams: Vec, - ) -> Mixer { - Mixer { - block_id: id, - x_cord, - y_cord, - inlet_streams: in_streams, - outlet_stream: None - - } - } - - /// Execute the mixer block (calculate balances, output streams, etc) - /// This function still needs to be implemented - pub fn execute_block(&mut self) { - self.outlet_stream = Some(connector::Stream { - s_id: String::from("Mass_Outlet"), - thermo: None, - from_block: String::from("M1"), - to_block: String::from("M2") - // m_flow_total: self.compute_total_outlet_mass_flow().unwrap(), - }); - // self.outlet_stream_energy = Some(connector::Econnector { - // e_conn_id: String::from("Energy Outlet"), - // energy_flow_total: self.compute_outlet_energy_flows().unwrap(), - // }); - } - - /// This private method will compute the outlet mass flows for the mixer block - /// - /// # Returns - /// - /// A Mass quantity (uom object) that holds the outlet mass flow - fn compute_total_outlet_mass_flow(&self) -> Option { - // TODO: steps to implement function: - // Need to loop through each of the connector structures and add up the mass flows - // During this process, need to make sure that all the mass flows are in the same units - // Use the UOM package to help with this part... - let mut mass_flow_sum: f64 = 0.0; - - for s in self.inlet_streams.iter() { - mass_flow_sum += s.thermo.as_ref().unwrap().total_mass(); - } - Some(mass_flow_sum) - } - - /// Determines the total energy flowing through the block - fn compute_outlet_energy_flows(&self) -> Option { - let mut energy_flow_sum: f64 = 0.0; - - for s in self.inlet_streams.iter() { - energy_flow_sum += (*(s.thermo.as_ref().unwrap().thermodynamic_package)).enthalpy().get::(); - } - Some(energy_flow_sum) +/// Block implements Clone +impl Clone for Box { + fn clone(&self) -> Self { + todo!(); } - - /// Determines the phase fractions of the output using thermodynamics. - /// TODO: Implement this function - fn compute_outlet_phase_fractions(&self) {} - - /// Computes the outlet temperature of the mixer (assumes no chemical - /// reactions) TODO: Implement this function - fn compute_outlet_temperature(&self) {} - - /// Computes the mixer outlet pressure. - /// TODO: Implement this function - fn compute_outlet_pressure(&self) {} } /// # Block Tests -/// +/// /// The following module holds all the unit test cases for the blocks module #[cfg(test)] mod block_tests { // use crate::connector::Stream; - use super::*; - use uom::si::energy::kilojoule; - use uom::si::f64::Energy; - use uom::si::mass::pound; - - #[test] - /// checks whether the mass balance check function was implemented properly - fn test_mass_balance_check_steady_state_for_mixer() { - // here you will need to check that the mass into the mixer = mass out of mixer - - let mixer_test_obj = Mixer { - block_id: String::from("Test Mixer"), - x_cord: 0, - y_cord: 0, - inlet_streams: Vec::new(), - outlet_stream: None, - }; - let mass_in = Mass::new::(100.0); - let mass_out = Mass::new::(95.0); - assert!(mixer_test_obj.mass_balance_check(mass_in, mass_out)); - } + // use super::*; + // use uom::si::energy::kilojoule; + // use uom::si::f64::Energy; + // use uom::si::mass::pound; - #[test] - /// checks if the 'energy_balance_check' function was implemented properly - fn test_energy_balance_check_steady_state_for_mixer() { - // energy into mixer = energy out of mixer - let mixer_test_obj = Mixer { - block_id: String::from("Test Mixer"), - x_cord: 0, - y_cord: 0, - inlet_streams: Vec::new(), - outlet_stream: None, - }; - let energy_in = Energy::new::(10.0); - let energy_out = Energy::new::(95.0); - assert!(mixer_test_obj.energy_balance_check(energy_in, energy_out)); - } + // #[test] + // /// checks whether the mass balance check function was implemented properly + // fn test_mass_balance_check_steady_state_for_mixer() { + // // here you will need to check that the mass into the mixer = mass out of mixer + + // let mixer_test_obj = Mixer { + // block_id: String::from("Test Mixer"), + // y_cord: 0, + // inlet_streams: Vec::new(), + // outlet_stream: None, + // }; + // let mass_in = Mass::new::(100.0); + // let mass_out = Mass::new::(95.0); + // assert!(mixer_test_obj.mass_balance_check(mass_in, mass_out)); + // } + + // #[test] + // /// checks if the 'energy_balance_check' function was implemented properly + // fn test_energy_balance_check_steady_state_for_mixer() { + // // energy into mixer = energy out of mixer + // let mixer_test_obj = Mixer { + // block_id: String::from("Test Mixer"), + // x_cord: 0, + // y_cord: 0, + // inlet_streams: Vec::new(), + // outlet_stream: None, + // }; + // let energy_in = Energy::new::(10.0); + // let energy_out = Energy::new::(95.0); + // assert!(mixer_test_obj.energy_balance_check(energy_in, energy_out)); + // } // #[test] // checking functionality of 'compute_total_outlet_mass_flow' From ccbd3a65b688a4f84577d3c4a82d2d71c1b6e13c Mon Sep 17 00:00:00 2001 From: Bhargav Date: Fri, 28 Mar 2025 20:13:20 -0400 Subject: [PATCH 43/49] adding doc strings --- oscps-lib/src/component.rs | 4 ++++ oscps-lib/src/thermodynamics.rs | 7 ++++--- oscps-lib/src/thermodynamics/ideal_gas_package.rs | 10 +++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/oscps-lib/src/component.rs b/oscps-lib/src/component.rs index bc50972..a4581cd 100644 --- a/oscps-lib/src/component.rs +++ b/oscps-lib/src/component.rs @@ -110,6 +110,10 @@ impl ChemicalProperties { critical_temp: 0.0, // K critical_pressure: 0.0, // Pa acentric_factor: 0.0, + const_a: 0.0, + const_b: 0.0, + const_c: 0.0, + const_d: 0.0 }) } } diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 35bda59..17afbbd 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -7,6 +7,7 @@ //! documentation is in place, but more descriptive documentation should be //! implemented in the future. +///Importing Ideal Gas Package pub mod ideal_gas_package; use crate::component::Chemical; @@ -82,11 +83,11 @@ pub struct ThermoState { pub temperature: Option, // temperature /// List of mole fractions. pub mass_list: Vec,//Information about each component within stream - // Total Mass + /// Total Mass pub total_mass : Option, // total mass in stream - // Total Moles + /// Total Moles pub total_mol : Option, // total moles in stream - // Total Volume + /// Total Volume pub total_volume : Option, // total volume in stream ///Thermo Package pub thermodynamic_package : Option> // thermodynamics package diff --git a/oscps-lib/src/thermodynamics/ideal_gas_package.rs b/oscps-lib/src/thermodynamics/ideal_gas_package.rs index e55ac2f..d399335 100644 --- a/oscps-lib/src/thermodynamics/ideal_gas_package.rs +++ b/oscps-lib/src/thermodynamics/ideal_gas_package.rs @@ -1,6 +1,8 @@ ///#IdealGasPackage /// ///Will contain equations related to ideal gases + + use crate::thermodynamics::*; use std::sync::Arc; use uom::si::f64::*; @@ -13,13 +15,19 @@ use uom::si::amount_of_substance; use uom::si::volume; use uom::si::ratio; - +///Creating the ideal gas thermodynamics package pub struct IdealGasPackage { + ///Temperature pub temperature : Arc, + /// Pressure pub pressure : Arc, + ///List of Species pub species_list : Vec>, + /// Mass pub total_mass : Arc, + /// Volume pub total_vol : Arc, + /// Moles pub total_mol : Arc } ///Implementing functions specific to the IdealGasPackage From aa932aef0e8ac7eaf6940b8e870ee47f8afb0924 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Fri, 28 Mar 2025 22:23:53 -0400 Subject: [PATCH 44/49] adding srk_package --- oscps-lib/src/thermodynamics.rs | 2 ++ oscps-lib/src/thermodynamics/srk_package.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 oscps-lib/src/thermodynamics/srk_package.rs diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 17afbbd..5df3443 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -9,6 +9,8 @@ ///Importing Ideal Gas Package pub mod ideal_gas_package; +///Importing SRK Package +pub mod srk_package; use crate::component::Chemical; use uom::si::f64::*; diff --git a/oscps-lib/src/thermodynamics/srk_package.rs b/oscps-lib/src/thermodynamics/srk_package.rs new file mode 100644 index 0000000..94a196a --- /dev/null +++ b/oscps-lib/src/thermodynamics/srk_package.rs @@ -0,0 +1,17 @@ + +///#SRKPackage +/// +///Will contain equations relating to the SRK Equation of state + + +use crate::thermodynamics::*; +use std::sync::Arc; +use uom::si::f64::*; +use uom::si::molar_energy; +use uom::si::molar_heat_capacity; +use uom::si::pressure; +use uom::si::thermodynamic_temperature; +use uom::si::energy; +use uom::si::amount_of_substance; +use uom::si::volume; +use uom::si::ratio; From a1d3e868d37f2d29ebcbe1860b8e0e9414138868 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Sat, 29 Mar 2025 09:35:11 -0400 Subject: [PATCH 45/49] updating doc-strings within the thermodynamics.rs file --- oscps-lib/src/thermodynamics.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 5df3443..2a44de5 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -2,10 +2,6 @@ //! //! This module will hold all the functions related to calculating //! themrodynamic properties for the blocks and chemical species. -//! -//! TODO: All public items, including struct members, must be documented. Placeholder -//! documentation is in place, but more descriptive documentation should be -//! implemented in the future. ///Importing Ideal Gas Package pub mod ideal_gas_package; From 6bbde75d71dfdc158c72dcc744f46d03332301ec Mon Sep 17 00:00:00 2001 From: Bhargav Akula Date: Sat, 29 Mar 2025 09:39:47 -0400 Subject: [PATCH 46/49] testing new git config --- oscps-lib/src/thermodynamics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs index 2a44de5..47e969c 100644 --- a/oscps-lib/src/thermodynamics.rs +++ b/oscps-lib/src/thermodynamics.rs @@ -53,7 +53,6 @@ impl ThermodynamicConstants { } } - #[allow(dead_code)] /// Species list pub struct ComponentData { From dd4e0c2d812270b31a1b59afe37b38ea177d0672 Mon Sep 17 00:00:00 2001 From: Bhargav Akula Date: Sat, 29 Mar 2025 10:35:37 -0400 Subject: [PATCH 47/49] updating the github workflows so that workflow is initiated when PRs are opened --- .github/workflows/check-docs.yml | 6 ++---- .github/workflows/rust-tests.yml | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index f514629..1e7fecd 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -5,10 +5,8 @@ on: branches: - main - develop - push: - branches: - - main - - develop + release: + types: [published] jobs: lint: diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index edc52b2..6bc022b 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -1,10 +1,6 @@ name: Rust Tests on: - push: - branches: - - main - - develop pull_request: branches: - main From eaf681302bd2c6b0f46cd3e7ca4f7695df7d7f0c Mon Sep 17 00:00:00 2001 From: Bhargav Akula Date: Sat, 29 Mar 2025 11:09:25 -0400 Subject: [PATCH 48/49] simple update to readme document --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf35226..0427d72 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OSCPS (Open Sourced Chemical Engineering Process Simulator) +# OSCPS (Open Source Chemical Process Simulator) ![Rust Tests](https://github.com/OSCPS-Project/OSCPS/actions/workflows/rust-tests.yml/badge.svg?branch=develop) ![Documentation](https://github.com/OSCPS-Project/OSCPS/actions/workflows/check-docs.yml/badge.svg?branch=develop) From 4ce96c813738dec440bde23e4442d0fb357dcc15 Mon Sep 17 00:00:00 2001 From: Bhargav Akula Date: Sat, 29 Mar 2025 18:38:23 -0400 Subject: [PATCH 49/49] adding a manual release github workflow --- .github/workflows/release.yml | 35 +++++++++++++++++++++++++++++++++++ oscps-lib/src/lib.rs | 4 ---- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..64f2d42 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +name: OSCPS_Release_Workflow + +on: + workflow_dispatch: # Enables manual triggering from GitHub Actions UI + +jobs: + build: + name: Build and Release + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build release binary + run: cargo build --release + + - name: Compress binary + run: | + mkdir -p release + cp target/release/your-binary-name release/ + cd release + tar -czvf your-binary-name-linux.tar.gz your-binary-name + shell: bash + + - name: Create GitHub Release and Upload Binary + uses: softprops/action-gh-release@v2 + with: + files: release/your-binary-name-linux.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/oscps-lib/src/lib.rs b/oscps-lib/src/lib.rs index 6a5cceb..e81652e 100644 --- a/oscps-lib/src/lib.rs +++ b/oscps-lib/src/lib.rs @@ -11,7 +11,3 @@ pub mod connector; pub mod simulation; pub mod thermodynamics; -/// An example function. This will be removed in the final release. -pub fn hello() -> String { - "Hello, world!".to_string() -}