From f7aa92aa72e2cd39847300aa50e598abd4c09339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20W=C3=BCllhorst?= Date: Wed, 6 Dec 2023 11:23:24 +0000 Subject: [PATCH] Update 'docs' folder with main and delete old branches. --- docs/main/coverage/.gitignore | 2 + docs/main/coverage/badge.svg | 21 + docs/main/coverage/coverage_html.js | 624 ++++++++ .../d_35fc0e049e81dfd6___init___py.html | 99 ++ .../d_35fc0e049e81dfd6_economizer_py.html | 169 +++ .../d_35fc0e049e81dfd6_heat_exchanger_py.html | 336 +++++ ...fc0e049e81dfd6_moving_boundary_ntu_py.html | 463 ++++++ .../coverage/d_35fc0e049e81dfd6_ntu_py.html | 270 ++++ .../d_38531a0795c587f9___init___py.html | 99 ++ .../d_38531a0795c587f9_bernoulli_py.html | 119 ++ ...d_38531a0795c587f9_expansion_valve_py.html | 153 ++ .../d_3aa0e56d51a2f798___init___py.html | 104 ++ .../d_3aa0e56d51a2f798_datamodels_py.html | 338 +++++ .../d_4deeb89b86ecce82___init___py.html | 100 ++ .../d_4deeb89b86ecce82_air_to_wall_py.html | 208 +++ .../d_4deeb89b86ecce82_constant_py.html | 156 ++ .../d_4deeb89b86ecce82_heat_transfer_py.html | 189 +++ .../d_4deeb89b86ecce82_pipe_to_wall_py.html | 196 +++ ...b89b86ecce82_vdi_atlas_air_to_wall_py.html | 265 ++++ .../coverage/d_4deeb89b86ecce82_wall_py.html | 128 ++ .../d_57d6adb9ba5af1cf___init___py.html | 99 ++ .../d_57d6adb9ba5af1cf_automation_py.html | 317 ++++ .../d_57d6adb9ba5af1cf_nominal_design_py.html | 183 +++ .../d_57d6adb9ba5af1cf_plotting_py.html | 328 ++++ .../coverage/d_57d6adb9ba5af1cf_sdf__py.html | 260 ++++ ..._coefficient_compressor_reqression_py.html | 278 ++++ .../d_c048ebee450da2af___init___py.html | 136 ++ .../d_c048ebee450da2af_cool_prop_py.html | 239 +++ .../coverage/d_c048ebee450da2af_media_py.html | 333 +++++ .../d_c048ebee450da2af_ref_prop_py.html | 1328 +++++++++++++++++ .../d_c048ebee450da2af_states_py.html | 279 ++++ .../d_d45aa4312ad3649a___init___py.html | 101 ++ .../d_d45aa4312ad3649a_compressor_py.html | 270 ++++ ...a4312ad3649a_constant_effectivness_py.html | 195 +++ .../d_d45aa4312ad3649a_rotary_py.html | 224 +++ ...d_d45aa4312ad3649a_ten_coefficient_py.html | 464 ++++++ .../d_dae209ba2e6604e9___init___py.html | 101 ++ .../coverage/d_dae209ba2e6604e9_base_py.html | 553 +++++++ .../d_dae209ba2e6604e9_standard_py.html | 189 +++ ...e6604e9_vapor_injection_economizer_py.html | 248 +++ ...e9_vapor_injection_phase_separator_py.html | 158 ++ ...d_dae209ba2e6604e9_vapor_injection_py.html | 287 ++++ .../d_f54be612a69fd5f7___init___py.html | 97 ++ .../d_f54be612a69fd5f7_component_py.html | 232 +++ ...d_f54be612a69fd5f7_phase_separator_py.html | 176 +++ docs/main/coverage/favicon_32.png | Bin 0 -> 1732 bytes docs/main/coverage/index.html | 389 +++++ docs/main/coverage/keybd_closed.png | Bin 0 -> 9004 bytes docs/main/coverage/keybd_open.png | Bin 0 -> 9003 bytes docs/main/coverage/status.json | 1 + docs/main/coverage/style.css | 309 ++++ ...vclibpy.components.heat_exchangers.doctree | Bin 174300 -> 174544 bytes docs/main/docs/.doctrees/environment.pickle | Bin 4520382 -> 4522048 bytes .../components/heat_exchangers/ntu.html | 3 +- .../vclibpy.components.heat_exchangers.html | 5 +- docs/main/docs/searchindex.js | 2 +- docs/main/pylint/pylint.html | 38 +- docs/main/pylint/pylint.json | 248 +-- docs/main/pylint/pylint.txt | 32 +- 59 files changed, 11978 insertions(+), 163 deletions(-) create mode 100644 docs/main/coverage/.gitignore create mode 100644 docs/main/coverage/badge.svg create mode 100644 docs/main/coverage/coverage_html.js create mode 100644 docs/main/coverage/d_35fc0e049e81dfd6___init___py.html create mode 100644 docs/main/coverage/d_35fc0e049e81dfd6_economizer_py.html create mode 100644 docs/main/coverage/d_35fc0e049e81dfd6_heat_exchanger_py.html create mode 100644 docs/main/coverage/d_35fc0e049e81dfd6_moving_boundary_ntu_py.html create mode 100644 docs/main/coverage/d_35fc0e049e81dfd6_ntu_py.html create mode 100644 docs/main/coverage/d_38531a0795c587f9___init___py.html create mode 100644 docs/main/coverage/d_38531a0795c587f9_bernoulli_py.html create mode 100644 docs/main/coverage/d_38531a0795c587f9_expansion_valve_py.html create mode 100644 docs/main/coverage/d_3aa0e56d51a2f798___init___py.html create mode 100644 docs/main/coverage/d_3aa0e56d51a2f798_datamodels_py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82___init___py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82_air_to_wall_py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82_constant_py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82_heat_transfer_py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82_pipe_to_wall_py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82_vdi_atlas_air_to_wall_py.html create mode 100644 docs/main/coverage/d_4deeb89b86ecce82_wall_py.html create mode 100644 docs/main/coverage/d_57d6adb9ba5af1cf___init___py.html create mode 100644 docs/main/coverage/d_57d6adb9ba5af1cf_automation_py.html create mode 100644 docs/main/coverage/d_57d6adb9ba5af1cf_nominal_design_py.html create mode 100644 docs/main/coverage/d_57d6adb9ba5af1cf_plotting_py.html create mode 100644 docs/main/coverage/d_57d6adb9ba5af1cf_sdf__py.html create mode 100644 docs/main/coverage/d_57d6adb9ba5af1cf_ten_coefficient_compressor_reqression_py.html create mode 100644 docs/main/coverage/d_c048ebee450da2af___init___py.html create mode 100644 docs/main/coverage/d_c048ebee450da2af_cool_prop_py.html create mode 100644 docs/main/coverage/d_c048ebee450da2af_media_py.html create mode 100644 docs/main/coverage/d_c048ebee450da2af_ref_prop_py.html create mode 100644 docs/main/coverage/d_c048ebee450da2af_states_py.html create mode 100644 docs/main/coverage/d_d45aa4312ad3649a___init___py.html create mode 100644 docs/main/coverage/d_d45aa4312ad3649a_compressor_py.html create mode 100644 docs/main/coverage/d_d45aa4312ad3649a_constant_effectivness_py.html create mode 100644 docs/main/coverage/d_d45aa4312ad3649a_rotary_py.html create mode 100644 docs/main/coverage/d_d45aa4312ad3649a_ten_coefficient_py.html create mode 100644 docs/main/coverage/d_dae209ba2e6604e9___init___py.html create mode 100644 docs/main/coverage/d_dae209ba2e6604e9_base_py.html create mode 100644 docs/main/coverage/d_dae209ba2e6604e9_standard_py.html create mode 100644 docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_economizer_py.html create mode 100644 docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_phase_separator_py.html create mode 100644 docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_py.html create mode 100644 docs/main/coverage/d_f54be612a69fd5f7___init___py.html create mode 100644 docs/main/coverage/d_f54be612a69fd5f7_component_py.html create mode 100644 docs/main/coverage/d_f54be612a69fd5f7_phase_separator_py.html create mode 100644 docs/main/coverage/favicon_32.png create mode 100644 docs/main/coverage/index.html create mode 100644 docs/main/coverage/keybd_closed.png create mode 100644 docs/main/coverage/keybd_open.png create mode 100644 docs/main/coverage/status.json create mode 100644 docs/main/coverage/style.css diff --git a/docs/main/coverage/.gitignore b/docs/main/coverage/.gitignore new file mode 100644 index 0000000..ccccf14 --- /dev/null +++ b/docs/main/coverage/.gitignore @@ -0,0 +1,2 @@ +# Created by coverage.py +* diff --git a/docs/main/coverage/badge.svg b/docs/main/coverage/badge.svg new file mode 100644 index 0000000..5d787db --- /dev/null +++ b/docs/main/coverage/badge.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 58% + 58% + + diff --git a/docs/main/coverage/coverage_html.js b/docs/main/coverage/coverage_html.js new file mode 100644 index 0000000..5934882 --- /dev/null +++ b/docs/main/coverage/coverage_html.js @@ -0,0 +1,624 @@ +// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// General helpers +function debounce(callback, wait) { + let timeoutId = null; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + callback.apply(this, args); + }, wait); + }; +}; + +function checkVisible(element) { + const rect = element.getBoundingClientRect(); + const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight); + const viewTop = 30; + return !(rect.bottom < viewTop || rect.top >= viewBottom); +} + +function on_click(sel, fn) { + const elt = document.querySelector(sel); + if (elt) { + elt.addEventListener("click", fn); + } +} + +// Helpers for table sorting +function getCellValue(row, column = 0) { + const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.childElementCount == 1) { + const child = cell.firstElementChild + if (child instanceof HTMLTimeElement && child.dateTime) { + return child.dateTime + } else if (child instanceof HTMLDataElement && child.value) { + return child.value + } + } + return cell.innerText || cell.textContent; +} + +function rowComparator(rowA, rowB, column = 0) { + let valueA = getCellValue(rowA, column); + let valueB = getCellValue(rowB, column); + if (!isNaN(valueA) && !isNaN(valueB)) { + return valueA - valueB + } + return valueA.localeCompare(valueB, undefined, {numeric: true}); +} + +function sortColumn(th) { + // Get the current sorting direction of the selected header, + // clear state on other headers and then set the new sorting direction + const currentSortOrder = th.getAttribute("aria-sort"); + [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + if (currentSortOrder === "none") { + th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending"); + } else { + th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending"); + } + + const column = [...th.parentElement.cells].indexOf(th) + + // Sort all rows and afterwards append them in order to move them in the DOM + Array.from(th.closest("table").querySelectorAll("tbody tr")) + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.getAttribute("aria-sort") === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr) ); +} + +// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. +coverage.assign_shortkeys = function () { + document.querySelectorAll("[data-shortcut]").forEach(element => { + document.addEventListener("keypress", event => { + if (event.target.tagName.toLowerCase() === "input") { + return; // ignore keypress from search filter + } + if (event.key === element.dataset.shortcut) { + element.click(); + } + }); + }); +}; + +// Create the events for the filter box. +coverage.wire_up_filter = function () { + // Cache elements. + const table = document.querySelector("table.index"); + const table_body_rows = table.querySelectorAll("tbody tr"); + const no_rows = document.getElementById("no_rows"); + + // Observe filter keyevents. + document.getElementById("filter").addEventListener("input", debounce(event => { + // Keep running total of each metric, first index contains number of shown rows + const totals = new Array(table.rows[0].cells.length).fill(0); + // Accumulate the percentage as fraction + totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection + + // Hide / show elements. + table_body_rows.forEach(row => { + if (!row.cells[0].textContent.includes(event.target.value)) { + // hide + row.classList.add("hidden"); + return; + } + + // show + row.classList.remove("hidden"); + totals[0]++; + + for (let column = 1; column < totals.length; column++) { + // Accumulate dynamic totals + cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (column === totals.length - 1) { + // Last column contains percentage + const [numer, denom] = cell.dataset.ratio.split(" "); + totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection + totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection + } else { + totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection + } + } + }); + + // Show placeholder if no rows will be displayed. + if (!totals[0]) { + // Show placeholder, hide table. + no_rows.style.display = "block"; + table.style.display = "none"; + return; + } + + // Hide placeholder, show table. + no_rows.style.display = null; + table.style.display = null; + + const footer = table.tFoot.rows[0]; + // Calculate new dynamic sum values based on visible rows. + for (let column = 1; column < totals.length; column++) { + // Get footer cell element. + const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + + // Set value into dynamic footer cell element. + if (column === totals.length - 1) { + // Percentage column uses the numerator and denominator, + // and adapts to the number of decimal places. + const match = /\.([0-9]+)/.exec(cell.textContent); + const places = match ? match[1].length : 0; + const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection + cell.dataset.ratio = `${numer} ${denom}`; + // Check denom to prevent NaN if filtered files contain no statements + cell.textContent = denom + ? `${(numer * 100 / denom).toFixed(places)}%` + : `${(100).toFixed(places)}%`; + } else { + cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection + } + } + })); + + // Trigger change event on setup, to force filter on page refresh + // (filter value may still be present). + document.getElementById("filter").dispatchEvent(new Event("input")); +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( + th => th.addEventListener("click", e => sortColumn(e.target)) + ); + + // Look for a localStorage item containing previous sort settings: + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + + if (stored_list) { + const {column, direction} = JSON.parse(stored_list); + const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; // nosemgrep: eslint.detect-object-injection + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() + } + + // Watch for page unload events so we can save the final sort settings: + window.addEventListener("unload", function () { + const th = document.querySelector('[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]'); + if (!th) { + return; + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + column: [...th.parentElement.cells].indexOf(th), + direction: th.getAttribute("aria-sort"), + })); + }); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + + on_click(".button_show_hide_help", coverage.show_hide_help); +}; + +// -- pyfile stuff -- + +coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; + +coverage.pyfile_ready = function () { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === "t") { + document.querySelector(frag).closest(".n").classList.add("highlight"); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } else { + coverage.set_sel(0); + } + + on_click(".button_toggle_run", coverage.toggle_lines); + on_click(".button_toggle_mis", coverage.toggle_lines); + on_click(".button_toggle_exc", coverage.toggle_lines); + on_click(".button_toggle_par", coverage.toggle_lines); + + on_click(".button_next_chunk", coverage.to_next_chunk_nicely); + on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely); + on_click(".button_top_of_page", coverage.to_top); + on_click(".button_first_chunk", coverage.to_first_chunk); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + on_click(".button_to_index", coverage.to_index); + + on_click(".button_show_hide_help", coverage.show_hide_help); + + coverage.filters = undefined; + try { + coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE); + } catch(err) {} + + if (coverage.filters) { + coverage.filters = JSON.parse(coverage.filters); + } + else { + coverage.filters = {run: false, exc: true, mis: true, par: true}; + } + + for (cls in coverage.filters) { + coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection + } + + coverage.assign_shortkeys(); + coverage.init_scroll_markers(); + coverage.wire_up_sticky_header(); + + document.querySelectorAll("[id^=ctxs]").forEach( + cbox => cbox.addEventListener("click", coverage.expand_contexts) + ); + + // Rebuild scroll markers when the window height changes. + window.addEventListener("resize", coverage.build_scroll_markers); +}; + +coverage.toggle_lines = function (event) { + const btn = event.target.closest("button"); + const category = btn.value + const show = !btn.classList.contains("show_" + category); + coverage.set_line_visibilty(category, show); + coverage.build_scroll_markers(); + coverage.filters[category] = show; + try { + localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters)); + } catch(err) {} +}; + +coverage.set_line_visibilty = function (category, should_show) { + const cls = "show_" + category; + const btn = document.querySelector(".button_toggle_" + category); + if (btn) { + if (should_show) { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls)); + btn.classList.add(cls); + } + else { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls)); + btn.classList.remove(cls); + } + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return document.getElementById("t" + n)?.closest("p"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.to_prev_file = function () { + window.location = document.getElementById("prevFileLink").href; +} + +coverage.to_next_file = function () { + window.location = document.getElementById("nextFileLink").href; +} + +coverage.to_index = function () { + location.href = document.getElementById("indexLink").href; +} + +coverage.show_hide_help = function () { + const helpCheck = document.getElementById("help_panel_state") + helpCheck.checked = !helpCheck.checked; +} + +// Return a string indicating what kind of chunk this line belongs to, +// or null if not a chunk. +coverage.chunk_indicator = function (line_elt) { + const classes = line_elt?.className; + if (!classes) { + return null; + } + const match = classes.match(/\bshow_\w+\b/); + if (!match) { + return null; + } + return match[0]; +}; + +coverage.to_next_chunk = function () { + const c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + var chunk_indicator, probe_line; + while (true) { + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + if (chunk_indicator) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_indicator = chunk_indicator; + while (next_indicator === chunk_indicator) { + probe++; + probe_line = c.line_elt(probe); + next_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + const c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + var chunk_indicator = c.chunk_indicator(probe_line); + while (probe > 1 && !chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_indicator = chunk_indicator; + while (prev_indicator === chunk_indicator) { + probe--; + if (probe <= 0) { + return; + } + probe_line = c.line_elt(probe); + prev_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + const begin = coverage.line_elt(coverage.sel_begin); + const end = coverage.line_elt(coverage.sel_end-1); + + return ( + (checkVisible(begin) ? 1 : 0) + + (checkVisible(end) ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the top line on the screen as selection. + + // This will select the top-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(0, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(1); + } else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the lowest line on the screen as selection. + + // This will select the bottom-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(coverage.lines_len); + } else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (!probe_line) { + return; + } + var the_indicator = c.chunk_indicator(probe_line); + if (the_indicator) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var indicator = the_indicator; + while (probe > 0 && indicator === the_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + break; + } + indicator = c.chunk_indicator(probe_line); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + indicator = the_indicator; + while (indicator === the_indicator) { + probe++; + probe_line = c.line_elt(probe); + indicator = c.chunk_indicator(probe_line); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + // Highlight the lines in the chunk + document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight")); + for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) { + coverage.line_elt(probe).querySelector(".n").classList.add("highlight"); + } + + coverage.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + const element = coverage.line_elt(coverage.sel_begin); + coverage.scroll_window(element.offsetTop - 60); + } +}; + +coverage.scroll_window = function (to_pos) { + window.scroll({top: to_pos, behavior: "smooth"}); +}; + +coverage.init_scroll_markers = function () { + // Init some variables + coverage.lines_len = document.querySelectorAll("#source > p").length; + + // Build html + coverage.build_scroll_markers(); +}; + +coverage.build_scroll_markers = function () { + const temp_scroll_marker = document.getElementById("scroll_marker") + if (temp_scroll_marker) temp_scroll_marker.remove(); + // Don't build markers if the window has no scroll bar. + if (document.body.scrollHeight <= window.innerHeight) { + return; + } + + const marker_scale = window.innerHeight / document.body.scrollHeight; + const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10); + + let previous_line = -99, last_mark, last_top; + + const scroll_marker = document.createElement("div"); + scroll_marker.id = "scroll_marker"; + document.getElementById("source").querySelectorAll( + "p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par" + ).forEach(element => { + const line_top = Math.floor(element.offsetTop * marker_scale); + const line_number = parseInt(element.querySelector(".n a").id.substr(1)); + + if (line_number === previous_line + 1) { + // If this solid missed block just make previous mark higher. + last_mark.style.height = `${line_top + line_height - last_top}px`; + } else { + // Add colored line in scroll_marker block. + last_mark = document.createElement("div"); + last_mark.id = `m${line_number}`; + last_mark.classList.add("marker"); + last_mark.style.height = `${line_height}px`; + last_mark.style.top = `${line_top}px`; + scroll_marker.append(last_mark); + last_top = line_top; + } + + previous_line = line_number; + }); + + // Append last to prevent layout calculation + document.body.append(scroll_marker); +}; + +coverage.wire_up_sticky_header = function () { + const header = document.querySelector("header"); + const header_bottom = ( + header.querySelector(".content h2").getBoundingClientRect().top - + header.getBoundingClientRect().top + ); + + function updateHeader() { + if (window.scrollY > header_bottom) { + header.classList.add("sticky"); + } else { + header.classList.remove("sticky"); + } + } + + window.addEventListener("scroll", updateHeader); + updateHeader(); +}; + +coverage.expand_contexts = function (e) { + var ctxs = e.target.parentNode.querySelector(".ctxs"); + + if (!ctxs.classList.contains("expanded")) { + var ctxs_text = ctxs.textContent; + var width = Number(ctxs_text[0]); + ctxs.textContent = ""; + for (var i = 1; i < ctxs_text.length; i += width) { + key = ctxs_text.substring(i, i + width).trim(); + ctxs.appendChild(document.createTextNode(contexts[key])); + ctxs.appendChild(document.createElement("br")); + } + ctxs.classList.add("expanded"); + } +}; + +document.addEventListener("DOMContentLoaded", () => { + if (document.body.classList.contains("indexfile")) { + coverage.index_ready(); + } else { + coverage.pyfile_ready(); + } +}); diff --git a/docs/main/coverage/d_35fc0e049e81dfd6___init___py.html b/docs/main/coverage/d_35fc0e049e81dfd6___init___py.html new file mode 100644 index 0000000..df6682a --- /dev/null +++ b/docs/main/coverage/d_35fc0e049e81dfd6___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/__init__.py: + 100% +

+ +

+ 2 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .heat_exchanger import HeatExchanger 

+

2from .moving_boundary_ntu import MovingBoundaryNTUCondenser, MovingBoundaryNTUEvaporator 

+
+ + + diff --git a/docs/main/coverage/d_35fc0e049e81dfd6_economizer_py.html b/docs/main/coverage/d_35fc0e049e81dfd6_economizer_py.html new file mode 100644 index 0000000..c818e2a --- /dev/null +++ b/docs/main/coverage/d_35fc0e049e81dfd6_economizer_py.html @@ -0,0 +1,169 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/economizer.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/economizer.py: + 100% +

+ +

+ 32 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import logging 

+

2 

+

3from vclibpy.components.heat_exchangers.ntu import BasicNTU 

+

4from vclibpy.media import ThermodynamicState 

+

5 

+

6logger = logging.getLogger(__name__) 

+

7 

+

8 

+

9class VaporInjectionEconomizerNTU(BasicNTU): 

+

10 """ 

+

11 Economizer heat exchanger which is NTU based. 

+

12 Used only for vapor injection cycles, as 

+

13 calculations are purely based for two-phase 

+

14 and liquid estimations. 

+

15 

+

16 See parent class for more arguments. 

+

17 

+

18 Assumptions: 

+

19 

+

20 - Default `flow_type` is counter_flow. 

+

21 - Default `ratio_outer_to_inner_area` is 1, as 

+

22 - pipes are nearly same diameter and length 

+

23 - Secondary heat transfer for alpha is disabled; gas, 

+

24 liquid and two-phase models are used for both sides. 

+

25 """ 

+

26 

+

27 def __init__(self, **kwargs): 

+

28 kwargs.pop("secondary_heat_transfer", None) 

+

29 kwargs.pop("secondary_medium", None) 

+

30 self._state_two_phase_outlet = None 

+

31 self._state_two_phase_inlet = None 

+

32 super().__init__( 

+

33 flow_type=kwargs.pop("flow_type", "counter"), 

+

34 secondary_heat_transfer="None", 

+

35 secondary_medium="None", 

+

36 ratio_outer_to_inner_area=kwargs.pop("ratio_outer_to_inner_area", 1), 

+

37 **kwargs) 

+

38 

+

39 @property 

+

40 def state_two_phase_inlet(self) -> ThermodynamicState: 

+

41 return self._state_two_phase_inlet 

+

42 

+

43 @state_two_phase_inlet.setter 

+

44 def state_two_phase_inlet(self, state_inlet: ThermodynamicState): 

+

45 self._state_two_phase_inlet = state_inlet 

+

46 

+

47 @property 

+

48 def state_two_phase_outlet(self) -> ThermodynamicState: 

+

49 return self._state_two_phase_outlet 

+

50 

+

51 @state_two_phase_outlet.setter 

+

52 def state_two_phase_outlet(self, state_outlet: ThermodynamicState): 

+

53 self._state_two_phase_outlet = state_outlet 

+

54 

+

55 def calc(self, inputs, fs_state) -> (float, float): 

+

56 raise NotImplementedError("Could be moved from VaporInjectionEconomizer") 

+

57 

+

58 def set_secondary_cp(self, cp: float): 

+

59 """Set primary m_flow_cp""" 

+

60 self._secondary_cp = cp 

+

61 

+

62 def start_secondary_med_prop(self): 

+

63 self.med_prop_sec = self.med_prop 

+

64 

+

65 def terminate_secondary_med_prop(self): 

+

66 pass # Not required as it is the central med_prop class 

+

67 

+

68 def calc_alpha_secondary(self, transport_properties): 

+

69 raise NotImplementedError("Economizer does not use secondary heat transfer model.") 

+

70 

+

71 def calc_transport_properties_secondary_medium(self, T, p=None): 

+

72 raise NotImplementedError("Economizer does not use this method") 

+
+ + + diff --git a/docs/main/coverage/d_35fc0e049e81dfd6_heat_exchanger_py.html b/docs/main/coverage/d_35fc0e049e81dfd6_heat_exchanger_py.html new file mode 100644 index 0000000..157f1bd --- /dev/null +++ b/docs/main/coverage/d_35fc0e049e81dfd6_heat_exchanger_py.html @@ -0,0 +1,336 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_exchanger.py: 97% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_exchanger.py: + 97% +

+ +

+ 70 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import abc 

+

2 

+

3from typing import Tuple 

+

4 

+

5from vclibpy import media 

+

6from vclibpy.datamodels import FlowsheetState, Inputs 

+

7from vclibpy.components.component import BaseComponent 

+

8from vclibpy.components.heat_exchangers.heat_transfer.heat_transfer import HeatTransfer, TwoPhaseHeatTransfer 

+

9 

+

10 

+

11class HeatExchanger(BaseComponent, abc.ABC): 

+

12 """ 

+

13 Class for a heat exchanger. 

+

14 

+

15 Args: 

+

16 A (float): 

+

17 Area of HE in m^2 for NTU calculation 

+

18 secondary_medium (str): 

+

19 Name for secondary medium, e.g. `water` or `air` 

+

20 wall_heat_transfer (HeatTransfer): 

+

21 Model for heat transfer inside wall 

+

22 secondary_heat_transfer (HeatTransfer): 

+

23 Model for heat transfer from secondary medium to wall 

+

24 gas_heat_transfer (HeatTransfer): 

+

25 Model for heat transfer from refrigerant gas to wall 

+

26 liquid_heat_transfer (HeatTransfer): 

+

27 Model for heat transfer from refrigerant liquid to wall 

+

28 two_phase_heat_transfer (TwoPhaseHeatTransfer): 

+

29 Model for heat transfer from refrigerant two phase to wall 

+

30 """ 

+

31 

+

32 def __init__( 

+

33 self, 

+

34 A: float, 

+

35 wall_heat_transfer: HeatTransfer, 

+

36 secondary_heat_transfer: HeatTransfer, 

+

37 gas_heat_transfer: HeatTransfer, 

+

38 liquid_heat_transfer: HeatTransfer, 

+

39 two_phase_heat_transfer: TwoPhaseHeatTransfer, 

+

40 secondary_medium: str 

+

41 ): 

+

42 super().__init__() 

+

43 self.A = A 

+

44 self.secondary_medium = secondary_medium.lower() 

+

45 

+

46 self._wall_heat_transfer = wall_heat_transfer 

+

47 self._secondary_heat_transfer = secondary_heat_transfer 

+

48 self._gas_heat_transfer = gas_heat_transfer 

+

49 self._liquid_heat_transfer = liquid_heat_transfer 

+

50 self._two_phase_heat_transfer = two_phase_heat_transfer 

+

51 

+

52 self.med_prop_sec = None # Later start in start_secondary_med_prop 

+

53 self._m_flow_secondary = None 

+

54 self._secondary_cp = 0 # Allow initial calculation of _m_flow_secondary_cp if cp is not set 

+

55 self._m_flow_secondary_cp = 0 

+

56 

+

57 def start_secondary_med_prop(self): 

+

58 """ 

+

59 Set up the wrapper for the secondary medium's media properties. 

+

60 """ 

+

61 # Set up the secondary_medium wrapper: 

+

62 med_prop_class, med_prop_kwargs = media.get_global_med_prop_and_kwargs() 

+

63 if self.secondary_medium == "air" and med_prop_class == media.RefProp: 

+

64 fluid_name = "AIR.PPF" 

+

65 else: 

+

66 fluid_name = self.secondary_medium 

+

67 if self.med_prop_sec is not None: 

+

68 if self.med_prop_sec.fluid_name == fluid_name: 

+

69 return 

+

70 self.med_prop_sec.terminate() 

+

71 self.med_prop_sec = med_prop_class(fluid_name=self.secondary_medium, **med_prop_kwargs) 

+

72 

+

73 def terminate_secondary_med_prop(self): 

+

74 if self.med_prop_sec is not None: 

+

75 self.med_prop_sec.terminate() 

+

76 

+

77 @abc.abstractmethod 

+

78 def calc(self, inputs: Inputs, fs_state: FlowsheetState) -> Tuple[float, float]: 

+

79 """ 

+

80 Calculate the heat exchanger based on the given inputs. 

+

81 

+

82 The flowsheet state can be used to save important variables 

+

83 during calculation for later analysis. 

+

84 

+

85 Both return values are used to check if the heat transfer is valid or not. 

+

86 

+

87 Args: 

+

88 inputs (Inputs): The inputs for the calculation. 

+

89 fs_state (FlowsheetState): The flowsheet state to save important variables. 

+

90 

+

91 Returns: 

+

92 Tuple[float, float]: 

+

93 error: Error in percentage between the required and calculated heat flow rates. 

+

94 dT_min: Minimal temperature difference (can be negative). 

+

95 """ 

+

96 raise NotImplementedError 

+

97 

+

98 def calc_alpha_two_phase(self, state_q0, state_q1, inputs: Inputs, fs_state: FlowsheetState) -> float: 

+

99 """ 

+

100 Calculate the two-phase heat transfer coefficient. 

+

101 

+

102 Args: 

+

103 state_q0: State at vapor quality 0. 

+

104 state_q1: State at vapor quality 1. 

+

105 inputs (Inputs): The inputs for the calculation. 

+

106 fs_state (FlowsheetState): The flowsheet state to save important variables. 

+

107 

+

108 Returns: 

+

109 float: The two-phase heat transfer coefficient. 

+

110 """ 

+

111 return self._two_phase_heat_transfer.calc( 

+

112 state_q0=state_q0, 

+

113 state_q1=state_q1, 

+

114 inputs=inputs, 

+

115 fs_state=fs_state, 

+

116 m_flow=self.m_flow, 

+

117 med_prop=self.med_prop, 

+

118 state_inlet=self.state_inlet, 

+

119 state_outlet=self.state_outlet 

+

120 ) 

+

121 

+

122 def calc_alpha_liquid(self, transport_properties) -> float: 

+

123 """ 

+

124 Calculate the liquid-phase heat transfer coefficient. 

+

125 

+

126 Args: 

+

127 transport_properties: Transport properties for the liquid phase. 

+

128 

+

129 Returns: 

+

130 float: The liquid-phase heat transfer coefficient. 

+

131 """ 

+

132 return self._liquid_heat_transfer.calc( 

+

133 transport_properties=transport_properties, 

+

134 m_flow=self.m_flow 

+

135 ) 

+

136 

+

137 def calc_alpha_gas(self, transport_properties) -> float: 

+

138 """ 

+

139 Calculate the gas-phase heat transfer coefficient. 

+

140 

+

141 Args: 

+

142 transport_properties: Transport properties for the gas phase. 

+

143 

+

144 Returns: 

+

145 float: The gas-phase heat transfer coefficient. 

+

146 """ 

+

147 return self._gas_heat_transfer.calc( 

+

148 transport_properties=transport_properties, 

+

149 m_flow=self.m_flow 

+

150 ) 

+

151 

+

152 def calc_alpha_secondary(self, transport_properties) -> float: 

+

153 """ 

+

154 Calculate the secondary-medium heat transfer coefficient. 

+

155 

+

156 Args: 

+

157 transport_properties: Transport properties for the secondary medium. 

+

158 

+

159 Returns: 

+

160 float: The secondary-medium heat transfer coefficient. 

+

161 """ 

+

162 return self._secondary_heat_transfer.calc( 

+

163 transport_properties=transport_properties, 

+

164 m_flow=self.m_flow_secondary 

+

165 ) 

+

166 

+

167 def calc_wall_heat_transfer(self) -> float: 

+

168 """ 

+

169 Calculate the heat transfer coefficient inside the wall. 

+

170 

+

171 Returns: 

+

172 float: The wall heat transfer coefficient. 

+

173 """ 

+

174 # Arguments are not required 

+

175 return self._wall_heat_transfer.calc( 

+

176 transport_properties=media.TransportProperties(), 

+

177 m_flow=0 

+

178 ) 

+

179 

+

180 @property 

+

181 def m_flow_secondary(self) -> float: 

+

182 return self._m_flow_secondary 

+

183 

+

184 @m_flow_secondary.setter 

+

185 def m_flow_secondary(self, m_flow: float): 

+

186 self._m_flow_secondary = m_flow 

+

187 self._m_flow_secondary_cp = self._m_flow_secondary * self._secondary_cp 

+

188 

+

189 @property 

+

190 def m_flow_secondary_cp(self): 

+

191 return self._m_flow_secondary_cp 

+

192 

+

193 def calc_secondary_cp(self, T: float, p=None): 

+

194 """ 

+

195 Calculate and set the heat capacity rate m_flow_cp of the secondary medium. 

+

196 

+

197 Args: 

+

198 T (float): Temperature of the secondary medium. 

+

199 p (float, optional): Pressure of the secondary medium. Defaults to None. 

+

200 """ 

+

201 self._secondary_cp = self.calc_transport_properties_secondary_medium(T=T, p=p).cp 

+

202 self._m_flow_secondary_cp = self.m_flow_secondary * self._secondary_cp 

+

203 

+

204 def calc_secondary_Q_flow(self, Q_flow: float) -> float: 

+

205 return Q_flow 

+

206 

+

207 def calc_Q_flow(self) -> float: 

+

208 """ 

+

209 Calculate the total heat flow rate. 

+

210 

+

211 Returns: 

+

212 float: The total heat flow rate. 

+

213 """ 

+

214 return self.m_flow * abs(self.state_inlet.h - self.state_outlet.h) 

+

215 

+

216 def calc_transport_properties_secondary_medium(self, T, p=None) -> media.TransportProperties: 

+

217 """ 

+

218 Calculate the transport properties for the selected secondary_medium. 

+

219 

+

220 Args: 

+

221 T (float): Temperature in K. 

+

222 p (float, optional): Pressure to use. Defaults to None. 

+

223 

+

224 Returns: 

+

225 media.TransportProperties: The calculated transport properties. 

+

226 """ 

+

227 if p is None: 

+

228 if self.secondary_medium == "water": 

+

229 p = 2e5 # 2 bar (default hydraulic pressure) 

+

230 elif self.secondary_medium == "air": 

+

231 p = 101325 # 1 atm 

+

232 else: 

+

233 raise NotImplementedError( 

+

234 "Default pressures for secondary_mediums aside from water and air are not supported yet." 

+

235 ) 

+

236 # Calc state 

+

237 state = self.med_prop_sec.calc_state("PT", p, T) 

+

238 # Return properties 

+

239 return self.med_prop_sec.calc_transport_properties(state) 

+
+ + + diff --git a/docs/main/coverage/d_35fc0e049e81dfd6_moving_boundary_ntu_py.html b/docs/main/coverage/d_35fc0e049e81dfd6_moving_boundary_ntu_py.html new file mode 100644 index 0000000..3169b07 --- /dev/null +++ b/docs/main/coverage/d_35fc0e049e81dfd6_moving_boundary_ntu_py.html @@ -0,0 +1,463 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/moving_boundary_ntu.py: 95% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/moving_boundary_ntu.py: + 95% +

+ +

+ 133 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import abc 

+

2import logging 

+

3 

+

4import numpy as np 

+

5from vclibpy.datamodels import FlowsheetState, Inputs 

+

6from vclibpy.components.heat_exchangers.ntu import BasicNTU 

+

7from vclibpy.media import ThermodynamicState 

+

8 

+

9logger = logging.getLogger(__name__) 

+

10 

+

11 

+

12class MovingBoundaryNTU(BasicNTU, abc.ABC): 

+

13 """ 

+

14 Moving boundary NTU based heat exchanger. 

+

15 

+

16 See parent classe for arguments. 

+

17 """ 

+

18 

+

19 def separate_phases(self, state_max: ThermodynamicState, state_min: ThermodynamicState, p: float): 

+

20 """ 

+

21 Separates a flow with possible phase changes into three parts: 

+

22 subcooling (sc), latent phase change (lat), and superheating (sh) 

+

23 at the given pressure. 

+

24 

+

25 Args: 

+

26 state_max (ThermodynamicState): State with higher enthalpy. 

+

27 state_min (ThermodynamicState): State with lower enthalpy. 

+

28 p (float): Pressure of phase change. 

+

29 

+

30 Returns: 

+

31 Tuple[float, float, float, ThermodynamicState, ThermodynamicState]: 

+

32 Q_sc: Heat for subcooling. 

+

33 Q_lat: Heat for latent phase change. 

+

34 Q_sh: Heat for superheating. 

+

35 state_q0: State at vapor quality 0 and the given pressure. 

+

36 state_q1: State at vapor quality 1 and the given pressure. 

+

37 """ 

+

38 # Get relevant states: 

+

39 state_q0 = self.med_prop.calc_state("PQ", p, 0) 

+

40 state_q1 = self.med_prop.calc_state("PQ", p, 1) 

+

41 Q_sc = max(0.0, 

+

42 min((state_q0.h - state_min.h), 

+

43 (state_max.h - state_min.h))) * self.m_flow 

+

44 Q_lat = max(0.0, 

+

45 (min(state_max.h, state_q1.h) - 

+

46 max(state_min.h, state_q0.h))) * self.m_flow 

+

47 Q_sh = max(0.0, 

+

48 min((state_max.h - state_q1.h), 

+

49 (state_max.h - state_min.h))) * self.m_flow 

+

50 return Q_sc, Q_lat, Q_sh, state_q0, state_q1 

+

51 

+

52 def iterate_area(self, dT_max, alpha_pri, alpha_sec, Q) -> float: 

+

53 """ 

+

54 Iteratively calculates the required area for the heat exchange. 

+

55 

+

56 Args: 

+

57 dT_max (float): Maximum temperature differential. 

+

58 alpha_pri (float): Heat transfer coefficient for the primary medium. 

+

59 alpha_sec (float): Heat transfer coefficient for the secondary medium. 

+

60 Q (float): Heat flow rate. 

+

61 

+

62 Returns: 

+

63 float: Required area for heat exchange. 

+

64 """ 

+

65 _accuracy = 1e-6 # square mm 

+

66 _step = 1.0 

+

67 R = self.calc_R() 

+

68 k = self.calc_k(alpha_pri, alpha_sec) 

+

69 m_flow_cp_min = self.calc_m_flow_cp_min() 

+

70 # First check if point is feasible at all 

+

71 if dT_max <= 0: 

+

72 return self.A 

+

73 eps_necessary = Q / (m_flow_cp_min * dT_max) 

+

74 

+

75 # Special cases: 

+

76 # --------------- 

+

77 # eps is equal or higher than 1, an infinite amount of area would be necessary. 

+

78 if eps_necessary >= 1: 

+

79 return self.A 

+

80 # eps is lower or equal to zero: No Area required (Q<=0) 

+

81 if eps_necessary <= 0: 

+

82 return 0 

+

83 

+

84 area = 0.0 

+

85 while True: 

+

86 NTU = self.calc_NTU(area, k, m_flow_cp_min) 

+

87 eps = self.calc_eps(R, NTU) 

+

88 if eps >= eps_necessary: 

+

89 if _step <= _accuracy: 

+

90 break 

+

91 else: 

+

92 # Go back 

+

93 area -= _step 

+

94 _step /= 10 

+

95 continue 

+

96 if _step < _accuracy and area > self.A: 

+

97 break 

+

98 area += _step 

+

99 

+

100 return min(area, self.A) 

+

101 

+

102 

+

103class MovingBoundaryNTUCondenser(MovingBoundaryNTU): 

+

104 """ 

+

105 Condenser class which implements the actual `calc` method. 

+

106 

+

107 Assumptions: 

+

108 - No phase changes in secondary medium 

+

109 - cp of secondary medium is constant over heat-exchanger 

+

110 

+

111 See parent classes for arguments. 

+

112 """ 

+

113 

+

114 def calc(self, inputs: Inputs, fs_state: FlowsheetState) -> (float, float): 

+

115 """ 

+

116 Calculate the heat exchanger with the NTU-Method based on the given inputs. 

+

117 

+

118 The flowsheet state can be used to save important variables 

+

119 during calculation for later analysis. 

+

120 

+

121 Both return values are used to check if the heat transfer is valid or not. 

+

122 

+

123 Args: 

+

124 inputs (Inputs): The inputs for the calculation. 

+

125 fs_state (FlowsheetState): The flowsheet state to save important variables. 

+

126 

+

127 Returns: 

+

128 Tuple[float, float]: 

+

129 error: Error in percentage between the required and calculated heat flow rates. 

+

130 dT_min: Minimal temperature difference (can be negative). 

+

131 """ 

+

132 self.m_flow_secondary = inputs.m_flow_con # [kg/s] 

+

133 self.calc_secondary_cp(T=inputs.T_con_in) 

+

134 

+

135 # First we separate the flow: 

+

136 Q_sc, Q_lat, Q_sh, state_q0, state_q1 = self.separate_phases( 

+

137 self.state_inlet, 

+

138 self.state_outlet, 

+

139 self.state_inlet.p 

+

140 ) 

+

141 Q = Q_sc + Q_lat + Q_sh 

+

142 

+

143 # Note: As Q_con_ntu has to converge to Q_con (m_ref*delta_h), we can safely 

+

144 # calculate the output temperature. 

+

145 

+

146 T_mean = inputs.T_con_in + self.calc_secondary_Q_flow(Q) / (self.m_flow_secondary_cp * 2) 

+

147 tra_prop_med = self.calc_transport_properties_secondary_medium(T_mean) 

+

148 alpha_med_wall = self.calc_alpha_secondary(tra_prop_med) 

+

149 

+

150 # Calculate secondary_medium side temperatures: 

+

151 # Assumption loss is the same correlation for each regime 

+

152 T_sc = inputs.T_con_in + self.calc_secondary_Q_flow(Q_sc) / self.m_flow_secondary_cp 

+

153 T_sh = T_sc + self.calc_secondary_Q_flow(Q_lat) / self.m_flow_secondary_cp 

+

154 T_out = T_sh + self.calc_secondary_Q_flow(Q_sh) / self.m_flow_secondary_cp 

+

155 

+

156 # 1. Regime: Subcooling 

+

157 Q_sc_ntu, A_sc = 0, 0 

+

158 if Q_sc > 0 and (state_q0.T != self.state_outlet.T): 

+

159 self.set_primary_cp((state_q0.h - self.state_outlet.h) / (state_q0.T - self.state_outlet.T)) 

+

160 # Get transport properties: 

+

161 tra_prop_ref_con = self.med_prop.calc_mean_transport_properties(state_q0, self.state_outlet) 

+

162 alpha_ref_wall = self.calc_alpha_liquid(tra_prop_ref_con) 

+

163 

+

164 # Only use still available area: 

+

165 A_sc = self.iterate_area(dT_max=(state_q0.T - inputs.T_con_in), 

+

166 alpha_pri=alpha_ref_wall, 

+

167 alpha_sec=alpha_med_wall, 

+

168 Q=Q_sc) 

+

169 A_sc = min(self.A, A_sc) 

+

170 

+

171 Q_sc_ntu, k_sc = self.calc_Q_ntu(dT_max=(state_q0.T - inputs.T_con_in), 

+

172 alpha_pri=alpha_ref_wall, 

+

173 alpha_sec=alpha_med_wall, 

+

174 A=A_sc) 

+

175 

+

176 # 2. Regime: Latent heat exchange 

+

177 Q_lat_ntu, A_lat = 0, 0 

+

178 if Q_lat > 0: 

+

179 self.set_primary_cp(np.inf) 

+

180 # Get transport properties: 

+

181 alpha_ref_wall = self.calc_alpha_two_phase( 

+

182 state_q0=state_q0, 

+

183 state_q1=state_q1, 

+

184 fs_state=fs_state, 

+

185 inputs=inputs 

+

186 ) 

+

187 

+

188 A_lat = self.iterate_area(dT_max=(state_q1.T - T_sc), 

+

189 alpha_pri=alpha_ref_wall, 

+

190 alpha_sec=alpha_med_wall, 

+

191 Q=Q_lat) 

+

192 # Only use still available area: 

+

193 A_lat = min(self.A - A_sc, A_lat) 

+

194 

+

195 Q_lat_ntu, k_lat = self.calc_Q_ntu(dT_max=(state_q1.T - T_sc), 

+

196 alpha_pri=alpha_ref_wall, 

+

197 alpha_sec=alpha_med_wall, 

+

198 A=A_lat) 

+

199 logger.debug(f"con_lat: pri: {round(alpha_ref_wall, 2)} sec: {round(alpha_med_wall, 2)}") 

+

200 

+

201 # 3. Regime: Superheat heat exchange 

+

202 Q_sh_ntu, A_sh = 0, 0 

+

203 if Q_sh and (self.state_inlet.T != state_q1.T): 

+

204 self.set_primary_cp((self.state_inlet.h - state_q1.h) / (self.state_inlet.T - state_q1.T)) 

+

205 # Get transport properties: 

+

206 tra_prop_ref_con = self.med_prop.calc_mean_transport_properties(self.state_inlet, state_q1) 

+

207 alpha_ref_wall = self.calc_alpha_gas(tra_prop_ref_con) 

+

208 

+

209 # Only use still available area: 

+

210 A_sh = self.A - A_sc - A_lat 

+

211 

+

212 Q_sh_ntu, k_sh = self.calc_Q_ntu(dT_max=(self.state_inlet.T - T_sh), 

+

213 alpha_pri=alpha_ref_wall, 

+

214 alpha_sec=alpha_med_wall, 

+

215 A=A_sh) 

+

216 logger.debug(f"con_sh: pri: {round(alpha_ref_wall, 2)} sec: {round(alpha_med_wall, 2)}") 

+

217 

+

218 Q_ntu = Q_sh_ntu + Q_sc_ntu + Q_lat_ntu 

+

219 error = (Q_ntu / Q - 1) * 100 

+

220 # Get possible dT_min: 

+

221 dT_min_in = self.state_outlet.T - inputs.T_con_in 

+

222 dT_min_out = self.state_inlet.T - T_out 

+

223 dT_min_LatSH = state_q1.T - T_sh 

+

224 

+

225 fs_state.set(name="A_con_sh", value=A_sh, unit="m2", description="Area for superheat heat exchange in condenser") 

+

226 fs_state.set(name="A_con_lat", value=A_lat, unit="m2", description="Area for latent heat exchange in condenser") 

+

227 fs_state.set(name="A_con_sc", value=A_sc, unit="m2", description="Area for subcooling heat exchange in condenser") 

+

228 

+

229 return error, min(dT_min_in, 

+

230 dT_min_LatSH, 

+

231 dT_min_out) 

+

232 

+

233 

+

234class MovingBoundaryNTUEvaporator(MovingBoundaryNTU): 

+

235 """ 

+

236 Evaporator class which implements the actual `calc` method. 

+

237 

+

238 Assumptions: 

+

239 - No phase changes in secondary medium 

+

240 - cp of secondary medium is constant over heat-exchanger 

+

241 

+

242 See parent classes for arguments. 

+

243 """ 

+

244 

+

245 def calc(self, inputs: Inputs, fs_state: FlowsheetState) -> (float, float): 

+

246 """ 

+

247 Calculate the heat exchanger with the NTU-Method based on the given inputs. 

+

248 

+

249 The flowsheet state can be used to save important variables 

+

250 during calculation for later analysis. 

+

251 

+

252 Both return values are used to check if the heat transfer is valid or not. 

+

253 

+

254 Args: 

+

255 inputs (Inputs): The inputs for the calculation. 

+

256 fs_state (FlowsheetState): The flowsheet state to save important variables. 

+

257 

+

258 Returns: 

+

259 Tuple[float, float]: 

+

260 error: Error in percentage between the required and calculated heat flow rates. 

+

261 dT_min: Minimal temperature difference (can be negative). 

+

262 """ 

+

263 self.m_flow_secondary = inputs.m_flow_eva # [kg/s] 

+

264 self.calc_secondary_cp(T=inputs.T_eva_in) 

+

265 

+

266 # First we separate the flow: 

+

267 Q_sc, Q_lat, Q_sh, state_q0, state_q1 = self.separate_phases( 

+

268 self.state_outlet, 

+

269 self.state_inlet, 

+

270 self.state_inlet.p 

+

271 ) 

+

272 

+

273 Q = Q_sc + Q_lat + Q_sh 

+

274 

+

275 # Note: As Q_eva_ntu has to converge to Q_eva (m_ref*delta_h), we can safely 

+

276 # calculate the output temperature. 

+

277 T_mean = inputs.T_eva_in - Q / (self.m_flow_secondary_cp * 2) 

+

278 tra_prop_med = self.calc_transport_properties_secondary_medium(T_mean) 

+

279 alpha_med_wall = self.calc_alpha_secondary(tra_prop_med) 

+

280 

+

281 # Calculate secondary_medium side temperatures: 

+

282 T_sh = inputs.T_eva_in - Q_sh / self.m_flow_secondary_cp 

+

283 T_sc = T_sh - Q_lat / self.m_flow_secondary_cp 

+

284 T_out = T_sc - Q_sc / self.m_flow_secondary_cp 

+

285 

+

286 # 1. Regime: Superheating 

+

287 Q_sh_ntu, A_sh = 0, 0 

+

288 if Q_sh and (self.state_outlet.T != state_q1.T): 

+

289 self.set_primary_cp((self.state_outlet.h - state_q1.h) / (self.state_outlet.T - state_q1.T)) 

+

290 # Get transport properties: 

+

291 tra_prop_ref_eva = self.med_prop.calc_mean_transport_properties(self.state_outlet, state_q1) 

+

292 alpha_ref_wall = self.calc_alpha_gas(tra_prop_ref_eva) 

+

293 

+

294 if Q_lat > 0: 

+

295 A_sh = self.iterate_area(dT_max=(inputs.T_eva_in - state_q1.T), 

+

296 alpha_pri=alpha_ref_wall, 

+

297 alpha_sec=alpha_med_wall, 

+

298 Q=Q_sh) 

+

299 else: 

+

300 # if only sh is present --> full area: 

+

301 A_sh = self.A 

+

302 

+

303 # Only use still available area 

+

304 A_sh = min(self.A, A_sh) 

+

305 

+

306 Q_sh_ntu, k_sh = self.calc_Q_ntu(dT_max=(inputs.T_eva_in - state_q1.T), 

+

307 alpha_pri=alpha_ref_wall, 

+

308 alpha_sec=alpha_med_wall, 

+

309 A=A_sh) 

+

310 

+

311 logger.debug(f"eva_sh: pri: {round(alpha_ref_wall, 2)} sec: {round(alpha_med_wall, 2)}") 

+

312 

+

313 # 2. Regime: Latent heat exchange 

+

314 Q_lat_ntu, A_lat = 0, 0 

+

315 if Q_lat > 0: 

+

316 self.set_primary_cp(np.inf) 

+

317 

+

318 alpha_ref_wall = self.calc_alpha_two_phase( 

+

319 state_q0=state_q0, 

+

320 state_q1=state_q1, 

+

321 fs_state=fs_state, 

+

322 inputs=inputs 

+

323 ) 

+

324 

+

325 if Q_sc > 0: 

+

326 A_lat = self.iterate_area(dT_max=(T_sh - self.state_inlet.T), 

+

327 alpha_pri=alpha_ref_wall, 

+

328 alpha_sec=alpha_med_wall, 

+

329 Q=Q_lat) 

+

330 else: 

+

331 A_lat = self.A - A_sh 

+

332 

+

333 # Only use still available area: 

+

334 A_lat = min(self.A - A_sh, A_lat) 

+

335 Q_lat_ntu, k_lat = self.calc_Q_ntu(dT_max=(T_sh - self.state_inlet.T), 

+

336 alpha_pri=alpha_ref_wall, 

+

337 alpha_sec=alpha_med_wall, 

+

338 A=A_lat) 

+

339 logger.debug(f"eva_lat: pri: {round(alpha_ref_wall, 2)} sec: {round(alpha_med_wall, 2)}") 

+

340 

+

341 # 3. Regime: Subcooling 

+

342 Q_sc_ntu, A_sc = 0, 0 

+

343 if Q_sc > 0 and (state_q0.T != self.state_inlet.T): 

+

344 self.set_primary_cp((state_q0.h - self.state_inlet.h) / (state_q0.T - self.state_inlet.T)) 

+

345 # Get transport properties: 

+

346 tra_prop_ref_eva = self.med_prop.calc_mean_transport_properties(state_q0, self.state_inlet) 

+

347 alpha_ref_wall = self.calc_alpha_liquid(tra_prop_ref_eva) 

+

348 

+

349 # Only use still available area: 

+

350 A_sc = self.A - A_sh - A_lat 

+

351 

+

352 Q_sc_ntu, k_sc = self.calc_Q_ntu(dT_max=(T_sc - self.state_inlet.T), 

+

353 alpha_pri=alpha_ref_wall, 

+

354 alpha_sec=alpha_med_wall, 

+

355 A=A_sc) 

+

356 

+

357 Q_ntu = Q_sh_ntu + Q_sc_ntu + Q_lat_ntu 

+

358 error = (Q_ntu / Q - 1) * 100 

+

359 # Get dT_min 

+

360 dT_min_in = inputs.T_eva_in - self.state_outlet.T 

+

361 dT_min_out = T_out - self.state_inlet.T 

+

362 

+

363 fs_state.set(name="A_eva_sh", value=A_sh, unit="m2", description="Area for superheat heat exchange in evaporator") 

+

364 fs_state.set(name="A_eva_lat", value=A_lat, unit="m2", description="Area for latent heat exchange in evaporator") 

+

365 

+

366 return error, min(dT_min_out, dT_min_in) 

+
+ + + diff --git a/docs/main/coverage/d_35fc0e049e81dfd6_ntu_py.html b/docs/main/coverage/d_35fc0e049e81dfd6_ntu_py.html new file mode 100644 index 0000000..2127302 --- /dev/null +++ b/docs/main/coverage/d_35fc0e049e81dfd6_ntu_py.html @@ -0,0 +1,270 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/ntu.py: 82% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/ntu.py: + 82% +

+ +

+ 50 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import logging 

+

2import abc 

+

3 

+

4import numpy as np 

+

5from vclibpy.components.heat_exchangers.heat_exchanger import HeatExchanger 

+

6 

+

7 

+

8logger = logging.getLogger(__name__) 

+

9 

+

10 

+

11class BasicNTU(HeatExchanger, abc.ABC): 

+

12 """ 

+

13 Moving boundary NTU based heat exchanger. 

+

14 

+

15 See parent class for more arguments. 

+

16 

+

17 Args: 

+

18 flow_type (str): 

+

19 Counter, Cross or parallel flow 

+

20 ratio_outer_to_inner_area (float): 

+

21 The NTU method uses the overall heat transfer coefficient `k` 

+

22 and multiplies it with the outer area `A` (area of the secondary side). 

+

23 However, depending on the heat exchanger type, the areas may 

+

24 differ drastically. For instance in an air-to-water heat exchanger. 

+

25 The VDI-Atlas proposes the ratio of outer area to inner pipe area 

+

26 to account for this mismatch in sizes. 

+

27 The calculation follows the code in the function `calc_k`. 

+

28 Typical values are around 20-30. 

+

29 """ 

+

30 

+

31 def __init__(self, flow_type: str, ratio_outer_to_inner_area: float, **kwargs): 

+

32 """ 

+

33 Initializes BasicNTU. 

+

34 

+

35 Args: 

+

36 flow_type (str): Type of flow: Counter, Cross, or Parallel. 

+

37 ratio_outer_to_inner_area (float): 

+

38 The NTU method uses the overall heat transfer coefficient `k` 

+

39 and multiplies it with the overall area `A`. 

+

40 However, depending on the heat exchanger type, the areas may 

+

41 differ drastically. For instance in an air-to-water heat exchanger. 

+

42 The VDI-Atlas proposes the ratio of outer area to inner pipe area 

+

43 to account for this mismatch in sizes. 

+

44 The calculation follows the code in the function `calc_k`. 

+

45 **kwargs: Additional keyword arguments passed to the parent class. 

+

46 """ 

+

47 super().__init__(**kwargs) 

+

48 self.ratio_outer_to_inner_area = ratio_outer_to_inner_area 

+

49 

+

50 # Set primary cp: 

+

51 self._primary_cp = None 

+

52 

+

53 # Type of HE: 

+

54 self.flow_type = flow_type.lower() 

+

55 if self.flow_type not in ["counter", "cross", "parallel"]: 

+

56 raise TypeError("Given flow_type is not supported") 

+

57 

+

58 def set_primary_cp(self, cp: float): 

+

59 """ 

+

60 Set the specific heat (cp) for the primary medium. 

+

61 

+

62 Args: 

+

63 cp (float): Specific heat for the primary medium. 

+

64 """ 

+

65 self._primary_cp = cp 

+

66 

+

67 def calc_eps(self, R: float, NTU: float) -> float: 

+

68 """ 

+

69 Calculate the effectiveness (eps) of the heat exchanger based on NTU. 

+

70 

+

71 Source of implementation: EBC Lecture SimModelle. 

+

72 

+

73 Args: 

+

74 R (float): Ratio of heat capacity rates (m_flow*cp) of the primary to secondary medium. 

+

75 NTU (float): Number of Transfer Units. 

+

76 

+

77 Returns: 

+

78 float: Effectiveness (eps) of the heat exchanger. 

+

79 """ 

+

80 if R in (0, 1): 

+

81 return NTU / (NTU + 1) 

+

82 if self.flow_type == "counter": 

+

83 return (1 - np.exp(-NTU * (1 - R))) / (1 - R * np.exp(-NTU * (1 - R))) 

+

84 if self.flow_type == "cross": 

+

85 if NTU == 0: 

+

86 return 0 

+

87 eta = NTU ** -0.22 

+

88 return 1 - np.exp((np.exp(- NTU * R * eta) - 1) / (R * eta)) 

+

89 if self.flow_type == "parallel": 

+

90 return (1 - np.exp(-NTU * (1 + R))) / (1 + R) 

+

91 raise TypeError(f"Flow type {self.flow_type} not supported") 

+

92 

+

93 def calc_R(self) -> float: 

+

94 """ 

+

95 Calculate the R value, which is the ratio of heat capacity rates (m_flow*cp) of the primary to secondary medium. 

+

96 

+

97 Returns: 

+

98 float: R value. 

+

99 """ 

+

100 m_flow_pri_cp = self.m_flow * self._primary_cp 

+

101 if m_flow_pri_cp > self.m_flow_secondary_cp: 

+

102 return self.m_flow_secondary_cp / m_flow_pri_cp 

+

103 return m_flow_pri_cp / self.m_flow_secondary_cp 

+

104 

+

105 def calc_k(self, alpha_pri: float, alpha_sec: float) -> float: 

+

106 """ 

+

107 Calculate the overall heat transfer coefficient (k) of the heat exchanger. 

+

108 

+

109 Args: 

+

110 alpha_pri (float): Heat transfer coefficient for the primary medium. 

+

111 alpha_sec (float): Heat transfer coefficient for the secondary medium. 

+

112 

+

113 Returns: 

+

114 float: Overall heat transfer coefficient (k). 

+

115 """ 

+

116 k_wall = self.calc_wall_heat_transfer() 

+

117 k = (1 / ( 

+

118 (1 / alpha_pri) * self.ratio_outer_to_inner_area + 

+

119 (1 / k_wall) * self.ratio_outer_to_inner_area + 

+

120 (1 / alpha_sec) 

+

121 ) 

+

122 ) 

+

123 return k 

+

124 

+

125 @staticmethod 

+

126 def calc_NTU(A: float, k: float, m_flow_cp: float) -> float: 

+

127 """ 

+

128 Calculate the Number of Transfer Units (NTU) for the heat exchanger. 

+

129 

+

130 Args: 

+

131 A (float): Area of the heat exchanger. 

+

132 k (float): Overall heat transfer coefficient. 

+

133 m_flow_cp (float): Minimal heat capacity rates (m_flow*cp) between primary and secondary side. 

+

134 

+

135 Returns: 

+

136 float: Number of Transfer Units (NTU). 

+

137 """ 

+

138 return k * A / m_flow_cp 

+

139 

+

140 def calc_m_flow_cp_min(self) -> float: 

+

141 """ 

+

142 Calculate the minimum value between the heat capacity rates (m_flow*cp) for the primary and secondary mediums. 

+

143 

+

144 Returns: 

+

145 float: Minimum value. 

+

146 """ 

+

147 return min( 

+

148 self.m_flow * self._primary_cp, 

+

149 self.m_flow_secondary_cp 

+

150 ) 

+

151 

+

152 def calc_Q_ntu(self, dT_max: float, alpha_pri: float, alpha_sec: float, A: float) -> (float, float): 

+

153 """ 

+

154 Calculate the heat transfer and overall heat transfer coefficient for the heat exchanger based on NTU. 

+

155 

+

156 Args: 

+

157 dT_max (float): Maximum temperature differential. 

+

158 alpha_pri (float): Heat transfer coefficient for the primary medium. 

+

159 alpha_sec (float): Heat transfer coefficient for the secondary medium. 

+

160 A (float): Area of the heat exchanger. 

+

161 

+

162 Returns: 

+

163 Tuple[float, float]: Heat transfer and overall heat transfer coefficient. 

+

164 """ 

+

165 R = self.calc_R() 

+

166 k = self.calc_k(alpha_pri, alpha_sec) 

+

167 m_flow_cp_min = self.calc_m_flow_cp_min() 

+

168 NTU = self.calc_NTU(A, k, m_flow_cp_min) 

+

169 eps = self.calc_eps(R, NTU) 

+

170 

+

171 # Get the maximal allowed heat flow 

+

172 Q_max = m_flow_cp_min * dT_max 

+

173 return Q_max * eps, k 

+
+ + + diff --git a/docs/main/coverage/d_38531a0795c587f9___init___py.html b/docs/main/coverage/d_38531a0795c587f9___init___py.html new file mode 100644 index 0000000..1c7f662 --- /dev/null +++ b/docs/main/coverage/d_38531a0795c587f9___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for vclibpy/components/expansion_valves/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/expansion_valves/__init__.py: + 100% +

+ +

+ 2 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .expansion_valve import ExpansionValve 

+

2from .bernoulli import Bernoulli 

+
+ + + diff --git a/docs/main/coverage/d_38531a0795c587f9_bernoulli_py.html b/docs/main/coverage/d_38531a0795c587f9_bernoulli_py.html new file mode 100644 index 0000000..a854652 --- /dev/null +++ b/docs/main/coverage/d_38531a0795c587f9_bernoulli_py.html @@ -0,0 +1,119 @@ + + + + + Coverage for vclibpy/components/expansion_valves/bernoulli.py: 83% + + + + + +
+
+

+ Coverage for vclibpy/components/expansion_valves/bernoulli.py: + 83% +

+ +

+ 6 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with simple bernoulli expansion valve 

+

3""" 

+

4from vclibpy.components.expansion_valves import ExpansionValve 

+

5 

+

6 

+

7class Bernoulli(ExpansionValve): 

+

8 """ 

+

9 Simple Bernoulli model. 

+

10 

+

11 Args: 

+

12 A (float): Cross-sectional area of the expansion valve. 

+

13 """ 

+

14 

+

15 def calc_m_flow_at_opening(self, opening): 

+

16 return opening * self.A * (2 * self.state_inlet.d * (self.state_inlet.p - self.state_outlet.p)) ** 0.5 

+

17 

+

18 def calc_opening_at_m_flow(self, m_flow, **kwargs): 

+

19 return ( 

+

20 m_flow / 

+

21 (self.A * (2 * self.state_inlet.d * (self.state_inlet.p - self.state_outlet.p)) ** 0.5) 

+

22 ) 

+
+ + + diff --git a/docs/main/coverage/d_38531a0795c587f9_expansion_valve_py.html b/docs/main/coverage/d_38531a0795c587f9_expansion_valve_py.html new file mode 100644 index 0000000..13496d7 --- /dev/null +++ b/docs/main/coverage/d_38531a0795c587f9_expansion_valve_py.html @@ -0,0 +1,153 @@ + + + + + Coverage for vclibpy/components/expansion_valves/expansion_valve.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/expansion_valves/expansion_valve.py: + 100% +

+ +

+ 12 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with classes for EV models. 

+

3They are not used for the calculation. 

+

4They may be used to see if the output is correct. 

+

5""" 

+

6import abc 

+

7 

+

8from vclibpy.components.component import BaseComponent 

+

9 

+

10 

+

11class ExpansionValve(BaseComponent, abc.ABC): 

+

12 """Base class for an expansion valve. 

+

13 

+

14 Args: 

+

15 A (float): Cross-sectional area of the expansion valve. 

+

16 """ 

+

17 

+

18 def __init__(self, A): 

+

19 super().__init__() 

+

20 self.A = A # Cross-sectional area of the expansion valve 

+

21 

+

22 @abc.abstractmethod 

+

23 def calc_m_flow_at_opening(self, opening) -> float: 

+

24 """ 

+

25 Calculate the mass flow rate for the given opening. 

+

26 

+

27 Args: 

+

28 opening (float): Opening of valve between 0 and 1 

+

29 

+

30 Returns: 

+

31 float: Mass flow rate in kg/s 

+

32 """ 

+

33 raise NotImplementedError 

+

34 

+

35 @abc.abstractmethod 

+

36 def calc_opening_at_m_flow(self, m_flow, **kwargs) -> float: 

+

37 """ 

+

38 Calculate the opening for the given mass flow rate 

+

39 

+

40 Args: 

+

41 m_flow (float): Mass flow rate in kg/s 

+

42 **kwargs: Possible keyword arguments for child classes 

+

43 

+

44 Returns: 

+

45 float: Opening 

+

46 """ 

+

47 raise NotImplementedError 

+

48 

+

49 def calc_outlet(self, p_outlet: float): 

+

50 """ 

+

51 Calculate isenthalpic expansion valve. 

+

52 

+

53 Args: 

+

54 p_outlet (float): Outlet pressure level 

+

55 """ 

+

56 self.state_outlet = self.med_prop.calc_state("PH", p_outlet, self.state_inlet.h) 

+
+ + + diff --git a/docs/main/coverage/d_3aa0e56d51a2f798___init___py.html b/docs/main/coverage/d_3aa0e56d51a2f798___init___py.html new file mode 100644 index 0000000..ea9afa4 --- /dev/null +++ b/docs/main/coverage/d_3aa0e56d51a2f798___init___py.html @@ -0,0 +1,104 @@ + + + + + Coverage for vclibpy/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/__init__.py: + 100% +

+ +

+ 2 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Package for stationary vapor compression models and their analysis 

+

3""" 

+

4 

+

5from vclibpy.datamodels import FlowsheetState, Inputs 

+

6 

+

7__version__ = "0.1.0" 

+
+ + + diff --git a/docs/main/coverage/d_3aa0e56d51a2f798_datamodels_py.html b/docs/main/coverage/d_3aa0e56d51a2f798_datamodels_py.html new file mode 100644 index 0000000..80f76c4 --- /dev/null +++ b/docs/main/coverage/d_3aa0e56d51a2f798_datamodels_py.html @@ -0,0 +1,338 @@ + + + + + Coverage for vclibpy/datamodels.py: 98% + + + + + +
+
+

+ Coverage for vclibpy/datamodels.py: + 98% +

+ +

+ 52 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module which contains datamodels which are used in this library. 

+

3""" 

+

4 

+

5from dataclasses import dataclass 

+

6from typing import Dict, Any 

+

7from copy import deepcopy 

+

8 

+

9 

+

10@dataclass 

+

11class Variable: 

+

12 """ 

+

13 Class for a variable used in analysis. 

+

14 

+

15 Args: 

+

16 name (str): The name of the variable. 

+

17 value (float): The numerical value of the variable. 

+

18 unit (str): The unit of measurement for the variable (optional). 

+

19 description (str): A description of the variable (optional). 

+

20 """ 

+

21 name: str 

+

22 value: float 

+

23 unit: str = None 

+

24 description: str = None 

+

25 

+

26 

+

27class VariableContainer: 

+

28 """ 

+

29 Class which holds Variables to be used anywhere in the models. 

+

30 

+

31 This class enables dynamic addition of Variables. 

+

32 """ 

+

33 def __init__(self): 

+

34 self._variables: Dict[str, Variable] = {} 

+

35 

+

36 def __str__(self): 

+

37 return f"{self.__class__.__name__}:\n" + "\n".join( 

+

38 [f"{var.name}={var.value} {var.unit} ({var.description})" 

+

39 for var in self._variables.values()] 

+

40 ) 

+

41 

+

42 def set(self, name: str, value: float, unit: str = None, description: str = None): 

+

43 """ 

+

44 Add or update a Variable in the container. 

+

45 

+

46 Args: 

+

47 name (str): The name of the variable. 

+

48 value (float): The numerical value of the variable. 

+

49 unit (str): The unit of measurement for the variable (optional). 

+

50 description (str): A description of the variable (optional). 

+

51 """ 

+

52 if name in self._variables: 

+

53 self._variables[name].value = value 

+

54 else: 

+

55 self._variables[name] = Variable( 

+

56 name=name, value=value, unit=unit, description=description 

+

57 ) 

+

58 

+

59 def __getattr__(self, item): 

+

60 """ 

+

61 Overwrite the dunder method to enable usage of e.g. 

+

62 fs_state.COP 

+

63 """ 

+

64 if item in {'__getstate__', '__setstate__'}: 

+

65 return super().__getattr__(self, item) 

+

66 if item in self._variables: 

+

67 return self._variables.get(item).value 

+

68 raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") 

+

69 

+

70 def get_variable_names(self) -> list: 

+

71 """ 

+

72 Get the names of all variables in the container. 

+

73 

+

74 Returns: 

+

75 list: A list of variable names. 

+

76 """ 

+

77 return list(self._variables.keys()) 

+

78 

+

79 def get_variables(self): 

+

80 """ 

+

81 Get all variables in the container. 

+

82 

+

83 Returns: 

+

84 Dict[str, Variable]: A dictionary of variable names and Variable objects. 

+

85 """ 

+

86 return self._variables 

+

87 

+

88 def items(self): 

+

89 """ 

+

90 Get items from the container. 

+

91 

+

92 Returns: 

+

93 Dict[str, Variable]: A dictionary of variable names and Variable objects. 

+

94 """ 

+

95 return self._variables.items() 

+

96 

+

97 def get(self, name: str, default: Any = None): 

+

98 """ 

+

99 Get the Variable with the specified name. 

+

100 

+

101 Args: 

+

102 name (str): The name of the variable. 

+

103 default (Any): Default value to return if the variable is not found. 

+

104 

+

105 Returns: 

+

106 Variable: The Variable object. 

+

107 

+

108 """ 

+

109 return self._variables.get(name, default) 

+

110 

+

111 def copy(self): 

+

112 """ 

+

113 Return a deepcopy of the instance as the variable dict is mutable. 

+

114 

+

115 Returns: 

+

116 VariableContainer: A deepcopy of the VariableContainer instance. 

+

117 """ 

+

118 return deepcopy(self) 

+

119 

+

120 def convert_to_str_value_format(self, with_unit_and_description: bool) -> Dict[str, float]: 

+

121 """ 

+

122 Returns a dictionary with a str: float format which is handy when storing results 

+

123 in files like .csv or .xlsx. 

+

124 

+

125 Args: 

+

126 with_unit_and_description (bool): When False, only the name is in the string. 

+

127 

+

128 Returns: 

+

129 dict: Containing all variables and values. 

+

130 """ 

+

131 if with_unit_and_description: 

+

132 return {f"{k} in {v.unit} ({v.description})": v.value for k, v in self.items()} 

+

133 return {k: v.value for k, v in self.items()} 

+

134 

+

135 

+

136class FlowsheetState(VariableContainer): 

+

137 """ 

+

138 This class is used to define the unique states of the flowsheet 

+

139 in the heat pump. 

+

140 

+

141 The class is dynamic in the sense that attributes may be 

+

142 added during calculation of new flowsheet. This enables 

+

143 the easy adding of custom values to analyze the whole flowsheet 

+

144 and not restrict to a certain naming convention. 

+

145 """ 

+

146 

+

147 

+

148class Inputs(VariableContainer): 

+

149 """ 

+

150 Class defining inputs to calculate the FlowsheetState. 

+

151 

+

152 While the inputs are pre-defined, you may add further ones 

+

153 using the `set` method. 

+

154 

+

155 Args: 

+

156 n (float): Relative compressor speed between 0 and 1. 

+

157 T_eva_in (float): Secondary side evaporator inlet temperature. 

+

158 T_con_in (float): Secondary side condenser inlet temperature. 

+

159 m_flow_eva (float): Secondary side evaporator mass flow rate. 

+

160 m_flow_con (float): Secondary side condenser mass flow rate. 

+

161 dT_eva_superheating (float): Super-heating after evaporator. 

+

162 dT_con_subcooling (float): Subcooling after condenser. 

+

163 T_ambient (float): Ambient temperature of the machine. 

+

164 """ 

+

165 

+

166 def __init__( 

+

167 self, 

+

168 n: float = None, 

+

169 T_eva_in: float = None, 

+

170 T_con_in: float = None, 

+

171 m_flow_eva: float = None, 

+

172 m_flow_con: float = None, 

+

173 dT_eva_superheating: float = None, 

+

174 dT_con_subcooling: float = None, 

+

175 T_ambient: float = None 

+

176 ): 

+

177 """ 

+

178 Initializes an Inputs object with parameters representing external conditions 

+

179 for the vapor compression cycle. 

+

180 

+

181 Args: 

+

182 n (float): Relative compressor speed between 0 and 1 (unit: -). 

+

183 T_eva_in (float): Secondary side evaporator inlet temperature (unit: K). 

+

184 T_con_in (float): Secondary side condenser inlet temperature (unit: K). 

+

185 m_flow_eva (float): Secondary side evaporator mass flow rate (unit: kg/s). 

+

186 m_flow_con (float): Secondary side condenser mass flow rate (unit: kg/s). 

+

187 dT_eva_superheating (float): Super-heating after evaporator (unit: K). 

+

188 dT_con_subcooling (float): Subcooling after condenser (unit: K). 

+

189 T_ambient (float): Ambient temperature of the machine (unit: K). 

+

190 """ 

+

191 super().__init__() 

+

192 self.set( 

+

193 name="n", 

+

194 value=n, 

+

195 unit="-", 

+

196 description="Relative compressor speed" 

+

197 ) 

+

198 self.set( 

+

199 name="T_eva_in", 

+

200 value=T_eva_in, 

+

201 unit="K", 

+

202 description="Secondary side evaporator inlet temperature" 

+

203 ) 

+

204 self.set( 

+

205 name="T_con_in", 

+

206 value=T_con_in, 

+

207 unit="K", 

+

208 description="Secondary side condenser inlet temperature" 

+

209 ) 

+

210 self.set( 

+

211 name="m_flow_con", 

+

212 value=m_flow_con, 

+

213 unit="kg/s", 

+

214 description="Secondary side condenser mass flow rate" 

+

215 ) 

+

216 self.set( 

+

217 name="m_flow_eva", 

+

218 value=m_flow_eva, 

+

219 unit="kg/s", 

+

220 description="Secondary side evaporator mass flow rate" 

+

221 ) 

+

222 self.set( 

+

223 name="dT_eva_superheating", 

+

224 value=dT_eva_superheating, 

+

225 unit="K", 

+

226 description="Super-heating after evaporator" 

+

227 ) 

+

228 self.set( 

+

229 name="dT_con_subcooling", 

+

230 value=dT_con_subcooling, 

+

231 unit="K", 

+

232 description="Subcooling after condenser" 

+

233 ) 

+

234 if T_ambient is None: 

+

235 T_ambient = T_eva_in 

+

236 self.set( 

+

237 name="T_ambient", 

+

238 value=T_ambient, 

+

239 unit="K", 

+

240 description="Ambient temperature of machine" 

+

241 ) 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82___init___py.html b/docs/main/coverage/d_4deeb89b86ecce82___init___py.html new file mode 100644 index 0000000..51bf0b6 --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82___init___py.html @@ -0,0 +1,100 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/__init__.py: + 100% +

+ +

+ 3 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .heat_transfer import TwoPhaseHeatTransfer, HeatTransfer, calc_reynolds_pipe 

+

2from vclibpy.components.heat_exchangers.heat_transfer import constant 

+

3from vclibpy.components.heat_exchangers.heat_transfer import wall 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82_air_to_wall_py.html b/docs/main/coverage/d_4deeb89b86ecce82_air_to_wall_py.html new file mode 100644 index 0000000..1a108fd --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82_air_to_wall_py.html @@ -0,0 +1,208 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/air_to_wall.py: 0% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/air_to_wall.py: + 0% +

+ +

+ 31 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import abc 

+

2import logging 

+

3 

+

4from .heat_transfer import HeatTransfer 

+

5from vclibpy.media import TransportProperties 

+

6 

+

7 

+

8logger = logging.getLogger(__name__) 

+

9 

+

10 

+

11class AirToWallTransfer(HeatTransfer, abc.ABC): 

+

12 """ 

+

13 Heat transfer model for air to wall. 

+

14 

+

15 Args: 

+

16 A_cross (float): 

+

17 Cross-section area in m2. 

+

18 characteristic_length (float): 

+

19 Length in m to calculate the similitude approach for the 

+

20 heat transfer from secondary_medium -> wall. 

+

21 """ 

+

22 

+

23 def __init__(self, A_cross: float, characteristic_length: float): 

+

24 self.A_cross = A_cross 

+

25 self.characteristic_length = characteristic_length 

+

26 

+

27 def calc(self, transport_properties: TransportProperties, m_flow: float) -> float: 

+

28 """ 

+

29 Heat transfer coefficient from air to the wall of the heat exchanger. 

+

30 The flow is assumed to be always laminar. 

+

31 

+

32 Returns: 

+

33 float: Heat transfer coefficient in W/(m^2*K). 

+

34 """ 

+

35 Re = self.calc_reynolds(dynamic_viscosity=transport_properties.dyn_vis, m_flow=m_flow) 

+

36 alpha_sec = self.calc_laminar_area_nusselt(Re, transport_properties.Pr, lambda_=transport_properties.lam) 

+

37 return alpha_sec 

+

38 

+

39 @abc.abstractmethod 

+

40 def calc_reynolds(self, dynamic_viscosity: float, m_flow: float): 

+

41 """ 

+

42 Calculate the reynolds number of the given flow rate. 

+

43 

+

44 Args: 

+

45 dynamic_viscosity (float): Dynamic viscosity. 

+

46 m_flow (float): Mass flow rate. 

+

47 

+

48 Returns: 

+

49 float: Reynolds number 

+

50 """ 

+

51 raise NotImplementedError 

+

52 

+

53 @abc.abstractmethod 

+

54 def calc_laminar_area_nusselt(self, Re, Pr, lambda_): 

+

55 """ 

+

56 Calculate the Nusselt number for laminar heat transfer 

+

57 on an area (Used for Air->Wall in the evaporator). 

+

58 

+

59 Args: 

+

60 Re (float): Reynolds number 

+

61 Pr (float): Prandlt number 

+

62 lambda_ (float): Lambda of air 

+

63 

+

64 Returns: 

+

65 float: Nusselt number 

+

66 """ 

+

67 raise NotImplementedError 

+

68 

+

69 

+

70class WSUAirToWall(AirToWallTransfer): 

+

71 """ 

+

72 Class to implement the heat transfer calculations 

+

73 based on the WSÜ-Script at the RWTH. 

+

74 """ 

+

75 

+

76 def calc_reynolds(self, dynamic_viscosity: float, m_flow: float) -> float: 

+

77 """ 

+

78 Calculate the Reynolds number on air side. 

+

79 

+

80 Args: 

+

81 dynamic_viscosity (float): Dynamic viscosity of air. 

+

82 m_flow (float): Mass flow rate of air. 

+

83 

+

84 Returns: 

+

85 float: Reynolds number. 

+

86 """ 

+

87 velocity_times_dens = m_flow / self.A_cross 

+

88 return (velocity_times_dens * self.characteristic_length) / dynamic_viscosity 

+

89 

+

90 def calc_laminar_area_nusselt(self, Re, Pr, lambda_) -> float: 

+

91 """ 

+

92 Calculate the Nusselt number for laminar heat transfer 

+

93 on an area (Used for Air->Wall in the evaporator). 

+

94 

+

95 Args: 

+

96 Re (float): Reynolds number of air. 

+

97 Pr (float): Prandtl number of air. 

+

98 lambda_ (float): Lambda of air. 

+

99 

+

100 Returns: 

+

101 float: Nusselt number of air. 

+

102 """ 

+

103 const_fac = 0.664 

+

104 exp_rey = 0.5 

+

105 exp_pra = 1 / 3 

+

106 if Re > 2e5: 

+

107 raise ValueError(f"Given Re {Re} is outside of allowed bounds for WSÜ-Script") 

+

108 if Pr > 10 or Pr < 0.6: 

+

109 raise ValueError(f"Given Pr {Pr} is outside of allowed bounds for WSÜ-Script") 

+

110 Nu = const_fac * Re ** exp_rey * Pr ** exp_pra 

+

111 return Nu * lambda_ / self.characteristic_length 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82_constant_py.html b/docs/main/coverage/d_4deeb89b86ecce82_constant_py.html new file mode 100644 index 0000000..e84b00c --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82_constant_py.html @@ -0,0 +1,156 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/constant.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/constant.py: + 100% +

+ +

+ 12 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with constant heat transfer assumptions 

+

3""" 

+

4import abc 

+

5 

+

6from vclibpy.media import TransportProperties 

+

7 

+

8 

+

9class ConstantHeatTransfer(abc.ABC): 

+

10 """ 

+

11 Constant heat transfer assumption 

+

12 

+

13 Args: 

+

14 alpha (float): 

+

15 Constant heat transfer coefficient in W/(m2*K) 

+

16 """ 

+

17 

+

18 def __init__(self, alpha: float): 

+

19 self.alpha = alpha 

+

20 

+

21 def calc(self, transport_properties: TransportProperties, m_flow: float) -> float: 

+

22 """ 

+

23 Calculate constant heat transfer coefficient. 

+

24 

+

25 Args: 

+

26 transport_properties (TransportProperties): Transport properties of the medium (not used). 

+

27 m_flow (float): Mass flow rate (not used). 

+

28 

+

29 Returns: 

+

30 float: Constant heat transfer coefficient in W/(m2*K). 

+

31 

+

32 """ 

+

33 return self.alpha 

+

34 

+

35 

+

36class ConstantTwoPhaseHeatTransfer(abc.ABC): 

+

37 """ 

+

38 Constant heat transfer assumption. 

+

39 

+

40 Args: 

+

41 alpha (float): 

+

42 Constant heat transfer coefficient in W/(m2*K). 

+

43 """ 

+

44 

+

45 def __init__(self, alpha: float): 

+

46 self.alpha = alpha 

+

47 

+

48 def calc(self, **kwargs) -> float: 

+

49 """ 

+

50 Calculate constant two-phase heat transfer coefficient. 

+

51 

+

52 Args: 

+

53 **kwargs: Allows to set arguments for different heat transfer classes which are not used here. 

+

54 

+

55 Returns: 

+

56 float: Constant two-phase heat transfer coefficient in W/(m2*K). 

+

57 

+

58 """ 

+

59 return self.alpha 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82_heat_transfer_py.html b/docs/main/coverage/d_4deeb89b86ecce82_heat_transfer_py.html new file mode 100644 index 0000000..160d549 --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82_heat_transfer_py.html @@ -0,0 +1,189 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/heat_transfer.py: 92% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/heat_transfer.py: + 92% +

+ +

+ 12 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with basic functions to calculate heat transfer coefficients. 

+

3""" 

+

4import abc 

+

5 

+

6import numpy as np 

+

7 

+

8from vclibpy.media import TransportProperties, ThermodynamicState, MedProp 

+

9from vclibpy.datamodels import FlowsheetState, Inputs 

+

10 

+

11 

+

12class HeatTransfer(abc.ABC): 

+

13 """ 

+

14 Base class to implement possible heat transfer models. 

+

15 

+

16 Methods: 

+

17 calc(transport_properties: TransportProperties, m_flow: float) -> float: 

+

18 Abstract method to calculate heat transfer. 

+

19 

+

20 """ 

+

21 

+

22 @abc.abstractmethod 

+

23 def calc(self, transport_properties: TransportProperties, m_flow: float) -> float: 

+

24 """ 

+

25 Calculate heat transfer. 

+

26 

+

27 Args: 

+

28 transport_properties (TransportProperties): Transport properties of the medium. 

+

29 m_flow (float): Mass flow rate. 

+

30 

+

31 Returns: 

+

32 float: Calculated heat transfer coefficient. 

+

33 

+

34 Raises: 

+

35 NotImplementedError: If the method is not implemented in the subclass. 

+

36 """ 

+

37 raise NotImplementedError 

+

38 

+

39 

+

40class TwoPhaseHeatTransfer(abc.ABC): 

+

41 """ 

+

42 Base class to implement possible heat transfer models 

+

43 """ 

+

44 

+

45 @abc.abstractmethod 

+

46 def calc( 

+

47 self, 

+

48 state_q0: ThermodynamicState, 

+

49 state_q1: ThermodynamicState, 

+

50 state_inlet: ThermodynamicState, 

+

51 state_outlet: ThermodynamicState, 

+

52 med_prop: MedProp, 

+

53 inputs: Inputs, 

+

54 fs_state: FlowsheetState, 

+

55 m_flow: float 

+

56 ) -> float: 

+

57 """ 

+

58 Calculate two-phase heat transfer. 

+

59 

+

60 Args: 

+

61 state_q0 (ThermodynamicState): Thermodynamic state at the beginning of the two-phase region. 

+

62 state_q1 (ThermodynamicState): Thermodynamic state at the end of the two-phase region. 

+

63 state_inlet (ThermodynamicState): Inlet thermodynamic state. 

+

64 state_outlet (ThermodynamicState): Outlet thermodynamic state. 

+

65 med_prop (MedProp): Medium properties class. 

+

66 inputs (Inputs): Input parameters. 

+

67 fs_state (FlowsheetState): Flowsheet state. 

+

68 m_flow (float): Mass flow rate. 

+

69 

+

70 Returns: 

+

71 float: Calculated two-phase heat transfer coefficient. 

+

72 

+

73 Raises: 

+

74 NotImplementedError: If the method is not implemented in the subclass. 

+

75 """ 

+

76 raise NotImplementedError 

+

77 

+

78 

+

79def calc_reynolds_pipe(dynamic_viscosity: float, m_flow: float, characteristic_length: float) -> float: 

+

80 """ 

+

81 Calculate Reynolds number for flow inside a pipe. 

+

82 

+

83 Args: 

+

84 dynamic_viscosity (float): Dynamic viscosity of the fluid. 

+

85 m_flow (float): Mass flow rate. 

+

86 characteristic_length (float): Characteristic length (e.g., diameter) of the pipe. 

+

87 

+

88 Returns: 

+

89 float: Reynolds number. 

+

90 

+

91 """ 

+

92 return 4 * m_flow / (np.pi * characteristic_length * dynamic_viscosity) 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82_pipe_to_wall_py.html b/docs/main/coverage/d_4deeb89b86ecce82_pipe_to_wall_py.html new file mode 100644 index 0000000..6c76a94 --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82_pipe_to_wall_py.html @@ -0,0 +1,196 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/pipe_to_wall.py: 0% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/pipe_to_wall.py: + 0% +

+ +

+ 36 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with models for pipe-to-wall heat transfer. 

+

3""" 

+

4 

+

5from .heat_transfer import HeatTransfer, calc_reynolds_pipe 

+

6from vclibpy.media import TransportProperties 

+

7 

+

8 

+

9class TurbulentFluidInPipeToWallTransfer(HeatTransfer): 

+

10 """ 

+

11 Class to model turbulent heat exchange in a pipe. 

+

12 

+

13 Args: 

+

14 method (str): 

+

15 Equation to calc the nusselt number of turbulent flow for 

+

16 a given Re and Pr number. 

+

17 Note: Just for one-phase heat transfer!! 

+

18 Implemented Options are: 

+

19 

+

20 - Taler2016 

+

21 - Domanski1989_sp_smooth 

+

22 - Amalfi2016 

+

23 - ScriptWSÜ. For turbulent regimes, eta_by_eta_w is assumed to be one. 

+

24 

+

25 Refer to the paper / documents or the function in this class for more 

+

26 info on numbers and assumptions 

+

27 characteristic_length (float): 

+

28 Length to calculate the similitude approach for the 

+

29 heat transfer from ref -> wall. For heat pumps this is 

+

30 always the Diameter of the HE in m 

+

31 """ 

+

32 

+

33 def __init__(self, method: str, characteristic_length: float): 

+

34 self.method = method 

+

35 self.characteristic_length = characteristic_length 

+

36 

+

37 def calc(self, transport_properties: TransportProperties, m_flow: float) -> float: 

+

38 """ 

+

39 Calculate heat transfer coefficient from refrigerant to the wall of the heat exchanger. 

+

40 

+

41 The flow is assumed to be always turbulent and is based on a calibrated 

+

42 Nusselt correlation. 

+

43 

+

44 Args: 

+

45 transport_properties (TransportProperties): Transport properties of the fluid. 

+

46 m_flow (float): Mass flow rate of the fluid. 

+

47 

+

48 Returns: 

+

49 float: Heat transfer coefficient from refrigerant to HE in W/(m^2*K). 

+

50 """ 

+

51 Re = calc_reynolds_pipe( 

+

52 characteristic_length=self.characteristic_length, 

+

53 dynamic_viscosity=transport_properties.dyn_vis, 

+

54 m_flow=m_flow 

+

55 ) 

+

56 Nu = self.calc_turbulent_tube_nusselt(Re, transport_properties.Pr) 

+

57 return Nu * transport_properties.lam / self.characteristic_length 

+

58 

+

59 def calc_turbulent_tube_nusselt(self, Re, Pr) -> float: 

+

60 """ 

+

61 Calculate the Nuseelt number for turbulent heat transfer 

+

62 in a tube (used for ref/water->Wall in the evaporator and condernser). 

+

63 

+

64 Args: 

+

65 Re (float): Reynolds number. 

+

66 Pr (float): Prandtl number. 

+

67 

+

68 Returns: 

+

69 float: Nusselt number. 

+

70 """ 

+

71 if self.method == "Taler2016": 

+

72 if Re < 3e3 or Re > 1e6: 

+

73 raise ValueError(f"Given Re {Re} is outside of allowed bounds for method {self.method}") 

+

74 if 0.1 <= Pr <= 1: 

+

75 return 0.02155 * Re ** 0.8018 * Pr ** 0.7095 

+

76 elif 1 < Pr <= 3: 

+

77 return 0.02155 * Re ** 0.8018 * Pr ** 0.7095 

+

78 elif 3 < Pr <= 1000: 

+

79 return 0.02155 * Re ** 0.8018 * Pr ** 0.7095 

+

80 else: 

+

81 raise ValueError(f"Given Pr {Pr} is outside of allowed bounds for method {self.method}") 

+

82 elif self.method == "ScriptWSÜ": 

+

83 if Re < 3000 or Re > 1e5: 

+

84 raise ValueError(f"Given Re {Re} is outside of allowed bounds for method {self.method}") 

+

85 eta_by_eta_w = 1 

+

86 return 0.027 * Re ** 0.8 ** Pr ** (1 / 3) * eta_by_eta_w ** 0.14 

+

87 elif self.method == "Amalfi2016": 

+

88 if Re <= 700: 

+

89 Nu = (0.0295 * Pr - 0.115) * Re ** 0.954 

+

90 else: 

+

91 Nu = (1.760 * Pr - 5.391) * Re ** 0.262 

+

92 if Nu < 0: 

+

93 raise ValueError(f"Given Pr {Pr} is outside of allowed bounds for method {self.method}") 

+

94 return Nu 

+

95 elif self.method == "Domanski1989_sp_smooth": 

+

96 # According to Domanski 1989 for singular phase and smooth surfaces 

+

97 return 0.023 * Re ** 0.8 * Pr ** 0.4 

+

98 else: 

+

99 raise TypeError(f"Method {self.method} not supported!") 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82_vdi_atlas_air_to_wall_py.html b/docs/main/coverage/d_4deeb89b86ecce82_vdi_atlas_air_to_wall_py.html new file mode 100644 index 0000000..7ad5a8f --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82_vdi_atlas_air_to_wall_py.html @@ -0,0 +1,265 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/vdi_atlas_air_to_wall.py: 0% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/vdi_atlas_air_to_wall.py: + 0% +

+ +

+ 72 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import logging 

+

2from dataclasses import dataclass 

+

3 

+

4import numpy as np 

+

5 

+

6from .air_to_wall import AirToWallTransfer 

+

7 

+

8logger = logging.getLogger(__name__) 

+

9 

+

10 

+

11@dataclass 

+

12class AirSourceHeatExchangerGeometry: 

+

13 """ 

+

14 Geometry of a fin and tube heat exchanger with two rows of pipes in a shifted arrangement. 

+

15 

+

16 Source: VDI-Wärmeatlas, Berechnungsblätter für den Wärmeübergang, 11. Auflage, S.1461 

+

17 

+

18 As the source is in German, the names are kept in German as well. 

+

19 """ 

+

20 t_l: float # Achsabstand der Rohre in Luftrichtung in m 

+

21 t_q: float # Achsabstand der Rohre quer zur Luftrichtung in m 

+

22 tiefe: float # Tiefe der Rippe gesamt 

+

23 d_a: float # Äußerer Rohrdurchmesser 

+

24 d_i: float # Innener Rohrdurchmesser 

+

25 lambda_R: float # Wärmeleitfähigkeit Material der Wand 

+

26 n_Rohre: int = 50 

+

27 n_Rippen: int = 500 

+

28 a: float = 1.95e-3 

+

29 dicke_rippe: float = 0.05e-3 

+

30 laenge: float = 1.040 

+

31 hoehe: float = 0.64 

+

32 

+

33 @property 

+

34 def char_length(self) -> float: 

+

35 """Return the characteristic length d_a.""" 

+

36 return self.d_a 

+

37 

+

38 @property 

+

39 def A_Rippen(self) -> float: 

+

40 """Return the total surface area of the fins.""" 

+

41 return 2 * self.n_Rippen * (self.tiefe * self.hoehe - self.n_Rohre * np.pi * 0.25 * self.d_a ** 2) 

+

42 

+

43 @property 

+

44 def A(self) -> float: 

+

45 """Total air side heat transfer area.""" 

+

46 return self.A_Rippen + self.A_FreieOberflaecheRohr 

+

47 

+

48 @property 

+

49 def A_FreieOberflaecheRohr(self) -> float: 

+

50 """Free air side area of the tubes.""" 

+

51 return self.n_Rippen * np.pi * self.d_a * self.a * self.n_Rohre 

+

52 

+

53 @property 

+

54 def A_RohrInnen(self) -> float: 

+

55 """Total refrigerant heat transfer area.""" 

+

56 return self.laenge * self.d_i * np.pi * self.n_Rohre 

+

57 

+

58 @property 

+

59 def A_RohrUnberippt(self) -> float: 

+

60 """Total outside area of the tubes without fins""" 

+

61 return self.laenge * self.d_a * np.pi * self.n_Rohre 

+

62 

+

63 @property 

+

64 def verjuengungsfaktor(self) -> float: 

+

65 """Reduction factor A_cross_free/A_cross_smallest""" 

+

66 return ((self.hoehe * self.laenge) / 

+

67 (self.hoehe * self.laenge - 

+

68 self.hoehe * self.n_Rippen * self.dicke_rippe - 

+

69 self.d_a * self.n_Rohre / 2 * (self.laenge - self.n_Rippen * self.dicke_rippe))) 

+

70 

+

71 @property 

+

72 def phi(self) -> float: 

+

73 """Auxiliary variable for fin efficiency""" 

+

74 if self.t_l >= 0.5 * self.t_q: 

+

75 b_r = self.t_q 

+

76 else: 

+

77 b_r = 2 * self.t_l 

+

78 l_r = np.sqrt(self.t_l **2 + self.t_q **2 / 4) 

+

79 phi_strich = 1.27 * b_r / self.d_a * np.sqrt(l_r / b_r - 0.3) 

+

80 return (phi_strich - 1) * (1 + 0.35 * np.log(phi_strich)) 

+

81 

+

82 def eta_R(self, alpha_R) -> float: 

+

83 """ 

+

84 Calculate fin efficiency. 

+

85 

+

86 Args: 

+

87 alpha_R (float): Average heat transfer coefficient for tube and fin. 

+

88 

+

89 Returns: 

+

90 float: Fin efficiency. 

+

91 """ 

+

92 X = self.phi * self.d_a / 2 * np.sqrt(2 * alpha_R / (self.lambda_R * self.dicke_rippe)) 

+

93 return 1 / X * (np.exp(X) - np.exp(-X)) / (np.exp(X) + np.exp(-X)) 

+

94 

+

95 def alpha_S(self, alpha_R) -> float: 

+

96 """ 

+

97 Calculate apparent heat transfer coefficient. 

+

98 

+

99 Args: 

+

100 alpha_R (float): Average heat transfer coefficient for tube and fin. 

+

101 

+

102 Returns: 

+

103 float: Apparent heat transfer coefficient. 

+

104 """ 

+

105 A_R_to_A = self.A_Rippen / self.A 

+

106 return alpha_R * (1 - (1 - self.eta_R(alpha_R)) * A_R_to_A) 

+

107 

+

108 

+

109class VDIAtlasAirToWallTransfer(AirToWallTransfer): 

+

110 """ 

+

111 VDI-Atlas based heat transfer estimation. 

+

112 Check `AirToWallTransfer` for further argument options. 

+

113 

+

114 This class assumes two pipes with a shifted arrangement. 

+

115 

+

116 Args: 

+

117 A_cross (float): Free cross-sectional area. 

+

118 characteristic_length (float): Characteristic length d_a. 

+

119 geometry_parameters (AirSourceHeatExchangerGeometry): 

+

120 Geometry parameters for heat exchanger according to VDI-Atlas 

+

121 """ 

+

122 

+

123 def __init__(self, 

+

124 A_cross: float, characteristic_length: float, 

+

125 geometry_parameters: AirSourceHeatExchangerGeometry): 

+

126 super().__init__(A_cross=A_cross, characteristic_length=characteristic_length) 

+

127 self.geometry_parameters = geometry_parameters 

+

128 

+

129 def calc_reynolds(self, dynamic_viscosity: float, m_flow: float) -> float: 

+

130 """ 

+

131 Calculate Reynolds number. 

+

132 

+

133 Args: 

+

134 dynamic_viscosity (float): Dynamic viscosity of the fluid. 

+

135 m_flow (float): Mass flow rate. 

+

136 

+

137 Returns: 

+

138 float: Reynolds number. 

+

139 """ 

+

140 velocity_times_dens = m_flow / self.A_cross * self.geometry_parameters.verjuengungsfaktor 

+

141 return (velocity_times_dens * self.characteristic_length) / dynamic_viscosity 

+

142 

+

143 def calc_laminar_area_nusselt(self, Re: float, Pr: float, lambda_: float) -> float: 

+

144 """ 

+

145 Calculate apparent heat transfer coefficient based on Nusselt correlation. 

+

146 

+

147 Args: 

+

148 Re (float): Reynolds number. 

+

149 Pr (float): Prandtl number. 

+

150 lambda_ (float): Thermal conductivity of air. 

+

151 

+

152 Returns: 

+

153 float: Apparent heat transfer coefficient. 

+

154 """ 

+

155 C = 0.33 # Versetzte Anordnung, zwei Rohre 

+

156 if Re < 1e3 or Re > 1e5: 

+

157 logger.debug("Given Re %s is outside of allowed bounds for VDI-Atlas", Re) 

+

158 A_div_A_0 = self.geometry_parameters.A / self.geometry_parameters.A_RohrUnberippt 

+

159 if A_div_A_0 < 5 or A_div_A_0 > 30: 

+

160 logger.debug("Given A/A0 is outside of bounds for method VDI-Atlas") 

+

161 Nu = ( 

+

162 C * Re ** 0.6 * 

+

163 A_div_A_0 ** (-0.15) * 

+

164 Pr ** (1 / 3) 

+

165 ) 

+

166 alpha_R = Nu * lambda_ / self.characteristic_length 

+

167 alpha_S = self.geometry_parameters.alpha_S(alpha_R=alpha_R) 

+

168 return alpha_S 

+
+ + + diff --git a/docs/main/coverage/d_4deeb89b86ecce82_wall_py.html b/docs/main/coverage/d_4deeb89b86ecce82_wall_py.html new file mode 100644 index 0000000..0c68186 --- /dev/null +++ b/docs/main/coverage/d_4deeb89b86ecce82_wall_py.html @@ -0,0 +1,128 @@ + + + + + Coverage for vclibpy/components/heat_exchangers/heat_transfer/wall.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/heat_exchangers/heat_transfer/wall.py: + 100% +

+ +

+ 10 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import logging 

+

2 

+

3from .heat_transfer import HeatTransfer 

+

4from vclibpy.media import TransportProperties 

+

5 

+

6 

+

7logger = logging.getLogger(__name__) 

+

8 

+

9 

+

10class WallTransfer(HeatTransfer): 

+

11 """ 

+

12 Simple wall heat transfer 

+

13 

+

14 Args: 

+

15 lambda_ (float): 

+

16 Heat conductivity of wall material in W/Km 

+

17 thickness (float): 

+

18 Thickness of wall in m^2 

+

19 """ 

+

20 def __init__(self, lambda_: float, thickness: float): 

+

21 self.thickness = thickness 

+

22 self.lambda_ = lambda_ 

+

23 

+

24 def calc(self, transport_properties: TransportProperties, m_flow: float) -> float: 

+

25 """ 

+

26 Heat transfer coefficient inside wall 

+

27 

+

28 Returns: 

+

29 float: Heat transfer coefficient in W/(m^2*K) 

+

30 """ 

+

31 return self.lambda_ / self.thickness 

+
+ + + diff --git a/docs/main/coverage/d_57d6adb9ba5af1cf___init___py.html b/docs/main/coverage/d_57d6adb9ba5af1cf___init___py.html new file mode 100644 index 0000000..c4a61e7 --- /dev/null +++ b/docs/main/coverage/d_57d6adb9ba5af1cf___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for vclibpy/utils/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/utils/__init__.py: + 100% +

+ +

+ 2 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .automation import full_factorial_map_generation, calc_multiple_states 

+

2from .sdf_ import save_to_sdf 

+
+ + + diff --git a/docs/main/coverage/d_57d6adb9ba5af1cf_automation_py.html b/docs/main/coverage/d_57d6adb9ba5af1cf_automation_py.html new file mode 100644 index 0000000..5aa4726 --- /dev/null +++ b/docs/main/coverage/d_57d6adb9ba5af1cf_automation_py.html @@ -0,0 +1,317 @@ + + + + + Coverage for vclibpy/utils/automation.py: 78% + + + + + +
+
+

+ Coverage for vclibpy/utils/automation.py: + 78% +

+ +

+ 99 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Functions to generate HP Maps automatically 

+

3""" 

+

4import logging 

+

5import pathlib 

+

6import os 

+

7from typing import List, Union 

+

8import multiprocessing 

+

9import numpy as np 

+

10import pandas as pd 

+

11from vclibpy.datamodels import FlowsheetState, Inputs 

+

12from vclibpy.flowsheets import BaseCycle 

+

13from vclibpy import utils 

+

14 

+

15logger = logging.getLogger(__name__) 

+

16 

+

17 

+

18def calc_multiple_states( 

+

19 save_path: pathlib.Path, 

+

20 heat_pump: BaseCycle, 

+

21 inputs: List[Inputs], 

+

22 **kwargs): 

+

23 """ 

+

24 Function to calculate the flowsheet states for all given inputs. 

+

25 All results are stored as a .xlsx file in the given save-path 

+

26 

+

27 Args: 

+

28 save_path (pathlib.Path): Location where to save the results as xlsx. 

+

29 heat_pump (BaseCycle): A valid flowsheet 

+

30 inputs (List[Inputs]): A list with all inputs to simulate 

+

31 **kwargs: Solver settings for the flowsheet 

+

32 """ 

+

33 rel_infos = [] 

+

34 for i, single_inputs in enumerate(inputs): 

+

35 fs_state = None 

+

36 logger.info(f"Running combination {i+1}/{len(inputs)}.") 

+

37 try: 

+

38 fs_state = heat_pump.calc_steady_state(inputs=single_inputs, 

+

39 **kwargs) 

+

40 except Exception as e: 

+

41 # Avoid loss of data if un-excepted errors occur. 

+

42 logger.error(f"An error occurred: {e}") 

+

43 if fs_state is None: 

+

44 fs_state = FlowsheetState() 

+

45 hp_state_dic = { 

+

46 **single_inputs.convert_to_str_value_format(with_unit_and_description=True), 

+

47 **fs_state.convert_to_str_value_format(with_unit_and_description=True) 

+

48 } 

+

49 rel_infos.append(hp_state_dic) 

+

50 df = pd.DataFrame(rel_infos) 

+

51 df.index.name = "State Number" 

+

52 df.to_excel(save_path.joinpath(f"{heat_pump}_{heat_pump.fluid}.xlsx"), sheet_name="HP_states", float_format="%.5f") 

+

53 

+

54 

+

55def full_factorial_map_generation( 

+

56 heat_pump: BaseCycle, 

+

57 T_eva_in_ar: Union[list, np.ndarray], 

+

58 T_con_in_ar: Union[list, np.ndarray], 

+

59 n_ar: Union[list, np.ndarray], 

+

60 m_flow_con: float, 

+

61 m_flow_eva: float, 

+

62 save_path: Union[pathlib.Path, str], 

+

63 dT_eva_superheating=5, 

+

64 dT_con_subcooling=0, 

+

65 use_multiprocessing: bool = False, 

+

66 save_plots: bool = False, 

+

67 **kwargs 

+

68) -> (pathlib.Path, pathlib.Path): 

+

69 """ 

+

70 Run a full-factorial simulation to create performance maps 

+

71 used in other simulation tools like Modelica or to analyze 

+

72 the off-design of the flowsheet. 

+

73 The results are stored and returned as .sdf and .csv files. 

+

74 Currently, only varying T_eva_in, T_con_in, and n is implemented. 

+

75 However, changing this to more dimensions or other variables 

+

76 is not much work. In this case, please raise an issue. 

+

77 

+

78 Args: 

+

79 heat_pump (BaseCycle): The flowsheet to use 

+

80 T_eva_in_ar: Array with inputs for T_eva_in 

+

81 T_con_in_ar: Array with inputs for T_con_in 

+

82 n_ar: Array with inputs for n_ar 

+

83 m_flow_con: Condenser mass flow rate 

+

84 m_flow_eva: Evaporator mass flow rate 

+

85 save_path: Where to save all results. 

+

86 dT_eva_superheating: Evaporator superheating 

+

87 dT_con_subcooling: Condenser subcooling 

+

88 use_multiprocessing: 

+

89 True to use multiprocessing. May speed up the calculation. Default is False 

+

90 save_plots: 

+

91 True to save plots of each steady state point. Default is False 

+

92 **kwargs: Solver settings for the flowsheet 

+

93 

+

94 Returns: 

+

95 tuple (pathlib.Path, pathlib.Path): 

+

96 Path to the created .sdf file and to the .csv file 

+

97 """ 

+

98 if isinstance(save_path, str): 

+

99 save_path = pathlib.Path(save_path) 

+

100 if save_plots: 

+

101 kwargs["save_path_plots"] = pathlib.Path(save_path).joinpath(f"plots_{heat_pump.flowsheet_name}_{heat_pump.fluid}") 

+

102 os.makedirs(kwargs["save_path_plots"], exist_ok=True) 

+

103 

+

104 list_mp_inputs = [] 

+

105 list_inputs = [] 

+

106 idx_for_access_later = [] 

+

107 for i_T_eva_in, T_eva_in in enumerate(T_eva_in_ar): 

+

108 for i_n, n in enumerate(n_ar): 

+

109 for i_T_con_in, T_con_in in enumerate(T_con_in_ar): 

+

110 idx_for_access_later.append([i_n, i_T_con_in, i_T_eva_in]) 

+

111 inputs = Inputs(n=n, 

+

112 T_eva_in=T_eva_in, 

+

113 T_con_in=T_con_in, 

+

114 m_flow_eva=m_flow_eva, 

+

115 m_flow_con=m_flow_con, 

+

116 dT_eva_superheating=dT_eva_superheating, 

+

117 dT_con_subcooling=dT_con_subcooling) 

+

118 list_mp_inputs.append([heat_pump, inputs, kwargs]) 

+

119 list_inputs.append(inputs) 

+

120 fs_states = [] 

+

121 i = 0 

+

122 if use_multiprocessing: 

+

123 pool = multiprocessing.Pool(processes=multiprocessing.cpu_count()) 

+

124 for fs_state in pool.imap(_calc_single_hp_state, list_mp_inputs): 

+

125 fs_states.append(fs_state) 

+

126 i += 1 

+

127 logger.info(f"Ran {i} of {len(list_mp_inputs)} points") 

+

128 else: 

+

129 for inputs in list_inputs: 

+

130 fs_state = _calc_single_hp_state([heat_pump, inputs, kwargs]) 

+

131 fs_states.append(fs_state) 

+

132 i += 1 

+

133 logger.info(f"Ran {i} of {len(list_mp_inputs)} points") 

+

134 

+

135 # Save to sdf 

+

136 result_shape = (len(n_ar), len(T_con_in_ar), len(T_eva_in_ar)) 

+

137 _dummy = np.zeros(result_shape) # Use a copy to avoid overwriting of values of any sort. 

+

138 _dummy[:] = np.nan 

+

139 # Get all possible values: 

+

140 all_variables = {} 

+

141 all_variables_info = {} 

+

142 variables_to_excel = [] 

+

143 for fs_state, inputs in zip(fs_states, list_inputs): 

+

144 all_variables.update({var: _dummy.copy() for var in fs_state.get_variable_names()}) 

+

145 all_variables_info.update({var: variable for var, variable in fs_state.get_variables().items()}) 

+

146 variables_to_excel.append({ 

+

147 **inputs.convert_to_str_value_format(with_unit_and_description=True), 

+

148 **fs_state.convert_to_str_value_format(with_unit_and_description=True), 

+

149 }) 

+

150 

+

151 # Save to excel 

+

152 save_path_sdf = save_path.joinpath(f"{heat_pump.flowsheet_name}_{heat_pump.fluid}.sdf") 

+

153 save_path_csv = save_path.joinpath(f"{heat_pump.flowsheet_name}_{heat_pump.fluid}.csv") 

+

154 pd.DataFrame(variables_to_excel).to_csv( 

+

155 save_path_csv 

+

156 ) 

+

157 

+

158 for fs_state, idx_triple in zip(fs_states, idx_for_access_later): 

+

159 i_n, i_T_con_in, i_T_eva_in = idx_triple 

+

160 for variable_name, variable in fs_state.get_variables().items(): 

+

161 all_variables[variable_name][i_n][i_T_con_in][i_T_eva_in] = variable.value 

+

162 

+

163 _nd_data = {} 

+

164 for variable, nd_data in all_variables.items(): 

+

165 _nd_data.update({ 

+

166 variable: { 

+

167 "data": nd_data, 

+

168 "unit": all_variables_info[variable].unit, 

+

169 "comment": all_variables_info[variable].description} 

+

170 }) 

+

171 

+

172 _scale_values = { 

+

173 "n": n_ar, 

+

174 "T_con_in": T_con_in_ar, 

+

175 "T_eva_in": T_eva_in_ar 

+

176 } 

+

177 inputs: Inputs = list_inputs[0] 

+

178 _parameters = {} 

+

179 for name, variable in inputs.items(): 

+

180 if name not in _scale_values: 

+

181 _parameters[name] = { 

+

182 "data": variable.value, 

+

183 "unit": variable.unit, 

+

184 "comment": variable.description 

+

185 } 

+

186 _scales = {} 

+

187 for name, data in _scale_values.items(): 

+

188 _scales[name] = { 

+

189 "data": data, 

+

190 "unit": inputs.get(name).unit, 

+

191 "comment": inputs.get(name).description 

+

192 } 

+

193 

+

194 sdf_data = { 

+

195 heat_pump.flowsheet_name: 

+

196 { 

+

197 heat_pump.fluid: (_scales, _nd_data, _parameters) 

+

198 } 

+

199 } 

+

200 utils.save_to_sdf(data=sdf_data, save_path=save_path_sdf) 

+

201 

+

202 # Terminate heat pump med-props: 

+

203 heat_pump.terminate() 

+

204 

+

205 return save_path_sdf, save_path_csv 

+

206 

+

207 

+

208def _calc_single_hp_state(data): 

+

209 """Helper function for a single state to enable multiprocessing""" 

+

210 heat_pump, inputs, kwargs = data 

+

211 fs_state = None 

+

212 try: 

+

213 fs_state = heat_pump.calc_steady_state(inputs=inputs, 

+

214 **kwargs) 

+

215 except Exception as e: 

+

216 logger.error(f"An error occurred for input: {inputs.__dict__}: {e}") 

+

217 if fs_state is None: 

+

218 fs_state = FlowsheetState() 

+

219 # Append the data to the dataframe 

+

220 return fs_state 

+
+ + + diff --git a/docs/main/coverage/d_57d6adb9ba5af1cf_nominal_design_py.html b/docs/main/coverage/d_57d6adb9ba5af1cf_nominal_design_py.html new file mode 100644 index 0000000..78a0943 --- /dev/null +++ b/docs/main/coverage/d_57d6adb9ba5af1cf_nominal_design_py.html @@ -0,0 +1,183 @@ + + + + + Coverage for vclibpy/utils/nominal_design.py: 0% + + + + + +
+
+

+ Coverage for vclibpy/utils/nominal_design.py: + 0% +

+ +

+ 34 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import time 

+

2import logging 

+

3 

+

4from vclibpy import Inputs 

+

5from vclibpy.flowsheets import BaseCycle 

+

6 

+

7logger = logging.getLogger(__name__) 

+

8 

+

9 

+

10def nominal_hp_design( 

+

11 heat_pump: BaseCycle, 

+

12 inputs: Inputs, 

+

13 fluid: str, 

+

14 dT_con: float = None, 

+

15 dT_eva: float = None, 

+

16 **kwargs) -> dict: 

+

17 """ 

+

18 Function to calculate the heat pump design 

+

19 at a given nominal point.  

+

20 Args: 

+

21 heat_pump (BaseCycle): A supported flowsheet 

+

22 inputs (Inputs): 

+

23 The input values at the nominal point. 

+

24 If the mass flow rates are not given, you 

+

25 can use dT_con and dT_eva to iteratively calculate 

+

26 the mass flow rates in order to achieve the required 

+

27 temperature differences at the nominal point. 

+

28 dT_con (float): 

+

29 Condenser temperature difference to calculate mass flow rate 

+

30 dT_eva (float): 

+

31 Evaporator temperature difference to calculate mass flow rate 

+

32 fluid (str): Fluid to be used. 

+

33 **kwargs:  

+

34 m_flow_eva_start: Guess start-value for iteration. Default 0.2 

+

35 m_flow_con_start: Guess start-value for iteration. Default 1 

+

36 accuracy: Minimal accuracy for mass flow rate iteration (in kg/s).  

+

37 Default 0.001 kg/s 

+

38  

+

39 Returns: 

+

40 dict: A dictionary with all flowsheet states and inputs containing 

+

41 information about the nominal design. 

+

42 """ 

+

43 t0 = time.time() 

+

44 # Define nominal values: 

+

45 m_flow_con_start = kwargs.get("m_flow_con_start", 0.2) 

+

46 m_flow_eva_start = kwargs.get("m_flow_eva_start", 1) 

+

47 accuracy = kwargs.get("accuracy", 0.001) 

+

48 calculate_m_flow = dT_eva is not None and dT_con is not None 

+

49 if calculate_m_flow: 

+

50 # Get nominal value: 

+

51 fs_state = heat_pump.calc_steady_state(fluid=fluid, inputs=inputs) 

+

52 if fs_state is None: 

+

53 raise ValueError("Given configuration is infeasible at nominal point.") 

+

54 

+

55 else: 

+

56 # We have to iterate to match the m_flows to the Q_cons: 

+

57 m_flow_eva_next = m_flow_eva_start 

+

58 m_flow_con_next = m_flow_con_start 

+

59 while True: 

+

60 # Set values 

+

61 m_flow_eva = m_flow_eva_next 

+

62 m_flow_con = m_flow_con_next 

+

63 inputs.set("m_flow_con", m_flow_con) 

+

64 inputs.set("m_flow_eva", m_flow_eva) 

+

65 # Get nominal value: 

+

66 fs_state = heat_pump.calc_steady_state(fluid=fluid, inputs=inputs) 

+

67 if fs_state is None: 

+

68 raise ValueError("Given configuration is infeasible at nominal point.") 

+

69 cp_eva = heat_pump.evaporator._secondary_cp 

+

70 cp_con = heat_pump.condenser._secondary_cp 

+

71 m_flow_con_next = fs_state.get("Q_con") / (dT_con * cp_con) 

+

72 m_flow_eva_next = (fs_state.get("Q_con") * (1 - 1 / fs_state.get("COP"))) / (dT_eva * cp_eva) 

+

73 # Check convergence: 

+

74 if abs(m_flow_eva_next - m_flow_eva) < accuracy and abs(m_flow_con-m_flow_con_next) < accuracy: 

+

75 break 

+

76 

+

77 nominal_design_info = { 

+

78 **inputs.convert_to_str_value_format(with_unit_and_description=False), 

+

79 **fs_state.convert_to_str_value_format(with_unit_and_description=False), 

+

80 "dT_con": dT_con, 

+

81 "dT_eva": dT_eva 

+

82 } 

+

83 logger.info("Auto-generation of nominal values took %s seconds", time.time()-t0) 

+

84 logger.info('Nominal values: %s', nominal_design_info) 

+

85 

+

86 return nominal_design_info 

+
+ + + diff --git a/docs/main/coverage/d_57d6adb9ba5af1cf_plotting_py.html b/docs/main/coverage/d_57d6adb9ba5af1cf_plotting_py.html new file mode 100644 index 0000000..27142aa --- /dev/null +++ b/docs/main/coverage/d_57d6adb9ba5af1cf_plotting_py.html @@ -0,0 +1,328 @@ + + + + + Coverage for vclibpy/utils/plotting.py: 52% + + + + + +
+
+

+ Coverage for vclibpy/utils/plotting.py: + 52% +

+ +

+ 141 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import pathlib 

+

2from typing import List 

+

3 

+

4import numpy as np 

+

5import matplotlib.pyplot as plt 

+

6import sdf 

+

7 

+

8 

+

9def plot_sdf_map( 

+

10 filepath_sdf: pathlib.Path, 

+

11 nd_data: str, 

+

12 first_dimension: str, 

+

13 second_dimension: str, 

+

14 fluids: List[str] = None, 

+

15 flowsheets: List[str] = None, 

+

16 violin_plot_variable: str = None, 

+

17 third_dimension: str = None 

+

18): 

+

19 """ 

+

20 Generate and display visualizations based on data from an SDF (Structured Data File) dataset. 

+

21 This function generates various types of visualizations based on the provided parameters, 

+

22 including 3D scatter plots, 3D surface plots, and violin plots, and displays them using Matplotlib. 

+

23 

+

24 Args: 

+

25 filepath_sdf (pathlib.Path): 

+

26 The path to the SDF dataset file. 

+

27 nd_data (str): 

+

28 The name of the primary data to be plotted. 

+

29 first_dimension (str): 

+

30 The name of the first dimension for the visualization. 

+

31 second_dimension (str): 

+

32 The name of the second dimension for the visualization. 

+

33 fluids (List[str], optional): 

+

34 List of specific fluids to include in the visualization. 

+

35 Default is None, which includes all fluids. 

+

36 flowsheets (List[str], optional): 

+

37 List of specific flowsheets to include in the visualization. 

+

38 Default is None, which includes all flowsheets. 

+

39 violin_plot_variable (str, optional): 

+

40 The variable to be used for creating violin plots. 

+

41 Default is None, which disables violin plots. 

+

42 third_dimension (str, optional): 

+

43 The name of the third dimension for 4D visualizations. 

+

44 Default is None, which disables 4D plotting. 

+

45 

+

46 Raises: 

+

47 KeyError: If the specified data or dimensions are not found in the dataset. 

+

48 

+

49 Examples: 

+

50 >>> FILEPATH_SDF = r"HeatPumpMaps.sdf" 

+

51 >>> plot_sdf_map( 

+

52 >>> filepath_sdf=FILEPATH_SDF, 

+

53 >>> nd_data="COP", 

+

54 >>> first_dimension="T_eva_in", 

+

55 >>> second_dimension="n", 

+

56 >>> fluids=["R410A"], 

+

57 >>> flowsheets=["OptiHorn"], 

+

58 >>> ) 

+

59 

+

60 """ 

+

61 if fluids is None: 

+

62 fluids = [] 

+

63 if flowsheets is None: 

+

64 flowsheets = [] 

+

65 if "T_" in second_dimension: 

+

66 offset_sec = -273.15 

+

67 else: 

+

68 offset_sec = 0 

+

69 if "T_" in first_dimension: 

+

70 offset_pri = -273.15 

+

71 else: 

+

72 offset_pri = 0 

+

73 offset_thi = 0 

+

74 plot_4D = False 

+

75 if third_dimension is not None: 

+

76 plot_4D = True 

+

77 if "T_" in third_dimension: 

+

78 offset_thi = -273.15 

+

79 

+

80 dataset = sdf.load(str(filepath_sdf)) 

+

81 plot_violin = True 

+

82 if violin_plot_variable is None: 

+

83 plot_violin = False 

+

84 violin_plot_variable = "" 

+

85 if plot_violin: 

+

86 if flowsheets: 

+

87 n_rows = len(flowsheets) 

+

88 else: 

+

89 n_rows = len(dataset.groups) 

+

90 fig_v, ax_v = plt.subplots(nrows=n_rows, ncols=1, sharex=True, 

+

91 squeeze=False) 

+

92 fig_v.suptitle(violin_plot_variable) 

+

93 i_fs = 0 

+

94 nd_str_plot = nd_data 

+

95 fac = 1 

+

96 if nd_data == "dT_eva_min": 

+

97 nd_data = "T_1" 

+

98 sub_str = "T_eva_in" 

+

99 fac = - 1 

+

100 elif nd_data == "dT_con": 

+

101 nd_data = "T_3" 

+

102 sub_str = "T_con_in" 

+

103 elif nd_data == "dT_sh": 

+

104 nd_data = "T_1" 

+

105 sub_str = "T_4" 

+

106 else: 

+

107 sub_str = "" 

+

108 

+

109 if nd_str_plot.startswith("T_"): 

+

110 offset_nd = -273.15 

+

111 else: 

+

112 offset_nd = 0 

+

113 

+

114 for flowsheet in dataset.groups: 

+

115 violin_data = {} 

+

116 if flowsheet.name not in flowsheets and len(flowsheets) > 0: 

+

117 continue 

+

118 for fluid in flowsheet.groups: 

+

119 if fluid.name not in fluids and len(fluids) > 0: 

+

120 continue 

+

121 nd, fd, sd, sub_data, td = None, None, None, None, None 

+

122 _other_scale = {} 

+

123 for ds in fluid.datasets: 

+

124 if ds.name == nd_data: 

+

125 nd = ds 

+

126 elif ds.name == first_dimension: 

+

127 fd = ds.data 

+

128 elif ds.name == second_dimension: 

+

129 sd = ds.data 

+

130 if ds.name == sub_str: 

+

131 sub_data = ds.data 

+

132 if ds.name == violin_plot_variable: 

+

133 data = ds.data.flatten() 

+

134 violin_data[fluid.name] = data[~np.isnan(data)] 

+

135 if plot_4D and ds.name == third_dimension: 

+

136 td = ds.data 

+

137 

+

138 if nd is None: 

+

139 raise KeyError("nd-String not found in dataset") 

+

140 

+

141 if sub_data is None: 

+

142 sub_data = np.zeros(nd.data.shape) 

+

143 

+

144 # Check other scales: 

+

145 for i, scale in enumerate(nd.scales): 

+

146 if scale.name not in [first_dimension, second_dimension]: 

+

147 _other_scale[i] = scale 

+

148 

+

149 if fd is None or sd is None or not _other_scale or (plot_4D and td is None): 

+

150 raise KeyError("One of the given strings was not found in dataset") 

+

151 

+

152 if plot_4D: 

+

153 fig = plt.figure() 

+

154 figtitle = f"{flowsheet.name}_{fluid.name}_{nd_str_plot}" 

+

155 fig.suptitle(figtitle) 

+

156 ax = fig.add_subplot(111, projection='3d') 

+

157 ax.set_xlabel(first_dimension) 

+

158 ax.set_ylabel(second_dimension) 

+

159 ax.set_zlabel(third_dimension) 

+

160 fourth_dim = (nd.data - sub_data) * fac + offset_nd 

+

161 # Scale values for better sizes of circles: 

+

162 bounds = [fourth_dim.min(), fourth_dim.max()] 

+

163 _max_circle_size = 30 

+

164 fourth_dim_scaled = (fourth_dim - bounds[0]) / (bounds[1] - bounds[0]) * _max_circle_size 

+

165 inp = [fd + offset_pri, sd + offset_sec, td + offset_thi] 

+

166 import itertools 

+

167 scattergrid = np.array([c for c in itertools.product(*inp)]) 

+

168 ax.scatter(scattergrid[:, 0], 

+

169 scattergrid[:, 1], 

+

170 scattergrid[:, 2], 

+

171 c=fourth_dim_scaled, 

+

172 s=fourth_dim_scaled) 

+

173 else: 

+

174 for index, scale in _other_scale.items(): 

+

175 for idx_data, value in enumerate(scale.data): 

+

176 if index==0: 

+

177 Z = nd.data[idx_data, :, :] 

+

178 if sub_str in ["T_4", ""]: 

+

179 sub_data_use = sub_data[idx_data, :, :] 

+

180 else: 

+

181 sub_data_use = sub_data 

+

182 elif index == 1: 

+

183 Z = nd.data[:, idx_data, :] 

+

184 if sub_str in ["T_4", ""]: 

+

185 sub_data_use = sub_data[:, idx_data, :] 

+

186 else: 

+

187 sub_data_use = sub_data 

+

188 else: 

+

189 Z = nd.data[:, :, idx_data] 

+

190 if sub_str in ["T_4", ""]: 

+

191 sub_data_use = sub_data[:, :, idx_data] 

+

192 else: 

+

193 sub_data_use = sub_data 

+

194 if not plot_violin: 

+

195 fig = plt.figure() 

+

196 figtitle = f"{flowsheet.name}_{fluid.name}_{nd_str_plot}_{scale.name}={round(value, 3)}" 

+

197 fig.suptitle(figtitle) 

+

198 ax = fig.add_subplot(111, projection='3d') 

+

199 ax.set_xlabel(first_dimension) 

+

200 ax.set_ylabel(second_dimension) 

+

201 X, Y = np.meshgrid(fd, sd) 

+

202 ax.plot_surface(X + offset_pri, Y + offset_sec, (Z - sub_data_use)*fac + offset_nd) 

+

203 

+

204 if plot_violin: 

+

205 for key, value in violin_data.items(): 

+

206 print(f"{violin_plot_variable}: {flowsheet.name}_{key}") 

+

207 print(f"Min: {np.min(value)}") 

+

208 print(f"Max: {np.max(value)}") 

+

209 print(f"Mean: {np.mean(value)}") 

+

210 print(f"Median: {np.median(value)}\n") 

+

211 ax_v[i_fs][0].violinplot( 

+

212 list(violin_data.values()), 

+

213 showextrema=True, 

+

214 showmeans=True, 

+

215 showmedians=True 

+

216 ) 

+

217 set_axis_style(ax_v[i_fs][0], list(violin_data.keys())) 

+

218 ax_v[i_fs][0].set_ylabel(flowsheet.name.replace("Flowsheet", "")) 

+

219 i_fs += 1 

+

220 plt.show() 

+

221 

+

222 

+

223def set_axis_style(ax, labels): 

+

224 """ 

+

225 From: https://matplotlib.org/3.1.1/gallery/statistics/customized_violin.html#sphx-glr-gallery-statistics-customized-violin-py 

+

226 """ 

+

227 ax.get_xaxis().set_tick_params(direction='out') 

+

228 ax.xaxis.set_ticks_position('bottom') 

+

229 ax.set_xticks(np.arange(1, len(labels) + 1)) 

+

230 ax.set_xticklabels(labels) 

+

231 ax.set_xlim(0.25, len(labels) + 0.75) 

+
+ + + diff --git a/docs/main/coverage/d_57d6adb9ba5af1cf_sdf__py.html b/docs/main/coverage/d_57d6adb9ba5af1cf_sdf__py.html new file mode 100644 index 0000000..5abb32f --- /dev/null +++ b/docs/main/coverage/d_57d6adb9ba5af1cf_sdf__py.html @@ -0,0 +1,260 @@ + + + + + Coverage for vclibpy/utils/sdf_.py: 42% + + + + + +
+
+

+ Coverage for vclibpy/utils/sdf_.py: + 42% +

+ +

+ 73 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import itertools 

+

2import pathlib 

+

3 

+

4import pandas as pd 

+

5import sdf 

+

6 

+

7 

+

8def save_to_sdf(data: dict, save_path: pathlib.Path): 

+

9 """ 

+

10 Save given input dictionary to a sdf file in the given save_path 

+

11 

+

12 Args: 

+

13 data (dict): 

+

14 A dictionary with the following structure: 

+

15 Keys: Flowsheet_name 

+

16 Values: A dictionary with the following structure: 

+

17 

+

18 - Keys: Fluids 

+

19 - Values: Data dictionaries with the following structure: 

+

20 A tuple with three values, in that order: 

+

21 

+

22 - scales: Of the given nd_data, e.g. T_Amb, n 

+

23 - nd_data: More-dimensional data, e.g. COP 

+

24 - parameters: Scalar values, like m_flow_con or similar 

+

25 

+

26 save_path (pathlib.Path): Where to store the data 

+

27 flowsheet_name (str): Name of the flowsheet. This is the top level group 

+

28 :return: 

+

29 """ 

+

30 if isinstance(save_path, str): 

+

31 save_path = pathlib.Path(save_path) 

+

32 _all_groups = [] 

+

33 

+

34 for flowsheet_name, fluid_dict in data.items(): 

+

35 _all_fluids = [] 

+

36 for fluid, fluid_data in fluid_dict.items(): 

+

37 # First write scales 

+

38 _scales = [] 

+

39 for scale_name, scale_values in fluid_data[0].items(): 

+

40 _scales.append(sdf.Dataset(scale_name, 

+

41 data=scale_values["data"], 

+

42 unit=scale_values["unit"], 

+

43 is_scale=True, 

+

44 display_name=scale_name, 

+

45 comment=scale_values.get("comment", ""))) 

+

46 # Now the ND-Data: 

+

47 _nd_data = [] 

+

48 for data_name, data_values in fluid_data[1].items(): 

+

49 _nd_data.append(sdf.Dataset(data_name, 

+

50 data=data_values["data"], 

+

51 unit=data_values["unit"], 

+

52 scales=_scales, 

+

53 comment=data_values.get("comment", ""))) 

+

54 # Now the constant parameters 

+

55 _paras = [] 

+

56 for para_name, para_value in fluid_data[2].items(): 

+

57 _paras.append(sdf.Dataset(para_name, 

+

58 data=para_value["data"], 

+

59 unit=para_value["unit"], 

+

60 comment=para_value.get("comment", ""))) 

+

61 

+

62 # Save everything 

+

63 fluid_group = sdf.Group(fluid, comment="Values for fluid", datasets=_scales + _nd_data + _paras) 

+

64 _all_fluids.append(fluid_group) 

+

65 

+

66 flowsheet_group = sdf.Group(flowsheet_name, 

+

67 comment="Multiple fluids for the flowsheet", 

+

68 groups=_all_fluids) 

+

69 _all_groups.append(flowsheet_group) 

+

70 

+

71 parent_group = sdf.Group("/", comment="Generated with VCLibPy", groups=_all_groups) 

+

72 sdf.save(save_path, group=parent_group) 

+

73 return save_path 

+

74 

+

75 

+

76def merge_sdfs(filepaths, save_path): 

+

77 """ 

+

78 Merge given files and return a merged file. 

+

79 Be careful if both files contain the same combination. 

+

80 Then, the latter element of the list will overwrite the first one. 

+

81 

+

82 Args: 

+

83 filepaths (list): List with paths to the files 

+

84 save_path (str): Save path for the new file 

+

85 """ 

+

86 _all_flowsheets = {} 

+

87 # Merge to structure 

+

88 for fpath in filepaths: 

+

89 dataset = sdf.load(fpath) 

+

90 for flowsheet in dataset.groups: 

+

91 fluid_data = {fluid.name: fluid for fluid in flowsheet.groups} 

+

92 if flowsheet.name not in _all_flowsheets: 

+

93 _all_flowsheets.update({flowsheet.name: fluid_data}) 

+

94 else: 

+

95 _all_flowsheets[flowsheet.name].update(fluid_data) 

+

96 

+

97 # Write structure 

+

98 _all_groups = [] 

+

99 for flowsheet_name, fluid_dict in _all_flowsheets.items(): 

+

100 _all_fluids = [] 

+

101 for fluid, data in fluid_dict.items(): 

+

102 _all_fluids.append(data) 

+

103 flowsheet_group = sdf.Group(flowsheet_name, 

+

104 comment="Multiple fluids for the flowsheet", 

+

105 groups=_all_fluids) 

+

106 _all_groups.append(flowsheet_group) 

+

107 

+

108 parent_group = sdf.Group("/", comment="Generated with python script", groups=_all_groups) 

+

109 sdf.save(save_path, group=parent_group) 

+

110 return save_path 

+

111 

+

112 

+

113def sdf_to_csv(filepath: pathlib.Path, save_path: pathlib.Path): 

+

114 """ 

+

115 Convert a given .sdf file to multiple excel files, 

+

116 for each combination of flowsheet and refrigerant one file. 

+

117 

+

118 Args: 

+

119 filepath (pathlib.Path): sdf file 

+

120 save_path (pathlib.Path): Directory where to store the csv files. 

+

121 """ 

+

122 dataset = sdf.load(str(filepath)) 

+

123 for flowsheet in dataset.groups: 

+

124 for fluid in flowsheet.groups: 

+

125 dfs = [] 

+

126 for data in fluid.datasets: 

+

127 if _is_nd(data): 

+

128 dfs.append(_unpack_nd_data(data)) 

+

129 df = pd.concat(dfs, axis=1) 

+

130 df = df.loc[:, ~df.columns.duplicated()] 

+

131 df.to_csv(save_path.joinpath(f"{flowsheet.name}_{fluid.name}.csv")) 

+

132 

+

133 

+

134def _get_name(data): 

+

135 return f"{data.name} in {data.unit} ({data.comment})" 

+

136 

+

137 

+

138def _is_nd(data): 

+

139 if data.scales == [None]: 

+

140 return False 

+

141 return True 

+

142 

+

143 

+

144def _unpack_nd_data(data): 

+

145 column_name = _get_name(data) 

+

146 scale_names = [_get_name(scale) for scale in data.scales] 

+

147 scales_with_idx = [[(idx, value) for idx, value in enumerate(scale.data)] for scale in data.scales] 

+

148 all_data = [] 

+

149 for scales in itertools.product(*scales_with_idx): 

+

150 indexer = tuple([scale[0] for scale in scales]) 

+

151 values = [scale[1] for scale in scales] 

+

152 all_data.append({ 

+

153 column_name: data.data[indexer], 

+

154 **{name: value for name, value in zip(scale_names, values)} 

+

155 }) 

+

156 return pd.DataFrame(all_data) 

+

157 

+

158 

+

159if __name__ == '__main__': 

+

160 sdf_to_csv( 

+

161 filepath=pathlib.Path(r"D:\00_temp\calibration_jmo\Optihorst_3D_vclibpy.sdf"), 

+

162 save_path=pathlib.Path(r"D:\04_git\vclibpy\tests\regression_data") 

+

163 ) 

+
+ + + diff --git a/docs/main/coverage/d_57d6adb9ba5af1cf_ten_coefficient_compressor_reqression_py.html b/docs/main/coverage/d_57d6adb9ba5af1cf_ten_coefficient_compressor_reqression_py.html new file mode 100644 index 0000000..c5273c6 --- /dev/null +++ b/docs/main/coverage/d_57d6adb9ba5af1cf_ten_coefficient_compressor_reqression_py.html @@ -0,0 +1,278 @@ + + + + + Coverage for vclibpy/utils/ten_coefficient_compressor_reqression.py: 0% + + + + + +
+
+

+ Coverage for vclibpy/utils/ten_coefficient_compressor_reqression.py: + 0% +

+ +

+ 64 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from typing import List 

+

2import csv 

+

3import pandas as pd 

+

4 

+

5from vclibpy.components.compressors import TenCoefficientCompressor 

+

6from vclibpy import media, Inputs 

+

7 

+

8try: 

+

9 from sklearn.linear_model import LinearRegression 

+

10 from sklearn.preprocessing import PolynomialFeatures 

+

11 from xlsxwriter.workbook import Workbook 

+

12except ImportError as err: 

+

13 raise ImportError("You have to install xlsxwriter and " 

+

14 "sklearn to use this regression tool") 

+

15 

+

16 

+

17def create_regression_data( 

+

18 variables: List[str], 

+

19 T_con: List[float], T_eva: List[float], n: List[float], 

+

20 T_sh: float, T_sc: float, n_max: int, V_h: float, fluid: str, datasheet: str, 

+

21 capacity_definition: str, assumed_eta_mech: int, 

+

22 folder_path: str): 

+

23 """ 

+

24 Performs multidimensional linear regression to create compressor learning data. 

+

25 

+

26 Args: 

+

27 variables: (List[str]): 

+

28 Variable names to create regressions for. 

+

29 Options are: eta_s, lambda_h, and eta_mech 

+

30 T_con (List[float]): Condensing temperatures in K 

+

31 T_eva (List[float]): Evaporation temperatures in K 

+

32 n (List[float]): Compressor speeds from 0 to 1 

+

33 T_sh (float): Superheat temperature. 

+

34 T_sc (float): Subcooling temperature. 

+

35 n_max (int): Maximum compressor speed. 

+

36 V_h (float): Compressor volume. 

+

37 fluid (str): Type of refrigerant. 

+

38 datasheet (str): Path to the modified datasheet. 

+

39 capacity_definition (str): Definition of compressor capacity (e.g., "cooling"). 

+

40 assumed_eta_mech (int): Assumed mechanical efficiency. 

+

41 folder_path (str): Path to the folder where the newly created table will be saved. 

+

42 

+

43 Returns: 

+

44 List[float]: A list containing the regression parameters [P0, P1, ..., P9]. 

+

45 

+

46 Raises: 

+

47 ValueError: If any specified variable column is not present in the DataFrame. 

+

48 

+

49 Example: 

+

50 >>> create_regression_data(11.1, 8.3, 120, 20.9e-6, "PROPANE", "path/to/datasheet.xlsx", 

+

51 ... "cooling", 1, 6, 5, 5, 5, 303.15, 243.15, "path/to/save", 3) 

+

52 [intercept, P1, P2, P3, P4, P5, P6, P7, P8, P9] 

+

53 """ 

+

54 # create RefProp, fluid & compressor instance 

+

55 med_prop = media.CoolProp(fluid) 

+

56 

+

57 compressor = TenCoefficientCompressor( 

+

58 N_max=n_max, V_h=V_h, T_sc=T_sc, T_sh=T_sh, 

+

59 capacity_definition=capacity_definition, 

+

60 assumed_eta_mech=assumed_eta_mech, 

+

61 datasheet=datasheet 

+

62 ) 

+

63 

+

64 compressor.med_prop = med_prop 

+

65 keywords = { 

+

66 "eta_s": "Isentropic Efficiency(-)", 

+

67 "lambda_h": "Volumentric Efficiency(-)", 

+

68 "eta_mech": "Mechanical Efficiency(-)" 

+

69 } 

+

70 

+

71 tuples_for_cols = [("", "n")] 

+

72 for _variable in variables: 

+

73 for _n in n: 

+

74 tuples_for_cols.append((keywords[_variable], compressor.get_n_absolute(_n))) 

+

75 # tuples_for_cols: 

+

76 # eta_s eta_s eta_s lambda_h lambda_h lambda_h eta_mech ... 

+

77 # n 30 60 90 30 60 90 30 ... 

+

78 cols = pd.MultiIndex.from_tuples(tuples_for_cols) 

+

79 final_df = pd.DataFrame( 

+

80 data={cols[0]: ["P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10"]}, 

+

81 columns=cols 

+

82 ) 

+

83 # final_df: column names are tuples (tuples_for_cols). 

+

84 # First column is filled with P1, P2, ... 

+

85 

+

86 # for-loop for multiple types(eta_s, eta_mech, etc) 

+

87 for m, _variable in enumerate(variables): 

+

88 for k, _n in enumerate(n): # for-loop for multiple rotation speeds 

+

89 T_eva_list = [] 

+

90 T_con_list = [] 

+

91 result_list = [] 

+

92 # for-loop for multiple evaporating temperatures 

+

93 for i in range(len(T_eva)): 

+

94 # for-loop for multiple condensing temperatures 

+

95 for j in range(len(T_con)): 

+

96 if T_eva[i] < T_con[j]: 

+

97 p1 = med_prop.calc_state("TQ", T_eva[i], 1).p 

+

98 state_1 = med_prop.calc_state("PT", p1, (T_eva[i] + T_sh)) 

+

99 compressor.state_inlet = state_1 

+

100 p2 = med_prop.calc_state("TQ", T_con[j], 1).p 

+

101 # The enthalpy and entropy of the outlet 

+

102 # state do not matter, only the pressure: 

+

103 # TODO: Enable calculation of get_lambda_h etc. with p2 only 

+

104 state_2 = med_prop.calc_state("PS", p2, state_1.s) 

+

105 compressor.state_outlet = state_2 

+

106 T_eva_list.append(T_eva[i]) 

+

107 T_con_list.append(T_con[j]) 

+

108 inputs = Inputs(n=_n) 

+

109 

+

110 if _variable == "eta_s": 

+

111 result_list.append(compressor.get_eta_isentropic( 

+

112 p_outlet=p2, inputs=inputs) 

+

113 ) 

+

114 elif _variable == "lambda_h": 

+

115 result_list.append(compressor.get_lambda_h(inputs=inputs)) 

+

116 elif _variable == "eta_mech": 

+

117 result_list.append(compressor.get_eta_mech(inputs=inputs)) 

+

118 

+

119 df = pd.DataFrame( 

+

120 data={"T_eva": T_eva_list, 

+

121 "T_con": T_con_list, 

+

122 _variable: result_list} 

+

123 ) 

+

124 

+

125 final_df[cols[m * len(n) + k + 1]] = create_regression_parameters(df, _variable) 

+

126 

+

127 # dataframes with a double column header can't be saved as a 

+

128 # .xlsx yet, if index = False (NotImplementedError). 

+

129 # .xlsx format is necessary, because TenCoefficientCompressor.get_parameter() 

+

130 # expects a .xlsx as an input 

+

131 # --> workaround: save the dataframe as a .csv, read it again and save it as a .xlsx 

+

132 # TODO: Revert once this feature is in pandas. 

+

133 final_df.to_csv(folder_path + r"\regressions.csv", index=False) 

+

134 

+

135 workbook = Workbook(folder_path + r"\regressions.xlsx") 

+

136 worksheet = workbook.add_worksheet() 

+

137 with open(folder_path + r"\regressions.csv", 'rt', encoding='utf8') as f: 

+

138 reader = csv.reader(f) 

+

139 for r, row in enumerate(reader): 

+

140 for c, col in enumerate(row): 

+

141 worksheet.write(r, c, col) 

+

142 workbook.close() 

+

143 

+

144 

+

145def create_regression_parameters(df: pd.DataFrame, variable: str): 

+

146 """ 

+

147 Performs multidimensional linear regression to calculate 

+

148 ten coefficient regression parameters. 

+

149 

+

150 Args: 

+

151 df (pd.DataFrame): The input DataFrame containing the necessary columns. 

+

152 variable (str): The column name for the dependent variable. 

+

153 

+

154 Returns: 

+

155 List[float]: A list containing the ten regression parameters. 

+

156 

+

157 Raises: 

+

158 ValueError: If the specified variable column is not present in the DataFrame. 

+

159 

+

160 Example: 

+

161 >>> df = pd.DataFrame({'T_eva': [1, 2, 3], 'T_con': [4, 5, 6], 'X': [7, 8, 9]}) 

+

162 >>> create_regression_parameters(df, 'X') 

+

163 [intercept, P1, P2, P3, P4, P5, P6, P7, P8, P9] 

+

164 """ 

+

165 # extract the columns x, y und z 

+

166 x = df['T_eva'].values 

+

167 y = df['T_con'].values 

+

168 z = df[variable].values 

+

169 

+

170 # define the features (x, y, x^2, xy, y^2, x^3, x^2y, xy^2, y^3) 

+

171 features = PolynomialFeatures(degree=3, include_bias=False) 

+

172 X = features.fit_transform(pd.concat([pd.DataFrame(x), pd.DataFrame(y)], axis=1)) 

+

173 

+

174 # Execute the multidimensional linear regression 

+

175 model = LinearRegression().fit(X, z) 

+

176 

+

177 output = [model.intercept_, model.coef_[0], model.coef_[1], model.coef_[2], 

+

178 model.coef_[3], model.coef_[4], 

+

179 model.coef_[5], model.coef_[6], model.coef_[7], model.coef_[8]] 

+

180 # output = P1-P10 

+

181 return output 

+
+ + + diff --git a/docs/main/coverage/d_c048ebee450da2af___init___py.html b/docs/main/coverage/d_c048ebee450da2af___init___py.html new file mode 100644 index 0000000..2857a04 --- /dev/null +++ b/docs/main/coverage/d_c048ebee450da2af___init___py.html @@ -0,0 +1,136 @@ + + + + + Coverage for vclibpy/media/__init__.py: 90% + + + + + +
+
+

+ Coverage for vclibpy/media/__init__.py: + 90% +

+ +

+ 10 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .states import ThermodynamicState, TransportProperties 

+

2from .media import get_two_phase_limits, MedProp 

+

3from .cool_prop import CoolProp 

+

4from .ref_prop import RefProp 

+

5 

+

6 

+

7__all__ = ['ThermodynamicState', 

+

8 'TransportProperties', 

+

9 'MedProp', 

+

10 'CoolProp', 

+

11 'RefProp'] 

+

12 

+

13USED_MED_PROP = (CoolProp, {}) 

+

14 

+

15 

+

16def set_global_media_properties(med_prop_class: object, **kwargs): 

+

17 """ 

+

18 Set the globally used MedProp class. 

+

19 

+

20 Args: 

+

21 med_prop_class (object): 

+

22 Available MedProp children class. 

+

23 kwargs (dict): 

+

24 Additional settings for the MedProp class, 

+

25 e.g. {"use_high_level_api": True} for CoolProp. 

+

26 """ 

+

27 global USED_MED_PROP 

+

28 USED_MED_PROP = (med_prop_class, kwargs) 

+

29 

+

30 

+

31def get_global_med_prop_and_kwargs(): 

+

32 """ 

+

33 Get the global MedProp class used 

+

34 for all calculations. 

+

35 Returns: 

+

36 MedProp: The class 

+

37 """ 

+

38 global USED_MED_PROP 

+

39 return USED_MED_PROP[0], USED_MED_PROP[1] 

+
+ + + diff --git a/docs/main/coverage/d_c048ebee450da2af_cool_prop_py.html b/docs/main/coverage/d_c048ebee450da2af_cool_prop_py.html new file mode 100644 index 0000000..319c329 --- /dev/null +++ b/docs/main/coverage/d_c048ebee450da2af_cool_prop_py.html @@ -0,0 +1,239 @@ + + + + + Coverage for vclibpy/media/cool_prop.py: 67% + + + + + +
+
+

+ Coverage for vclibpy/media/cool_prop.py: + 67% +

+ +

+ 57 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with cool prop wrapper. 

+

3""" 

+

4import logging 

+

5 

+

6import CoolProp.CoolProp as CoolPropInternal 

+

7 

+

8from vclibpy.media.media import MedProp 

+

9from vclibpy.media.states import ThermodynamicState, TransportProperties 

+

10 

+

11logger = logging.getLogger(__name__) 

+

12 

+

13 

+

14class CoolProp(MedProp): 

+

15 """ 

+

16 Class using the open-source CoolProp package 

+

17 to access the properties. 

+

18 

+

19 Args: 

+

20 use_high_level_api (bool): 

+

21 True to use the high-level api, which is much slower, 

+

22 but you can use all modes in calc_state. 

+

23 Default is False. 

+

24 """ 

+

25 

+

26 _mode_map = { 

+

27 "PT": (CoolPropInternal.PT_INPUTS, True), 

+

28 "TQ": (CoolPropInternal.QT_INPUTS, False), 

+

29 "PS": (CoolPropInternal.PSmass_INPUTS, True), 

+

30 "PH": (CoolPropInternal.HmassP_INPUTS, False), 

+

31 "PQ": (CoolPropInternal.PQ_INPUTS, True) 

+

32 } 

+

33 

+

34 def __init__(self, fluid_name, use_high_level_api: bool = False): 

+

35 super().__init__(fluid_name=fluid_name) 

+

36 # Set molar mass and trigger a possible fluid-name error 

+

37 # if the fluid is not supported. 

+

38 self._helmholtz_equation_of_state = CoolPropInternal.AbstractState("HEOS", self.fluid_name) 

+

39 self.M = self._helmholtz_equation_of_state.molar_mass() 

+

40 self.use_high_level_api = use_high_level_api 

+

41 

+

42 def calc_state(self, mode: str, var1: float, var2: float): 

+

43 super().calc_state(mode=mode, var1=var1, var2=var2) 

+

44 

+

45 if self.use_high_level_api: 

+

46 _var1_str, _var2_str = mode[0], mode[1] 

+

47 # CoolProp returns Pa 

+

48 p = CoolPropInternal.PropsSI('P', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

49 # CoolProp returns kg/m^3 

+

50 d = CoolPropInternal.PropsSI('D', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

51 # CoolProp returns K 

+

52 T = CoolPropInternal.PropsSI('T', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

53 # CoolProp returns J/kg 

+

54 u = CoolPropInternal.PropsSI('U', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

55 # CoolProp returns J/kg 

+

56 h = CoolPropInternal.PropsSI('H', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

57 # CoolProp returns J/kg/K 

+

58 s = CoolPropInternal.PropsSI('S', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

59 # CoolProp returns mol/mol 

+

60 q = CoolPropInternal.PropsSI('Q', _var1_str, var1, _var2_str, var2, self.fluid_name) 

+

61 # Return new state 

+

62 return ThermodynamicState(p=p, T=T, u=u, h=h, s=s, d=d, q=q) 

+

63 

+

64 self._update_coolprop_heos(mode=mode, var1=var1, var2=var2) 

+

65 # Return new state 

+

66 return ThermodynamicState( 

+

67 p=self._helmholtz_equation_of_state.p(), 

+

68 T=self._helmholtz_equation_of_state.T(), 

+

69 u=self._helmholtz_equation_of_state.umass(), 

+

70 h=self._helmholtz_equation_of_state.hmass(), 

+

71 s=self._helmholtz_equation_of_state.smass(), 

+

72 d=self._helmholtz_equation_of_state.rhomass(), 

+

73 q=self._helmholtz_equation_of_state.Q() 

+

74 ) 

+

75 

+

76 def calc_transport_properties(self, state: ThermodynamicState): 

+

77 if 0 <= state.q <= 1: 

+

78 mode = "PQ" 

+

79 var1, var2 = state.p, state.q 

+

80 else: 

+

81 # Get using p and T 

+

82 mode = "PT" 

+

83 var1, var2 = state.p, state.T 

+

84 

+

85 if self.use_high_level_api: 

+

86 args = [mode[0], var1, mode[1], var2, self.fluid_name] 

+

87 # CoolProp returns - 

+

88 pr = CoolPropInternal.PropsSI('PRANDTL', *args) 

+

89 # CoolProp returns J/kg/K 

+

90 cp = CoolPropInternal.PropsSI('C', *args) 

+

91 # CoolProp returns J/kg/K 

+

92 cv = CoolPropInternal.PropsSI('CVMASS', *args) 

+

93 # CoolProp returns W/m/K 

+

94 lam = CoolPropInternal.PropsSI('CONDUCTIVITY', *args) 

+

95 # CoolProp returns Pa*s 

+

96 dyn_vis = CoolPropInternal.PropsSI('VISCOSITY', *args) 

+

97 # Internal calculation as kinematic vis is ration of dyn_vis to density 

+

98 # In m^2/s 

+

99 kin_vis = dyn_vis / state.d 

+

100 

+

101 # Create transport properties instance 

+

102 return TransportProperties(lam=lam, dyn_vis=dyn_vis, kin_vis=kin_vis, 

+

103 pr=pr, cp=cp, cv=cv, state=state) 

+

104 # Low-level API 

+

105 self._update_coolprop_heos(mode=mode, var1=var1, var2=var2) 

+

106 # Create transport properties instance 

+

107 return TransportProperties( 

+

108 lam=self._helmholtz_equation_of_state.conductivity(), 

+

109 dyn_vis=self._helmholtz_equation_of_state.viscosity(), 

+

110 kin_vis=self._helmholtz_equation_of_state.viscosity() / state.d, 

+

111 pr=self._helmholtz_equation_of_state.Prandtl(), 

+

112 cp=self._helmholtz_equation_of_state.cpmass(), 

+

113 cv=self._helmholtz_equation_of_state.cvmass(), 

+

114 state=state 

+

115 ) 

+

116 

+

117 def _update_coolprop_heos(self, mode, var1, var2): 

+

118 if mode not in self._mode_map: 

+

119 raise KeyError( 

+

120 f"Given mode '{mode}' is currently not supported with the " 

+

121 f"faster low-level API to cool-prop. " 

+

122 f"Either use the high-level API or raise an issue. " 

+

123 f"Supported modes: {', '.join(self._mode_map.keys())}" 

+

124 ) 

+

125 i_input, not_reverse_variables = self._mode_map[mode] 

+

126 if not_reverse_variables: 

+

127 self._helmholtz_equation_of_state.update(i_input, var1, var2) 

+

128 else: 

+

129 self._helmholtz_equation_of_state.update(i_input, var2, var1) 

+

130 

+

131 def get_critical_point(self): 

+

132 Tc = CoolPropInternal.PropsSI("TCRIT", self.fluid_name) 

+

133 pc = CoolPropInternal.PropsSI("PCRIT", self.fluid_name) 

+

134 dc = CoolPropInternal.PropsSI("RHOCRIT", self.fluid_name) 

+

135 return Tc, pc, dc 

+

136 

+

137 def get_molar_mass(self): 

+

138 return self.M 

+

139 

+

140 

+

141if __name__ == '__main__': 

+

142 CoolProp("Propan") 

+
+ + + diff --git a/docs/main/coverage/d_c048ebee450da2af_media_py.html b/docs/main/coverage/d_c048ebee450da2af_media_py.html new file mode 100644 index 0000000..3f08c2c --- /dev/null +++ b/docs/main/coverage/d_c048ebee450da2af_media_py.html @@ -0,0 +1,333 @@ + + + + + Coverage for vclibpy/media/media.py: 85% + + + + + +
+
+

+ Coverage for vclibpy/media/media.py: + 85% +

+ +

+ 53 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1"""Module with wrappers to access and handle media property databases. 

+

2 

+

3This module provides interfaces to load media properties using various wrappers and 

+

4handle calculations related to media properties. 

+

5 

+

6Classes: 

+

7 MedProp: Base class for all media property interfaces. 

+

8 

+

9Functions: 

+

10 get_two_phase_limits: Return the states of the boundaries of the two-phase section for a given fluid. 

+

11 

+

12""" 

+

13import abc 

+

14import logging 

+

15import warnings 

+

16from typing import List 

+

17import numpy as np 

+

18 

+

19from vclibpy.media import ThermodynamicState, TransportProperties 

+

20 

+

21 

+

22logger = logging.getLogger(__name__) 

+

23 

+

24 

+

25class MedProp(abc.ABC): 

+

26 """Base class for all media property interfaces. 

+

27 

+

28 This class serves as the base for defining interfaces to access and compute media properties. 

+

29 

+

30 Methods: 

+

31 calc_state: Calculate the thermodynamic state based on mode and state variables. 

+

32 calc_transport_properties: Calculate transport properties for a given state. 

+

33 get_critical_point: Retrieve critical point information. 

+

34 get_molar_mass: Retrieve molar mass information. 

+

35 get_two_phase_limits: Retrieve the two-phase limits for plotting. 

+

36 calc_mean_transport_properties: Calculate the average transport properties for given states. 

+

37 """ 

+

38 _fluid_mapper = {} 

+

39 

+

40 def __init__(self, fluid_name): 

+

41 """Initialize the MedProp class instance. 

+

42 

+

43 Args: 

+

44 fluid_name (str): The name of the fluid. 

+

45 """ 

+

46 # Check if better internal names exist (e.g. air is modelled as air.ppf) 

+

47 self.fluid_name = self._fluid_mapper.get(fluid_name, fluid_name) 

+

48 self._two_phase_limits: dict = None 

+

49 

+

50 def calc_state(self, mode: str, var1: float, var2: float): 

+

51 """Calculate the thermodynamic state based on the specified mode and state variables. 

+

52 

+

53 This function calculates the thermodynamic state based on the chosen mode and provided state variables. 

+

54 The input state variables need to be in SI units. 

+

55 

+

56 Notes: 

+

57 - PT does not work when the state might fall within the two-phase region. 

+

58 - Only functions for density are implemented. In cases where you know the specific volume, use the density 

+

59 functions with the inverse value. 

+

60 - Quality (q) may have values outside the 'physical' scope: 

+

61 - q = -998: Subcooled liquid 

+

62 - q = 998: Superheated vapor 

+

63 - q = 999: Supercritical state 

+

64 

+

65 Possible modes include: 

+

66 - "PD": Pressure, Density 

+

67 - "PH": Pressure, Enthalpy 

+

68 - "PQ": Pressure, Quality 

+

69 - "PS": Pressure, Entropy 

+

70 - "PT": Pressure, Temperature 

+

71 - "PU": Pressure, Internal Energy 

+

72 - "TD": Temperature, Density 

+

73 - "TH": Temperature, Enthalpy 

+

74 - "TQ": Temperature, Quality 

+

75 - "TS": Temperature, Entropy 

+

76 - "TU": Temperature, Internal Energy 

+

77 - "DH": Density, Enthalpy 

+

78 - "DS": Density, Entropy 

+

79 - "DU": Density, Internal Energy 

+

80 

+

81 Args: 

+

82 mode (str): Defines the given input state variables (see possible modes above). 

+

83 var1 (float): Value of the first state variable (as specified in the mode) in SI units. 

+

84 var2 (float): Value of the second state variable (as specified in the mode) in SI units. 

+

85 

+

86 Returns: 

+

87 ThermodynamicState: A ThermodynamicState instance with state variables. 

+

88 

+

89 Raises: 

+

90 AssertionError: If the given mode is not within the available options. 

+

91 """ 

+

92 available_options = ['PD', 'PH', 'PQ', 'PS', 'PT', 

+

93 'PU', 'TD', 'TH', 'TQ', 'TS', 

+

94 'TU', 'DH', 'DS', 'DU', ] 

+

95 assert mode in available_options, f'Given mode {mode} is not in available options' 

+

96 

+

97 def terminate(self): 

+

98 """ 

+

99 Terminate the class. 

+

100 Default behaviour does nothing. 

+

101 """ 

+

102 pass 

+

103 

+

104 @abc.abstractmethod 

+

105 def calc_transport_properties(self, state: ThermodynamicState): 

+

106 """Calculate the transport properties for the given state. 

+

107 

+

108 Args: 

+

109 state (ThermodynamicState): The current thermodynamic state. 

+

110 

+

111 Returns: 

+

112 TransportProperties: An instance of TransportProperties. 

+

113 """ 

+

114 pass 

+

115 

+

116 @abc.abstractmethod 

+

117 def get_critical_point(self): 

+

118 """Retrieve critical point information for the fluid. 

+

119 

+

120 Returns: 

+

121 Tuple[float, float, float]: A tuple containing critical point information 

+

122 (Temperature Tc [K], Pressure pc [Pa], Density dc [kg/m^3]). 

+

123 """ 

+

124 pass 

+

125 

+

126 @abc.abstractmethod 

+

127 def get_molar_mass(self): 

+

128 """Retrieve the molar mass of the current fluid. 

+

129 

+

130 Returns: 

+

131 float: The molar mass M of the current fluid in kg/mol. 

+

132 """ 

+

133 pass 

+

134 

+

135 def get_two_phase_limits(self, quantity: str, p_min: int = 100000, p_step: int = 5000): 

+

136 """ 

+

137 Retrieve the two-phase limits for plotting a specified quantity. 

+

138 

+

139 This method returns the two-phase limits for a specified quantity (T, h, s, or p) in an array used for 

+

140 plotting purposes. It calculates the limits within the pressure range from p_min and quality (q) 0 to the 

+

141 critical pressure (pc), and then from the critical pressure to the pressure p_min and quality 1. 

+

142 

+

143 Args: 

+

144 quantity (str): The specified quantity (T, h, s, or p). 

+

145 p_min (int, optional): The minimum pressure value to start iteration. Default is 100000 Pa. 

+

146 p_step (int, optional): The step size for pressure variation. Default is 5000 Pa. 

+

147 

+

148 Returns: 

+

149 numpy.ndarray: An array containing the two-phase limits for the specified quantity. 

+

150 

+

151 Raises: 

+

152 ValueError: If the given quantity is not supported (T, h, s, or p). 

+

153 """ 

+

154 if self._two_phase_limits is not None: 

+

155 # Check existing two-phase limits 

+

156 p_min_old = self._two_phase_limits['p'][0] 

+

157 p_step_old = self._two_phase_limits['p'][1] - p_min_old 

+

158 if not np.isclose(p_min_old, p_min, 0, 10) or not np.isclose(p_step_old, p_step, 0, 10): 

+

159 warnings.warn(f"Overwriting previously calculated two-phase limits with " 

+

160 f"p_min={p_min_old} and p_step={p_step_old}. This might take a few seconds.\n" 

+

161 f"The quantity might not match with the previously calculated quantities.") 

+

162 self._two_phase_limits = None 

+

163 

+

164 if self._two_phase_limits is None: 

+

165 # Calculate new two-phase limits for plotting 

+

166 _two_phase_limits = get_two_phase_limits(self, p_step=p_step, p_min=p_min) 

+

167 self._two_phase_limits = { 

+

168 "T": np.array([state.T for state in _two_phase_limits]), 

+

169 "h": np.array([state.h for state in _two_phase_limits]), 

+

170 "s": np.array([state.s for state in _two_phase_limits]), 

+

171 "p": np.array([state.p for state in _two_phase_limits]), 

+

172 } 

+

173 

+

174 if quantity not in self._two_phase_limits: 

+

175 raise ValueError("The given quantity is not supported. T, h, s, or p are supported.") 

+

176 return self._two_phase_limits[quantity] 

+

177 

+

178 def calc_mean_transport_properties(self, state_in, state_out): 

+

179 """ 

+

180 Calculate the average transport properties for the given states. 

+

181 

+

182 Args: 

+

183 state_in (ThermodynamicState): First state 

+

184 state_out (ThermodynamicState): Second state 

+

185 

+

186 Returns: 

+

187 TransportProperties: Average transport properties 

+

188 

+

189 Notes: 

+

190 The TransportProperties does not contain a state, as an average 

+

191 state is not possible to calculate. 

+

192 """ 

+

193 tr_pr_in = self.calc_transport_properties(state_in) 

+

194 tr_pr_out = self.calc_transport_properties(state_out) 

+

195 

+

196 return TransportProperties( 

+

197 lam=0.5 * (tr_pr_in.lam + tr_pr_out.lam), 

+

198 dyn_vis=0.5 * (tr_pr_in.dyn_vis + tr_pr_out.dyn_vis), 

+

199 kin_vis=0.5 * (tr_pr_in.kin_vis + tr_pr_out.kin_vis), 

+

200 pr=0.5 * (tr_pr_in.Pr + tr_pr_out.Pr), 

+

201 cp=0.5 * (tr_pr_in.cp + tr_pr_out.cp), 

+

202 cv=0.5 * (tr_pr_in.cv + tr_pr_out.cv), 

+

203 state=None) 

+

204 

+

205 

+

206def get_two_phase_limits(med_prop: MedProp, p_step: int = 1000, p_min: int = int(1e3)) -> List[ThermodynamicState]: 

+

207 """ 

+

208 Return the states representing the boundaries of the two-phase section for the given fluid. 

+

209 

+

210 This function is primarily used for visualizing the two-phase section and validating the accuracy of calculations. 

+

211 

+

212 Args: 

+

213 med_prop (MedProp): An instance of a valid MedProp-Class. 

+

214 p_step (int): The step size for pressure variation in Pa. Default is 1000 Pa. 

+

215 p_min (int): The minimum pressure in Pa from where to start calculation. Default is 1000 Pa. 

+

216 

+

217 Returns: 

+

218 List[ThermodynamicState]: A list of ThermodynamicState instances representing the two-phase limits. 

+

219 

+

220 Notes: 

+

221 The two-phase limits are computed by iterating over a range of pressures from the minimum pressure up to the 

+

222 critical point pressure (exclusive) with a specified step size. States at quality 0 (saturated liquid) 

+

223 and quality 1 (saturated vapor) are appended to form the two-phase boundary. The list is reversed to 

+

224 maintain the correct order for visualization purposes. 

+

225 """ 

+

226 _, _p_max, _ = med_prop.get_critical_point() 

+

227 q0, q1 = [], [] 

+

228 for _p in range(p_min, int(_p_max), p_step): 

+

229 try: 

+

230 q0.append(med_prop.calc_state("PQ", _p, 0)) 

+

231 q1.append(med_prop.calc_state("PQ", _p, 1)) 

+

232 except ValueError as err: 

+

233 logger.info("Could not calculate two-phase limits for p=%s: %s", 

+

234 _p, err) 

+

235 # Reverse list for correct order 

+

236 return q0 + q1[::-1] 

+
+ + + diff --git a/docs/main/coverage/d_c048ebee450da2af_ref_prop_py.html b/docs/main/coverage/d_c048ebee450da2af_ref_prop_py.html new file mode 100644 index 0000000..4be0e60 --- /dev/null +++ b/docs/main/coverage/d_c048ebee450da2af_ref_prop_py.html @@ -0,0 +1,1328 @@ + + + + + Coverage for vclibpy/media/ref_prop.py: 12% + + + + + +
+
+

+ Coverage for vclibpy/media/ref_prop.py: + 12% +

+ +

+ 429 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1# -*- coding: utf-8 -*- 

+

2""" 

+

3Created on 21.04.2020 

+

4 

+

5@author: Christoph Hoeges, Fabian Wuellhorst, Jona Brach 

+

6 

+

7To test: 

+

8- Transport properties are not fully tested (Status: 11.06.2020) 

+

9- Error raising is not implemented at all refprop calls 

+

10 - Additional change might be that not all errors and warning are excluded when 'use_error_check' is set to false but 

+

11 only warnings or errors? 

+

12""" 

+

13import logging 

+

14import os 

+

15import warnings 

+

16import shutil 

+

17import atexit 

+

18 

+

19from ctREFPROP.ctREFPROP import REFPROPFunctionLibrary 

+

20 

+

21from vclibpy.media import ThermodynamicState, TransportProperties, MedProp 

+

22 

+

23 

+

24logger = logging.getLogger(__name__) 

+

25 

+

26 

+

27class RefProp(MedProp): 

+

28 """ 

+

29 Class to connect to refProp package. 

+

30 

+

31 Args: 

+

32 :param string fluid_name: 

+

33 Fluid name for RefProp use 

+

34 :param list or None z: 

+

35 Fluid composition. Should only be used, when a self-design mixture shall be used. Further information 

+

36 see notes. 

+

37 When you want to use a self-design mixture to as follows: 

+

38 - Fluid-name needs to contain all component names within mixture: "R32.FLD|R125.FLD" 

+

39 - z needs to be a list with molar fractions: [0.697, 0.303] 

+

40 - Used example would be similar to R410A 

+

41 

+

42 :param string dll_path: 

+

43 Specifier for the dll path used for RefProp, 

+

44 e.g. dll_path='C:\\path_to_dll\\RefProp64.dll'. 

+

45 If None, the `ref_prop_path` and function `get_dll_path` are 

+

46 used to determine the dll path 

+

47 :param boolean use_error_check: 

+

48 Specifier whether errors and warnings shall be checked when calling RefProp or not 

+

49 :param boolean use_warnings: 

+

50 Specifier whether warnings shall be used 

+

51 :param str ref_prop_path: 

+

52 Path to RefProp package. Default is the ENV variable `RPPREFIX`. 

+

53 :param bool copy_dll: 

+

54 If True (not the default), a copy of the dll is created to enable simultaneous use of 

+

55 multiple fluids in multiprocessing. 

+

56 :param str copy_dll_directory: 

+

57 If `copy_dll` is True, the DLL is copied to this directory. 

+

58 If None (default), the current working directory is used. 

+

59 

+

60 Note: 

+

61 - You need to install package ctREFPROP Package 

+

62 https://github.com/usnistgov/REFPROP-wrappers/tree/master/wrappers/python 

+

63 - In case you use a self-defined mixture: Entropy reference state might deviate from GUI!! 

+

64 

+

65 Functionality: 

+

66 - It does not work to have multiple instances simultaneously. When calculating values the last fluid name 

+

67 somehow will be used even though instance includes "new" name. Need to be fixed at some point. 

+

68 

+

69 

+

70 How to use RefProp-Wrapper: 

+

71 --------------------------- 

+

72 1.) Create RefProp instance: rp = RefProp("R32") 

+

73 2.) In case you want to calculate fluid properties (state variables) for a specific state: Use calc_state() function 

+

74 Multiple inputs can be given (but you need to now two in order to define the values). For further 

+

75 information see function header. 

+

76 3.) Further get-Functions implemented 

+

77 - get_gwp(): Global warming potential 

+

78 - get_odp(): Ozone depletion potential 

+

79 - get_safety(): Safety class 

+

80 - get_mass_fraction(): Mass fractions of pure substances in fluid 

+

81 - get_molar_mass(): Molar mass of fluid 

+

82 - get_mol_fraction(): Mol fractions of pure substances in fluid 

+

83 - get_comp_names(): Pure substances names in fluid 

+

84 - get_longname(): Long name of fluid within RefProp 

+

85 - get_critical_point(): Crit. Temperature and Pressure 

+

86 - get_version(): Version of wrapper and of RefProp dll 

+

87 

+

88 

+

89 Version notes: 

+

90 -------------- 

+

91 0.1.0 (21.04.2020, Christoph Hoeges): 

+

92 First implementation 

+

93 - Contains multiple functions to call RefProp instance 

+

94 - Can calculate state, crit, GWP, ODP, Safety class, molar mass etc. of fluid 

+

95 - Mixtures might work but it wasn't fully testet - R455A e.g. still deviates slightly 

+

96 

+

97 0.1.1 (25.04.2020, Christoph Hoeges): 

+

98 Multiple changes, added functionality. Commands are still the same though 

+

99 - Self designed mixtures are possible now as well (see instructions in init 

+

100 - Added composition input to __init__ function 

+

101 - Added modes in calc_state function and removed bug in PH calculation 

+

102 - Additional protected functions due to different input 

+

103 - Adjusted _call_refprop function (z is not mass frac but mol fraction) 

+

104 - Added documentation / instruction 

+

105 

+

106 0.1.2 (08.05.2020, Christoph Hoeges): 

+

107 Multiple adjustments 

+

108 - Added function to call ABFLSHdll function in refprop (high level function) 

+

109 - Changed function calls in calc_state function to ABFLSH and adjusted conversion 

+

110 - Change init-function so user can choose which dll shall be used 

+

111 - Add function to get version of this current wrapper as well as RefProp-dll version 

+

112 

+

113 0.1.3 (12.05.2020, Christoph Hoeges): 

+

114 Multiple changes 

+

115 - Added function to get all files in MIXTURE and FLUIDS directory to check available fluids 

+

116 - Added error function in order to return errors when RefProp-Functions are called. 

+

117 NOTE: Not all instances where refprop is called are checked for errors. Currently, it is only used in 

+

118 init and calc_state 

+

119 

+

120 0.1.4 (19.05.2020, Christoph Hoeges): 

+

121 Multiple changes: 

+

122 - Debugged fluid properties calculation for predefined mixtures 

+

123 - Fixed option of self-defined mixtures 

+

124 

+

125 0.1.5 (22.05.2020, Christoph Hoeges): 

+

126 Include transport properties calculation into wrapper 

+

127 

+

128 0.1.6 (10.06.2020, Fabian Wuellhorst): 

+

129 Add option to use a custom dll path. This necessary to use multiple instances with possible 

+

130 different fluids at the same time. 

+

131 """ 

+

132 # General information 

+

133 # 

+

134 __version__ = "0.1.6" 

+

135 __author__ = "Christoph Hoeges" 

+

136 

+

137 _fluid_mapper = {'air': 'air.ppf'} 

+

138 

+

139 def __init__(self, 

+

140 fluid_name, 

+

141 z=None, 

+

142 dll_path: str = None, 

+

143 use_error_check: bool = True, 

+

144 use_warnings: bool = True, 

+

145 ref_prop_path: str = None, 

+

146 copy_dll: bool = True, 

+

147 copy_dll_directory: str = None 

+

148 ): 

+

149 if ref_prop_path is None: 

+

150 # Get environment variable for path to dll 

+

151 ref_prop_path = os.environ["RPPREFIX"] 

+

152 if dll_path is None: 

+

153 path_to_dll = self.get_dll_path(ref_prop_path) 

+

154 else: 

+

155 path_to_dll = dll_path 

+

156 

+

157 if copy_dll: 

+

158 if copy_dll_directory is None: 

+

159 copy_dll_directory = os.getcwd() 

+

160 try: 

+

161 self._delete_dll_path = os.path.join( 

+

162 copy_dll_directory, 

+

163 f"med_prop_{fluid_name}_REFPRP64.dll" 

+

164 ) 

+

165 shutil.copyfile(path_to_dll, self._delete_dll_path) 

+

166 atexit.register(self.terminate) 

+

167 path_to_dll = self._delete_dll_path 

+

168 except (PermissionError, FileExistsError) as err: 

+

169 logger.error("Can't copy file to new path: %s", err) 

+

170 else: 

+

171 self._delete_dll_path = None 

+

172 logger.info("Using dll: %s", path_to_dll) 

+

173 

+

174 super().__init__(fluid_name=fluid_name) 

+

175 

+

176 self._flag_check_errors = use_error_check 

+

177 self._flag_warnings = use_warnings 

+

178 # Set path to RefProp package 

+

179 self._ref_prop_path = ref_prop_path 

+

180 self.rp = REFPROPFunctionLibrary(path_to_dll) 

+

181 self.rp.SETPATHdll(ref_prop_path) 

+

182 self.molar_base_si = self.rp.GETENUMdll(0, "MOLAR BASE SI").iEnum 

+

183 # Set fluid name 

+

184 self.fluid_name = fluid_name 

+

185 # Get mass and mol fraction and number of components 

+

186 self._get_comp_frac(z) 

+

187 # Get component names 

+

188 self._comp_names = self._get_comp_names() 

+

189 # Mixture flag 

+

190 if self._n_comp > 1: 

+

191 self._mix_flag = True 

+

192 else: 

+

193 self._mix_flag = False 

+

194 

+

195 # Setup 

+

196 self._setup_rp() 

+

197 # Calculate molar mass in kg/mol 

+

198 # self.M = self._call_refprop_allprop("M").Output[0] 

+

199 self.M = self._call_refprop(inp_name="", out_name="M").Output[0] # kg/mol 

+

200 

+

201 self._nbp = None 

+

202 

+

203 def terminate(self): 

+

204 if self._delete_dll_path is not None: 

+

205 self._delete_dll() 

+

206 

+

207 def _delete_dll(self): 

+

208 try: 

+

209 # Taken from here: https://stackoverflow.com/questions/21770419/free-the-opened-ctypes-library-in-python 

+

210 import _ctypes 

+

211 import sys 

+

212 _handle = self.rp.dll._handle 

+

213 if sys.platform.startswith('win'): 

+

214 _ctypes.FreeLibrary(_handle) 

+

215 else: 

+

216 _ctypes.dlclose(_handle) 

+

217 os.remove(self._delete_dll_path) 

+

218 self._delete_dll_path = None 

+

219 except (FileNotFoundError, PermissionError) as err: 

+

220 logger.error( 

+

221 "Could not automatically delete the copied RefProp dll at %s. " 

+

222 "Delete it yourself! Error message: %s", self._delete_dll_path, err 

+

223 ) 

+

224 

+

225 def _call_refprop_abflsh(self, 

+

226 inp_name, 

+

227 value_a, 

+

228 value_b, 

+

229 i_flag=1): 

+

230 """ Call RefProp via ABFLSHdll method 

+

231 You can define multiple inputs but only "specific ones" where no input values are needed for 

+

232 e.g. M, Tcrit, pcrit 

+

233 

+

234 Parameters: 

+

235 ----------- 

+

236 :param string inp_name: 

+

237 Input commands: "PQ" 

+

238 :param float value_a: 

+

239 Value of parameter b defined in inp_name. In case of None 0 will be used. 

+

240 :param float value_b: 

+

241 Value of parameter b defined in inp_name. In case of None 0 will be used. 

+

242 :param int i_flag: 

+

243 Flag 

+

244 Return: 

+

245 ------- 

+

246 :return ABFLSHdlloutput tmp: 

+

247 Returns ABFLSH output 

+

248 

+

249 """ 

+

250 # TODO 

+

251 tmp = self.rp.ABFLSHdll(inp_name, value_a, value_b, self._mol_frac, i_flag) 

+

252 

+

253 return tmp 

+

254 

+

255 def _call_refprop_allprop(self, 

+

256 out_name, 

+

257 T_val=None, 

+

258 d_val=None, 

+

259 i_mass=0, 

+

260 i_flag=1): 

+

261 """ Call RefProp via ALLPROPSdll-method 

+

262 

+

263 Parameters: 

+

264 ----------- 

+

265 :param string out_name: 

+

266 Variables you want to calculate. Multiple outputs are possible: 

+

267 - Single: "M" 

+

268 - Multiple: "M,TC,PC" 

+

269 :param float T_val: 

+

270 Temperature in current state in K. 

+

271 Note: In case you want to get general fluid parameters such as M, Tcrit, .. Stick to default value! 

+

272 :param float d_val: 

+

273 Density in current state (unit depending on i_mass flag - either mol/m^3 or kg/m^3) 

+

274 Note: In case you want to get general fluid parameters such as M, Tcrit, .. Stick to default value! 

+

275 :param int i_mass: 

+

276 Specifies which units the inputs are given in. 

+

277 - 0: Molar based 

+

278 - 1: Mass based 

+

279 Note: In current version (10.0.0.72) Ian Bell says in multiple Git-Issues that you should stick to molar 

+

280 base! 

+

281 :param int i_flag: 

+

282 In current version (10.0.0.72) i_flag is used to define whether a string containing the units is written in 

+

283 'hUnits'. 

+

284 - 0: Deactivated (increases the calculation speed) 

+

285 - 1: activated 

+

286 Return: 

+

287 ------- 

+

288 :return ALLPROPSdlloutput result: 

+

289 List with values for parameters 

+

290 

+

291 """ 

+

292 # Check values of T and d 

+

293 if T_val is None: 

+

294 T_val = 0 

+

295 if d_val is None: 

+

296 d_val = 0 

+

297 # Define fraction used depending on i_mass flag 

+

298 if i_mass == 0: 

+

299 frac = self._mol_frac 

+

300 elif i_mass == 1: 

+

301 frac = self._mass_frac 

+

302 else: 

+

303 raise ValueError("Chosen i_mass flag '{}' is not possible in ALLPROPSdll function!".format(i_mass)) 

+

304 

+

305 # Call RefProp 

+

306 res = self.rp.ALLPROPSdll(out_name, self.molar_base_si, i_mass, i_flag, T_val, d_val, frac) 

+

307 

+

308 return res 

+

309 

+

310 def _call_refprop(self, 

+

311 inp_name, 

+

312 out_name, 

+

313 value_a=None, 

+

314 value_b=None, 

+

315 i_mass=0, 

+

316 i_flag=1, 

+

317 frac=None, 

+

318 fluid=None): 

+

319 """ Call general refProp function and calculate values 

+

320 

+

321 Parameters: 

+

322 ----------- 

+

323 :param string fluid: 

+

324 Fluid name - in case default value None is used, stored fluid name will be used for command 

+

325 :param string inp_name: 

+

326 Input parameter specification 

+

327 :param string out_name: 

+

328 Output string name 

+

329 :param float value_a: 

+

330 Value of parameter b defined in inp_name. In case of None 0 will be used. 

+

331 :param float value_b: 

+

332 Value of parameter b defined in inp_name. In case of None 0 will be used. 

+

333 :param integer i_flag: 

+

334 Defines further settings (see documentation) 

+

335 :param int i_mass: 

+

336 Specifies which units the inputs are given in. # TODO: WRONG! iMass determines composition, iUnits determines properties (except q) 

+

337 - 0: Molar based 

+

338 - 1: Mass based 

+

339 Note: In current version (10.0.0.72) Ian Bell says in multiple Git-Issues that you should stick to molar 

+

340 base! 

+

341 :param list frac: 

+

342 List with either mol or mass fraction of pure substances in current fluid 

+

343 (in case of single pure substance: [1]). 

+

344 

+

345 Return: 

+

346 ------- 

+

347 :return REFPROPdlloutput output: 

+

348 Command of refprop 

+

349 """ 

+

350 # Check inputs 

+

351 if value_a is None: 

+

352 value_a = 0 

+

353 if value_b is None: 

+

354 value_b = 0 

+

355 

+

356 if fluid is None: 

+

357 if self._predefined: 

+

358 fluid = self.fluid_name # TODO: in general not necessary, decreases performance 

+

359 else: 

+

360 fluid = "" 

+

361 if frac is None: 

+

362 if self._predefined: 

+

363 frac = "" 

+

364 else: 

+

365 if i_mass == 0: 

+

366 frac = self._mol_frac 

+

367 elif i_mass == 1: 

+

368 frac = self._mass_frac 

+

369 else: 

+

370 raise ValueError("Variable i_mass has invalid input '{}'".format(i_mass)) 

+

371 

+

372 # Call refprop function 

+

373 tmp = self.rp.REFPROPdll(fluid, 

+

374 inp_name, 

+

375 out_name, 

+

376 self.molar_base_si, 

+

377 i_mass, 

+

378 i_flag, 

+

379 value_a, 

+

380 value_b, 

+

381 frac) 

+

382 

+

383 return tmp 

+

384 

+

385 def _check_error(self, 

+

386 err_num, 

+

387 err_msg, 

+

388 func_name=""): 

+

389 """ Check error code and raise error in case it is critical 

+

390 

+

391 Parameters: 

+

392 ----------- 

+

393 :param integer err_num: 

+

394 Error return code 

+

395 :param string err_msg: 

+

396 Error message given in RefProp call 

+

397 :param string func_name: 

+

398 Name of function error needs to be checked in 

+

399 """ 

+

400 # All fine in case error number is 0 

+

401 # Throw warning in case error number different than 0 and smaller than 100 is given 

+

402 # Throw error in case error number higher than 100 is given 

+

403 if err_num: 

+

404 if err_num < 100: 

+

405 if self._flag_warnings: 

+

406 warnings.warn("[WARNING] Error number {} was given in function '{}'. No critical error but " 

+

407 "something went wrong maybe. \n Error message is: '{}'".format(str(err_num), 

+

408 func_name, err_msg)) 

+

409 else: 

+

410 if self._flag_check_errors: 

+

411 raise TypeError("[ERROR] When calling RefProp in function '{}' error number {} was " 

+

412 "returned. \n Error message is: '{}'".format(func_name, str(err_num), err_msg)) 

+

413 

+

414 def _get_comp_names(self): 

+

415 """ Get component names. In case current fluid is mixture, only component names will be returned. 

+

416 In case fluid is a pure substance, substance name is returned. 

+

417 

+

418 Return: 

+

419 ------- 

+

420 :return list comp_names: 

+

421 List with pure substances in current refrigerant 

+

422 """ 

+

423 comp_names = [] 

+

424 if self._predefined: 

+

425 # Note: While trying it was possible to get fluid name as well, therefore n_comp+1 is used. 

+

426 if self._n_comp > 1: 

+

427 for i in range(1, self._n_comp + 3): 

+

428 test = self.rp.NAMEdll(i) 

+

429 tmp = test.hn80.replace(".FLD", "") 

+

430 if not tmp == "": 

+

431 comp_names.append(tmp) 

+

432 else: 

+

433 for i in range(self._n_comp + 3): 

+

434 tmp = self.rp.NAMEdll(i).hnam 

+

435 if not tmp == "": 

+

436 comp_names.append(tmp) 

+

437 

+

438 else: 

+

439 # Self-defined 

+

440 tmp_str = self.fluid_name.split("|") 

+

441 for i in tmp_str: 

+

442 i = i.replace(".FLD", "") 

+

443 i = i.replace(".MIX", "") 

+

444 if len(i) < 2: 

+

445 continue 

+

446 else: 

+

447 comp_names.append(i) 

+

448 

+

449 # Replace Carbon Dioxide for CO2 

+

450 for i, tmp in enumerate(comp_names): 

+

451 if "Carbon dio" in tmp: 

+

452 comp_names[i] = "CO2" 

+

453 

+

454 return comp_names 

+

455 

+

456 def _get_comp_frac(self, z): 

+

457 """ Get mass/mol fraction and number of components of current fluid 

+

458 

+

459 Parameters: 

+

460 ----------- 

+

461 :param list z: 

+

462 Contains predefined molar fractions in case one is given. Otherwise, z will be None 

+

463 """ 

+

464 # Check if predefined or not 

+

465 # Predefined 

+

466 if z is None: 

+

467 self._predefined = True 

+

468 # Pure substance 

+

469 elif len(z) == 1: 

+

470 self._predefined = True 

+

471 # Self-designed mixture 

+

472 else: 

+

473 self._predefined = False 

+

474 

+

475 # In case predefined mixture or pure substance is used 

+

476 if self._predefined: 

+

477 # Dummy function to get values for z in order to specify number of components 

+

478 tmp_mol = self.rp.REFPROPdll(self.fluid_name, "PQ", "H", self.molar_base_si, 0, 0, 101325, 0, [1]) 

+

479 tmp_mass = self.rp.REFPROPdll(self.fluid_name, "PQ", "H", self.molar_base_si, 1, 0, 101325, 0, []) 

+

480 # Check for errors 

+

481 self._check_error(tmp_mol.ierr, tmp_mol.herr, self._get_comp_frac.__name__) 

+

482 self._check_error(tmp_mass.ierr, tmp_mass.herr, self._get_comp_frac.__name__) 

+

483 # Mass and molar fractions of components 

+

484 self._mol_frac = [zi for zi in tmp_mol.z if zi > 0] 

+

485 self._mass_frac = [zi for zi in tmp_mass.z if zi > 0] 

+

486 # Get number of components 

+

487 self._n_comp = len(self._mol_frac) 

+

488 # Check, whether error occurred 

+

489 if self._n_comp < 1: 

+

490 # It might be possible that z value bugs when calling RefProp. In case of a pure substance this does not 

+

491 # matter so an additional filter is included 

+

492 if len(self._mass_frac) == 1: 

+

493 self._n_comp = 1 

+

494 self._mol_frac = [1] 

+

495 else: 

+

496 raise ValueError("Number of components for current fluid '{}' is less than " 

+

497 "one!".format(self.fluid_name)) 

+

498 

+

499 # Get mol fraction 

+

500 # self._mol_frac = self._transform_to_molfraction(self._mass_frac) 

+

501 else: 

+

502 # Mol fraction 

+

503 self._mol_frac = z 

+

504 self._mass_frac = [] 

+

505 # Get number of components 

+

506 self._n_comp = len(self._mol_frac) 

+

507 

+

508 def _setup_rp(self): 

+

509 """ Setup for RefProp """ 

+

510 # Errors can occur in case REFPROP is initalizated multiple time with same fluid - thus a pre setup is used here 

+

511 self.rp.SETUPdll(1, "N2", "HMX.BNC", "DEF") 

+

512 # In case of pure substance 

+

513 if self._n_comp == 1: 

+

514 self.rp.SETUPdll(self._n_comp, self.fluid_name, "HMX.BNC", "DEF") 

+

515 # In case of mixtures 

+

516 else: 

+

517 # Check if mixture is predefined 

+

518 if self._predefined: 

+

519 # Pre defined mixture - different baseline operating point is used 

+

520 mode = 2 

+

521 mixture = "|".join([f+".FLD" for f in self._comp_names]) 

+

522 n_comp = self._n_comp 

+

523 else: 

+

524 # Self defined mixture 

+

525 mode = 1 

+

526 n_comp = self._n_comp 

+

527 # Define mixtures name 

+

528 # TODO: Ending is not necessary to create mixtures.... 

+

529 mixture = "|".join([f+".FLD" for f in self._comp_names]) 

+

530 

+

531 # Setup for mixture 

+

532 setup = self.rp.SETUPdll(n_comp, mixture, 'HMX.BNC', 'DEF') 

+

533 setref = self.rp.SETREFdll("DEF", mode, self._mol_frac, 0, 0, 0, 0) 

+

534 # z = self._mol_frac 

+

535 # Get mass fraction 

+

536 self._mass_frac = self._transform_to_massfraction(self._mol_frac) 

+

537 

+

538 # Check whether mixing rules are available 

+

539 if setup.ierr == 117: 

+

540 if self._flag_check_errors: 

+

541 raise ValueError( 

+

542 "[MIXING ERROR] Mixing rules for mixture '{}' do not exist!".format(self._comp_names)) 

+

543 else: 

+

544 print( 

+

545 "[MIXING ERROR] Mixing rules for mixture '{}' do not exist!".format(self._comp_names)) 

+

546 elif setup.ierr == -117: 

+

547 if self._flag_warnings: 

+

548 warnings.warn( 

+

549 "[MIXING ERROR] Mixing rules for mixture '{}' are estimated!".format(self._comp_names)) 

+

550 else: 

+

551 print( 

+

552 "[MIXING WARNING] Mixing rules for mixture '{}' are estimated!".format(self._comp_names)) 

+

553 

+

554 def _transform_to_massfraction(self, 

+

555 mol_frac): 

+

556 """ Transforms mol fraction to mass fraction 

+

557 

+

558 Parameters: 

+

559 ----------- 

+

560 :param list mol_frac: 

+

561 List containing floats for mol fraction 

+

562 

+

563 Return: 

+

564 ------- 

+

565 :return list mass_frac: 

+

566 List containing floats for mass fraction 

+

567 """ 

+

568 tmp = self.rp.XMASSdll(mol_frac) 

+

569 mass_frac = [yi for yi in tmp.xkg if yi > 0] 

+

570 return mass_frac 

+

571 

+

572 def _transform_to_molfraction(self, 

+

573 mass_frac): 

+

574 """ Transforms mass fraction to mol fraction 

+

575 

+

576 Parameters: 

+

577 ----------- 

+

578 :param list mass_frac: 

+

579 List containing floats for mass fraction 

+

580 

+

581 Return: 

+

582 ------- 

+

583 :return list frac: 

+

584 List containing floats for mol fraction 

+

585 """ 

+

586 tmp = self.rp.XMOLEdll(mass_frac) 

+

587 mol_frac = [xi for xi in tmp.xmol if xi > 0] 

+

588 return mol_frac 

+

589 

+

590 def calc_state(self, mode: str, var1: float, var2: float, kr=1): 

+

591 """ Calculate state. Depending on mode, different function will be chosen. Input state variables need to be in 

+

592 SI units! 

+

593 

+

594 Notes: 

+

595 ------ 

+

596 1.) PT does not work when state might be within the two-phase region! 

+

597 2.) Only functions for density are implemented. In case you know the specific volume instead use density 

+

598 functions with inverse value! 

+

599 3.) Quality can have values outside of 'physical' scope: 

+

600 q = -998: Subcooled liquid 

+

601 q = 998: Superheated vapor 

+

602 q = 999: Supercritical state 

+

603 

+

604 Possible modes are currently: 

+

605 - "PD": Pressure, Density 

+

606 - "PH": Pressure, Enthalpy 

+

607 - "PQ": Pressure, Quality 

+

608 - "PS": Pressure, Entropy 

+

609 - "PT": Pressure, Temperature 

+

610 - "PU": Pressure, Internal Energy 

+

611 - "TD": Temperature, Density 

+

612 - "TH": Temperature, Enthalpy 

+

613 - "TQ": Temperature, Quality 

+

614 - "TS": Temperature, Entropy 

+

615 - "TU": Temperature, Internal Energy 

+

616 - "DH": Density, Enthalpy 

+

617 - "DS": Density, Entropy 

+

618 - "DU": Density, Internal Energy 

+

619 - "HS": Enthalpy, Entropy 

+

620 

+

621 Parameters: 

+

622 ----------- 

+

623 :param string mode: 

+

624 Defines which input state variables are given (see possible modes above) 

+

625 :param float var1: 

+

626 Value of state variable 1 (first one in name) - use SI units! 

+

627 :param float var2: 

+

628 Value of state variable 2 (second one in name) - use SI units! 

+

629 :param int kr: 

+

630 phase flag (kr=1: lower density, kr=2: higher density) 

+

631 relevant for "TH", "TS", "TU" 

+

632 

+

633 Return: 

+

634 ------- 

+

635 :return ThermodynamicState state: 

+

636 Thermodynamic state with state variables 

+

637 """ 

+

638 # Multiplier for pressure since kPa is used in RefProp 

+

639 p_multi = 1e-3 

+

640 # Multiplier for energy 

+

641 e_multi = self.M 

+

642 # Multiplier for density 

+

643 d_multi = 1 / self.M / 1000 

+

644 

+

645 # Init all parameters 

+

646 p = None 

+

647 d = None 

+

648 T = None 

+

649 u = None 

+

650 h = None 

+

651 s = None 

+

652 q = None 

+

653 

+

654 # Check modi 

+

655 

+

656 # Pressure and density 

+

657 if mode == "PD": 

+

658 p = var1 

+

659 d = var2 

+

660 var1 = var1 * p_multi 

+

661 var2 = var2 * d_multi 

+

662 tmp = self.rp.PDFLSHdll(var1, var2, self._mol_frac) 

+

663 # Pressure and enthalpy 

+

664 elif mode == "PH": 

+

665 p = var1 

+

666 var1 = var1 * p_multi 

+

667 h = var2 

+

668 var2 = var2 * e_multi 

+

669 tmp = self.rp.PHFLSHdll(var1, var2, self._mol_frac) 

+

670 # Pressure and quality 

+

671 elif mode == "PQ": 

+

672 p = var1 

+

673 var1 = var1 * p_multi 

+

674 q = var2 

+

675 # In case current fluid is mixture you need to transform Q to molar base for RefProp-function 

+

676 if self._mix_flag: 

+

677 var2 = self._call_refprop("PQMASS", "QMOLE", p, q, i_mass=1).Output[0] 

+

678 tmp = self.rp.PQFLSHdll(var1, var2, self._mol_frac, 0) 

+

679 # Pressure and entropy 

+

680 elif mode == "PS": 

+

681 p = var1 

+

682 var1 = var1 * p_multi 

+

683 s = var2 

+

684 var2 = var2 * e_multi 

+

685 tmp = self.rp.PSFLSHdll(var1, var2, self._mol_frac) 

+

686 # Pressure and Temperature 

+

687 elif mode == "PT": 

+

688 p = var1 

+

689 var1 = var1 * p_multi 

+

690 T = var2 

+

691 tmp = self.rp.TPFLSHdll(var2, var1, self._mol_frac) 

+

692 # Pressure and internal energy 

+

693 elif mode == "PU": 

+

694 p = var1 

+

695 var1 = var1 * p_multi 

+

696 u = var2 

+

697 var2 = var2 * e_multi 

+

698 # mode = "PE" 

+

699 tmp = self.rp.PEFLSHdll(var1, var2, self._mol_frac) 

+

700 # Temperature and density 

+

701 elif mode == "TD": 

+

702 T = var1 

+

703 d = var2 

+

704 var2 = var2 * d_multi 

+

705 tmp = self.rp.TDFLSHdll(var1, var2, self._mol_frac) 

+

706 # Temperature and enthalpy 

+

707 elif mode == "TH": 

+

708 T = var1 

+

709 h = var2 

+

710 var2 = var2 * e_multi 

+

711 tmp = self.rp.THFLSHdll(T, var2, self._mol_frac, kr) 

+

712 # Temperature and quality 

+

713 elif mode == "TQ": 

+

714 T = var1 

+

715 q = var2 

+

716 # In case current fluid is mixture you need to transform Q to molar base for RefProp-function 

+

717 if self._mix_flag: 

+

718 var2 = self._call_refprop("TQMASS", "QMOLE", T, q, i_mass=1).Output[0] 

+

719 tmp = self.rp.TQFLSHdll(T, var2, self._mol_frac, 1) 

+

720 # Temperature and entropy 

+

721 elif mode == "TS": 

+

722 T = var1 

+

723 s = var2 

+

724 var2 = var2 * e_multi 

+

725 tmp = self.rp.TSFLSHdll(T, var2, self._mol_frac, kr) 

+

726 # Temperature and internal energy 

+

727 elif mode == "TU": 

+

728 T = var1 

+

729 u = var2 

+

730 var2 = var2 * e_multi 

+

731 # mode = "TE" 

+

732 tmp = self.rp.TEFLSHdll(T, var2, self._mol_frac, kr) 

+

733 # Density and enthalpy 

+

734 elif mode == "DH": 

+

735 d = var1 

+

736 var1 = var1 * d_multi 

+

737 h = var2 

+

738 var2 = var2 * e_multi 

+

739 tmp = self.rp.DHFLSHdll(var1, var2, self._mol_frac) 

+

740 # Density and entropy 

+

741 elif mode == "DS": 

+

742 d = var1 

+

743 var1 = var1 * d_multi 

+

744 s = var2 

+

745 var2 = var2 * e_multi 

+

746 tmp = self.rp.DSFLSHdll(var1, var2, self._mol_frac) 

+

747 # Density and inner energy 

+

748 elif mode == "DU": 

+

749 d = var1 

+

750 var1 = var1 * d_multi 

+

751 u = var2 

+

752 var2 = var2 * e_multi 

+

753 # mode = "DE" 

+

754 tmp = self.rp.DEFLSHdll(var1, var2, self._mol_frac) 

+

755 elif mode == "HS": 

+

756 h = var1 

+

757 var1 = var1 * e_multi 

+

758 s = var2 

+

759 var2 = var2 * e_multi 

+

760 tmp = self.rp.HSFLSHdll(var1, var2, self._mol_frac) 

+

761 else: 

+

762 raise ValueError("Chosen mode is not available in refprop calc_state function!") 

+

763 

+

764 # Check for errors 

+

765 self._check_error(tmp.ierr, tmp.herr, self.calc_state.__name__) 

+

766 

+

767 # Get all state variables 

+

768 if p is None: 

+

769 p = tmp.P / p_multi 

+

770 if T is None: 

+

771 T = tmp.T 

+

772 if u is None: 

+

773 u = tmp.e / e_multi 

+

774 if h is None: 

+

775 h = tmp.h / e_multi 

+

776 if s is None: 

+

777 s = tmp.s / e_multi 

+

778 if d is None: 

+

779 d = tmp.D / d_multi 

+

780 if q is None: 

+

781 if self._mix_flag: 

+

782 # Transform current q (molar) to mass based quality 

+

783 tmp2 = self._call_refprop("PH", "QMASS", p, h * e_multi, i_mass=1) 

+

784 if tmp2.Output[0] < 0: 

+

785 q_mass = tmp2.ierr 

+

786 else: 

+

787 q_mass = tmp2.Output[0] 

+

788 q = q_mass 

+

789 else: 

+

790 q = tmp.q 

+

791 

+

792 # # In case not in two phase region reset q to -1 

+

793 # if q > 1 or q < 0: 

+

794 # q = -1 

+

795 

+

796 # Define state 

+

797 state = ThermodynamicState(p=p, T=T, u=u, h=h, s=s, d=d, q=q) 

+

798 return state 

+

799 

+

800 def calc_satliq_state(self, s): 

+

801 """s in kJ/kgK""" 

+

802 s = s * self.M * 1000 # kJ/kgK -> J/molK 

+

803 tmp = self.rp.SATSdll(s=s, z="", kph=1) 

+

804 self._check_error(tmp.ierr, tmp.herr, self.calc_satliq_state.__name__) 

+

805 if tmp.k1 != 1: 

+

806 raise TypeError 

+

807 p = tmp.P1 * 1000 # kPa -> Pa 

+

808 d = tmp.D1 * self.M * 1000 # mol/l -> kg/mol 

+

809 return self.calc_state("PD", p, d) 

+

810 

+

811 def calc_transport_properties(self, state: ThermodynamicState): 

+

812 """ Calculate transport properties of RefProp fluid at given state 

+

813 

+

814 Parameters: 

+

815 ----------- 

+

816 :param ThermodynamicState state: 

+

817 Current thermodynamic state 

+

818 Return: 

+

819 ------- 

+

820 :return TransportProperties props: 

+

821 Instance of TransportProperties 

+

822 """ 

+

823 # Get properties 

+

824 tmp = self._call_refprop_allprop("PRANDTL,VIS,TCX,KV,CV,CP,BETA,STN,ACF", state.T, state.d / self.M, i_mass=0) 

+

825 # Create transport properties instance 

+

826 props = TransportProperties(lam=tmp.Output[2], 

+

827 dyn_vis=tmp.Output[1], 

+

828 kin_vis=tmp.Output[3], 

+

829 pr=tmp.Output[0], 

+

830 cp=tmp.Output[5] / self.M, 

+

831 cv=tmp.Output[4] / self.M, 

+

832 beta=tmp.Output[6], 

+

833 sur_ten=tmp.Output[7], 

+

834 ace_fac=tmp.Output[8], 

+

835 state=state) 

+

836 # Return props 

+

837 return props 

+

838 

+

839 def get_available_substances(self, 

+

840 mode="all", 

+

841 include_ending=False, 

+

842 save_txt=False): 

+

843 """ Get all available RefProp fluids (mixtures and / or pure substances depending on mode) 

+

844 

+

845 Parameters: 

+

846 ----------- 

+

847 :param string mode: 

+

848 Mode defining which kind of fluids you want to have: 'pure': pure substances, 'mix': mixtures, 'all': all 

+

849 :param boolean include_ending: 

+

850 Defines, whether file ending shall be returned as well or not 

+

851 :param boolean save_txt: 

+

852 Defines, whether a text file with names shall be created in current working directory 

+

853 Return: 

+

854 ------- 

+

855 :return list names: 

+

856 String list containing names of available fluids (depending on defined mode) 

+

857 """ 

+

858 # Possible endings 

+

859 _endings = ["MIX", "FLD", "PPF"] 

+

860 

+

861 # Folders where fluid data is located 

+

862 folders = [r"FLUIDS", r"MIXTURES"] 

+

863 

+

864 # Define paths by mode 

+

865 if mode == "pure": 

+

866 paths = [os.path.join(self._ref_prop_path, folders[0])] 

+

867 elif mode == "mix": 

+

868 paths = [os.path.join(self._ref_prop_path, folders[1])] 

+

869 elif mode == "all": 

+

870 paths = [os.path.join(self._ref_prop_path, folders[0]), 

+

871 os.path.join(self._ref_prop_path, folders[1])] 

+

872 else: 

+

873 raise ValueError("Chosen mode '{}' is not possible!".format(mode)) 

+

874 

+

875 # Get files in folders, remove ending and append to names 

+

876 names = [] 

+

877 for p in paths: 

+

878 # Get all files in directory 

+

879 files = [f for f in os.listdir(p) if os.path.isfile(os.path.join(p, f))] 

+

880 # Remove ending 

+

881 if include_ending: 

+

882 tmp = [path for path in files if path.split(".")[1] in _endings] 

+

883 else: 

+

884 tmp = [path.split(".")[0] for path in files if path.split(".")[1] in _endings] 

+

885 # Append names to names list 

+

886 names.extend(tmp) 

+

887 

+

888 # In case names shall be stored 

+

889 if save_txt: 

+

890 with open("available_fluids.txt", "w") as output: 

+

891 for i in names: 

+

892 output.write("{} \n".format(i)) 

+

893 

+

894 if not names: 

+

895 raise ValueError("No fluids are in current RefProp directory. Check path '{}'!".format(self._ref_prop_path)) 

+

896 

+

897 # Return names 

+

898 return names 

+

899 

+

900 def get_comp_names(self): 

+

901 """ Get name of components within current fluid" 

+

902 

+

903 Return: 

+

904 ------- 

+

905 :return list comp_names: 

+

906 String names of components within current fluid 

+

907 """ 

+

908 return self._comp_names 

+

909 

+

910 def get_nbp(self): 

+

911 """ Get normal boiling point (T @ q=0 and 1 bar) 

+

912 

+

913 Return: 

+

914 ------- 

+

915 :return float nbp: 

+

916 Normal boiling point of refrigerant in °C 

+

917 """ 

+

918 if not self._nbp: 

+

919 self._nbp = self.calc_state("PQ", 1e5, 0.0).T - 273.15 

+

920 

+

921 return self._nbp 

+

922 

+

923 def get_molar_composition(self, state: ThermodynamicState, z_molar=None): 

+

924 """ Get composition on molar base. Liquid phase, vapor phase and total. 

+

925 

+

926 :param ThermodynamicState state: the state whose compositions will be returned 

+

927 :param list z_molar: molar composition of fluid. In case of None, default value _mol_frac is used 

+

928 :return: 

+

929 - list x: 

+

930 composition of liquid phase 

+

931 - list y: 

+

932 composition of vapor phase 

+

933 - list z: 

+

934 composition in total 

+

935 """ 

+

936 if z_molar is None: 

+

937 z = self._mol_frac 

+

938 M = self.M 

+

939 else: 

+

940 z = z_molar 

+

941 M = self.rp.REFPROPdll(hFld="", 

+

942 hIn="", 

+

943 hOut="M", 

+

944 iUnits=self.molar_base_si, 

+

945 iMass=0, 

+

946 iFlag=1, 

+

947 a=0, 

+

948 b=0, 

+

949 z=z_molar).Output[0] 

+

950 num_components = len(z) 

+

951 

+

952 tmp = self.rp.TDFLSHdll(T=state.T, 

+

953 D=state.d / M / 1000, 

+

954 z=z) 

+

955 # TDFLSHdll is deprecated, use the following in future: 

+

956 # tmp = self.rp.ABFLSHdll(ab="TD", 

+

957 # a=state.T, 

+

958 # b=state.d / self.M / 1000, 

+

959 # z=self._mol_frac, 

+

960 # iFlag=0) # molar units 

+

961 

+

962 x = list(tmp.x[:num_components]) 

+

963 y = list(tmp.y[:num_components]) 

+

964 

+

965 return x, y, z 

+

966 

+

967 def get_critical_point(self): 

+

968 """ Get T and p of critical point 

+

969 

+

970 Return: 

+

971 ------- 

+

972 :return float Tc: 

+

973 Temperature at critical point in K 

+

974 :return float pc: 

+

975 Pressure at critical point in Pa 

+

976 :return float dc: 

+

977 Density at critical point in kg/m^3 

+

978 """ 

+

979 mode = 2 

+

980 if mode == 1: 

+

981 tmp = self._call_refprop("CRIT", "T,P,D", i_mass=1) 

+

982 Tc = tmp.Output[0] 

+

983 pc = tmp.Output[1] 

+

984 dc = tmp.Output[2] * self.M 

+

985 else: 

+

986 res = self._call_refprop_allprop("TC,PC,DC") 

+

987 Tc = res.Output[0] 

+

988 pc = res.Output[1] 

+

989 dc = res.Output[2] * self.M 

+

990 return Tc, pc, dc 

+

991 

+

992 # 

+

993 # 

+

994 def get_def_limits(self): 

+

995 """ Get limits of current ref prop fluid 

+

996 Limits contain Tmin, Tmax, Dmax and Pmax (Temperatures, density, pressure) 

+

997 

+

998 :return dict limits: 

+

999 Dictionary with definition limits in RefProp. Contains min/max temperature, max density, max pressure. 

+

1000 """ 

+

1001 tmp = self._call_refprop_allprop("TMIN,TMAX,DMAX,PMAX") 

+

1002 limits = {"Tmin": tmp.Output[0], 

+

1003 "Tmax": tmp.Output[1], 

+

1004 "Dmax": tmp.Output[2] * self.M, 

+

1005 "Pmax": tmp.Output[3]} 

+

1006 return limits 

+

1007 

+

1008 @staticmethod 

+

1009 def get_dll_path(ref_prop_path: str): 

+

1010 """ 

+

1011 Return the location of the dll 

+

1012 

+

1013 Return: 

+

1014 :return: string path_to_dll: 

+

1015 Path of a valid dll 

+

1016 """ 

+

1017 path_to_dll = os.path.join(ref_prop_path, r"REFPRP64.DLL") 

+

1018 

+

1019 # Check if dll actually exists: 

+

1020 if not os.path.exists(path_to_dll): 

+

1021 raise FileNotFoundError("Selected dll not found automatically. " 

+

1022 "Please alter the local attribute or search for yourself.") 

+

1023 

+

1024 return path_to_dll 

+

1025 

+

1026 def get_fluid_name(self): 

+

1027 """ Get fluid name. 

+

1028 

+

1029 Return: 

+

1030 ------- 

+

1031 :return string fluid_name: 

+

1032 Fluid name 

+

1033 """ 

+

1034 return self.fluid_name 

+

1035 

+

1036 def get_gwp(self): 

+

1037 """ Get gwp of current fluid from refProp 

+

1038 

+

1039 Return: 

+

1040 ------- 

+

1041 :return float gwp: 

+

1042 Global warming potential of fluid. In case calculation failed, None will be returned. 

+

1043 """ 

+

1044 # Call refProp 

+

1045 tmp = self._call_refprop("", 

+

1046 "GWP", 

+

1047 i_mass=1) 

+

1048 # Calculate gwp 

+

1049 gwp = round(sum(max(tmp.Output[i], 0) * self._mass_frac[i] for i in range(self._n_comp)), 2) 

+

1050 # In case GWP cannot be calculated 

+

1051 if gwp < 0: 

+

1052 gwp = 0 

+

1053 return gwp 

+

1054 

+

1055 def get_longname(self): 

+

1056 """ Get longname of fluid 

+

1057 

+

1058 Return: 

+

1059 ------- 

+

1060 :return string longname: 

+

1061 Name of current fluid in refprop - provides mass fractions and components as well (in case of mixture) 

+

1062 """ 

+

1063 longname = self._call_refprop("", "LONGNAME(0)").hUnits 

+

1064 return longname 

+

1065 

+

1066 def get_mass_fraction(self, use_round=True): 

+

1067 """ Get mass fractions of pure substances in current fluid 

+

1068 

+

1069 Parameters: 

+

1070 :param boolean use_round: 

+

1071 Flag to define, whether the exact values or rounded values (by the fourth number) shall be used 

+

1072 Return: 

+

1073 ------- 

+

1074 :return list mass_frac: 

+

1075 List of component mass fractions 

+

1076 """ 

+

1077 if use_round: 

+

1078 mass_frac = [round(i, 4) for i in self._mass_frac] 

+

1079 else: 

+

1080 mass_frac = self._mass_frac 

+

1081 return mass_frac 

+

1082 

+

1083 def get_molar_mass(self): 

+

1084 """ Get molar mass of current fluid 

+

1085 

+

1086 Return: 

+

1087 ------- 

+

1088 :return float M: 

+

1089 Molar mass of current fluid in kg/mol 

+

1090 """ 

+

1091 return self.M 

+

1092 

+

1093 def get_mol_fraction(self, use_round=True): 

+

1094 """ Get mol fractions of pure substances in current fluid 

+

1095 

+

1096 Parameters: 

+

1097 :param boolean use_round: 

+

1098 Flag to define, whether the exact values or rounded values (by the fourth number) shall be used 

+

1099 Return: 

+

1100 ------- 

+

1101 :return list frac: 

+

1102 List of component mol fractions 

+

1103 """ 

+

1104 if use_round: 

+

1105 mol_frac = [round(i, 4) for i in self._mol_frac] 

+

1106 else: 

+

1107 mol_frac = self._mol_frac 

+

1108 return mol_frac 

+

1109 

+

1110 def get_odp(self): 

+

1111 """ Calculate ozone depletion potential 

+

1112 In case of mixtures: Maximum value of pure substances will be used 

+

1113 

+

1114 Return: 

+

1115 ------- 

+

1116 :return float odp: 

+

1117 ODP of fluid. In case calculation failed, None will be returned. 

+

1118 """ 

+

1119 # Call refProp 

+

1120 tmp = self._call_refprop("", 

+

1121 "ODP", 

+

1122 i_mass=1) 

+

1123 # Calculate odp 

+

1124 odp = max(max(tmp.Output), 0) 

+

1125 # In case some error occured 

+

1126 if odp < 0: 

+

1127 odp = 0 

+

1128 return odp 

+

1129 

+

1130 def get_safety(self): 

+

1131 """ Calculate safety class of refrigerant 

+

1132 

+

1133 Return: 

+

1134 ------- 

+

1135 :return string safety: 

+

1136 Safety class of fluid. 

+

1137 """ 

+

1138 # Call refProp 

+

1139 tmp = self._call_refprop("", 

+

1140 "SAFETY", 

+

1141 i_mass=1) 

+

1142 # Get safety class 

+

1143 safety = tmp.hUnits 

+

1144 # Return safety 

+

1145 return safety 

+

1146 

+

1147 def get_sat_vap_pressure(self, T_sat): 

+

1148 """ Get pressure of saturated vapor for defined temperature 

+

1149 

+

1150 Note: 

+

1151 - works for vapor, liquid and solid 

+

1152 - returns equilibrium pressure at defined line (q=1) 

+

1153 

+

1154 Parameters: 

+

1155 :param float T_sat: 

+

1156 Temperature in K 

+

1157 Return: 

+

1158 :return float p_sat: 

+

1159 Vapor pressure in Pa 

+

1160 """ 

+

1161 trip = self.get_triple_point() 

+

1162 if trip[0] <= T_sat: 

+

1163 p_sat = self.calc_state("TQ", T_sat, 1.0).p 

+

1164 else: 

+

1165 tmp = self.rp.REFPROPdll("", "TSUBL", "P", self.molar_base_si, 0, 0, T_sat, 0.0, self._mol_frac) 

+

1166 p_sat = tmp.Output[0] 

+

1167 return p_sat 

+

1168 

+

1169 def get_triple_point(self): 

+

1170 """ Get temperature and pressure at triple point of current fluid 

+

1171 

+

1172 Note: Works fine for pure substances, mixtures might not work properly 

+

1173 

+

1174 Return: 

+

1175 :return float T_tpl: 

+

1176 Temperature at triple point in K 

+

1177 :return float p_tpl: 

+

1178 Pressure at triple point in Pa 

+

1179 """ 

+

1180 # Call Refprop 

+

1181 tmp = self._call_refprop("TRIP", "T;P") 

+

1182 return tmp.Output[0], tmp.Output[1] 

+

1183 

+

1184 def get_version(self): 

+

1185 """ Get version of wrapper and used RefProp dll 

+

1186 

+

1187 Return: 

+

1188 :return string wrapper_version: 

+

1189 Refprop wrapper version 

+

1190 :return string refprop_version: 

+

1191 Version of used RefProp dll 

+

1192 """ 

+

1193 return self.__version__, self.rp.RPVersion() 

+

1194 

+

1195 def is_mixture(self): 

+

1196 """ Find out if fluid is mixture or not. 

+

1197 In case current fluid is mixture, true is returned. 

+

1198 In case current fluid is pure substance, false is returned. 

+

1199 

+

1200 Return: 

+

1201 ------- 

+

1202 :return boolean _mix_flag: 

+

1203 Boolean for mixture (True), pure substance (False) 

+

1204 """ 

+

1205 return self._mix_flag 

+

1206 

+

1207 def set_error_flag(self, flag): 

+

1208 """ Set error flag 

+

1209 

+

1210 Parameters: 

+

1211 :param boolean flag: 

+

1212 New error flag 

+

1213 Return: 

+

1214 :return int err: 

+

1215 Notifier for error code - in case everything went fine, 0 is returned 

+

1216 """ 

+

1217 self._flag_check_errors = flag 

+

1218 return 0 

+

1219 

+

1220 def set_warning_flag(self, flag): 

+

1221 """ Set warning flag 

+

1222 

+

1223 Parameters: 

+

1224 :param boolean flag: 

+

1225 New warning flag 

+

1226 Return: 

+

1227 :return int err: 

+

1228 Notifier for error code - in case everything went fine, 0 is returned 

+

1229 """ 

+

1230 self._flag_warnings = flag 

+

1231 return 0 

+
+ + + diff --git a/docs/main/coverage/d_c048ebee450da2af_states_py.html b/docs/main/coverage/d_c048ebee450da2af_states_py.html new file mode 100644 index 0000000..fa4b85b --- /dev/null +++ b/docs/main/coverage/d_c048ebee450da2af_states_py.html @@ -0,0 +1,279 @@ + + + + + Coverage for vclibpy/media/states.py: 92% + + + + + +
+
+

+ Coverage for vclibpy/media/states.py: + 92% +

+ +

+ 60 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module containing classes for thermodynamic state and transport properties. 

+

3""" 

+

4from vclibpy.datamodels import VariableContainer 

+

5 

+

6 

+

7__all__ = [ 

+

8 'ThermodynamicState', 

+

9 'TransportProperties', 

+

10] 

+

11 

+

12 

+

13class ThermodynamicState: 

+

14 """ 

+

15 Represents a thermodynamic state within a cycle. 

+

16 

+

17 Notes: 

+

18 Does not necessarily need to have all state variables defined! 

+

19 

+

20 Args: 

+

21 p (float): Pressure at the state in Pa. 

+

22 T (float): Temperature at the state in K. 

+

23 u (float): Inner energy at the state in J/kg. 

+

24 h (float): Enthalpy at the state in J/kg. 

+

25 s (float): Entropy at the state in J/(kg * K). 

+

26 v (float): Specific volume at the state in m^3/kg. 

+

27 q (float): Quality at the state (between 0 and 1). 

+

28 d (float): Density at the state in kg/m^3. 

+

29 

+

30 Methods: 

+

31 __init__: Initializes the state class. 

+

32 __str__: Provides a string representation of the state. 

+

33 get_pretty_print: Formats the state with names, units, and descriptions. 

+

34 """ 

+

35 

+

36 def __init__(self, 

+

37 p=None, 

+

38 T=None, 

+

39 u=None, 

+

40 h=None, 

+

41 s=None, 

+

42 v=None, 

+

43 q=None, 

+

44 d=None): 

+

45 """ 

+

46 Initializes a thermodynamic state. 

+

47 

+

48 Args: 

+

49 p (float): Pressure at the state in Pa. 

+

50 T (float): Temperature at the state in K. 

+

51 u (float): Inner energy at the state in J/kg. 

+

52 h (float): Enthalpy at the state in J/kg. 

+

53 s (float): Entropy at the state in J/(kg * K). 

+

54 v (float): Specific volume at the state in m^3/kg. 

+

55 q (float): Quality at the state (between 0 and 1). 

+

56 d (float): Density at the state in kg/m^3. 

+

57 

+

58 Notes: 

+

59 If only v or d is provided, the other attribute will be calculated. If both are given and they are similar, 

+

60 an error will be raised. 

+

61 """ 

+

62 self.p = p 

+

63 self.T = T 

+

64 self.u = u 

+

65 self.h = h 

+

66 self.s = s 

+

67 self.v = v 

+

68 self.q = q 

+

69 self.d = d 

+

70 # Define density 

+

71 if v and d: 

+

72 if not round(1/v, 4) == round(d, 4): 

+

73 raise ValueError("At current state d and v do not match", d, v) 

+

74 elif v: 

+

75 self.d = 1/v 

+

76 elif d: 

+

77 self.v = 1/d 

+

78 

+

79 def __str__(self): 

+

80 """ 

+

81 Returns a string representation of the state. 

+

82 """ 

+

83 return ";".join([f"{k}={v}" for k, v in self.__dict__.items()]) 

+

84 

+

85 def get_pretty_print(self): 

+

86 """ 

+

87 Provides a formatted representation of the state with names, units, and descriptions. 

+

88 """ 

+

89 _container = VariableContainer() 

+

90 _container.__class__.__name__ = self.__class__.__name__ 

+

91 _container.set(name="p", value=self.p, unit="Pa", description="Pressure") 

+

92 _container.set(name="T", value=self.T, unit="K", description="Temperature") 

+

93 _container.set(name="u", value=self.u, unit="J/kg", description="Inner energy") 

+

94 _container.set(name="h", value=self.h, unit="J/kg", description="Enthalpy") 

+

95 _container.set(name="s", value=self.s, unit="J/(kg*K)", description="Entropy") 

+

96 _container.set(name="v", value=self.v, unit="m^3/kg", description="Specific volume") 

+

97 _container.set(name="q", value=self.q, unit="-", description="Quality") 

+

98 _container.set(name="d", value=self.d, unit="kg/m^3", description="Density") 

+

99 return str(_container) 

+

100 

+

101 

+

102class TransportProperties: 

+

103 """ 

+

104 Represents transport properties at a specific thermodynamic state. 

+

105 

+

106 Args: 

+

107 lam (float): Thermal conductivity in W/(m*K). 

+

108 dyn_vis (float): Dynamic viscosity in Pa*s. 

+

109 kin_vis (float): Kinematic viscosity in m^2/s. 

+

110 Pr (float): Prandtl number. 

+

111 cp (float): Isobaric specific heat capacity in J/(kg*K). 

+

112 cv (float): Isochoric specific heat capacity in J/(kg*K). 

+

113 beta (float): Thermal expansion coefficient in 1/K. 

+

114 sur_ten (float): Surface tension in N/m. 

+

115 ace_fac (float): Acentric factor. 

+

116 state (ThermodynamicState): The state the transport properties belong to. 

+

117 

+

118 Methods: 

+

119 __init__: Initializes the transport properties class. 

+

120 __str__: Provides a string representation of the transport properties. 

+

121 get_pretty_print: Formats the properties with names, units, and descriptions. 

+

122 """ 

+

123 

+

124 def __init__(self, 

+

125 lam=None, 

+

126 dyn_vis=None, 

+

127 kin_vis=None, 

+

128 pr=None, 

+

129 cp=None, 

+

130 cv=None, 

+

131 beta=None, 

+

132 sur_ten=None, 

+

133 ace_fac=None, 

+

134 state=None): 

+

135 """ 

+

136 Initializes transport properties. 

+

137 

+

138 Args: 

+

139 lam (float): Thermal conductivity in W/(m*K). 

+

140 dyn_vis (float): Dynamic viscosity in Pa*s. 

+

141 kin_vis (float): Kinematic viscosity in m^2/s. 

+

142 pr (float): Prandtl number. 

+

143 cp (float): Isobaric specific heat capacity in J/(kg*K). 

+

144 cv (float): Isochoric specific heat capacity in J/(kg*K). 

+

145 beta (float): Thermal expansion coefficient in 1/K. 

+

146 sur_ten (float): Surface tension in N/m. 

+

147 ace_fac (float): Acentric factor. 

+

148 state (ThermodynamicState): The state the transport properties belong to. 

+

149 """ 

+

150 self.lam = lam 

+

151 self.dyn_vis = dyn_vis 

+

152 self.kin_vis = kin_vis 

+

153 self.Pr = pr 

+

154 self.cp = cp 

+

155 self.cv = cv 

+

156 self.beta = beta 

+

157 self.sur_ten = sur_ten 

+

158 self.ace_fac = ace_fac 

+

159 self.state = state 

+

160 

+

161 def __str__(self): 

+

162 """ 

+

163 Returns a string representation of the transport properties. 

+

164 """ 

+

165 return ";".join([f"{k}={v}" for k, v in self.__dict__.items()]) 

+

166 

+

167 def get_pretty_print(self): 

+

168 """ 

+

169 Provides a formatted representation of the properties with names, units, and descriptions. 

+

170 """ 

+

171 _container = VariableContainer() 

+

172 _container.__class__.__name__ = self.__class__.__name__ 

+

173 _container.set(name="lam", value=self.lam, unit="W/(m*K)", description="Thermal conductivity") 

+

174 _container.set(name="dyn_vis", value=self.dyn_vis, unit="Pa*s", description="Dynamic viscosity") 

+

175 _container.set(name="kin_vis", value=self.kin_vis, unit="m^2/s", description="Kinematic viscosity") 

+

176 _container.set(name="pr", value=self.Pr, unit="-", description="Prandtl number") 

+

177 _container.set(name="cp", value=self.cp, unit="J/(kg*K)", description="Isobaric specific heat capacity") 

+

178 _container.set(name="cv", value=self.cv, unit="J/(kg*K)", description="Isochoric specific heat capacity") 

+

179 _container.set(name="beta", value=self.beta, unit="1/K", description="Thermal expansion coefficient") 

+

180 _container.set(name="sur_ten", value=self.sur_ten, unit="N/m", description="Surface tension") 

+

181 _container.set(name="ace_fac", value=self.ace_fac, unit="-", description="Acentric factor") 

+

182 return str(_container) 

+
+ + + diff --git a/docs/main/coverage/d_d45aa4312ad3649a___init___py.html b/docs/main/coverage/d_d45aa4312ad3649a___init___py.html new file mode 100644 index 0000000..df37c47 --- /dev/null +++ b/docs/main/coverage/d_d45aa4312ad3649a___init___py.html @@ -0,0 +1,101 @@ + + + + + Coverage for vclibpy/components/compressors/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/compressors/__init__.py: + 100% +

+ +

+ 4 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .compressor import Compressor 

+

2from .rotary import RotaryCompressor 

+

3from .ten_coefficient import TenCoefficientCompressor, DataSheetCompressor 

+

4from .constant_effectivness import ConstantEffectivenessCompressor 

+
+ + + diff --git a/docs/main/coverage/d_d45aa4312ad3649a_compressor_py.html b/docs/main/coverage/d_d45aa4312ad3649a_compressor_py.html new file mode 100644 index 0000000..00756f7 --- /dev/null +++ b/docs/main/coverage/d_d45aa4312ad3649a_compressor_py.html @@ -0,0 +1,270 @@ + + + + + Coverage for vclibpy/components/compressors/compressor.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/compressors/compressor.py: + 100% +

+ +

+ 35 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module for different compressor models 

+

3""" 

+

4 

+

5from vclibpy.components.component import BaseComponent 

+

6from vclibpy.datamodels import Inputs, FlowsheetState 

+

7 

+

8 

+

9class Compressor(BaseComponent): 

+

10 """ 

+

11 Base compressor class to be extended for specific compressor models. 

+

12 

+

13 Args: 

+

14 N_max (float): Maximal rotations per second of the compressor. 

+

15 V_h (float): Volume of the compressor in m^3. 

+

16 

+

17 Methods: 

+

18 get_lambda_h(inputs: Inputs) -> float: 

+

19 Get the volumetric efficiency. 

+

20 

+

21 get_eta_isentropic(p_outlet: float, inputs: Inputs) -> float: 

+

22 Get the isentropic efficiency. 

+

23 

+

24 get_eta_mech(inputs: Inputs) -> float: 

+

25 Get the mechanical efficiency. 

+

26 

+

27 get_p_outlet() -> float: 

+

28 Get the outlet pressure. 

+

29 

+

30 get_n_absolute(n: float) -> float: 

+

31 Return the absolute compressor frequency based on the relative speed. 

+

32 

+

33 calc_state_outlet(p_outlet: float, inputs: Inputs, fs_state: FlowsheetState): 

+

34 Calculate the outlet state based on the high pressure level and provided inputs. 

+

35 

+

36 calc_m_flow(inputs: Inputs, fs_state: FlowsheetState) -> float: 

+

37 Calculate the refrigerant mass flow rate. 

+

38 

+

39 calc_electrical_power(inputs: Inputs, fs_state: FlowsheetState) -> float: 

+

40 Calculate the electrical power consumed by the compressor based on an adiabatic energy balance. 

+

41 """ 

+

42 

+

43 def __init__(self, N_max: float, V_h: float): 

+

44 """ 

+

45 Initialize the compressor. 

+

46 

+

47 Args: 

+

48 N_max (float): Maximal rotations per second of the compressor. 

+

49 V_h (float): Volume of the compressor in m^3. 

+

50 """ 

+

51 super().__init__() 

+

52 self.N_max = N_max 

+

53 self.V_h = V_h 

+

54 

+

55 def get_lambda_h(self, inputs: Inputs) -> float: 

+

56 """ 

+

57 Get the volumetric efficiency. 

+

58 

+

59 Args: 

+

60 inputs (Inputs): Inputs for the calculation. 

+

61 

+

62 Returns: 

+

63 float: Volumetric efficiency. 

+

64 """ 

+

65 raise NotImplementedError("Re-implement this function to use it") 

+

66 

+

67 def get_eta_isentropic(self, p_outlet: float, inputs: Inputs) -> float: 

+

68 """ 

+

69 Get the isentropic efficiency. 

+

70 

+

71 Args: 

+

72 p_outlet (float): High pressure value. 

+

73 inputs (Inputs): Inputs for the calculation. 

+

74 

+

75 Returns: 

+

76 float: Isentropic efficiency. 

+

77 """ 

+

78 raise NotImplementedError("Re-implement this function to use it") 

+

79 

+

80 def get_eta_mech(self, inputs: Inputs) -> float: 

+

81 """ 

+

82 Get the mechanical efficiency including motor and inverter efficiencies. 

+

83 

+

84 Args: 

+

85 inputs (Inputs): Inputs for the calculation. 

+

86 

+

87 Returns: 

+

88 float: Mechanical efficiency including motor and inverter efficiencies. 

+

89 """ 

+

90 raise NotImplementedError("Re-implement this function to use it") 

+

91 

+

92 def get_p_outlet(self) -> float: 

+

93 """ 

+

94 Get the outlet pressure. 

+

95 

+

96 Returns: 

+

97 float: Outlet pressure. 

+

98 """ 

+

99 assert self.state_outlet is not None, "You have to calculate the outlet state first." 

+

100 return self.state_outlet.p 

+

101 

+

102 def get_n_absolute(self, n: float) -> float: 

+

103 """ 

+

104 Return given relative n as absolute rounds/sec based on self.N_max. 

+

105 

+

106 Args: 

+

107 n (float): Relative compressor speed between 0 and 1. 

+

108 

+

109 Returns: 

+

110 float: Absolute compressor frequency in rounds/sec. 

+

111 """ 

+

112 return self.N_max * n 

+

113 

+

114 def calc_state_outlet(self, p_outlet: float, inputs: Inputs, fs_state: FlowsheetState): 

+

115 """ 

+

116 Calculate the output state based on the high pressure level and the provided inputs. 

+

117 The state is automatically set as the outlet state of this component. 

+

118 

+

119 Args: 

+

120 p_outlet (float): High pressure value. 

+

121 inputs (Inputs): Inputs for calculation. 

+

122 fs_state (FlowsheetState): Flowsheet state. 

+

123 """ 

+

124 state_outlet_isentropic = self.med_prop.calc_state("PS", p_outlet, self.state_inlet.s) 

+

125 eta_is = self.get_eta_isentropic(p_outlet=p_outlet, inputs=inputs) 

+

126 h_outlet = ( 

+

127 self.state_inlet.h + (state_outlet_isentropic.h - self.state_inlet.h) / 

+

128 eta_is 

+

129 ) 

+

130 fs_state.set(name="eta_is", value=eta_is, unit="%", description="Isentropic efficiency") 

+

131 self.state_outlet = self.med_prop.calc_state("PH", p_outlet, h_outlet) 

+

132 

+

133 def calc_m_flow(self, inputs: Inputs, fs_state: FlowsheetState) -> float: 

+

134 """ 

+

135 Calculate the refrigerant mass flow rate. 

+

136 

+

137 Args: 

+

138 inputs (Inputs): Inputs for the calculation. 

+

139 fs_state (FlowsheetState): Flowsheet state. 

+

140 

+

141 Returns: 

+

142 float: Refrigerant mass flow rate. 

+

143 """ 

+

144 lambda_h = self.get_lambda_h(inputs=inputs) 

+

145 V_flow_ref = ( 

+

146 lambda_h * 

+

147 self.V_h * 

+

148 self.get_n_absolute(inputs.n) 

+

149 ) 

+

150 self.m_flow = self.state_inlet.d * V_flow_ref 

+

151 fs_state.set(name="lambda_h", value=lambda_h, unit="%", description="Volumetric efficiency") 

+

152 fs_state.set(name="V_flow_ref", value=V_flow_ref, unit="m3/s", description="Refrigerant volume flow rate") 

+

153 fs_state.set(name="m_flow_ref", value=self.m_flow, unit="kg/s", description="Refrigerant mass flow rate") 

+

154 return self.m_flow 

+

155 

+

156 def calc_electrical_power(self, inputs: Inputs, fs_state: FlowsheetState) -> float: 

+

157 """ 

+

158 Calculate the electrical power consumed by the compressor based on an adiabatic energy balance. 

+

159 

+

160 Args: 

+

161 inputs (Inputs): Inputs for the calculation. 

+

162 fs_state (FlowsheetState): Flowsheet state. 

+

163 

+

164 Returns: 

+

165 float: Electrical power consumed. 

+

166 """ 

+

167 # Heat flow in the compressor 

+

168 P_t = self.m_flow * (self.state_outlet.h - self.state_inlet.h) 

+

169 # Electrical power consumed 

+

170 eta_mech = self.get_eta_mech(inputs=inputs) 

+

171 P_el = P_t / eta_mech 

+

172 fs_state.set(name="eta_mech", value=eta_mech, unit="-", description="Mechanical efficiency") 

+

173 return P_el 

+
+ + + diff --git a/docs/main/coverage/d_d45aa4312ad3649a_constant_effectivness_py.html b/docs/main/coverage/d_d45aa4312ad3649a_constant_effectivness_py.html new file mode 100644 index 0000000..8123568 --- /dev/null +++ b/docs/main/coverage/d_d45aa4312ad3649a_constant_effectivness_py.html @@ -0,0 +1,195 @@ + + + + + Coverage for vclibpy/components/compressors/constant_effectivness.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/compressors/constant_effectivness.py: + 100% +

+ +

+ 14 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from vclibpy.components.compressors.compressor import Compressor 

+

2from vclibpy.datamodels import Inputs 

+

3 

+

4 

+

5class ConstantEffectivenessCompressor(Compressor): 

+

6 """ 

+

7 Compressor model with constant efficiencies. 

+

8 

+

9 Inherits from the Compressor class, which defines the basic properties and behavior of a compressor in a vapor 

+

10 compression cycle. 

+

11 

+

12 Parameters: 

+

13 N_max (float): Maximal rotations per second of the compressor. 

+

14 V_h (float): Volume of the compressor in m^3. 

+

15 eta_isentropic (float): Constant isentropic efficiency of the compressor. 

+

16 eta_mech (float): Constant mechanical efficiency of the compressor. 

+

17 lambda_h (float): Constant volumetric efficiency. 

+

18 

+

19 Args: 

+

20 N_max (float): Maximal rotations per second of the compressor. 

+

21 V_h (float): Volume of the compressor in m^3. 

+

22 eta_isentropic (float): Constant isentropic efficiency of the compressor. 

+

23 eta_inverter (float): Constant inverter efficiency of the compressor. 

+

24 eta_motor (float): Constant motor efficiency of the compressor. 

+

25 eta_mech (float): Constant mechanical efficiency of the compressor including motor and inverter efficiencies. 

+

26 lambda_h (float): Constant volumetric efficiency. 

+

27 

+

28 Methods: 

+

29 get_lambda_h(inputs: Inputs) -> float: 

+

30 Returns the constant volumetric efficiency of the compressor. 

+

31 

+

32 get_eta_isentropic(p_outlet: float, inputs: Inputs) -> float: 

+

33 Returns the constant isentropic efficiency of the compressor. 

+

34 

+

35 get_eta_mech(inputs: Inputs) -> float: 

+

36 Returns the constant mechanical efficiency including motor and inverter efficiencies. 

+

37 

+

38 """ 

+

39 

+

40 def __init__(self, 

+

41 N_max: float, V_h: float, 

+

42 eta_isentropic: float, 

+

43 eta_mech: float, 

+

44 lambda_h: float): 

+

45 """ 

+

46 Initialize the ConstantEffectivenessCompressor. 

+

47 

+

48 Args: 

+

49 N_max (float): Maximal rotations per second of the compressor. 

+

50 V_h (float): Volume of the compressor in m^3. 

+

51 eta_isentropic (float): Constant isentropic efficiency of the compressor. 

+

52 eta_inverter (float): Constant inverter efficiency of the compressor. 

+

53 eta_motor (float): Constant motor efficiency of the compressor. 

+

54 eta_mech (float): Constant mechanical efficiency of the compressor. 

+

55 lambda_h (float): Constant volumetric efficiency. 

+

56 """ 

+

57 super().__init__(N_max=N_max, V_h=V_h) 

+

58 self.eta_isentropic = eta_isentropic 

+

59 self.eta_mech = eta_mech 

+

60 self.lambda_h = lambda_h 

+

61 

+

62 def get_lambda_h(self, inputs: Inputs) -> float: 

+

63 """ 

+

64 Returns the constant volumetric efficiency of the compressor. 

+

65 

+

66 Args: 

+

67 inputs (Inputs): Input parameters for the calculation. 

+

68 

+

69 Returns: 

+

70 float: Constant volumetric efficiency. 

+

71 """ 

+

72 return self.lambda_h 

+

73 

+

74 def get_eta_isentropic(self, p_outlet: float, inputs: Inputs) -> float: 

+

75 """ 

+

76 Returns the constant isentropic efficiency of the compressor. 

+

77 

+

78 Args: 

+

79 p_outlet (float): High pressure value. 

+

80 inputs (Inputs): Input parameters for the calculation. 

+

81 

+

82 Returns: 

+

83 float: Constant isentropic efficiency. 

+

84 """ 

+

85 return self.eta_isentropic 

+

86 

+

87 def get_eta_mech(self, inputs: Inputs) -> float: 

+

88 """ 

+

89 Returns the product of the constant mechanical, motor, and inverter efficiencies 

+

90 as the effective mechanical efficiency of the compressor. 

+

91 

+

92 Args: 

+

93 inputs (Inputs): Input parameters for the calculation. 

+

94 

+

95 Returns: 

+

96 float: Effective mechanical efficiency. 

+

97 """ 

+

98 return self.eta_mech 

+
+ + + diff --git a/docs/main/coverage/d_d45aa4312ad3649a_rotary_py.html b/docs/main/coverage/d_d45aa4312ad3649a_rotary_py.html new file mode 100644 index 0000000..a2355c1 --- /dev/null +++ b/docs/main/coverage/d_d45aa4312ad3649a_rotary_py.html @@ -0,0 +1,224 @@ + + + + + Coverage for vclibpy/components/compressors/rotary.py: 98% + + + + + +
+
+

+ Coverage for vclibpy/components/compressors/rotary.py: + 98% +

+ +

+ 49 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from vclibpy.components.compressors.compressor import Compressor 

+

2from vclibpy.datamodels import Inputs 

+

3 

+

4 

+

5class RotaryCompressor(Compressor): 

+

6 """ 

+

7 Compressor model based on the thesis of Mirko Engelpracht. 

+

8 

+

9 This compressor is characterized by using regressions provided by Mirko Engelpracht for a family of rotary 

+

10 compressors. The coefficients used in the regressions are sourced from his Master's thesis. 

+

11 

+

12 Parameters: 

+

13 N_max (float): Maximal rotations per second of the compressor. 

+

14 V_h (float): Volume of the compressor in m^3. 

+

15 

+

16 Methods: 

+

17 get_lambda_h(inputs: Inputs) -> float: 

+

18 Returns the volumetric efficiency based on the regressions of Mirko Engelpracht. 

+

19 

+

20 get_eta_isentropic(p_outlet: float, inputs: Inputs) -> float: 

+

21 Returns the isentropic efficiency based on the regressions of Mirko Engelpracht. 

+

22 

+

23 get_eta_mech(inputs: Inputs) -> float: 

+

24 Returns the mechanical efficiency based on the regressions of Mirko Engelpracht. 

+

25 

+

26 """ 

+

27 

+

28 def get_lambda_h(self, inputs: Inputs) -> float: 

+

29 """ 

+

30 Returns the volumetric efficiency based on the regressions of Mirko Engelpracht. 

+

31 

+

32 Args: 

+

33 inputs (Inputs): Input parameters for the calculation. 

+

34 

+

35 Returns: 

+

36 float: Volumetric efficiency. 

+

37 """ 

+

38 p_outlet = self.get_p_outlet() 

+

39 # If not constant value is given, eta_is is calculated based on the regression of Mirko Engelpracht 

+

40 n = self.get_n_absolute(inputs.n) 

+

41 T_1 = self.state_inlet.T 

+

42 

+

43 a_1 = 0.80179 

+

44 a_2 = -0.05210 

+

45 sigma_pi = 1.63495 

+

46 pi_ave = 4.54069 

+

47 a_3 = 3.21616e-4 

+

48 sigma_T_1 = 8.43797 

+

49 T_1_ave = 263.86428 

+

50 a_4 = -0.00494 

+

51 a_5 = 0.04981 

+

52 sigma_n = 20.81378 

+

53 n_ave = 64.41071 

+

54 a_6 = -0.02190 

+

55 

+

56 pi = p_outlet / self.state_inlet.p 

+

57 return ( 

+

58 a_1 + 

+

59 a_2 * (pi - pi_ave) / sigma_pi + 

+

60 a_3 * (T_1 - T_1_ave) / sigma_T_1 * (pi - pi_ave) / sigma_pi + 

+

61 a_4 * (T_1 - T_1_ave) / sigma_T_1 + 

+

62 a_5 * (n - n_ave) / sigma_n + 

+

63 a_6 * ((n - n_ave) / sigma_n) ** 2 

+

64 ) 

+

65 

+

66 def get_eta_isentropic(self, p_outlet: float, inputs: Inputs) -> float: 

+

67 """ 

+

68 Returns the isentropic efficiency based on the regressions of Mirko Engelpracht. 

+

69 

+

70 Args: 

+

71 p_outlet (float): High pressure value. 

+

72 inputs (Inputs): Input parameters for the calculation. 

+

73 

+

74 Returns: 

+

75 float: Isentropic efficiency. 

+

76 """ 

+

77 # If not constant value is given, eta_is is calculated based on the regression of Mirko Engelpracht 

+

78 n = self.get_n_absolute(inputs.n) 

+

79 

+

80 a_1 = 0.5816 

+

81 a_2 = 0.002604 

+

82 a_3 = -1.515e-7 

+

83 a_4 = -0.00473 

+

84 pi = p_outlet / self.state_inlet.p 

+

85 eta = ( 

+

86 a_1 + 

+

87 a_2 * n + 

+

88 a_3 * n ** 3 + 

+

89 a_4 * pi ** 2 

+

90 ) 

+

91 if eta <= 0: 

+

92 raise ValueError("Efficiency is lower or equal to 0") 

+

93 return eta 

+

94 

+

95 def get_eta_mech(self, inputs: Inputs) -> float: 

+

96 """ 

+

97 Returns the mechanical efficiency based on the regressions of Mirko Engelpracht. 

+

98 

+

99 Args: 

+

100 inputs (Inputs): Input parameters for the calculation. 

+

101 

+

102 Returns: 

+

103 float: Mechanical efficiency. 

+

104 """ 

+

105 p_outlet = self.get_p_outlet() 

+

106 n = self.get_n_absolute(inputs.n) 

+

107 # If not constant value is given, eta_is is calculated based on the regression of Mirko Engelpracht 

+

108 a_00 = 0.2199 

+

109 a_10 = -0.0193 

+

110 a_01 = 0.02503 

+

111 a_11 = 8.817e-5 

+

112 a_20 = -0.001345 

+

113 a_02 = -0.0003382 

+

114 a_21 = 1.584e-5 

+

115 a_12 = -1.083e-6 

+

116 a_22 = -5.321e-8 

+

117 a_03 = 1.976e-6 

+

118 a_13 = 4.053e-9 

+

119 a_04 = -4.292e-9 

+

120 pi = p_outlet / self.state_inlet.p 

+

121 return ( 

+

122 a_00 + 

+

123 a_10 * pi + a_01 * n + a_11 * pi * n + 

+

124 a_20 * pi ** 2 + a_02 * n ** 2 + a_21 * pi ** 2 * n + a_12 * pi * n ** 2 + a_22 * pi ** 2 * n ** 2 + 

+

125 a_03 * n ** 3 + a_13 * pi * n ** 3 + 

+

126 a_04 * n ** 4 

+

127 ) 

+
+ + + diff --git a/docs/main/coverage/d_d45aa4312ad3649a_ten_coefficient_py.html b/docs/main/coverage/d_d45aa4312ad3649a_ten_coefficient_py.html new file mode 100644 index 0000000..39941c8 --- /dev/null +++ b/docs/main/coverage/d_d45aa4312ad3649a_ten_coefficient_py.html @@ -0,0 +1,464 @@ + + + + + Coverage for vclibpy/components/compressors/ten_coefficient.py: 22% + + + + + +
+
+

+ Coverage for vclibpy/components/compressors/ten_coefficient.py: + 22% +

+ +

+ 95 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import warnings 

+

2from abc import ABC 

+

3import numpy as np 

+

4import pandas as pd 

+

5 

+

6from vclibpy.components.compressors.compressor import Compressor 

+

7from vclibpy.datamodels import Inputs 

+

8 

+

9 

+

10def calc_ten_coefficients(T_eva, T_con, coef_list): 

+

11 """ 

+

12 Calculate the result using the ten-coefficient method. 

+

13 

+

14 Args: 

+

15 T_eva (float): Evaporator temperature in Celsius. 

+

16 T_con (float): Condenser temperature in Celsius. 

+

17 coef_list (list): List of coefficients. 

+

18 

+

19 Returns: 

+

20 float: Result of the calculation. 

+

21 """ 

+

22 # Formula for the ten-coefficient method according to the datasheet 

+

23 z = coef_list[0] + coef_list[1] * T_eva + coef_list[2] * T_con + coef_list[3] * T_eva ** 2 + \ 

+

24 coef_list[4] * T_eva * T_con + coef_list[5] * T_con ** 2 + coef_list[6] * T_eva ** 3 + \ 

+

25 coef_list[7] * T_eva ** 2 * T_con + coef_list[8] * T_con ** 2 * T_eva + coef_list[9] * T_con ** 3 

+

26 return z 

+

27 

+

28 

+

29class BaseTenCoefficientCompressor(Compressor, ABC): 

+

30 """ 

+

31 Base class for compressors using the ten-coefficient method. 

+

32 

+

33 Used table has to be in this format 

+

34 (order of the columns is not important). 

+

35 The values must be the same as in the example tabel. 

+

36 The column names can be different but must 

+

37 then be matched with argument parameter_names. 

+

38 (All typed in numbers are fictional placeholders) 

+

39 

+

40 Capacity(W) Input Power(W) Flow Rate(kg/h) Capacity(W) ... Flow Rate(kg/h) 

+

41 n n1 n1 n1 n2 ... n_last 

+

42 P1 42 12 243 32 ... 412 

+

43 ... ... ... ... ... ... ... 

+

44 P10 10 23 21 41 ... 2434 

+

45 

+

46 Args: 

+

47 N_max (float): Maximal rotations per second of the compressor. 

+

48 V_h (float): Volume of the compressor in m^3. 

+

49 datasheet (str): Path of the datasheet file. 

+

50 **kwargs: 

+

51 parameter_names (dict, optional): 

+

52 Dictionary to match internal parameter names (keys) to the names used in the table values. 

+

53 Default 

+

54 { 

+

55 "m_flow": "Flow Rate(kg/h)", 

+

56 "capacity": "Capacity(W)", 

+

57 "input_power": "Input Power(W)", 

+

58 "eta_s": "Isentropic Efficiency(-)", 

+

59 "lambda_h": "Volumentric Efficiency(-)", 

+

60 "eta_mech": "Mechanical Efficiency(-)" 

+

61 } 

+

62 sheet_name (str, optional): Name of the sheet in the datasheet. Defaults to None. 

+

63 """ 

+

64 

+

65 def __init__(self, N_max, V_h, datasheet, **kwargs): 

+

66 """ 

+

67 Initialize the BaseTenCoefficientCompressor. 

+

68 

+

69 Args: 

+

70 N_max (float): Maximal rotations per second of the compressor. 

+

71 V_h (float): Volume of the compressor in m^3. 

+

72 datasheet (str): Path of the datasheet file. 

+

73 parameter_names (dict, optional): Dictionary of parameter names. Defaults to None. 

+

74 sheet_name (str, optional): Name of the sheet in the datasheet. Defaults to None. 

+

75 """ 

+

76 

+

77 super().__init__(N_max, V_h) 

+

78 sheet_name = kwargs.get('sheet_name', None) 

+

79 self.md = pd.read_excel(datasheet, sheet_name=sheet_name) 

+

80 parameter_names = kwargs.get('parameter_names', None) 

+

81 if parameter_names is None: 

+

82 self.parameter_names = { 

+

83 "m_flow": "Flow Rate(kg/h)", 

+

84 "capacity": "Capacity(W)", 

+

85 "input_power": "Input Power(W)", 

+

86 "eta_s": "Isentropic Efficiency(-)", 

+

87 "lambda_h": "Volumentric Efficiency(-)", 

+

88 "eta_mech": "Mechanical Efficiency(-)" 

+

89 } 

+

90 else: 

+

91 self.parameter_names = parameter_names 

+

92 

+

93 def get_parameter(self, T_eva, T_con, n, type_): 

+

94 """ 

+

95 Get a parameter based on temperatures, rotations, and parameter type from the datasheet. 

+

96 

+

97 Args: 

+

98 T_eva (float): Evaporator temperature in Celsius. 

+

99 T_con (float): Condenser temperature in Celsius. 

+

100 n (float): Rotations per second. 

+

101 type_ (str): Parameter type in parameter_names. 

+

102 

+

103 Returns: 

+

104 float: Interpolated parameter value. 

+

105 """ 

+

106 param_list = [] 

+

107 n_list = [] 

+

108 

+

109 sampling_points = sum( 

+

110 self.parameter_names[type_] in s for s in list(self.md.columns.values)) # counts number of sampling points 

+

111 

+

112 for i in range(sampling_points): 

+

113 if i == 0: 

+

114 coefficients = self.md[self.parameter_names[type_]].tolist() 

+

115 else: 

+

116 coefficients = self.md[str(self.parameter_names[type_] + "." + str(i))].tolist() 

+

117 n_list.append(coefficients.pop(0)) 

+

118 param_list.append(calc_ten_coefficients(T_eva, T_con, coefficients)) 

+

119 

+

120 return np.interp(self.get_n_absolute(n), n_list, param_list) # linear interpolation 

+

121 

+

122 

+

123class TenCoefficientCompressor(BaseTenCoefficientCompressor): 

+

124 """ 

+

125 Compressor based on the ten coefficient method. 

+

126 

+

127 Used table has to be in this format 

+

128 (order of the columns is not important). 

+

129 The values must be the same as in the example tabel. 

+

130 The column names can be different but must 

+

131 then be matched with the keyword argument parameter_names. 

+

132 (All typed in numbers are fictional placeholders) 

+

133 

+

134 Capacity(W) Input Power(W) Flow Rate(kg/h) Capacity(W) ... Flow Rate(kg/h) 

+

135 n n1 n1 n1 n2 ... n_last 

+

136 P1 42 12 243 32 ... 412 

+

137 ... ... ... ... ... ... ... 

+

138 P10 10 23 21 41 ... 2434 

+

139 

+

140 T_sh and T_sc have to be set according to the data sheet of your compressor. capacity_definition defines the 

+

141 parameter "capacity" in the datasheet. If capacity is the specific cooling capacity (h1-h4), set it on "cooling". 

+

142 If capacity is the specific heating capacity (h2-h3), set it on "heating". 

+

143 In the case of cooling capacity, the mechanical efficiency of the compressor has to be assumed (assumed_eta_mech) 

+

144 as h2 can't be calculated otherwise. Summary: 

+

145 - For the case heating capacity: h2 = h3 + capacity / m_flow 

+

146 - For the case cooling capacity: h2 = h3 + (capacity + p_el * assumed_eta_mech) / m_flow 

+

147 

+

148 Args: 

+

149 N_max (float): Maximal rotations per second of the compressor. 

+

150 V_h (float): Volume of the compressor in m^3. 

+

151 T_sc (float): Subcooling according to datasheet in K. 

+

152 T_sh (float): Superheating according to datasheet in K. 

+

153 capacity_definition (str): Definition of "capacity" in the datasheet. "cooling" or "heating". 

+

154 assumed_eta_mech (float): Assumed mechanical efficiency of the compressor (only needed if cooling). 

+

155 datasheet (str): Path of the modified datasheet. 

+

156 **kwargs: 

+

157 parameter_names (dict, optional): 

+

158 Dictionary to match internal parameter names (keys) to the names used in the table values. 

+

159 Default 

+

160 { 

+

161 "m_flow": "Flow Rate(kg/h)", 

+

162 "capacity": "Capacity(W)", 

+

163 "input_power": "Input Power(W)" 

+

164 } 

+

165 sheet_name (str, optional): Name of the sheet in the datasheet. Defaults to None. 

+

166 """ 

+

167 

+

168 def __init__(self, N_max, V_h, T_sc, T_sh, capacity_definition, assumed_eta_mech, datasheet, **kwargs): 

+

169 super().__init__(N_max=N_max, V_h=V_h, datasheet=datasheet, **kwargs) 

+

170 self.T_sc = T_sc 

+

171 self.T_sh = T_sh 

+

172 if capacity_definition not in ["cooling", "heating"]: 

+

173 raise ValueError("capacity_definition has to be either 'heating' or 'cooling'") 

+

174 self._capacity_definition = capacity_definition 

+

175 self.assumed_eta_mech = assumed_eta_mech 

+

176 self.datasheet = datasheet 

+

177 

+

178 def get_lambda_h(self, inputs: Inputs): 

+

179 """ 

+

180 Get the volumetric efficiency. 

+

181 

+

182 Args: 

+

183 inputs (Inputs): Input parameters. 

+

184 

+

185 Returns: 

+

186 float: Volumetric efficiency. 

+

187 """ 

+

188 p_outlet = self.get_p_outlet() 

+

189 

+

190 n_abs = self.get_n_absolute(inputs.n) 

+

191 T_eva = self.med_prop.calc_state("PQ", self.state_inlet.p, 1).T - 273.15 # [°C] 

+

192 T_con = self.med_prop.calc_state("PQ", p_outlet, 0).T - 273.15 # [°C] 

+

193 

+

194 if round((self.state_inlet.T - T_eva - 273.15), 2) != round(self.T_sh, 2): 

+

195 warnings.warn("The superheating of the given state is not " 

+

196 "equal to the superheating of the datasheet. " 

+

197 "State1.T_sh= " + str(round((self.state_inlet.T - T_eva - 273.15), 2)) + 

+

198 ". Datasheet.T_sh = " + str(self.T_sh)) 

+

199 # The datasheet has a given superheating temperature which can 

+

200 # vary from the superheating of the real state 1 

+

201 # which is given by the user. 

+

202 # Thus a new self.state_inlet_datasheet has to 

+

203 # be defined for all further calculations 

+

204 state_inlet_datasheet = self.med_prop.calc_state("PT", self.state_inlet.p, T_eva + 273.15 + self.T_sh) 

+

205 

+

206 m_flow = self.get_parameter(T_eva, T_con, inputs.n, "m_flow") / 3600 # [kg/s] 

+

207 

+

208 lambda_h = m_flow / (n_abs * state_inlet_datasheet.d * self.V_h) 

+

209 return lambda_h 

+

210 

+

211 def get_eta_isentropic(self, p_outlet: float, inputs: Inputs): 

+

212 """ 

+

213 Get the isentropic efficiency. 

+

214 

+

215 Args: 

+

216 p_outlet (float): Outlet pressure in Pa. 

+

217 inputs (Inputs): Input parameters. 

+

218 

+

219 Returns: 

+

220 float: Isentropic efficiency. 

+

221 """ 

+

222 T_con, state_inlet_datasheet, m_flow, capacity, p_el = self._calculate_values( 

+

223 p_2=p_outlet, inputs=inputs 

+

224 ) 

+

225 

+

226 h3 = self.med_prop.calc_state("PT", p_outlet, T_con + 273.15 - self.T_sc).h # [J/kg] 

+

227 h2s = self.med_prop.calc_state("PS", p_outlet, state_inlet_datasheet.s).h # [J/kg] 

+

228 

+

229 if self._capacity_definition == "heating": 

+

230 h2 = h3 + capacity / m_flow # [J/kg] 

+

231 else: 

+

232 h2 = h3 + (capacity + p_el * self.assumed_eta_mech) / m_flow # [J/kg] 

+

233 

+

234 if h2s > h2: 

+

235 raise ValueError("The calculated eta_s is above 1. You probably chose the wrong capacity_definition") 

+

236 

+

237 eta_s = (h2s - state_inlet_datasheet.h) / (h2 - state_inlet_datasheet.h) 

+

238 return eta_s 

+

239 

+

240 def get_eta_mech(self, inputs: Inputs): 

+

241 """ 

+

242 Get the mechanical efficiency. 

+

243 

+

244 Args: 

+

245 inputs (Inputs): Input parameters. 

+

246 

+

247 Returns: 

+

248 float: Mechanical efficiency. 

+

249 """ 

+

250 p_outlet = self.get_p_outlet() 

+

251 

+

252 if self._capacity_definition == "cooling": 

+

253 return self.assumed_eta_mech 

+

254 # Else heating 

+

255 T_con, state_inlet_datasheet, m_flow, capacity, p_el = self._calculate_values( 

+

256 p_2=p_outlet, inputs=inputs 

+

257 ) 

+

258 

+

259 h3 = self.med_prop.calc_state("PT", p_outlet, T_con + 273.15 - self.T_sc).h # [J/kg] 

+

260 h2 = h3 + capacity / m_flow # [J/kg] 

+

261 

+

262 eta_mech = p_el / (m_flow * (h2 - state_inlet_datasheet.h)) 

+

263 return eta_mech 

+

264 

+

265 def _calculate_values(self, p_2: float, inputs: Inputs): 

+

266 """ 

+

267 Calculate intermediate values for efficiency calculations. 

+

268 

+

269 Args: 

+

270 p_2 (float): Outlet pressure in Pa. 

+

271 inputs (Inputs): Input parameters. 

+

272 

+

273 Returns: 

+

274 Tuple[float, State, float, float, float]: Intermediate values. 

+

275 """ 

+

276 T_eva = self.med_prop.calc_state("PQ", self.state_inlet.p, 1).T - 273.15 # [°C] 

+

277 T_con = self.med_prop.calc_state("PQ", p_2, 0).T - 273.15 # [°C] 

+

278 

+

279 state_inlet_datasheet = self.med_prop.calc_state("PT", self.state_inlet.p, T_eva + 273.15 + self.T_sh) 

+

280 

+

281 m_flow = self.get_parameter(T_eva, T_con, inputs.n, "m_flow") / 3600 # [kg/s] 

+

282 capacity = self.get_parameter(T_eva, T_con, inputs.n, "capacity") # [W] 

+

283 p_el = self.get_parameter(T_eva, T_con, inputs.n, "input_power") # [W] 

+

284 return T_con, state_inlet_datasheet, m_flow, capacity, p_el 

+

285 

+

286 

+

287class DataSheetCompressor(BaseTenCoefficientCompressor): 

+

288 """ 

+

289 Compressor based on the ten coefficient method. 

+

290 

+

291 Used table has to be in this format 

+

292 (order of the columns is not important). 

+

293 The values must be the same as in the example tabel. 

+

294 The column names can be different but must 

+

295 then be matched with the keyword argument parameter_names. 

+

296 (All typed in numbers are fictional placeholders) 

+

297 

+

298 Isentropic Volumetric Mechanical Isentropic Mechanical 

+

299 Efficiency(-) Efficiency(-) Efficiency(-) Efficiency(-) ... Efficiency(-) 

+

300 n n1 n1 n1 n2 ... n_last 

+

301 P1 42 12 243 32 ... 412 

+

302 ... ... ... ... ... ... ... 

+

303 P10 10 23 21 41 ... 2434 

+

304 

+

305 Args: 

+

306 N_max (float): Maximal rotations per second of the compressor. 

+

307 V_h (float): Volume of the compressor in m^3. 

+

308 datasheet (str): Path of the datasheet file. 

+

309 **kwargs: 

+

310 parameter_names (dict, optional): 

+

311 Dictionary to match internal parameter names (keys) to the names used in the table values. 

+

312 Default 

+

313 { 

+

314 "eta_s": "Isentropic Efficiency(-)", 

+

315 "lambda_h": "Volumetric Efficiency(-)", 

+

316 "eta_mech": "Mechanical Efficiency(-)" 

+

317 } 

+

318 sheet_name (str, optional): Name of the sheet in the datasheet. Defaults to None. 

+

319 """ 

+

320 

+

321 def __init__(self, N_max, V_h, datasheet, **kwargs): 

+

322 super().__init__(N_max=N_max, V_h=V_h, datasheet=datasheet, **kwargs) 

+

323 

+

324 def get_lambda_h(self, inputs: Inputs): 

+

325 """ 

+

326 Get the volumetric efficiency. 

+

327 

+

328 Args: 

+

329 inputs (Inputs): Input parameters. 

+

330 

+

331 Returns: 

+

332 float: Volumetric efficiency. 

+

333 """ 

+

334 p_outlet = self.get_p_outlet() 

+

335 T_eva = self.med_prop.calc_state("PQ", self.state_inlet.p, 0).T 

+

336 T_con = self.med_prop.calc_state("PQ", p_outlet, 0).T 

+

337 return self.get_parameter(T_eva, T_con, inputs.n, "lambda_h") 

+

338 

+

339 def get_eta_isentropic(self, p_outlet: float, inputs: Inputs): 

+

340 """ 

+

341 Get the isentropic efficiency. 

+

342 

+

343 Args: 

+

344 p_outlet (float): Outlet pressure in Pa. 

+

345 inputs (Inputs): Input parameters. 

+

346 

+

347 Returns: 

+

348 float: Isentropic efficiency. 

+

349 """ 

+

350 T_eva = self.med_prop.calc_state("PQ", self.state_inlet.p, 0).T 

+

351 T_con = self.med_prop.calc_state("PQ", p_outlet, 0).T 

+

352 return self.get_parameter(T_eva, T_con, inputs.n, "eta_s") 

+

353 

+

354 def get_eta_mech(self, inputs: Inputs): 

+

355 """ 

+

356 Get the mechanical efficiency. 

+

357 

+

358 Args: 

+

359 inputs (Inputs): Input parameters. 

+

360 

+

361 Returns: 

+

362 float: Mechanical efficiency. 

+

363 """ 

+

364 p_outlet = self.get_p_outlet() 

+

365 T_eva = self.med_prop.calc_state("PQ", self.state_inlet.p, 0).T 

+

366 T_con = self.med_prop.calc_state("PQ", p_outlet, 0).T 

+

367 return self.get_parameter(T_eva, T_con, inputs.n, "eta_mech") 

+
+ + + diff --git a/docs/main/coverage/d_dae209ba2e6604e9___init___py.html b/docs/main/coverage/d_dae209ba2e6604e9___init___py.html new file mode 100644 index 0000000..1c0147d --- /dev/null +++ b/docs/main/coverage/d_dae209ba2e6604e9___init___py.html @@ -0,0 +1,101 @@ + + + + + Coverage for vclibpy/flowsheets/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/flowsheets/__init__.py: + 100% +

+ +

+ 4 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from .base import BaseCycle 

+

2from .standard import StandardCycle 

+

3from .vapor_injection_economizer import VaporInjectionEconomizer 

+

4from .vapor_injection_phase_separator import VaporInjectionPhaseSeparator 

+
+ + + diff --git a/docs/main/coverage/d_dae209ba2e6604e9_base_py.html b/docs/main/coverage/d_dae209ba2e6604e9_base_py.html new file mode 100644 index 0000000..f0ae4c7 --- /dev/null +++ b/docs/main/coverage/d_dae209ba2e6604e9_base_py.html @@ -0,0 +1,553 @@ + + + + + Coverage for vclibpy/flowsheets/base.py: 80% + + + + + +
+
+

+ Coverage for vclibpy/flowsheets/base.py: + 80% +

+ +

+ 228 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import logging 

+

2from typing import List 

+

3import numpy as np 

+

4 

+

5from abc import abstractmethod 

+

6import matplotlib.pyplot as plt 

+

7from vclibpy import media, Inputs 

+

8from vclibpy.datamodels import FlowsheetState 

+

9from vclibpy.components.heat_exchangers import HeatExchanger 

+

10from vclibpy.components.component import BaseComponent 

+

11 

+

12logger = logging.getLogger(__name__) 

+

13 

+

14 

+

15class BaseCycle: 

+

16 """ 

+

17 Base class for a heat pump. More complex systems may inherit from this class 

+

18 All HP have a compressor, two HE and a source and sink. 

+

19 Therefore, the parameters defined here are general parameters. 

+

20 

+

21 Args: 

+

22 fluid (str): Name of the fluid 

+

23 evaporator (HeatExchanger): Instance of a heat exchanger used for the evaporator 

+

24 condenser (HeatExchanger): Instance of a heat exchanger used for the condenser 

+

25 """ 

+

26 

+

27 flowsheet_name: str = "BaseCLass of all HP classes - not to use for map generation" 

+

28 

+

29 def __init__( 

+

30 self, 

+

31 fluid: str, 

+

32 evaporator: HeatExchanger, 

+

33 condenser: HeatExchanger 

+

34 ): 

+

35 self.fluid: str = fluid 

+

36 self.evaporator = evaporator 

+

37 self.condenser = condenser 

+

38 # Instantiate dummy values 

+

39 self.med_prop = None 

+

40 self._p_min = 10000 # So that p>0 at all times 

+

41 self._p_max = None # Is set by med-prop 

+

42 

+

43 def __str__(self): 

+

44 return self.flowsheet_name 

+

45 

+

46 def setup_new_fluid(self, fluid): 

+

47 # Only do so if new fluid is given 

+

48 if self.med_prop is not None: 

+

49 if self.med_prop.fluid_name == fluid: 

+

50 return 

+

51 self.med_prop.terminate() 

+

52 

+

53 # Else create new instance of MedProp 

+

54 med_prop_class, med_prop_kwargs = media.get_global_med_prop_and_kwargs() 

+

55 self.med_prop = med_prop_class(fluid_name=fluid, **med_prop_kwargs) 

+

56 

+

57 # Write the instance to the components 

+

58 for component in self.get_all_components(): 

+

59 component.med_prop = self.med_prop 

+

60 component.start_secondary_med_prop() 

+

61 

+

62 # Get max and min pressure 

+

63 _, self._p_max, _ = self.med_prop.get_critical_point() 

+

64 self.fluid = fluid 

+

65 

+

66 def terminate(self): 

+

67 self.med_prop.terminate() 

+

68 for component in self.get_all_components(): 

+

69 component.terminate_secondary_med_prop() 

+

70 

+

71 def get_all_components(self) -> List[BaseComponent]: 

+

72 return [self.condenser, self.evaporator] 

+

73 

+

74 def calc_steady_state(self, inputs: Inputs, fluid: str = None, **kwargs): 

+

75 """ 

+

76 Calculate the steady-state performance of a vapor compression cycle 

+

77 based on given inputs and assumptions. 

+

78 

+

79 This function ensures consistent assumptions across different cycles. 

+

80 It calculates the performance of the heat pump under 

+

81 specific conditions while adhering to several general assumptions. 

+

82 

+

83 General Assumptions: 

+

84 --------------------- 

+

85 - Isenthalpic expansion valves: 

+

86 The enthalpy at the inlet equals the enthalpy at the outlet. 

+

87 - No heat losses in any component: 

+

88 The heat input to the condenser equals the heat 

+

89 output of the evaporator plus the power input. 

+

90 - Input to the evaporator is always in the two-phase region. 

+

91 - Output of the evaporator and output of the condenser maintain 

+

92 a constant overheating or subcooling (can be set in Inputs). 

+

93 

+

94 Args: 

+

95 inputs (Inputs): 

+

96 An instance of the Inputs class containing the 

+

97 necessary parameters to calculate the flowsheet state. 

+

98 fluid (str): 

+

99 The fluid to be used in the calculations. 

+

100 Required only if 'fluid' is not specified during the object's initialization. 

+

101 

+

102 Keyword Arguments: 

+

103 min_iteration_step (int): 

+

104 The minimum step size for iterations (default: 1). 

+

105 save_path_plots (str or None): 

+

106 The path to save plots (default: None). 

+

107 If None, no plots are created. 

+

108 show_iteration (bool): 

+

109 Whether to display iteration progress (default: False). 

+

110 T_max (float): 

+

111 Maximum temperature allowed (default: 273.15 + 150). 

+

112 use_quick_solver (bool): 

+

113 Whether to use a quick solver (default: True). 

+

114 max_err_ntu (float): 

+

115 Maximum allowable error for the heat exchanger in percent (default: 0.5). 

+

116 max_err_dT_min (float): 

+

117 Maximum allowable error for minimum temperature difference in K (default: 0.1). 

+

118 max_num_iterations (int or None): 

+

119 Maximum number of iterations allowed (default: None). 

+

120 

+

121 Returns: 

+

122 fs_state (FlowsheetState): 

+

123 An instance of the FlowsheetState class representing 

+

124 the calculated state of the vapor compression cycle. 

+

125 """ 

+

126 # Settings 

+

127 min_iteration_step = kwargs.pop("min_iteration_step", 1) 

+

128 save_path_plots = kwargs.get("save_path_plots", None) 

+

129 input_name = ";".join([k + "=" + str(np.round(v.value, 3)).replace(".", "_") 

+

130 for k, v in inputs.get_variables().items()]) 

+

131 show_iteration = kwargs.get("show_iteration", False) 

+

132 use_quick_solver = kwargs.pop("use_quick_solver", True) 

+

133 err_ntu = kwargs.pop("max_err_ntu", 0.5) 

+

134 err_dT_min = kwargs.pop("max_err_dT_min", 0.1) 

+

135 max_num_iterations = kwargs.pop("max_num_iterations", 1e5) 

+

136 p_1_history = [] 

+

137 p_2_history = [] 

+

138 

+

139 if use_quick_solver: 

+

140 step_p1 = kwargs.get("step_max", 10000) 

+

141 step_p2 = kwargs.get("step_max", 10000) 

+

142 else: 

+

143 step_p1 = min_iteration_step 

+

144 step_p2 = min_iteration_step 

+

145 

+

146 # Setup fluid: 

+

147 if fluid is None: 

+

148 fluid = self.fluid 

+

149 self.setup_new_fluid(fluid) 

+

150 

+

151 # First: Iterate with given conditions to get the 4 states and the mass flow rate: 

+

152 T_1_start = inputs.T_eva_in - inputs.dT_eva_superheating 

+

153 T_3_start = inputs.T_con_in + inputs.dT_con_subcooling 

+

154 p_1_start = self.med_prop.calc_state("TQ", T_1_start, 1).p 

+

155 p_2_start = self.med_prop.calc_state("TQ", T_3_start, 0).p 

+

156 p_1_next = p_1_start 

+

157 p_2_next = p_2_start 

+

158 

+

159 fs_state = FlowsheetState() # Always log what is happening in the whole flowsheet 

+

160 fs_state.set(name="Q_con", value=1, unit="W", description="Condenser heat flow rate") 

+

161 fs_state.set(name="COP", value=0, unit="-", description="Coefficient of performance") 

+

162 

+

163 if show_iteration: 

+

164 fig_iterations, ax_iterations = plt.subplots(2) 

+

165 

+

166 num_iterations = 0 

+

167 

+

168 while True: 

+

169 if isinstance(max_num_iterations, (int, float)): 

+

170 if num_iterations > max_num_iterations: 

+

171 logger.warning("Maximum number of iterations %s exceeded. Stopping.", 

+

172 max_num_iterations) 

+

173 return 

+

174 

+

175 if (num_iterations + 1) % (0.1 * max_num_iterations) == 0: 

+

176 logger.info("Info: %s percent of max_num_iterations %s used", 

+

177 100 * (num_iterations + 1) / max_num_iterations, max_num_iterations) 

+

178 

+

179 p_1 = p_1_next 

+

180 p_2 = p_2_next 

+

181 p_1_history.append(p_1) 

+

182 p_2_history.append(p_2) 

+

183 if show_iteration: 

+

184 ax_iterations[0].cla() 

+

185 ax_iterations[1].cla() 

+

186 ax_iterations[0].scatter(list(range(len(p_1_history))), p_1_history) 

+

187 ax_iterations[1].scatter(list(range(len(p_2_history))), p_2_history) 

+

188 plt.draw() 

+

189 plt.pause(1e-5) 

+

190 

+

191 # Increase counter 

+

192 num_iterations += 1 

+

193 # Check critical pressures: 

+

194 if p_2 >= self._p_max: 

+

195 if step_p2 == min_iteration_step: 

+

196 logger.error("Pressure too high. Configuration is infeasible.") 

+

197 return 

+

198 p_2_next = p_2 - step_p2 

+

199 step_p2 /= 10 

+

200 continue 

+

201 if p_1 <= self._p_min: 

+

202 if p_1_next == min_iteration_step: 

+

203 logger.error("Pressure too low. Configuration is infeasible.") 

+

204 return 

+

205 p_1_next = p_1 + step_p1 

+

206 step_p1 /= 10 

+

207 continue 

+

208 

+

209 # Calculate the states based on the given flowsheet 

+

210 try: 

+

211 self.calc_states(p_1, p_2, inputs=inputs, fs_state=fs_state) 

+

212 except ValueError as err: 

+

213 logger.error("An error occurred while calculating states. " 

+

214 "Can't guess next pressures, thus, exiting: %s", err) 

+

215 return 

+

216 if save_path_plots is not None and num_iterations == 1 and show_iteration: 

+

217 self.plot_cycle(save_path=save_path_plots.joinpath(f"{input_name}_initialization.png"), inputs=inputs) 

+

218 

+

219 # Check heat exchangers: 

+

220 error_eva, dT_min_eva = self.evaporator.calc(inputs=inputs, fs_state=fs_state) 

+

221 if not isinstance(error_eva, float): 

+

222 print(error_eva) 

+

223 if error_eva < 0: 

+

224 p_1_next = p_1 - step_p1 

+

225 continue 

+

226 else: 

+

227 if step_p1 > min_iteration_step: 

+

228 p_1_next = p_1 + step_p1 

+

229 step_p1 /= 10 

+

230 continue 

+

231 elif error_eva > err_ntu and dT_min_eva > err_dT_min: 

+

232 step_p1 = 1000 

+

233 p_1_next = p_1 + step_p1 

+

234 continue 

+

235 

+

236 error_con, dT_min_con = self.condenser.calc(inputs=inputs, fs_state=fs_state) 

+

237 if error_con < 0: 

+

238 p_2_next = p_2 + step_p2 

+

239 continue 

+

240 else: 

+

241 if step_p2 > min_iteration_step: 

+

242 p_2_next = p_2 - step_p2 

+

243 step_p2 /= 10 

+

244 continue 

+

245 elif error_con > err_ntu and dT_min_con > err_dT_min: 

+

246 p_2_next = p_2 - step_p2 

+

247 step_p2 = 1000 

+

248 continue 

+

249 

+

250 # If still here, and the values are equal, we may break. 

+

251 if p_1 == p_1_next and p_2 == p_2_next: 

+

252 # Check if solution was too far away. If so, jump back 

+

253 # And decrease the iteration step by factor 10. 

+

254 if step_p2 > min_iteration_step: 

+

255 p_2_next = p_2 - step_p2 

+

256 step_p2 /= 10 

+

257 continue 

+

258 if step_p1 > min_iteration_step: 

+

259 p_1_next = p_1 + step_p1 

+

260 step_p1 /= 10 

+

261 continue 

+

262 logger.info("Breaking: Converged") 

+

263 break 

+

264 

+

265 # Check if values are not converging at all: 

+

266 p_1_unique = set(p_1_history[-10:]) 

+

267 p_2_unique = set(p_2_history[-10:]) 

+

268 if len(p_1_unique) == 2 and len(p_2_unique) == 2 \ 

+

269 and step_p1 == min_iteration_step and step_p2 == min_iteration_step: 

+

270 logger.critical("Breaking: not converging at all") 

+

271 break 

+

272 

+

273 if show_iteration: 

+

274 plt.close(fig_iterations) 

+

275 

+

276 # Calculate the heat flow rates for the selected states. 

+

277 Q_con = self.condenser.calc_Q_flow() 

+

278 Q_con_outer = self.condenser.calc_secondary_Q_flow(Q_con) 

+

279 Q_eva = self.evaporator.calc_Q_flow() 

+

280 Q_eva_outer = self.evaporator.calc_secondary_Q_flow(Q_eva) 

+

281 self.evaporator.calc(inputs=inputs, fs_state=fs_state) 

+

282 self.condenser.calc(inputs=inputs, fs_state=fs_state) 

+

283 P_el = self.calc_electrical_power(fs_state=fs_state, inputs=inputs) 

+

284 T_con_out = inputs.T_con_in + Q_con_outer / self.condenser.m_flow_secondary_cp 

+

285 

+

286 # COP based on P_el and Q_con: 

+

287 COP_inner = Q_con / P_el 

+

288 COP_outer = Q_con_outer / P_el 

+

289 # Calculate carnot quality as a measure of reliability of model: 

+

290 COP_carnot = (T_con_out / (T_con_out - inputs.T_eva_in)) 

+

291 carnot_quality = COP_inner / COP_carnot 

+

292 # Calc return temperature: 

+

293 fs_state.set( 

+

294 name="P_el", value=P_el, unit="W", 

+

295 description="Power consumption" 

+

296 ) 

+

297 fs_state.set( 

+

298 name="carnot_quality", value=carnot_quality, 

+

299 unit="-", description="Carnot Quality" 

+

300 ) 

+

301 fs_state.set( 

+

302 name="Q_con", value=Q_con, unit="W", 

+

303 description="Condenser refrigerant heat flow rate" 

+

304 ) 

+

305 # COP based on P_el and Q_con: 

+

306 fs_state.set( 

+

307 name="Q_con_outer", value=Q_con_outer, unit="W", 

+

308 description="Secondary medium condenser heat flow rate" 

+

309 ) 

+

310 fs_state.set( 

+

311 name="Q_eva_outer", value=Q_eva_outer, unit="W", 

+

312 description="Secondary medium evaporator heat flow rate" 

+

313 ) 

+

314 fs_state.set( 

+

315 name="COP", value=COP_inner, 

+

316 unit="-", description="Coefficient of Performance" 

+

317 ) 

+

318 fs_state.set( 

+

319 name="COP_outer", value=COP_outer, 

+

320 unit="-", description="Outer COP, including heat losses" 

+

321 ) 

+

322 

+

323 if save_path_plots is not None: 

+

324 self.plot_cycle(save_path=save_path_plots.joinpath(f"{input_name}_final_result.png"), inputs=inputs) 

+

325 

+

326 return fs_state 

+

327 

+

328 @abstractmethod 

+

329 def get_states_in_order_for_plotting(self): 

+

330 """ 

+

331 Function to return all thermodynamic states of cycle 

+

332 in the correct order for plotting. 

+

333 Include phase change states to see if your simulation 

+

334 runs plausible cycles. 

+

335 

+

336 Returns: 

+

337 - List with tuples, first entry being the state and second the mass flow rate 

+

338 """ 

+

339 return [] 

+

340 

+

341 def set_evaporator_outlet_based_on_superheating(self, p_eva: float, inputs: Inputs): 

+

342 """ 

+

343 Calculate the outlet state of the evaporator based on 

+

344 the required degree of superheating. 

+

345 

+

346 Args: 

+

347 p_eva (float): Evaporation pressure 

+

348 inputs (Inputs): Inputs with superheating level 

+

349 """ 

+

350 T_1 = self.med_prop.calc_state("PQ", p_eva, 1).T + inputs.dT_eva_superheating 

+

351 if inputs.dT_eva_superheating > 0: 

+

352 self.evaporator.state_outlet = self.med_prop.calc_state("PT", p_eva, T_1) 

+

353 else: 

+

354 self.evaporator.state_outlet = self.med_prop.calc_state("PQ", p_eva, 1) 

+

355 

+

356 def set_condenser_outlet_based_on_subcooling(self, p_con: float, inputs: Inputs): 

+

357 """ 

+

358 Calculate the outlet state of the evaporator based on 

+

359 the required degree of superheating. 

+

360 

+

361 Args: 

+

362 p_con (float): Condensing pressure 

+

363 inputs (Inputs): Inputs with superheating level 

+

364 """ 

+

365 T_3 = self.med_prop.calc_state("PQ", p_con, 0).T - inputs.dT_con_subcooling 

+

366 if inputs.dT_con_subcooling > 0: 

+

367 self.condenser.state_outlet = self.med_prop.calc_state("PT", p_con, T_3) 

+

368 else: 

+

369 self.condenser.state_outlet = self.med_prop.calc_state("PQ", p_con, 0) 

+

370 

+

371 def plot_cycle(self, save_path: bool, inputs: Inputs, states: list = None): 

+

372 """Function to plot the resulting flowsheet of the steady state config.""" 

+

373 if states is None: 

+

374 states = self.get_states_in_order_for_plotting() 

+

375 states.append(states[0]) # Plot full cycle 

+

376 # Unpack state var: 

+

377 h_T = np.array([state.h for state in states]) / 1000 

+

378 T = [state.T - 273.15 for state in states] 

+

379 p = np.array([state.p for state in states]) 

+

380 h_p = h_T 

+

381 

+

382 fig, ax = plt.subplots(2, 1, sharex=True) 

+

383 ax[0].set_ylabel("$T$ in °C") 

+

384 ax[1].set_xlabel("$h$ in kJ/kgK") 

+

385 # Two phase limits 

+

386 ax[0].plot( 

+

387 self.med_prop.get_two_phase_limits("h") / 1000, 

+

388 self.med_prop.get_two_phase_limits("T") - 273.15, color="black" 

+

389 ) 

+

390 

+

391 ax[0].plot(h_T, T, color="r", marker="s") 

+

392 self._plot_secondary_heat_flow_rates(ax=ax[0], inputs=inputs) 

+

393 ax[1].plot(h_p, np.log(p), marker="s", color="r") 

+

394 # Two phase limits 

+

395 ax[1].plot( 

+

396 self.med_prop.get_two_phase_limits("h") / 1000, 

+

397 np.log(self.med_prop.get_two_phase_limits("p")), 

+

398 color="black" 

+

399 ) 

+

400 plt.plot() 

+

401 ax[1].set_ylabel("$log(p)$") 

+

402 ax[1].set_ylim([np.min(np.log(p)) * 0.9, np.max(np.log(p)) * 1.1]) 

+

403 ax[0].set_ylim([np.min(T) - 5, np.max(T) + 5]) 

+

404 ax[1].set_xlim([np.min(h_T) * 0.9, np.max(h_T) * 1.1]) 

+

405 ax[0].set_xlim([np.min(h_T) * 0.9, np.max(h_T) * 1.1]) 

+

406 fig.tight_layout() 

+

407 fig.savefig(save_path) 

+

408 plt.close(fig) 

+

409 

+

410 def _plot_secondary_heat_flow_rates(self, ax, inputs): 

+

411 Q_con = self.condenser.calc_Q_flow() 

+

412 Q_eva = self.evaporator.calc_Q_flow() 

+

413 

+

414 delta_H_con = np.array([ 

+

415 self.condenser.state_outlet.h * self.condenser.m_flow, 

+

416 self.condenser.state_outlet.h * self.condenser.m_flow + Q_con 

+

417 ]) / self.condenser.m_flow 

+

418 delta_H_eva = np.array([ 

+

419 self.evaporator.state_outlet.h * self.evaporator.m_flow, 

+

420 self.evaporator.state_outlet.h * self.evaporator.m_flow - Q_eva 

+

421 ]) / self.evaporator.m_flow 

+

422 self.condenser.m_flow_secondary = inputs.m_flow_con 

+

423 self.condenser.calc_secondary_cp(T=inputs.T_con_in) 

+

424 self.evaporator.m_flow_secondary = inputs.m_flow_eva 

+

425 self.evaporator.calc_secondary_cp(T=inputs.T_eva_in) 

+

426 ax.plot(delta_H_con / 1000, [ 

+

427 inputs.T_con_in - 273.15, 

+

428 inputs.T_con_in + Q_con / self.condenser.m_flow_secondary_cp - 273.15 

+

429 ], color="b") 

+

430 ax.plot(delta_H_eva / 1000, [ 

+

431 inputs.T_eva_in - 273.15, 

+

432 inputs.T_eva_in - Q_eva / self.evaporator.m_flow_secondary_cp - 273.15 

+

433 ], color="b") 

+

434 

+

435 @abstractmethod 

+

436 def calc_electrical_power(self, inputs: Inputs, fs_state: FlowsheetState): 

+

437 """Function to calc the electrical power consumption based on the flowsheet used""" 

+

438 raise NotImplementedError 

+

439 

+

440 @abstractmethod 

+

441 def calc_states(self, p_1, p_2, inputs: Inputs, fs_state: FlowsheetState): 

+

442 """ 

+

443 Function to calculate the states and mass flow rates of the flowsheet 

+

444 and set these into each component based on the given pressure levels p_1 and p_2. 

+

445 

+

446 Args: 

+

447 p_1 (float): 

+

448 Lower pressure level. If no pressure losses are assumed, 

+

449 this equals the evaporation pressure and the compressor inlet pressure. 

+

450 p_2 (float): 

+

451 Higher pressure level. If no pressure losses are assumed, 

+

452 this equals the condensing pressure and the compressor outlet pressure. 

+

453 inputs (Inputs): Inputs of calculation. 

+

454 fs_state (FlowsheetState): Flowsheet state to save important variables. 

+

455 """ 

+

456 raise NotImplementedError 

+
+ + + diff --git a/docs/main/coverage/d_dae209ba2e6604e9_standard_py.html b/docs/main/coverage/d_dae209ba2e6604e9_standard_py.html new file mode 100644 index 0000000..99b8392 --- /dev/null +++ b/docs/main/coverage/d_dae209ba2e6604e9_standard_py.html @@ -0,0 +1,189 @@ + + + + + Coverage for vclibpy/flowsheets/standard.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/flowsheets/standard.py: + 100% +

+ +

+ 36 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from vclibpy.flowsheets import BaseCycle 

+

2from vclibpy.datamodels import FlowsheetState, Inputs 

+

3from vclibpy.components.compressors import Compressor 

+

4from vclibpy.components.expansion_valves import ExpansionValve 

+

5 

+

6 

+

7class StandardCycle(BaseCycle): 

+

8 """ 

+

9 Class for a standard cycle with four components. 

+

10 

+

11 For the standard cycle, we have 4 possible states: 

+

12 

+

13 1. Before compressor, after evaporator 

+

14 2. Before condenser, after compressor 

+

15 3. Before EV, after condenser 

+

16 4. Before Evaporator, after EV 

+

17 """ 

+

18 

+

19 flowsheet_name = "Standard" 

+

20 

+

21 def __init__( 

+

22 self, 

+

23 compressor: Compressor, 

+

24 expansion_valve: ExpansionValve, 

+

25 **kwargs 

+

26 ): 

+

27 super().__init__(**kwargs) 

+

28 self.compressor = compressor 

+

29 self.expansion_valve = expansion_valve 

+

30 

+

31 def get_all_components(self): 

+

32 return super().get_all_components() + [ 

+

33 self.compressor, 

+

34 self.expansion_valve 

+

35 ] 

+

36 

+

37 def get_states_in_order_for_plotting(self): 

+

38 return [ 

+

39 self.evaporator.state_inlet, 

+

40 self.med_prop.calc_state("PQ", self.evaporator.state_inlet.p, 1), 

+

41 self.evaporator.state_outlet, 

+

42 self.compressor.state_inlet, 

+

43 self.compressor.state_outlet, 

+

44 self.condenser.state_inlet, 

+

45 self.med_prop.calc_state("PQ", self.condenser.state_inlet.p, 1), 

+

46 self.med_prop.calc_state("PQ", self.condenser.state_inlet.p, 0), 

+

47 self.condenser.state_outlet, 

+

48 self.expansion_valve.state_inlet, 

+

49 self.expansion_valve.state_outlet, 

+

50 ] 

+

51 

+

52 def calc_states(self, p_1, p_2, inputs: Inputs, fs_state: FlowsheetState): 

+

53 self.set_condenser_outlet_based_on_subcooling(p_con=p_2, inputs=inputs) 

+

54 self.expansion_valve.state_inlet = self.condenser.state_outlet 

+

55 self.expansion_valve.calc_outlet(p_outlet=p_1) 

+

56 self.evaporator.state_inlet = self.expansion_valve.state_outlet 

+

57 self.set_evaporator_outlet_based_on_superheating(p_eva=p_1, inputs=inputs) 

+

58 self.compressor.state_inlet = self.evaporator.state_outlet 

+

59 self.compressor.calc_state_outlet(p_outlet=p_2, inputs=inputs, fs_state=fs_state) 

+

60 self.condenser.state_inlet = self.compressor.state_outlet 

+

61 

+

62 # Mass flow rate: 

+

63 self.compressor.calc_m_flow(inputs=inputs, fs_state=fs_state) 

+

64 self.condenser.m_flow = self.compressor.m_flow 

+

65 self.evaporator.m_flow = self.compressor.m_flow 

+

66 self.expansion_valve.m_flow = self.compressor.m_flow 

+

67 fs_state.set( 

+

68 name="y_EV", value=self.expansion_valve.calc_opening_at_m_flow(m_flow=self.expansion_valve.m_flow), 

+

69 unit="-", description="Expansion valve opening" 

+

70 ) 

+

71 fs_state.set( 

+

72 name="T_1", value=self.evaporator.state_outlet.T, 

+

73 unit="K", description="Refrigerant temperature at evaporator outlet" 

+

74 ) 

+

75 fs_state.set( 

+

76 name="T_2", value=self.compressor.state_outlet.T, 

+

77 unit="K", description="Compressor outlet temperature" 

+

78 ) 

+

79 fs_state.set( 

+

80 name="T_3", value=self.condenser.state_outlet.T, unit="K", 

+

81 description="Refrigerant temperature at condenser outlet" 

+

82 ) 

+

83 fs_state.set( 

+

84 name="T_4", value=self.evaporator.state_inlet.T, 

+

85 unit="K", description="Refrigerant temperature at evaporator inlet" 

+

86 ) 

+

87 fs_state.set(name="p_con", value=p_2, unit="Pa", description="Condensation pressure") 

+

88 fs_state.set(name="p_eva", value=p_1, unit="Pa", description="Evaporation pressure") 

+

89 

+

90 def calc_electrical_power(self, inputs: Inputs, fs_state: FlowsheetState): 

+

91 """Based on simple energy balance - Adiabatic""" 

+

92 return self.compressor.calc_electrical_power(inputs=inputs, fs_state=fs_state) 

+
+ + + diff --git a/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_economizer_py.html b/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_economizer_py.html new file mode 100644 index 0000000..921efd5 --- /dev/null +++ b/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_economizer_py.html @@ -0,0 +1,248 @@ + + + + + Coverage for vclibpy/flowsheets/vapor_injection_economizer.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/flowsheets/vapor_injection_economizer.py: + 100% +

+ +

+ 48 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import numpy as np 

+

2 

+

3from vclibpy.flowsheets.vapor_injection import BaseVaporInjection 

+

4from vclibpy.components.heat_exchangers.economizer import VaporInjectionEconomizerNTU 

+

5 

+

6 

+

7class VaporInjectionEconomizer(BaseVaporInjection): 

+

8 """ 

+

9 Cycle with vapor injection using an economizer. 

+

10 

+

11 For this cycle, we have 9 relevant states: 

+

12 

+

13 - 1: Before compressor, after evaporator 

+

14 - 2: Before condenser, after compressor 

+

15 - 3: Before ihx, after condenser 

+

16 - 4: Before Evaporator, after ihx 

+

17 - 5_ihx: Before ihx, after condenser and EV 

+

18 - 6_ihx: Before Mixing with 1_VI, after ihx 

+

19 - 7_ihx: Before second EV, after ihx 

+

20 - 1_VI: Before Mixing with 6_ihx, After first stage 

+

21 - 1_VI_mixed. Before second stage of compressor, after mixing with 6_ihx 

+

22 

+

23 Additional Assumptions: 

+

24 ----------------------- 

+

25 - No heat losses in ihx 

+

26 - No pressure loss in ihx 

+

27 - No losses for splitting of streams 

+

28 - Isenthalpic second EV 

+

29 

+

30 Notes 

+

31 ----- 

+

32 See parent docstring for info on further assumptions and parameters. 

+

33 """ 

+

34 

+

35 flowsheet_name = "VaporInjectionEconomizer" 

+

36 

+

37 def __init__(self, economizer: VaporInjectionEconomizerNTU, **kwargs): 

+

38 self.economizer = economizer 

+

39 super().__init__(**kwargs) 

+

40 

+

41 def get_all_components(self): 

+

42 return super().get_all_components() + [ 

+

43 self.economizer 

+

44 ] 

+

45 

+

46 def calc_injection(self): 

+

47 """ 

+

48 This calculation assumes that the heat transfer 

+

49 of the higher temperature liquid is always in the subcooling 

+

50 region, while the vapor injection is always a two-phase heat 

+

51 transfer. 

+

52 In reality, you would need to achieve a certain degree of superheat. 

+

53 Thus, a moving boundary approach would be more fitting. For simplicity, 

+

54 we assume no superheat. 

+

55 

+

56 This function iterates the amount of vapor injected. 

+

57 The iteration starts with close to no injection and increases 

+

58 the amount of injection as long as enough hotter sub-cooled liquid is 

+

59 present to fully vaporize the injected part. 

+

60 """ 

+

61 self.economizer.state_inlet = self.condenser.state_outlet 

+

62 self.economizer.state_two_phase_inlet = self.high_pressure_valve.state_outlet 

+

63 self.economizer.state_two_phase_outlet = self.med_prop.calc_state( 

+

64 "PQ", self.high_pressure_valve.state_outlet.p, 1 

+

65 ) 

+

66 m_flow_evaporator = self.evaporator.m_flow 

+

67 

+

68 dh_ihe_goal = ( 

+

69 self.economizer.state_two_phase_outlet.h - 

+

70 self.economizer.state_two_phase_inlet.h 

+

71 ) 

+

72 

+

73 # Get transport properties: 

+

74 tra_properties_liquid = self.med_prop.calc_transport_properties( 

+

75 self.economizer.state_inlet 

+

76 ) 

+

77 alpha_liquid = self.economizer.calc_alpha_liquid(tra_properties_liquid) 

+

78 tra_properties_two_phase = self.med_prop.calc_mean_transport_properties( 

+

79 self.economizer.state_two_phase_inlet, 

+

80 self.economizer.state_two_phase_outlet 

+

81 ) 

+

82 alpha_two_phase = self.economizer.calc_alpha_liquid(tra_properties_two_phase) 

+

83 

+

84 # Set cp based on transport properties 

+

85 dT_secondary = ( 

+

86 self.economizer.state_two_phase_outlet.T - 

+

87 self.economizer.state_two_phase_inlet.T 

+

88 ) 

+

89 if dT_secondary == 0: 

+

90 cp_4 = np.inf 

+

91 else: 

+

92 cp_4 = dh_ihe_goal / dT_secondary 

+

93 self.economizer.set_secondary_cp(cp=cp_4) 

+

94 self.economizer.set_primary_cp(cp=tra_properties_liquid.cp) 

+

95 

+

96 # We have to iterate to ensure the correct fraction of mass is 

+

97 # used to ensure state5 has q=1 

+

98 _x_vi_step = 0.1 

+

99 _min_step_x_vi = 0.0001 

+

100 

+

101 x_vi_next = _min_step_x_vi # Don't start with zero! 

+

102 while True: 

+

103 x_vi = x_vi_next 

+

104 x_eva = 1 - x_vi 

+

105 m_flow_vapor_injection = x_vi * m_flow_evaporator 

+

106 Q_flow_goal = dh_ihe_goal * m_flow_vapor_injection 

+

107 

+

108 self.economizer.m_flow = x_eva * m_flow_evaporator 

+

109 self.economizer.m_flow_secondary = m_flow_vapor_injection 

+

110 

+

111 # This dT_max is always valid, as the primary inlet is cooled 

+

112 # and the secondary inlet (the vapor) is either heated 

+

113 # or isothermal for pure fluids 

+

114 Q_flow, k = self.economizer.calc_Q_ntu( 

+

115 dT_max=( 

+

116 self.economizer.state_inlet.T - 

+

117 self.economizer.state_two_phase_inlet.T 

+

118 ), 

+

119 alpha_pri=alpha_liquid, 

+

120 alpha_sec=alpha_two_phase, 

+

121 A=self.economizer.A 

+

122 ) 

+

123 if Q_flow > Q_flow_goal: 

+

124 if _x_vi_step <= _min_step_x_vi: 

+

125 break 

+

126 # We can increase x_vi_next further, as more heat can be extracted 

+

127 x_vi_next = x_vi + _x_vi_step 

+

128 else: 

+

129 x_vi_next = x_vi - _x_vi_step * 0.9 

+

130 _x_vi_step /= 10 

+

131 

+

132 # Solve Energy Balance 

+

133 h_7 = self.economizer.state_inlet.h - dh_ihe_goal * self.economizer.m_flow 

+

134 state_7_ihx = self.med_prop.calc_state("PH", self.economizer.state_inlet.p, h_7) 

+

135 self.economizer.state_outlet = state_7_ihx 

+

136 return x_vi, self.economizer.state_two_phase_outlet.h, state_7_ihx 

+

137 

+

138 def get_states_in_order_for_plotting(self): 

+

139 return super().get_states_in_order_for_plotting() + [ 

+

140 self.economizer.state_two_phase_inlet, 

+

141 self.economizer.state_two_phase_outlet, 

+

142 self.high_pressure_compressor.state_inlet, 

+

143 # Go back to the condenser outlet 

+

144 self.economizer.state_two_phase_outlet, 

+

145 self.economizer.state_two_phase_inlet, 

+

146 self.high_pressure_valve.state_outlet, 

+

147 self.high_pressure_valve.state_inlet, 

+

148 self.condenser.state_outlet, 

+

149 self.economizer.state_inlet, 

+

150 self.economizer.state_outlet 

+

151 ] 

+
+ + + diff --git a/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_phase_separator_py.html b/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_phase_separator_py.html new file mode 100644 index 0000000..27c3964 --- /dev/null +++ b/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_phase_separator_py.html @@ -0,0 +1,158 @@ + + + + + Coverage for vclibpy/flowsheets/vapor_injection_phase_separator.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/flowsheets/vapor_injection_phase_separator.py: + 100% +

+ +

+ 18 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import logging 

+

2 

+

3from vclibpy.flowsheets.vapor_injection import BaseVaporInjection 

+

4from vclibpy.components.phase_separator import PhaseSeparator 

+

5 

+

6 

+

7logger = logging.getLogger(__name__) 

+

8 

+

9 

+

10class VaporInjectionPhaseSeparator(BaseVaporInjection): 

+

11 """ 

+

12 Cycle with vapor injection using an adiabatic ideal phase seperator. 

+

13 

+

14 For this cycle, we have 9 relevant states: 

+

15 

+

16 - 1: Before compressor, after evaporator 

+

17 - 2: Before condenser, after compressor 

+

18 - 3: Before PS, after condenser 

+

19 - 4: Before Evaporator, after PS 

+

20 - 5_vips: Before PS, after first EV 

+

21 - 6_vips: Before Mixing with 1_VI, after PS 

+

22 - 7_vips: Before second EV, after PS 

+

23 - 1_VI: Before Mixing with 6_vips, After first stage 

+

24 - 1_VI_mixed. Before second stage of compressor, after mixing with 6_vips 

+

25 

+

26 Additional Assumptions: 

+

27 ----------------------- 

+

28 - Ideal mixing in compressor of state 5 and state 4 

+

29 

+

30 Notes 

+

31 ----- 

+

32 See parent docstring for info on further assumptions and parameters. 

+

33 """ 

+

34 

+

35 flowsheet_name = "VaporInjectionPhaseSeparator" 

+

36 

+

37 def __init__(self, **kwargs): 

+

38 self.phase_separator = PhaseSeparator() 

+

39 super().__init__(**kwargs) 

+

40 

+

41 def get_all_components(self): 

+

42 return super().get_all_components() + [ 

+

43 self.phase_separator 

+

44 ] 

+

45 

+

46 def calc_injection(self): 

+

47 # Phase separator 

+

48 self.phase_separator.state_inlet = self.high_pressure_valve.state_outlet 

+

49 x_vapor_injection = self.phase_separator.state_inlet.q 

+

50 h_vapor_injection = self.phase_separator.state_outlet_vapor.h 

+

51 return x_vapor_injection, h_vapor_injection, self.phase_separator.state_outlet_liquid 

+

52 

+

53 def get_states_in_order_for_plotting(self): 

+

54 return super().get_states_in_order_for_plotting() + [ 

+

55 self.phase_separator.state_inlet, 

+

56 self.phase_separator.state_outlet_vapor, 

+

57 self.high_pressure_compressor.state_inlet, 

+

58 # Go back to separator for clear lines 

+

59 self.phase_separator.state_outlet_vapor, 

+

60 self.phase_separator.state_outlet_liquid 

+

61 ] 

+
+ + + diff --git a/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_py.html b/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_py.html new file mode 100644 index 0000000..ad7e03e --- /dev/null +++ b/docs/main/coverage/d_dae209ba2e6604e9_vapor_injection_py.html @@ -0,0 +1,287 @@ + + + + + Coverage for vclibpy/flowsheets/vapor_injection.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/flowsheets/vapor_injection.py: + 100% +

+ +

+ 63 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1import abc 

+

2import logging 

+

3from copy import deepcopy 

+

4import numpy as np 

+

5 

+

6from vclibpy.flowsheets import BaseCycle 

+

7from vclibpy.datamodels import Inputs, FlowsheetState 

+

8from vclibpy.components.compressors import Compressor 

+

9from vclibpy.components.expansion_valves import ExpansionValve 

+

10from vclibpy.media import ThermodynamicState 

+

11 

+

12logger = logging.getLogger(__name__) 

+

13 

+

14 

+

15class BaseVaporInjection(BaseCycle, abc.ABC): 

+

16 """ 

+

17 Partial cycle with vapor injection, using 

+

18 two separated compressors and expansion valves. 

+

19 

+

20 Notes 

+

21 ----- 

+

22 See parent docstring for info on further assumptions and parameters. 

+

23 """ 

+

24 

+

25 flowsheet_name = "VaporInjectionPhaseSeparator" 

+

26 

+

27 def __init__( 

+

28 self, 

+

29 high_pressure_compressor: Compressor, 

+

30 low_pressure_compressor: Compressor, 

+

31 high_pressure_valve: ExpansionValve, 

+

32 low_pressure_valve: ExpansionValve, 

+

33 **kwargs): 

+

34 super().__init__(**kwargs) 

+

35 self.high_pressure_compressor = high_pressure_compressor 

+

36 self.low_pressure_compressor = low_pressure_compressor 

+

37 self.high_pressure_valve = high_pressure_valve 

+

38 self.low_pressure_valve = low_pressure_valve 

+

39 # Avoid nasty bugs for setting states 

+

40 if id(high_pressure_compressor) == id(low_pressure_compressor): 

+

41 self.high_pressure_compressor = deepcopy(low_pressure_compressor) 

+

42 if id(low_pressure_valve) == id(high_pressure_valve): 

+

43 self.high_pressure_valve = deepcopy(low_pressure_valve) 

+

44 

+

45 def get_all_components(self): 

+

46 return super().get_all_components() + [ 

+

47 self.high_pressure_compressor, 

+

48 self.low_pressure_compressor, 

+

49 self.high_pressure_valve, 

+

50 self.low_pressure_valve, 

+

51 ] 

+

52 

+

53 def calc_states(self, p_1, p_2, inputs: Inputs, fs_state: FlowsheetState): 

+

54 k_vapor_injection = inputs.get("k_vapor_injection", default=1) 

+

55 # Default according to Xu, 2019 

+

56 

+

57 p_vapor_injection = k_vapor_injection * np.sqrt(p_1 * p_2) 

+

58 

+

59 # Condenser outlet 

+

60 self.set_condenser_outlet_based_on_subcooling(p_con=p_2, inputs=inputs) 

+

61 # High pressure EV 

+

62 self.high_pressure_valve.state_inlet = self.condenser.state_outlet 

+

63 self.high_pressure_valve.calc_outlet(p_outlet=p_vapor_injection) 

+

64 

+

65 # Calculate low compressor stage to already have access to the mass flow rates. 

+

66 self.set_evaporator_outlet_based_on_superheating(p_eva=p_1, inputs=inputs) 

+

67 self.low_pressure_compressor.state_inlet = self.evaporator.state_outlet 

+

68 self.low_pressure_compressor.calc_state_outlet( 

+

69 p_outlet=p_vapor_injection, inputs=inputs, fs_state=fs_state 

+

70 ) 

+

71 m_flow_low = self.low_pressure_compressor.calc_m_flow(inputs=inputs, fs_state=fs_state) 

+

72 self.evaporator.m_flow = self.low_pressure_compressor.m_flow 

+

73 

+

74 # Injection component: 

+

75 x_vapor_injection, h_vapor_injection, state_low_ev_inlet = self.calc_injection() 

+

76 

+

77 # Low pressure EV 

+

78 self.low_pressure_valve.state_inlet = state_low_ev_inlet 

+

79 self.low_pressure_valve.calc_outlet(p_outlet=p_1) 

+

80 # Evaporator 

+

81 self.evaporator.state_inlet = self.low_pressure_valve.state_outlet 

+

82 

+

83 # Ideal Mixing of state_5 and state_1_VI: 

+

84 h_1_VI_mixed = ( 

+

85 (1-x_vapor_injection) * self.low_pressure_compressor.state_outlet.h + 

+

86 x_vapor_injection * h_vapor_injection 

+

87 ) 

+

88 self.high_pressure_compressor.state_inlet = self.med_prop.calc_state( 

+

89 "PH", p_vapor_injection, h_1_VI_mixed 

+

90 ) 

+

91 self.high_pressure_compressor.calc_state_outlet( 

+

92 p_outlet=p_2, inputs=inputs, fs_state=fs_state 

+

93 ) 

+

94 

+

95 # Check m_flow of both compressor stages to check if 

+

96 # there would be an asymmetry of how much refrigerant is transported 

+

97 m_flow_high = self.high_pressure_compressor.calc_m_flow( 

+

98 inputs=inputs, fs_state=fs_state 

+

99 ) 

+

100 m_flow_low_should = m_flow_high * (1-x_vapor_injection) 

+

101 percent_deviation = (m_flow_low - m_flow_low_should) / m_flow_low_should * 100 

+

102 logger.debug("Deviation of mass flow rates is %s percent", percent_deviation) 

+

103 

+

104 # Set states 

+

105 self.condenser.m_flow = self.high_pressure_compressor.m_flow 

+

106 self.condenser.state_inlet = self.high_pressure_compressor.state_outlet 

+

107 

+

108 fs_state.set( 

+

109 name="T_1", value=self.evaporator.state_outlet.T, 

+

110 unit="K", description="Refrigerant temperature at evaporator outlet" 

+

111 ) 

+

112 fs_state.set( 

+

113 name="T_2", value=self.high_pressure_compressor.state_outlet.T, 

+

114 unit="K", description="Compressor outlet temperature" 

+

115 ) 

+

116 fs_state.set( 

+

117 name="T_3", value=self.condenser.state_outlet.T, 

+

118 unit="K", description="Refrigerant temperature at condenser outlet" 

+

119 ) 

+

120 fs_state.set( 

+

121 name="T_4", value=self.evaporator.state_inlet.T, 

+

122 unit="K", description="Refrigerant temperature at evaporator inlet" 

+

123 ) 

+

124 fs_state.set( 

+

125 name="p_con", value=p_2, 

+

126 unit="Pa", description="Condensation pressure" 

+

127 ) 

+

128 fs_state.set( 

+

129 name="p_eva", value=p_1, 

+

130 unit="Pa", description="Evaporation pressure" 

+

131 ) 

+

132 

+

133 def calc_injection(self) -> (float, float, ThermodynamicState): 

+

134 """ 

+

135 Calculate the injection component, e.g. phase separator 

+

136 or heat exchanger. 

+

137 In this function, child classes must set inlets 

+

138 and calculate outlets of additional components. 

+

139 

+

140 Returns: 

+

141 float: Portion of vapor injected (x) 

+

142 float: Enthalpy of vapor injected 

+

143 ThermodynamicState: Inlet state of low pressure expansion valve 

+

144 """ 

+

145 raise NotImplementedError 

+

146 

+

147 def calc_electrical_power(self, inputs: Inputs, fs_state: FlowsheetState): 

+

148 P_el_low = self.low_pressure_compressor.calc_electrical_power( 

+

149 inputs=inputs, fs_state=fs_state 

+

150 ) 

+

151 P_el_high = self.high_pressure_compressor.calc_electrical_power( 

+

152 inputs=inputs, fs_state=fs_state 

+

153 ) 

+

154 fs_state.set( 

+

155 name="P_el_low", 

+

156 value=P_el_low, 

+

157 unit="W", 

+

158 description="Electrical power consumption of low stage compressor" 

+

159 ) 

+

160 fs_state.set( 

+

161 name="P_el_high", 

+

162 value=P_el_high, 

+

163 unit="W", 

+

164 description="Electrical power consumption of high stage compressor" 

+

165 ) 

+

166 return P_el_low + P_el_high 

+

167 

+

168 def get_states_in_order_for_plotting(self): 

+

169 """ 

+

170 List with all relevant states of two-stage cycle 

+

171 except the intermediate component, e.g. phase separator 

+

172 or heat exchanger. 

+

173 """ 

+

174 return [ 

+

175 self.low_pressure_valve.state_inlet, 

+

176 self.low_pressure_valve.state_outlet, 

+

177 self.evaporator.state_inlet, 

+

178 self.med_prop.calc_state("PQ", self.evaporator.state_inlet.p, 1), 

+

179 self.evaporator.state_outlet, 

+

180 self.low_pressure_compressor.state_inlet, 

+

181 self.low_pressure_compressor.state_outlet, 

+

182 self.high_pressure_compressor.state_inlet, 

+

183 self.high_pressure_compressor.state_outlet, 

+

184 self.condenser.state_inlet, 

+

185 self.med_prop.calc_state("PQ", self.condenser.state_inlet.p, 1), 

+

186 self.med_prop.calc_state("PQ", self.condenser.state_inlet.p, 0), 

+

187 self.condenser.state_outlet, 

+

188 self.high_pressure_valve.state_inlet, 

+

189 self.high_pressure_valve.state_outlet, 

+

190 ] 

+
+ + + diff --git a/docs/main/coverage/d_f54be612a69fd5f7___init___py.html b/docs/main/coverage/d_f54be612a69fd5f7___init___py.html new file mode 100644 index 0000000..52af686 --- /dev/null +++ b/docs/main/coverage/d_f54be612a69fd5f7___init___py.html @@ -0,0 +1,97 @@ + + + + + Coverage for vclibpy/components/__init__.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/__init__.py: + 100% +

+ +

+ 0 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+
+ + + diff --git a/docs/main/coverage/d_f54be612a69fd5f7_component_py.html b/docs/main/coverage/d_f54be612a69fd5f7_component_py.html new file mode 100644 index 0000000..3eaf084 --- /dev/null +++ b/docs/main/coverage/d_f54be612a69fd5f7_component_py.html @@ -0,0 +1,232 @@ + + + + + Coverage for vclibpy/components/component.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/component.py: + 100% +

+ +

+ 36 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1from abc import ABC 

+

2from vclibpy.media import ThermodynamicState, MedProp 

+

3 

+

4 

+

5class BaseComponent(ABC): 

+

6 """ 

+

7 Abstract base class for defining interfaces of components in the vapor compression cycle. 

+

8 

+

9 Methods: 

+

10 start_secondary_med_prop(): 

+

11 To use multiprocessing, MedProp can't start in the main thread, as the object can't be pickled. 

+

12 This function starts possible secondary MedProp classes, for instance in heat exchangers. 

+

13 The default component does not have to override this function. 

+

14 

+

15 Properties: 

+

16 state_inlet (ThermodynamicState): 

+

17 Property for accessing and setting the inlet state of the component. 

+

18 state_outlet (ThermodynamicState): 

+

19 Property for accessing and setting the outlet state of the component. 

+

20 m_flow (float): 

+

21 Property for accessing and setting the mass flow rate through the component. 

+

22 med_prop (MedProp): 

+

23 Property for accessing and setting the property wrapper for the working fluid. 

+

24 """ 

+

25 

+

26 def __init__(self): 

+

27 """ 

+

28 Initialize the BaseComponent. 

+

29 """ 

+

30 self._state_inlet: ThermodynamicState = None 

+

31 self._state_outlet: ThermodynamicState = None 

+

32 self._m_flow: float = None 

+

33 self._med_prop: MedProp = None 

+

34 

+

35 def start_secondary_med_prop(self): 

+

36 """ 

+

37 Start secondary MedProp classes for multiprocessing. 

+

38 

+

39 To use multiprocessing, MedProp can't start in the main thread, as the object can't be pickled. 

+

40 This function starts possible secondary MedProp classes, for instance in heat exchangers. 

+

41 The default component does not have to override this function. 

+

42 """ 

+

43 pass 

+

44 

+

45 def terminate_secondary_med_prop(self): 

+

46 """ 

+

47 To use multi-processing, MedProp can't start 

+

48 in the main thread, as the object can't be pickled. 

+

49 

+

50 This function terminates possible secondary med-prop 

+

51 classes, for instance in heat exchangers. 

+

52 The default component does not have to override 

+

53 this function. 

+

54 """ 

+

55 pass 

+

56 

+

57 @property 

+

58 def state_inlet(self) -> ThermodynamicState: 

+

59 """ 

+

60 Get or set the inlet state of the component. 

+

61 

+

62 Returns: 

+

63 ThermodynamicState: Inlet state of the component. 

+

64 """ 

+

65 return self._state_inlet 

+

66 

+

67 @state_inlet.setter 

+

68 def state_inlet(self, state_inlet: ThermodynamicState): 

+

69 """ 

+

70 Set the inlet state of the component. 

+

71 

+

72 Args: 

+

73 state_inlet (ThermodynamicState): Inlet state to set. 

+

74 """ 

+

75 self._state_inlet = state_inlet 

+

76 

+

77 @property 

+

78 def state_outlet(self) -> ThermodynamicState: 

+

79 """ 

+

80 Get or set the outlet state of the component. 

+

81 

+

82 Returns: 

+

83 ThermodynamicState: Outlet state of the component. 

+

84 """ 

+

85 return self._state_outlet 

+

86 

+

87 @state_outlet.setter 

+

88 def state_outlet(self, state_outlet: ThermodynamicState): 

+

89 """ 

+

90 Set the outlet state of the component. 

+

91 

+

92 Args: 

+

93 state_outlet (ThermodynamicState): Outlet state to set. 

+

94 """ 

+

95 self._state_outlet = state_outlet 

+

96 

+

97 @property 

+

98 def m_flow(self) -> float: 

+

99 """ 

+

100 Get or set the mass flow rate through the component. 

+

101 

+

102 Returns: 

+

103 float: Mass flow rate through the component. 

+

104 """ 

+

105 return self._m_flow 

+

106 

+

107 @m_flow.setter 

+

108 def m_flow(self, m_flow: float): 

+

109 """ 

+

110 Set the mass flow rate through the component. 

+

111 

+

112 Args: 

+

113 m_flow (float): Mass flow rate to set. 

+

114 """ 

+

115 self._m_flow = m_flow 

+

116 

+

117 @property 

+

118 def med_prop(self) -> MedProp: 

+

119 """ 

+

120 Get or set the property wrapper for the working fluid. 

+

121 

+

122 Returns: 

+

123 MedProp: Property wrapper for the working fluid. 

+

124 """ 

+

125 return self._med_prop 

+

126 

+

127 @med_prop.setter 

+

128 def med_prop(self, med_prop: MedProp): 

+

129 """ 

+

130 Set the property wrapper for the working fluid. 

+

131 

+

132 Args: 

+

133 med_prop (MedProp): Property wrapper to set. 

+

134 """ 

+

135 self._med_prop = med_prop 

+
+ + + diff --git a/docs/main/coverage/d_f54be612a69fd5f7_phase_separator_py.html b/docs/main/coverage/d_f54be612a69fd5f7_phase_separator_py.html new file mode 100644 index 0000000..dc38bbc --- /dev/null +++ b/docs/main/coverage/d_f54be612a69fd5f7_phase_separator_py.html @@ -0,0 +1,176 @@ + + + + + Coverage for vclibpy/components/phase_separator.py: 100% + + + + + +
+
+

+ Coverage for vclibpy/components/phase_separator.py: + 100% +

+ +

+ 26 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+ +
+
+
+

1""" 

+

2Module with a simple phase separator model. 

+

3""" 

+

4 

+

5from vclibpy.media import ThermodynamicState 

+

6from vclibpy.components.component import BaseComponent 

+

7 

+

8 

+

9class PhaseSeparator(BaseComponent): 

+

10 """ 

+

11 A simple phase separator model. 

+

12 """ 

+

13 

+

14 def __init__(self): 

+

15 super().__init__() 

+

16 self._state_outlet_liquid: ThermodynamicState = None 

+

17 self._state_outlet_vapor: ThermodynamicState = None 

+

18 

+

19 @BaseComponent.state_inlet.setter 

+

20 def state_inlet(self, state_inlet: ThermodynamicState): 

+

21 """ 

+

22 Set the state of the inlet and calculate the outlet states for liquid and vapor phases. 

+

23 

+

24 Args: 

+

25 state_inlet (ThermodynamicState): Inlet state. 

+

26 """ 

+

27 self._state_inlet = state_inlet 

+

28 self.state_outlet_vapor = self.med_prop.calc_state("PQ", self.state_inlet.p, 1) 

+

29 self.state_outlet_liquid = self.med_prop.calc_state("PQ", self.state_inlet.p, 0) 

+

30 

+

31 @BaseComponent.state_outlet.setter 

+

32 def state_outlet(self, state: ThermodynamicState): 

+

33 """ 

+

34 This outlet is disabled for this component. 

+

35 

+

36 Args: 

+

37 state (ThermodynamicState): Outlet state. 

+

38 """ 

+

39 raise NotImplementedError("This outlet is disabled for this component") 

+

40 

+

41 @property 

+

42 def state_outlet_vapor(self) -> ThermodynamicState: 

+

43 """ 

+

44 Getter for the outlet state of the vapor phase. 

+

45 

+

46 Returns: 

+

47 ThermodynamicState: Outlet state for the vapor phase. 

+

48 """ 

+

49 return self._state_outlet_vapor 

+

50 

+

51 @state_outlet_vapor.setter 

+

52 def state_outlet_vapor(self, state: ThermodynamicState): 

+

53 """ 

+

54 Setter for the outlet state of the vapor phase. 

+

55 

+

56 Args: 

+

57 state (ThermodynamicState): Outlet state for the vapor phase. 

+

58 """ 

+

59 self._state_outlet_vapor = state 

+

60 

+

61 @property 

+

62 def state_outlet_liquid(self) -> ThermodynamicState: 

+

63 """ 

+

64 Getter for the outlet state of the liquid phase. 

+

65 

+

66 Returns: 

+

67 ThermodynamicState: Outlet state for the liquid phase. 

+

68 """ 

+

69 return self._state_outlet_liquid 

+

70 

+

71 @state_outlet_liquid.setter 

+

72 def state_outlet_liquid(self, state: ThermodynamicState): 

+

73 """ 

+

74 Setter for the outlet state of the liquid phase. 

+

75 

+

76 Args: 

+

77 state (ThermodynamicState): Outlet state for the liquid phase. 

+

78 """ 

+

79 self._state_outlet_liquid = state 

+
+ + + diff --git a/docs/main/coverage/favicon_32.png b/docs/main/coverage/favicon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..8649f0475d8d20793b2ec431fe25a186a414cf10 GIT binary patch literal 1732 zcmV;#20QtQP)K2KOkBOVxIZChq#W-v7@TU%U6P(wycKT1hUJUToW3ke1U1ONa4 z000000000000000bb)GRa9mqwR9|UWHy;^RUrt?IT__Y0JUcxmBP0(51q1>E00030 z|NrOz)aw7%8sJzM<5^g%z7^qE`}_Ot|JUUG(NUkWzR|7K?Zo%@_v-8G-1N%N=D$;; zw;keH4dGY$`1t4M=HK_s*zm^0#KgqfwWhe3qO_HtvXYvtjgX>;-~C$L`&k>^R)9)7 zdPh2TL^pCnHC#0+_4D)M`p?qp!pq{jO_{8;$fbaflbx`Tn52n|n}8VFRTA1&ugOP< zPd{uvFjz7t*Vot1&d$l-xWCk}s;sQL&#O(Bskh6gqNJv>#iB=ypG1e3K!K4yc7!~M zfj4S*g^zZ7eP$+_Sl07Z646l;%urinP#D8a6TwRtnLIRcI!r4f@bK~9-`~;E(N?Lv zSEst7s;rcxsi~}{Nsytfz@MtUoR*iFc8!#vvx}Umhm4blk(_~MdVD-@dW&>!Nn~ro z_E~-ESVQAj6Wmn;(olz(O&_{U2*pZBc1aYjMh>Dq3z|6`jW`RDHV=t3I6yRKJ~LOX zz_z!!vbVXPqob#=pj3^VMT?x6t(irRmSKsMo1~LLkB&=#j!=M%NP35mfqim$drWb9 zYIb>no_LUwc!r^NkDzs4YHu@=ZHRzrafWDZd1EhEVq=tGX?tK$pIa)DTh#bkvh!J- z?^%@YS!U*0E8$q$_*aOTQ&)Ra64g>ep;BdcQgvlg8qQHrP*E$;P{-m=A*@axn@$bO zO-Y4JzS&EAi%YG}N?cn?YFS7ivPY=EMV6~YH;+Xxu|tefLS|Aza)Cg6us#)=JW!uH zQa?H>d^j+YHCtyjL^LulF*05|F$RG!AX_OHVI&MtA~_@=5_lU|0000rbW%=J06GH4 z^5LD8b8apw8vNh1ua1mF{{Hy)_U`NA;Nacc+sCpuHXa-V{r&yz?c(9#+}oX+NmiRW z+W-IqK1oDDR5;6GfCDCOP5}iL5fK(cB~ET81`MFgF2kGa9AjhSIk~-E-4&*tPPKdiilQJ11k_J082ZS z>@TvivP!5ZFG?t@{t+GpR3XR&@*hA_VE1|Lo8@L@)l*h(Z@=?c-NS$Fk&&61IzUU9 z*nPqBM=OBZ-6ka1SJgGAS-Us5EN)r#dUX%>wQZLa2ytPCtMKp)Ob z*xcu38Z&d5<-NBS)@jRD+*!W*cf-m_wmxDEqBf?czI%3U0J$Xik;lA`jg}VH?(S(V zE!M3;X2B8w0TnnW&6(8;_Uc)WD;Ms6PKP+s(sFgO!}B!^ES~GDt4qLPxwYB)^7)XA zZwo9zDy-B0B+jT6V=!=bo(zs_8{eBA78gT9GH$(DVhz;4VAYwz+bOIdZ-PNb|I&rl z^XG=vFLF)1{&nT2*0vMz#}7^9hXzzf&ZdKlEj{LihP;|;Ywqn35ajP?H?7t|i-Un% z&&kxee@9B{nwgv1+S-~0)E1{ob1^Wn`F2isurqThKK=3%&;`@{0{!D- z&CSj80t;uPu&FaJFtSXKH#ajgGj}=sEad7US6jP0|Db@0j)?(5@sf<7`~a9>s;wCa zm^)spe{uxGFmrJYI9cOh7s$>8Npkt-5EWB1UKc`{W{y5Ce$1+nM9Cr;);=Ju#N^62OSlJMn7omiUgP&ErsYzT~iGxcW aE(`!K@+CXylaC4j0000 + + + + Coverage report + + + + + +
+
+

Coverage report: + 58% +

+ +
+ +
+

+ coverage.py v7.3.2, + created at 2023-12-06 11:19 +0000 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modulestatementsmissingexcludedcoverage
vclibpy/__init__.py200100%
vclibpy/components/__init__.py000100%
vclibpy/components/component.py3600100%
vclibpy/components/compressors/__init__.py400100%
vclibpy/components/compressors/compressor.py3503100%
vclibpy/components/compressors/constant_effectivness.py1400100%
vclibpy/components/compressors/rotary.py491098%
vclibpy/components/compressors/ten_coefficient.py9574022%
vclibpy/components/expansion_valves/__init__.py200100%
vclibpy/components/expansion_valves/bernoulli.py61083%
vclibpy/components/expansion_valves/expansion_valve.py1202100%
vclibpy/components/heat_exchangers/__init__.py200100%
vclibpy/components/heat_exchangers/economizer.py3203100%
vclibpy/components/heat_exchangers/heat_exchanger.py702297%
vclibpy/components/heat_exchangers/heat_transfer/__init__.py300100%
vclibpy/components/heat_exchangers/heat_transfer/air_to_wall.py313120%
vclibpy/components/heat_exchangers/heat_transfer/constant.py1200100%
vclibpy/components/heat_exchangers/heat_transfer/heat_transfer.py121292%
vclibpy/components/heat_exchangers/heat_transfer/pipe_to_wall.py363600%
vclibpy/components/heat_exchangers/heat_transfer/vdi_atlas_air_to_wall.py727200%
vclibpy/components/heat_exchangers/heat_transfer/wall.py1000100%
vclibpy/components/heat_exchangers/moving_boundary_ntu.py1337095%
vclibpy/components/heat_exchangers/ntu.py509082%
vclibpy/components/phase_separator.py2601100%
vclibpy/datamodels.py521098%
vclibpy/flowsheets/__init__.py400100%
vclibpy/flowsheets/base.py22846280%
vclibpy/flowsheets/standard.py3600100%
vclibpy/flowsheets/vapor_injection.py6301100%
vclibpy/flowsheets/vapor_injection_economizer.py4800100%
vclibpy/flowsheets/vapor_injection_phase_separator.py1800100%
vclibpy/media/__init__.py101090%
vclibpy/media/cool_prop.py5719267%
vclibpy/media/media.py538085%
vclibpy/media/ref_prop.py429379012%
vclibpy/media/states.py605092%
vclibpy/utils/__init__.py200100%
vclibpy/utils/automation.py9922078%
vclibpy/utils/nominal_design.py343400%
vclibpy/utils/plotting.py14167052%
vclibpy/utils/sdf_.py7342242%
vclibpy/utils/ten_coefficient_compressor_reqression.py646410%
Total22159222358%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/docs/main/coverage/keybd_closed.png b/docs/main/coverage/keybd_closed.png new file mode 100644 index 0000000000000000000000000000000000000000..ba119c47df81ed2bbd27a06988abf700139c4f99 GIT binary patch literal 9004 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 literal 0 HcmV?d00001 diff --git a/docs/main/coverage/keybd_open.png b/docs/main/coverage/keybd_open.png new file mode 100644 index 0000000000000000000000000000000000000000..a8bac6c9de256626c680f9e9e3f8ee81d9713ecd GIT binary patch literal 9003 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 G%6|bpD&^7u literal 0 HcmV?d00001 diff --git a/docs/main/coverage/status.json b/docs/main/coverage/status.json new file mode 100644 index 0000000..fa82127 --- /dev/null +++ b/docs/main/coverage/status.json @@ -0,0 +1 @@ +{"format":2,"version":"7.3.2","globals":"7fa90da61ddfd9490e91f7910b612228","files":{"d_3aa0e56d51a2f798___init___py":{"hash":"870c0676e94c5bfc237601cbc9f77cd0","index":{"nums":[0,1,2,0,0,0,0,0],"html_filename":"d_3aa0e56d51a2f798___init___py.html","relative_filename":"vclibpy/__init__.py"}},"d_f54be612a69fd5f7___init___py":{"hash":"3c77fc9ef7f887ac2508d4109cf92472","index":{"nums":[0,1,0,0,0,0,0,0],"html_filename":"d_f54be612a69fd5f7___init___py.html","relative_filename":"vclibpy/components/__init__.py"}},"d_f54be612a69fd5f7_component_py":{"hash":"3db0ffe89ce541833830e98d0d13344d","index":{"nums":[0,1,36,0,0,0,0,0],"html_filename":"d_f54be612a69fd5f7_component_py.html","relative_filename":"vclibpy/components/component.py"}},"d_d45aa4312ad3649a___init___py":{"hash":"58c79786dd810b608768b6b102ce33bc","index":{"nums":[0,1,4,0,0,0,0,0],"html_filename":"d_d45aa4312ad3649a___init___py.html","relative_filename":"vclibpy/components/compressors/__init__.py"}},"d_d45aa4312ad3649a_compressor_py":{"hash":"e610ba34470ef26ae9ddeb217e8497e8","index":{"nums":[0,1,35,3,0,0,0,0],"html_filename":"d_d45aa4312ad3649a_compressor_py.html","relative_filename":"vclibpy/components/compressors/compressor.py"}},"d_d45aa4312ad3649a_constant_effectivness_py":{"hash":"6c8c3d90d9037e1e4af2177944012a83","index":{"nums":[0,1,14,0,0,0,0,0],"html_filename":"d_d45aa4312ad3649a_constant_effectivness_py.html","relative_filename":"vclibpy/components/compressors/constant_effectivness.py"}},"d_d45aa4312ad3649a_rotary_py":{"hash":"af7b7c2885234cb21c632f304450a2e5","index":{"nums":[0,1,49,0,1,0,0,0],"html_filename":"d_d45aa4312ad3649a_rotary_py.html","relative_filename":"vclibpy/components/compressors/rotary.py"}},"d_d45aa4312ad3649a_ten_coefficient_py":{"hash":"402ba402d992eab2a54e3fdb4a5d0d64","index":{"nums":[0,1,95,0,74,0,0,0],"html_filename":"d_d45aa4312ad3649a_ten_coefficient_py.html","relative_filename":"vclibpy/components/compressors/ten_coefficient.py"}},"d_38531a0795c587f9___init___py":{"hash":"15a488013d0d4fec574cfa29a480f8ec","index":{"nums":[0,1,2,0,0,0,0,0],"html_filename":"d_38531a0795c587f9___init___py.html","relative_filename":"vclibpy/components/expansion_valves/__init__.py"}},"d_38531a0795c587f9_bernoulli_py":{"hash":"8143e3e6a35f9f70527ec9f6f6f58e98","index":{"nums":[0,1,6,0,1,0,0,0],"html_filename":"d_38531a0795c587f9_bernoulli_py.html","relative_filename":"vclibpy/components/expansion_valves/bernoulli.py"}},"d_38531a0795c587f9_expansion_valve_py":{"hash":"c1f6e24fb01bea7b352f52a083ea35f4","index":{"nums":[0,1,12,2,0,0,0,0],"html_filename":"d_38531a0795c587f9_expansion_valve_py.html","relative_filename":"vclibpy/components/expansion_valves/expansion_valve.py"}},"d_35fc0e049e81dfd6___init___py":{"hash":"62edc4ea1ff8cc29b75a2661f82df77c","index":{"nums":[0,1,2,0,0,0,0,0],"html_filename":"d_35fc0e049e81dfd6___init___py.html","relative_filename":"vclibpy/components/heat_exchangers/__init__.py"}},"d_35fc0e049e81dfd6_economizer_py":{"hash":"024c3260fd54d92584c950eed4083cbb","index":{"nums":[0,1,32,3,0,0,0,0],"html_filename":"d_35fc0e049e81dfd6_economizer_py.html","relative_filename":"vclibpy/components/heat_exchangers/economizer.py"}},"d_35fc0e049e81dfd6_heat_exchanger_py":{"hash":"2a399393ce01d2dd626d5e6e0fa8e0a8","index":{"nums":[0,1,70,2,2,0,0,0],"html_filename":"d_35fc0e049e81dfd6_heat_exchanger_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_exchanger.py"}},"d_4deeb89b86ecce82___init___py":{"hash":"cd0aefd2cbed7a9db71739b8c9cb8b50","index":{"nums":[0,1,3,0,0,0,0,0],"html_filename":"d_4deeb89b86ecce82___init___py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/__init__.py"}},"d_4deeb89b86ecce82_air_to_wall_py":{"hash":"877afa8d94728f82ab6de27f7fd55dd4","index":{"nums":[0,1,31,2,31,0,0,0],"html_filename":"d_4deeb89b86ecce82_air_to_wall_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/air_to_wall.py"}},"d_4deeb89b86ecce82_constant_py":{"hash":"0b266129fe2feea7b8baf9c46c055fb7","index":{"nums":[0,1,12,0,0,0,0,0],"html_filename":"d_4deeb89b86ecce82_constant_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/constant.py"}},"d_4deeb89b86ecce82_heat_transfer_py":{"hash":"f51fdbae938471bf019d6c28e7cb7872","index":{"nums":[0,1,12,2,1,0,0,0],"html_filename":"d_4deeb89b86ecce82_heat_transfer_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/heat_transfer.py"}},"d_4deeb89b86ecce82_pipe_to_wall_py":{"hash":"47a45d1a3a597da79c6d257efeab6863","index":{"nums":[0,1,36,0,36,0,0,0],"html_filename":"d_4deeb89b86ecce82_pipe_to_wall_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/pipe_to_wall.py"}},"d_4deeb89b86ecce82_vdi_atlas_air_to_wall_py":{"hash":"624025a66375e3409fda3c8494e98e4e","index":{"nums":[0,1,72,0,72,0,0,0],"html_filename":"d_4deeb89b86ecce82_vdi_atlas_air_to_wall_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/vdi_atlas_air_to_wall.py"}},"d_4deeb89b86ecce82_wall_py":{"hash":"ac2945ad9159828416ff549823978b51","index":{"nums":[0,1,10,0,0,0,0,0],"html_filename":"d_4deeb89b86ecce82_wall_py.html","relative_filename":"vclibpy/components/heat_exchangers/heat_transfer/wall.py"}},"d_35fc0e049e81dfd6_moving_boundary_ntu_py":{"hash":"5516ecdbe35bed99c3c3519d7c8581af","index":{"nums":[0,1,133,0,7,0,0,0],"html_filename":"d_35fc0e049e81dfd6_moving_boundary_ntu_py.html","relative_filename":"vclibpy/components/heat_exchangers/moving_boundary_ntu.py"}},"d_35fc0e049e81dfd6_ntu_py":{"hash":"b66e671c1b58cf60272dbcb359925ef8","index":{"nums":[0,1,50,0,9,0,0,0],"html_filename":"d_35fc0e049e81dfd6_ntu_py.html","relative_filename":"vclibpy/components/heat_exchangers/ntu.py"}},"d_f54be612a69fd5f7_phase_separator_py":{"hash":"d94ca0bf3649559551e67fc1eee4561d","index":{"nums":[0,1,26,1,0,0,0,0],"html_filename":"d_f54be612a69fd5f7_phase_separator_py.html","relative_filename":"vclibpy/components/phase_separator.py"}},"d_3aa0e56d51a2f798_datamodels_py":{"hash":"67175039a9e725edaa7d401028c8d40b","index":{"nums":[0,1,52,0,1,0,0,0],"html_filename":"d_3aa0e56d51a2f798_datamodels_py.html","relative_filename":"vclibpy/datamodels.py"}},"d_dae209ba2e6604e9___init___py":{"hash":"7281f14e47e81fe0b9ad9a12b319bd57","index":{"nums":[0,1,4,0,0,0,0,0],"html_filename":"d_dae209ba2e6604e9___init___py.html","relative_filename":"vclibpy/flowsheets/__init__.py"}},"d_dae209ba2e6604e9_base_py":{"hash":"3441f14891bcc589b61e48331cf68930","index":{"nums":[0,1,228,2,46,0,0,0],"html_filename":"d_dae209ba2e6604e9_base_py.html","relative_filename":"vclibpy/flowsheets/base.py"}},"d_dae209ba2e6604e9_standard_py":{"hash":"4fb0cef586438a2d17f270cc784297b1","index":{"nums":[0,1,36,0,0,0,0,0],"html_filename":"d_dae209ba2e6604e9_standard_py.html","relative_filename":"vclibpy/flowsheets/standard.py"}},"d_dae209ba2e6604e9_vapor_injection_py":{"hash":"abc9fb0b06ee82c48c40c721eeba403d","index":{"nums":[0,1,63,1,0,0,0,0],"html_filename":"d_dae209ba2e6604e9_vapor_injection_py.html","relative_filename":"vclibpy/flowsheets/vapor_injection.py"}},"d_dae209ba2e6604e9_vapor_injection_economizer_py":{"hash":"84bb7ee40ee94ba593266d83ea146d2b","index":{"nums":[0,1,48,0,0,0,0,0],"html_filename":"d_dae209ba2e6604e9_vapor_injection_economizer_py.html","relative_filename":"vclibpy/flowsheets/vapor_injection_economizer.py"}},"d_dae209ba2e6604e9_vapor_injection_phase_separator_py":{"hash":"3aded356bf6949c71b6701c3ee34d559","index":{"nums":[0,1,18,0,0,0,0,0],"html_filename":"d_dae209ba2e6604e9_vapor_injection_phase_separator_py.html","relative_filename":"vclibpy/flowsheets/vapor_injection_phase_separator.py"}},"d_c048ebee450da2af___init___py":{"hash":"87b8fac9e8e02ce5d66655daa17176d8","index":{"nums":[0,1,10,0,1,0,0,0],"html_filename":"d_c048ebee450da2af___init___py.html","relative_filename":"vclibpy/media/__init__.py"}},"d_c048ebee450da2af_cool_prop_py":{"hash":"73fcbd3358d6190bf6a5b01479fe8439","index":{"nums":[0,1,57,2,19,0,0,0],"html_filename":"d_c048ebee450da2af_cool_prop_py.html","relative_filename":"vclibpy/media/cool_prop.py"}},"d_c048ebee450da2af_media_py":{"hash":"4d3971c7dd207fc4eae4646296604790","index":{"nums":[0,1,53,0,8,0,0,0],"html_filename":"d_c048ebee450da2af_media_py.html","relative_filename":"vclibpy/media/media.py"}},"d_c048ebee450da2af_ref_prop_py":{"hash":"1044a06eaaea85af194aace054ea8ffc","index":{"nums":[0,1,429,0,379,0,0,0],"html_filename":"d_c048ebee450da2af_ref_prop_py.html","relative_filename":"vclibpy/media/ref_prop.py"}},"d_c048ebee450da2af_states_py":{"hash":"15f3bffaf147424b6befa389fb5307b8","index":{"nums":[0,1,60,0,5,0,0,0],"html_filename":"d_c048ebee450da2af_states_py.html","relative_filename":"vclibpy/media/states.py"}},"d_57d6adb9ba5af1cf___init___py":{"hash":"a655ec0211a7f7e93d3f8f156fdafef8","index":{"nums":[0,1,2,0,0,0,0,0],"html_filename":"d_57d6adb9ba5af1cf___init___py.html","relative_filename":"vclibpy/utils/__init__.py"}},"d_57d6adb9ba5af1cf_automation_py":{"hash":"97a292dbb7aa146c23aadc4aec252f7e","index":{"nums":[0,1,99,0,22,0,0,0],"html_filename":"d_57d6adb9ba5af1cf_automation_py.html","relative_filename":"vclibpy/utils/automation.py"}},"d_57d6adb9ba5af1cf_nominal_design_py":{"hash":"4a08a048acd7717988bf6f5af8e281a8","index":{"nums":[0,1,34,0,34,0,0,0],"html_filename":"d_57d6adb9ba5af1cf_nominal_design_py.html","relative_filename":"vclibpy/utils/nominal_design.py"}},"d_57d6adb9ba5af1cf_plotting_py":{"hash":"12543d26375c3dbcd575bd9455c4f54c","index":{"nums":[0,1,141,0,67,0,0,0],"html_filename":"d_57d6adb9ba5af1cf_plotting_py.html","relative_filename":"vclibpy/utils/plotting.py"}},"d_57d6adb9ba5af1cf_sdf__py":{"hash":"f5df962d66e060e06edca81baf7c1bf8","index":{"nums":[0,1,73,2,42,0,0,0],"html_filename":"d_57d6adb9ba5af1cf_sdf__py.html","relative_filename":"vclibpy/utils/sdf_.py"}},"d_57d6adb9ba5af1cf_ten_coefficient_compressor_reqression_py":{"hash":"687adf63f4c478a46a5dcf8332602b77","index":{"nums":[0,1,64,1,64,0,0,0],"html_filename":"d_57d6adb9ba5af1cf_ten_coefficient_compressor_reqression_py.html","relative_filename":"vclibpy/utils/ten_coefficient_compressor_reqression.py"}}}} \ No newline at end of file diff --git a/docs/main/coverage/style.css b/docs/main/coverage/style.css new file mode 100644 index 0000000..11b24c4 --- /dev/null +++ b/docs/main/coverage/style.css @@ -0,0 +1,309 @@ +@charset "UTF-8"; +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ +/* Don't edit this .css file. Edit the .scss file instead! */ +html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } + +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { body { color: #eee; } } + +html > body { font-size: 16px; } + +a:active, a:focus { outline: 2px dashed #007acc; } + +p { font-size: .875em; line-height: 1.4em; } + +table { border-collapse: collapse; } + +td { vertical-align: top; } + +table tr.hidden { display: none !important; } + +p#no_rows { display: none; font-size: 1.2em; } + +a.nav { text-decoration: none; color: inherit; } + +a.nav:hover { text-decoration: underline; color: inherit; } + +.hidden { display: none; } + +header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; } + +@media (prefers-color-scheme: dark) { header { background: black; } } + +@media (prefers-color-scheme: dark) { header { border-color: #333; } } + +header .content { padding: 1rem 3.5rem; } + +header h2 { margin-top: .5em; font-size: 1em; } + +header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } + +header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; } + +header.sticky .text { display: none; } + +header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; } + +header.sticky .content { padding: 0.5rem 3.5rem; } + +header.sticky .content p { font-size: 1em; } + +header.sticky ~ #source { padding-top: 6.5em; } + +main { position: relative; z-index: 1; } + +footer { margin: 1rem 3.5rem; } + +footer .content { padding: 0; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } } + +#index { margin: 1rem 0 0 3.5rem; } + +h1 { font-size: 1.25em; display: inline-block; } + +#filter_container { float: right; margin: 0 2em 0 0; } + +#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } } + +@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } } + +#filter_container input:focus { border-color: #007acc; } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { border-color: #444; } } + +header button:active, header button:focus { outline: 2px dashed #007acc; } + +header button.run { background: #eeffee; } + +@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } } + +header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } } + +header button.mis { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } } + +header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } } + +header button.exc { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { header button.exc { background: #333; } } + +header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } } + +header button.par { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { header button.par { background: #650; } } + +header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } } + +#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } + +#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } + +#help_panel_wrapper { float: right; position: relative; } + +#keyboard_icon { margin: 5px; } + +#help_panel_state { display: none; } + +#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; } + +#help_panel .keyhelp p { margin-top: .75em; } + +#help_panel .legend { font-style: italic; margin-bottom: 1em; } + +.indexfile #help_panel { width: 25em; } + +.pyfile #help_panel { width: 18em; } + +#help_panel_state:checked ~ #help_panel { display: block; } + +kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; } + +#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } + +#source p { position: relative; white-space: pre; } + +#source p * { box-sizing: border-box; } + +#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n { color: #777; } } + +#source p .n.highlight { background: #ffdd00; } + +#source p .n a { margin-top: -4em; padding-top: 4em; text-decoration: none; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } + +#source p .n a:hover { text-decoration: underline; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } + +#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } + +@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } + +#source p .t:hover { background: #f2f2f2; } + +@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } + +#source p .t:hover ~ .r .annotate.long { display: block; } + +#source p .t .com { color: #008000; font-style: italic; line-height: 1px; } + +@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } } + +#source p .t .key { font-weight: bold; line-height: 1px; } + +#source p .t .str { color: #0451a5; } + +@media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } } + +#source p.mis .t { border-left: 0.2em solid #ff0000; } + +#source p.mis.show_mis .t { background: #fdd; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } + +#source p.mis.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } + +#source p.run .t { border-left: 0.2em solid #00dd00; } + +#source p.run.show_run .t { background: #dfd; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } + +#source p.run.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } + +#source p.exc .t { border-left: 0.2em solid #808080; } + +#source p.exc.show_exc .t { background: #eee; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } + +#source p.exc.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } + +#source p.par .t { border-left: 0.2em solid #bbbb00; } + +#source p.par.show_par .t { background: #ffa; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } + +#source p.par.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } + +#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } + +@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } + +#source p .annotate.short:hover ~ .long { display: block; } + +#source p .annotate.long { width: 30em; right: 2.5em; } + +#source p input { display: none; } + +#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } + +#source p input ~ .r label.ctx::before { content: "▶ "; } + +#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } + +#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } + +#source p input:checked ~ .r label.ctx::before { content: "▼ "; } + +#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } + +#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } + +@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } + +#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; } + +@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } + +#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } + +#index table.index { margin-left: -.5em; } + +#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; } + +@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } + +#index td.name, #index th.name { text-align: left; width: auto; } + +#index th { font-style: italic; color: #333; cursor: pointer; } + +@media (prefers-color-scheme: dark) { #index th { color: #ddd; } } + +#index th:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } + +#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } + +@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } + +#index th[aria-sort="ascending"]::after { font-family: sans-serif; content: " ↑"; } + +#index th[aria-sort="descending"]::after { font-family: sans-serif; content: " ↓"; } + +#index td.name a { text-decoration: none; color: inherit; } + +#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } + +#index tr.file:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } } + +#index tr.file:hover td.name { text-decoration: underline; color: inherit; } + +#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } + +@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } + +#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } + +@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } } diff --git a/docs/main/docs/.doctrees/code/vclibpy.components.heat_exchangers.doctree b/docs/main/docs/.doctrees/code/vclibpy.components.heat_exchangers.doctree index 907abadeacf0b6d21f29397b77d4b948c76f9715..739010166b669eb9ab95bd7c4086cbb0217c5be5 100644 GIT binary patch delta 526 zcmca}k?X=`u7(!IDNKtv)K)Tp!Q=+5{_U2GOc!Msy{B`_F&)+_ElDj>NGwWCR7h}4 zP|yIg^3xPbGExvc#Oy)MAi! zAkHt%OHnW~&^0#DU&%6%^6|(-XCrlqY}iWuJafiz${-aJ#%V)81MDs%ogZ delta 290 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 a57TK?JndM0Go^;V2v(1tlbYJQ>i+=A%5W(F delta 60037 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

=pHyB1Bu3 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 diff --git a/docs/main/docs/_modules/vclibpy/components/heat_exchangers/ntu.html b/docs/main/docs/_modules/vclibpy/components/heat_exchangers/ntu.html index 17471fc..028da63 100644 --- a/docs/main/docs/_modules/vclibpy/components/heat_exchangers/ntu.html +++ b/docs/main/docs/_modules/vclibpy/components/heat_exchangers/ntu.html @@ -98,12 +98,13 @@

Source code for vclibpy.components.heat_exchangers.ntu

Counter, Cross or parallel flow ratio_outer_to_inner_area (float): The NTU method uses the overall heat transfer coefficient `k` - and multiplies it with the overall area `A`. + and multiplies it with the outer area `A` (area of the secondary side). However, depending on the heat exchanger type, the areas may differ drastically. For instance in an air-to-water heat exchanger. The VDI-Atlas proposes the ratio of outer area to inner pipe area to account for this mismatch in sizes. The calculation follows the code in the function `calc_k`. + Typical values are around 20-30. """ def __init__(self, flow_type: str, ratio_outer_to_inner_area: float, **kwargs): diff --git a/docs/main/docs/code/vclibpy.components.heat_exchangers.html b/docs/main/docs/code/vclibpy.components.heat_exchangers.html index 70d8b80..af8706a 100644 --- a/docs/main/docs/code/vclibpy.components.heat_exchangers.html +++ b/docs/main/docs/code/vclibpy.components.heat_exchangers.html @@ -597,12 +597,13 @@

SubmodulesModule vclibpy.components.heat_exchangers.ntu (vclibpy/co - 66 + 67 33 convention invalid-name @@ -2759,7 +2759,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 92 + 93 4 convention invalid-name @@ -2769,7 +2769,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 94 + 95 0 convention line-too-long @@ -2779,7 +2779,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 125 + 126 4 convention invalid-name @@ -2789,7 +2789,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 125 + 126 17 convention invalid-name @@ -2799,7 +2799,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 132 + 133 0 convention line-too-long @@ -2809,7 +2809,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 141 + 142 0 convention line-too-long @@ -2819,7 +2819,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 151 + 152 0 convention line-too-long @@ -2829,7 +2829,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 151 + 152 4 convention invalid-name @@ -2839,7 +2839,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 151 + 152 25 convention invalid-name @@ -2849,7 +2849,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 151 + 152 76 convention invalid-name @@ -2859,7 +2859,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 153 + 154 0 convention line-too-long @@ -2869,7 +2869,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 167 + 168 8 convention invalid-name @@ -2879,7 +2879,7 @@

Module vclibpy.components.heat_exchangers.ntu (vclibpy/co - 171 + 172 8 convention invalid-name @@ -2977,7 +2977,7 @@

Module vclibpy.flowsheets.base (vclibpy/flowsheets/base.p cyclic-import R0401 -
Cyclic import (vclibpy.media -> vclibpy.media.cool_prop -> vclibpy.media.media)
+
Cyclic import (vclibpy.media -> vclibpy.media.ref_prop)
@@ -2997,7 +2997,7 @@

Module vclibpy.flowsheets.base (vclibpy/flowsheets/base.p cyclic-import R0401 -
Cyclic import (vclibpy.media -> vclibpy.media.ref_prop)
+
Cyclic import (vclibpy.media -> vclibpy.media.cool_prop -> vclibpy.media.media)
@@ -3007,7 +3007,7 @@

Module vclibpy.flowsheets.base (vclibpy/flowsheets/base.p cyclic-import R0401 -
Cyclic import (vclibpy.components.expansion_valves -> vclibpy.components.expansion_valves.bernoulli)
+
Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_economizer -> vclibpy.flowsheets.vapor_injection)
@@ -3017,7 +3017,7 @@

Module vclibpy.flowsheets.base (vclibpy/flowsheets/base.p cyclic-import R0401 -
Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_phase_separator -> vclibpy.flowsheets.vapor_injection)
+
Cyclic import (vclibpy.components.expansion_valves -> vclibpy.components.expansion_valves.bernoulli)
@@ -3047,7 +3047,7 @@

Module vclibpy.flowsheets.base (vclibpy/flowsheets/base.p cyclic-import R0401 -
Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_economizer -> vclibpy.flowsheets.vapor_injection)
+
Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_phase_separator -> vclibpy.flowsheets.vapor_injection)
diff --git a/docs/main/pylint/pylint.json b/docs/main/pylint/pylint.json index a01d17c..45b3dc4 100644 --- a/docs/main/pylint/pylint.json +++ b/docs/main/pylint/pylint.json @@ -3040,7 +3040,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "", - "line": 94, + "line": 95, "column": 0, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "line-too-long", @@ -3051,7 +3051,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "", - "line": 132, + "line": 133, "column": 0, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "line-too-long", @@ -3062,7 +3062,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "", - "line": 141, + "line": 142, "column": 0, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "line-too-long", @@ -3073,7 +3073,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "", - "line": 151, + "line": 152, "column": 0, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "line-too-long", @@ -3084,7 +3084,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "", - "line": 153, + "line": 154, "column": 0, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "line-too-long", @@ -3106,7 +3106,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_eps", - "line": 66, + "line": 67, "column": 33, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3117,7 +3117,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_R", - "line": 92, + "line": 93, "column": 4, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3128,7 +3128,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_NTU", - "line": 125, + "line": 126, "column": 4, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3139,7 +3139,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_NTU", - "line": 125, + "line": 126, "column": 17, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3150,7 +3150,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_Q_ntu", - "line": 151, + "line": 152, "column": 4, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3161,7 +3161,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_Q_ntu", - "line": 151, + "line": 152, "column": 25, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3172,7 +3172,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_Q_ntu", - "line": 151, + "line": 152, "column": 76, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3183,7 +3183,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_Q_ntu", - "line": 167, + "line": 168, "column": 8, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -3194,7 +3194,7 @@ "type": "convention", "module": "vclibpy.components.heat_exchangers.ntu", "obj": "BasicNTU.calc_Q_ntu", - "line": 171, + "line": 172, "column": 8, "path": "vclibpy/components/heat_exchangers/ntu.py", "symbol": "invalid-name", @@ -6333,7 +6333,7 @@ "column": 0, "path": "vclibpy/flowsheets/base.py", "symbol": "cyclic-import", - "message": "Cyclic import (vclibpy.media -> vclibpy.media.cool_prop -> vclibpy.media.media)", + "message": "Cyclic import (vclibpy.media -> vclibpy.media.ref_prop)", "message-id": "R0401" }, { @@ -6355,7 +6355,7 @@ "column": 0, "path": "vclibpy/flowsheets/base.py", "symbol": "cyclic-import", - "message": "Cyclic import (vclibpy.media -> vclibpy.media.ref_prop)", + "message": "Cyclic import (vclibpy.media -> vclibpy.media.cool_prop -> vclibpy.media.media)", "message-id": "R0401" }, { @@ -6366,7 +6366,7 @@ "column": 0, "path": "vclibpy/flowsheets/base.py", "symbol": "cyclic-import", - "message": "Cyclic import (vclibpy.components.expansion_valves -> vclibpy.components.expansion_valves.bernoulli)", + "message": "Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_economizer -> vclibpy.flowsheets.vapor_injection)", "message-id": "R0401" }, { @@ -6377,7 +6377,7 @@ "column": 0, "path": "vclibpy/flowsheets/base.py", "symbol": "cyclic-import", - "message": "Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_phase_separator -> vclibpy.flowsheets.vapor_injection)", + "message": "Cyclic import (vclibpy.components.expansion_valves -> vclibpy.components.expansion_valves.bernoulli)", "message-id": "R0401" }, { @@ -6410,7 +6410,7 @@ "column": 0, "path": "vclibpy/flowsheets/base.py", "symbol": "cyclic-import", - "message": "Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_economizer -> vclibpy.flowsheets.vapor_injection)", + "message": "Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_phase_separator -> vclibpy.flowsheets.vapor_injection)", "message-id": "R0401" } ], @@ -6864,63 +6864,63 @@ }, "dependencies": { "vclibpy.datamodels": [ - "vclibpy.components.heat_exchangers.heat_exchanger", + "vclibpy", + "vclibpy.flowsheets.standard", + "vclibpy.flowsheets.vapor_injection", "vclibpy.components.compressors.ten_coefficient", - "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", - "vclibpy.components.compressors.rotary", "vclibpy.utils.automation", + "vclibpy.media.states", "vclibpy.flowsheets.base", - "vclibpy.components.compressors.constant_effectivness", "vclibpy.components.heat_exchangers.moving_boundary_ntu", - "vclibpy", - "vclibpy.flowsheets.standard", + "vclibpy.components.compressors.rotary", + "vclibpy.components.compressors.constant_effectivness", "vclibpy.components.compressors.compressor", - "vclibpy.flowsheets.vapor_injection", - "vclibpy.media.states" + "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", + "vclibpy.components.heat_exchangers.heat_exchanger" ], "CoolProp.CoolProp": [ "vclibpy.media.cool_prop" ], "vclibpy.media.media": [ - "vclibpy.media.cool_prop", - "vclibpy.media" + "vclibpy.media", + "vclibpy.media.cool_prop" ], "vclibpy.media.states": [ - "vclibpy.media.cool_prop", - "vclibpy.media" + "vclibpy.media", + "vclibpy.media.cool_prop" ], "ctREFPROP.ctREFPROP": [ "vclibpy.media.ref_prop" ], "vclibpy.media": [ - "vclibpy.components.heat_exchangers.heat_exchanger", - "vclibpy.media.media", - "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", + "vclibpy.components.heat_exchangers.heat_transfer.constant", + "vclibpy.components.component", + "vclibpy.flowsheets.vapor_injection", "vclibpy.media.ref_prop", + "vclibpy.components.heat_exchangers.economizer", + "vclibpy.flowsheets.base", "vclibpy.components.heat_exchangers.heat_transfer.wall", - "vclibpy.components.component", - "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", - "vclibpy.components.heat_exchangers.heat_transfer.constant", - "vclibpy.components.heat_exchangers.heat_transfer.pipe_to_wall", "vclibpy.components.heat_exchangers.moving_boundary_ntu", + "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", + "vclibpy.media.media", + "vclibpy.components.phase_separator", + "vclibpy.components.heat_exchangers.heat_transfer.pipe_to_wall", "vclibpy.utils.ten_coefficient_compressor_reqression", - "vclibpy.components.heat_exchangers.economizer", - "vclibpy.flowsheets.vapor_injection", - "vclibpy.flowsheets.base", - "vclibpy.components.phase_separator" + "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", + "vclibpy.components.heat_exchangers.heat_exchanger" ], "numpy": [ - "vclibpy.media.media", - "vclibpy.components.compressors.ten_coefficient", "vclibpy.components.heat_exchangers.ntu", - "vclibpy.utils.plotting", - "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", + "vclibpy.flowsheets.vapor_injection", + "vclibpy.components.compressors.ten_coefficient", "vclibpy.utils.automation", + "vclibpy.flowsheets.base", "vclibpy.components.heat_exchangers.moving_boundary_ntu", "vclibpy.flowsheets.vapor_injection_economizer", + "vclibpy.utils.plotting", + "vclibpy.media.media", "vclibpy.components.heat_exchangers.heat_transfer.vdi_atlas_air_to_wall", - "vclibpy.flowsheets.base", - "vclibpy.flowsheets.vapor_injection" + "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer" ], "vclibpy.media.cool_prop": [ "vclibpy.media" @@ -6929,34 +6929,34 @@ "vclibpy.media" ], "matplotlib.pyplot": [ - "vclibpy.utils.plotting", - "vclibpy.flowsheets.base" + "vclibpy.flowsheets.base", + "vclibpy.utils.plotting" ], "sdf": [ "vclibpy.utils.plotting", "vclibpy.utils.sdf_" ], "pandas": [ - "vclibpy.components.compressors.ten_coefficient", - "vclibpy.utils.ten_coefficient_compressor_reqression", "vclibpy.utils.automation", - "vclibpy.utils.sdf_" + "vclibpy.utils.ten_coefficient_compressor_reqression", + "vclibpy.utils.sdf_", + "vclibpy.components.compressors.ten_coefficient" ], "vclibpy.components.compressors": [ - "vclibpy.flowsheets.standard", "vclibpy.utils.ten_coefficient_compressor_reqression", + "vclibpy.flowsheets.standard", "vclibpy.flowsheets.vapor_injection" ], "vclibpy": [ - "vclibpy.utils.nominal_design", "vclibpy.utils.ten_coefficient_compressor_reqression", - "vclibpy.flowsheets.base" + "vclibpy.flowsheets.base", + "vclibpy.utils.nominal_design" ], "vclibpy.flowsheets": [ - "vclibpy.utils.nominal_design", - "vclibpy.flowsheets.standard", "vclibpy.utils.automation", - "vclibpy.flowsheets.vapor_injection" + "vclibpy.flowsheets.standard", + "vclibpy.flowsheets.vapor_injection", + "vclibpy.utils.nominal_design" ], "vclibpy.utils.automation": [ "vclibpy.utils" @@ -6968,15 +6968,15 @@ "vclibpy.utils.automation" ], "vclibpy.components.component": [ - "vclibpy.components.heat_exchangers.heat_exchanger", + "vclibpy.components.phase_separator", "vclibpy.components.compressors.compressor", "vclibpy.components.expansion_valves.expansion_valve", "vclibpy.flowsheets.base", - "vclibpy.components.phase_separator" + "vclibpy.components.heat_exchangers.heat_exchanger" ], "vclibpy.components.expansion_valves": [ - "vclibpy.components.expansion_valves.bernoulli", "vclibpy.flowsheets.standard", + "vclibpy.components.expansion_valves.bernoulli", "vclibpy.flowsheets.vapor_injection" ], "vclibpy.components.expansion_valves.expansion_valve": [ @@ -6990,18 +6990,18 @@ "vclibpy.components.heat_exchangers" ], "vclibpy.components.heat_exchangers.ntu": [ - "vclibpy.components.heat_exchangers.moving_boundary_ntu", - "vclibpy.components.heat_exchangers.economizer" + "vclibpy.components.heat_exchangers.economizer", + "vclibpy.components.heat_exchangers.moving_boundary_ntu" ], "vclibpy.components.heat_exchangers.moving_boundary_ntu": [ "vclibpy.components.heat_exchangers" ], "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer": [ - "vclibpy.components.heat_exchangers.heat_exchanger", - "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", "vclibpy.components.heat_exchangers.heat_transfer.wall", + "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", + "vclibpy.components.heat_exchangers.heat_transfer", "vclibpy.components.heat_exchangers.heat_transfer.pipe_to_wall", - "vclibpy.components.heat_exchangers.heat_transfer" + "vclibpy.components.heat_exchangers.heat_exchanger" ], "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall": [ "vclibpy.components.heat_exchangers.heat_transfer.vdi_atlas_air_to_wall" @@ -7013,10 +7013,10 @@ "vclibpy.components.heat_exchangers.heat_transfer" ], "vclibpy.components.compressors.compressor": [ - "vclibpy.components.compressors.rotary", - "vclibpy.components.compressors.ten_coefficient", "vclibpy.components.compressors", - "vclibpy.components.compressors.constant_effectivness" + "vclibpy.components.compressors.constant_effectivness", + "vclibpy.components.compressors.rotary", + "vclibpy.components.compressors.ten_coefficient" ], "vclibpy.components.compressors.rotary": [ "vclibpy.components.compressors" @@ -7028,8 +7028,8 @@ "vclibpy.components.compressors" ], "vclibpy.flowsheets.vapor_injection": [ - "vclibpy.flowsheets.vapor_injection_phase_separator", - "vclibpy.flowsheets.vapor_injection_economizer" + "vclibpy.flowsheets.vapor_injection_economizer", + "vclibpy.flowsheets.vapor_injection_phase_separator" ], "vclibpy.components.heat_exchangers.economizer": [ "vclibpy.flowsheets.vapor_injection_economizer" @@ -7530,63 +7530,63 @@ }, "dependencies": { "vclibpy.datamodels": [ - "vclibpy.components.heat_exchangers.heat_exchanger", + "vclibpy", + "vclibpy.flowsheets.standard", + "vclibpy.flowsheets.vapor_injection", "vclibpy.components.compressors.ten_coefficient", - "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", - "vclibpy.components.compressors.rotary", - "vclibpy.flowsheets.base", "vclibpy.utils.automation", - "vclibpy.components.compressors.constant_effectivness", + "vclibpy.media.states", + "vclibpy.flowsheets.base", "vclibpy.components.heat_exchangers.moving_boundary_ntu", - "vclibpy", - "vclibpy.flowsheets.standard", + "vclibpy.components.compressors.rotary", + "vclibpy.components.compressors.constant_effectivness", "vclibpy.components.compressors.compressor", - "vclibpy.flowsheets.vapor_injection", - "vclibpy.media.states" + "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", + "vclibpy.components.heat_exchangers.heat_exchanger" ], "CoolProp.CoolProp": [ "vclibpy.media.cool_prop" ], "vclibpy.media.media": [ - "vclibpy.media.cool_prop", - "vclibpy.media" + "vclibpy.media", + "vclibpy.media.cool_prop" ], "vclibpy.media.states": [ - "vclibpy.media.cool_prop", - "vclibpy.media" + "vclibpy.media", + "vclibpy.media.cool_prop" ], "ctREFPROP.ctREFPROP": [ "vclibpy.media.ref_prop" ], "vclibpy.media": [ - "vclibpy.components.heat_exchangers.heat_exchanger", - "vclibpy.media.media", - "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", + "vclibpy.components.heat_exchangers.heat_transfer.constant", + "vclibpy.flowsheets.vapor_injection", + "vclibpy.components.component", "vclibpy.media.ref_prop", + "vclibpy.components.heat_exchangers.economizer", + "vclibpy.flowsheets.base", "vclibpy.components.heat_exchangers.heat_transfer.wall", - "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", - "vclibpy.components.component", - "vclibpy.components.heat_exchangers.heat_transfer.pipe_to_wall", - "vclibpy.components.heat_exchangers.heat_transfer.constant", "vclibpy.components.heat_exchangers.moving_boundary_ntu", + "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", + "vclibpy.media.media", + "vclibpy.components.phase_separator", + "vclibpy.components.heat_exchangers.heat_transfer.pipe_to_wall", "vclibpy.utils.ten_coefficient_compressor_reqression", - "vclibpy.components.heat_exchangers.economizer", - "vclibpy.flowsheets.vapor_injection", - "vclibpy.flowsheets.base", - "vclibpy.components.phase_separator" + "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", + "vclibpy.components.heat_exchangers.heat_exchanger" ], "numpy": [ - "vclibpy.media.media", - "vclibpy.components.compressors.ten_coefficient", "vclibpy.components.heat_exchangers.ntu", - "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer", - "vclibpy.utils.plotting", + "vclibpy.flowsheets.vapor_injection", + "vclibpy.components.compressors.ten_coefficient", "vclibpy.utils.automation", + "vclibpy.flowsheets.base", "vclibpy.components.heat_exchangers.moving_boundary_ntu", "vclibpy.flowsheets.vapor_injection_economizer", + "vclibpy.utils.plotting", + "vclibpy.media.media", "vclibpy.components.heat_exchangers.heat_transfer.vdi_atlas_air_to_wall", - "vclibpy.flowsheets.base", - "vclibpy.flowsheets.vapor_injection" + "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer" ], "vclibpy.media.cool_prop": [ "vclibpy.media" @@ -7595,33 +7595,33 @@ "vclibpy.media" ], "matplotlib.pyplot": [ - "vclibpy.utils.plotting", - "vclibpy.flowsheets.base" + "vclibpy.flowsheets.base", + "vclibpy.utils.plotting" ], "sdf": [ "vclibpy.utils.plotting", "vclibpy.utils.sdf_" ], "pandas": [ - "vclibpy.components.compressors.ten_coefficient", - "vclibpy.utils.ten_coefficient_compressor_reqression", "vclibpy.utils.automation", - "vclibpy.utils.sdf_" + "vclibpy.utils.ten_coefficient_compressor_reqression", + "vclibpy.utils.sdf_", + "vclibpy.components.compressors.ten_coefficient" ], "vclibpy.components.compressors": [ - "vclibpy.flowsheets.standard", "vclibpy.utils.ten_coefficient_compressor_reqression", + "vclibpy.flowsheets.standard", "vclibpy.flowsheets.vapor_injection" ], "vclibpy": [ - "vclibpy.utils.nominal_design", "vclibpy.utils.ten_coefficient_compressor_reqression", - "vclibpy.flowsheets.base" + "vclibpy.flowsheets.base", + "vclibpy.utils.nominal_design" ], "vclibpy.flowsheets": [ - "vclibpy.utils.nominal_design", - "vclibpy.flowsheets.standard", "vclibpy.utils.automation", + "vclibpy.flowsheets.standard", + "vclibpy.utils.nominal_design", "vclibpy.flowsheets.vapor_injection" ], "vclibpy.utils.automation": [ @@ -7634,15 +7634,15 @@ "vclibpy.utils.automation" ], "vclibpy.components.component": [ - "vclibpy.components.heat_exchangers.heat_exchanger", + "vclibpy.components.phase_separator", "vclibpy.components.compressors.compressor", "vclibpy.components.expansion_valves.expansion_valve", "vclibpy.flowsheets.base", - "vclibpy.components.phase_separator" + "vclibpy.components.heat_exchangers.heat_exchanger" ], "vclibpy.components.expansion_valves": [ - "vclibpy.components.expansion_valves.bernoulli", "vclibpy.flowsheets.standard", + "vclibpy.components.expansion_valves.bernoulli", "vclibpy.flowsheets.vapor_injection" ], "vclibpy.components.expansion_valves.expansion_valve": [ @@ -7656,18 +7656,18 @@ "vclibpy.components.heat_exchangers" ], "vclibpy.components.heat_exchangers.ntu": [ - "vclibpy.components.heat_exchangers.moving_boundary_ntu", - "vclibpy.components.heat_exchangers.economizer" + "vclibpy.components.heat_exchangers.economizer", + "vclibpy.components.heat_exchangers.moving_boundary_ntu" ], "vclibpy.components.heat_exchangers.moving_boundary_ntu": [ "vclibpy.components.heat_exchangers" ], "vclibpy.components.heat_exchangers.heat_transfer.heat_transfer": [ - "vclibpy.components.heat_exchangers.heat_exchanger", - "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", "vclibpy.components.heat_exchangers.heat_transfer.wall", + "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall", + "vclibpy.components.heat_exchangers.heat_transfer", "vclibpy.components.heat_exchangers.heat_transfer.pipe_to_wall", - "vclibpy.components.heat_exchangers.heat_transfer" + "vclibpy.components.heat_exchangers.heat_exchanger" ], "vclibpy.components.heat_exchangers.heat_transfer.air_to_wall": [ "vclibpy.components.heat_exchangers.heat_transfer.vdi_atlas_air_to_wall" @@ -7680,9 +7680,9 @@ ], "vclibpy.components.compressors.compressor": [ "vclibpy.components.compressors", - "vclibpy.components.compressors.ten_coefficient", + "vclibpy.components.compressors.constant_effectivness", "vclibpy.components.compressors.rotary", - "vclibpy.components.compressors.constant_effectivness" + "vclibpy.components.compressors.ten_coefficient" ], "vclibpy.components.compressors.rotary": [ "vclibpy.components.compressors" @@ -7694,8 +7694,8 @@ "vclibpy.components.compressors" ], "vclibpy.flowsheets.vapor_injection": [ - "vclibpy.flowsheets.vapor_injection_phase_separator", - "vclibpy.flowsheets.vapor_injection_economizer" + "vclibpy.flowsheets.vapor_injection_economizer", + "vclibpy.flowsheets.vapor_injection_phase_separator" ], "vclibpy.components.heat_exchangers.economizer": [ "vclibpy.flowsheets.vapor_injection_economizer" diff --git a/docs/main/pylint/pylint.txt b/docs/main/pylint/pylint.txt index f424fc6..acae9aa 100644 --- a/docs/main/pylint/pylint.txt +++ b/docs/main/pylint/pylint.txt @@ -291,21 +291,21 @@ vclibpy/components/expansion_valves/__init__.py:1:0: C0114: Missing module docst ************* Module vclibpy.components.expansion_valves.expansion_valve vclibpy/components/expansion_valves/expansion_valve.py:20:8: C0103: Attribute name "A" doesn't conform to snake_case naming style (invalid-name) ************* Module vclibpy.components.heat_exchangers.ntu -vclibpy/components/heat_exchangers/ntu.py:94:0: C0301: Line too long (120/100) (line-too-long) -vclibpy/components/heat_exchangers/ntu.py:132:0: C0301: Line too long (106/100) (line-too-long) -vclibpy/components/heat_exchangers/ntu.py:141:0: C0301: Line too long (118/100) (line-too-long) -vclibpy/components/heat_exchangers/ntu.py:151:0: C0301: Line too long (104/100) (line-too-long) -vclibpy/components/heat_exchangers/ntu.py:153:0: C0301: Line too long (110/100) (line-too-long) +vclibpy/components/heat_exchangers/ntu.py:95:0: C0301: Line too long (120/100) (line-too-long) +vclibpy/components/heat_exchangers/ntu.py:133:0: C0301: Line too long (106/100) (line-too-long) +vclibpy/components/heat_exchangers/ntu.py:142:0: C0301: Line too long (118/100) (line-too-long) +vclibpy/components/heat_exchangers/ntu.py:152:0: C0301: Line too long (104/100) (line-too-long) +vclibpy/components/heat_exchangers/ntu.py:154:0: C0301: Line too long (110/100) (line-too-long) vclibpy/components/heat_exchangers/ntu.py:1:0: C0114: Missing module docstring (missing-module-docstring) -vclibpy/components/heat_exchangers/ntu.py:66:33: C0103: Argument name "NTU" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:92:4: C0103: Method name "calc_R" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:125:4: C0103: Method name "calc_NTU" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:125:17: C0103: Argument name "A" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:151:4: C0103: Method name "calc_Q_ntu" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:151:25: C0103: Argument name "dT_max" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:151:76: C0103: Argument name "A" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:167:8: C0103: Variable name "NTU" doesn't conform to snake_case naming style (invalid-name) -vclibpy/components/heat_exchangers/ntu.py:171:8: C0103: Variable name "Q_max" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:67:33: C0103: Argument name "NTU" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:93:4: C0103: Method name "calc_R" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:126:4: C0103: Method name "calc_NTU" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:126:17: C0103: Argument name "A" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:152:4: C0103: Method name "calc_Q_ntu" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:152:25: C0103: Argument name "dT_max" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:152:76: C0103: Argument name "A" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:168:8: C0103: Variable name "NTU" doesn't conform to snake_case naming style (invalid-name) +vclibpy/components/heat_exchangers/ntu.py:172:8: C0103: Variable name "Q_max" doesn't conform to snake_case naming style (invalid-name) ************* Module vclibpy.components.heat_exchangers.economizer vclibpy/components/heat_exchangers/economizer.py:1:0: C0114: Missing module docstring (missing-module-docstring) vclibpy/components/heat_exchangers/economizer.py:40:4: C0116: Missing function or method docstring (missing-function-docstring) @@ -613,13 +613,13 @@ vclibpy/flowsheets/base.py:414:8: C0103: Variable name "delta_H_con" doesn't con vclibpy/flowsheets/base.py:418:8: C0103: Variable name "delta_H_eva" doesn't conform to snake_case naming style (invalid-name) vclibpy/flowsheets/base.py:5:0: C0411: standard import "from abc import abstractmethod" should be placed before "import numpy as np" (wrong-import-order) vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.media -> vclibpy.media.media) (cyclic-import) -vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.media -> vclibpy.media.ref_prop) (cyclic-import) vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.media -> vclibpy.media.cool_prop -> vclibpy.media.media) (cyclic-import) +vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.media -> vclibpy.media.ref_prop) (cyclic-import) vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.standard) (cyclic-import) vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.components.expansion_valves -> vclibpy.components.expansion_valves.bernoulli) (cyclic-import) vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.utils -> vclibpy.utils.automation) (cyclic-import) -vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_economizer -> vclibpy.flowsheets.vapor_injection) (cyclic-import) vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_phase_separator -> vclibpy.flowsheets.vapor_injection) (cyclic-import) +vclibpy/flowsheets/base.py:1:0: R0401: Cyclic import (vclibpy.flowsheets -> vclibpy.flowsheets.vapor_injection_economizer -> vclibpy.flowsheets.vapor_injection) (cyclic-import) ----------------------------------- Your code has been rated at 7.41/10