Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.2.0 added creating Flowsheet from file #17

Merged
merged 1 commit into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 24 additions & 21 deletions docs/source/sg_execution_times.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Computation times
=================
**00:36.744** total execution time for 11 files **from all galleries**:
**00:00.442** total execution time for 12 files **from all galleries**:

.. container::

Expand All @@ -32,36 +32,39 @@ Computation times
* - Example
- Time
- Mem (MB)
* - :ref:`sphx_glr_auto_examples_examples_04_block_model_02_create_block_model.py` (``..\..\examples\04_block_model\02_create_block_model.py``)
- 00:25.667
* - :ref:`sphx_glr_auto_examples_examples_04_block_model_01_consuming_omf.py` (``..\..\examples\04_block_model\01_consuming_omf.py``)
- 00:00.442
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_04_block_model_03_load_block_model.py` (``..\..\examples\04_block_model\03_load_block_model.py``)
- 00:04.207
* - :ref:`sphx_glr_auto_examples_examples_01_getting_started_01_create_sample.py` (``..\..\examples\01_getting_started\01_create_sample.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_01_getting_started_03_plot_demo.py` (``..\..\examples\01_getting_started\03_plot_demo.py``)
- 00:02.871
* - :ref:`sphx_glr_auto_examples_examples_01_getting_started_02_math_operations.py` (``..\..\examples\01_getting_started\02_math_operations.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_03_flowsheet_01_flowsheet_basics.py` (``..\..\examples\03_flowsheet\01_flowsheet_basics.py``)
- 00:01.544
* - :ref:`sphx_glr_auto_examples_examples_01_getting_started_03_plot_demo.py` (``..\..\examples\01_getting_started\03_plot_demo.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_02_interval_sample_01_interval_sample.py` (``..\..\examples\02_interval_sample\01_interval_sample.py``)
- 00:00.961
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_01_getting_started_02_math_operations.py` (``..\..\examples\01_getting_started\02_math_operations.py``)
- 00:00.608
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_02_interval_sample_02_interval_data_sink_float.py` (``..\..\examples\02_interval_sample\02_interval_data_sink_float.py``)
- 00:00.356
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_01_getting_started_01_create_sample.py` (``..\..\examples\01_getting_started\01_create_sample.py``)
- 00:00.209
* - :ref:`sphx_glr_auto_examples_examples_03_flowsheet_01_flowsheet_basics.py` (``..\..\examples\03_flowsheet\01_flowsheet_basics.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_04_block_model_01_consuming_omf.py` (``..\..\examples\04_block_model\01_consuming_omf.py``)
- 00:00.187
* - :ref:`sphx_glr_auto_examples_examples_03_flowsheet_02_flowsheet_from_file.py` (``..\..\examples\03_flowsheet\02_flowsheet_from_file.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_06_map_01_mapping.py` (``..\..\examples\06_map\01_mapping.py``)
- 00:00.069
* - :ref:`sphx_glr_auto_examples_examples_04_block_model_02_create_block_model.py` (``..\..\examples\04_block_model\02_create_block_model.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_04_block_model_03_load_block_model.py` (``..\..\examples\04_block_model\03_load_block_model.py``)
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_05_mass_balance_01_mass_balance.py` (``..\..\examples\05_mass_balance\01_mass_balance.py``)
- 00:00.065
- 00:00.000
- 0.0
* - :ref:`sphx_glr_auto_examples_examples_06_map_01_mapping.py` (``..\..\examples\06_map\01_mapping.py``)
- 00:00.000
- 0.0
31 changes: 15 additions & 16 deletions elphick/geomet/config/flowsheet_example.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
FLOWSHEET:
flowsheet:
name: Flowsheet
streams:
streams: # graph edges
Feed:
node_in: 0
node_out: 1
name: Feed
node_in: feed
node_out: screen
Coarse:
node_in: 1
node_out: 2
name: Coarse
node_in: screen
node_out: lump
Fine:
node_in: 1
node_out: 3
nodes:
0:
name: Fine
node_in: screen
node_out: fines
operations: # graph nodes
feed:
name: feed
subset: 0
1:
screen:
name: screen
subset: 0
2:
lump:
name: lump
subset: -1
3:
fines:
name: fines
subset: 1
92 changes: 81 additions & 11 deletions elphick/geomet/flowsheet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import logging
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union, TypeVar

import matplotlib
Expand All @@ -8,6 +10,7 @@
import pandas as pd
import plotly.graph_objects as go
import seaborn as sns
import yaml
from matplotlib import pyplot as plt
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
from plotly.subplots import make_subplots
Expand Down Expand Up @@ -104,6 +107,71 @@ def from_dataframe(cls, df: pd.DataFrame, name: Optional[str] = 'Flowsheet',
streams: list[Sample] = streams_from_dataframe(df=df, mc_name_col=mc_name_col, n_jobs=n_jobs)
return cls().from_objects(objects=streams, name=name)

@classmethod
def from_dict(cls, config: dict) -> FS:
"""Create a flowsheet from a dictionary

Args:
config: dictionary containing the flowsheet configuration

Returns:
A Flowsheet object with no data on the edges
"""
if 'FLOWSHEET' not in config:
raise ValueError("Dictionary does not contain 'FLOWSHEET' root node")

flowsheet_config = config['FLOWSHEET']

# create the Stream objects
bunch_of_edges: list = []
for stream, stream_config in flowsheet_config['streams'].items():
bunch_of_edges.append(
(stream_config['node_in'], stream_config['node_out'], {'mc': None, 'name': stream_config['name']}))

graph = nx.DiGraph(name=flowsheet_config['flowsheet']['name'])
graph.add_edges_from(bunch_of_edges)
operation_objects: dict = {}
for node in graph.nodes:
operation_objects[node] = Operation(name=node)
nx.set_node_attributes(graph, operation_objects, 'mc')

graph = nx.convert_node_labels_to_integers(graph)

obj = cls()
obj.graph = graph

return obj

@classmethod
def from_yaml(cls, file_path: Path) -> FS:
"""Create a flowsheet from yaml

Args:
file_path: path to the yaml file

Returns:
A Flowsheet object with no data on the edges
"""
with open(file_path, 'r') as file:
config = yaml.safe_load(file)

return cls.from_dict(config)

@classmethod
def from_json(cls, file_path: Path) -> FS:
"""Create a flowsheet from json

Args:
file_path: path to the json file

Returns:
A Flowsheet object with no data on the edges
"""
with open(file_path, 'r') as file:
config = json.load(file)

return cls.from_dict(config)

def solve(self):
"""Solve missing streams"""

Expand Down Expand Up @@ -180,8 +248,8 @@ def plot(self, orientation: str = 'horizontal') -> plt.Figure:
node_colors: List = []

for node1, node2, data in self.graph.edges(data=True):
edge_labels[(node1, node2)] = data['mc'].name
if data['mc'].status.ok:
edge_labels[(node1, node2)] = data['mc'].name if data['mc'] is not None else data['name']
if data['mc'] and data['mc'].status.ok:
edge_colors.append('gray')
else:
edge_colors.append('red')
Expand Down Expand Up @@ -727,23 +795,25 @@ def set_node_names(self, node_names: Dict[int, str]):
self.graph.nodes[node]['mc'].name = node_names[node]

def set_stream_data(self, stream_data: dict[str, Optional[MC]]):
"""Set the data (MassComposition) of network edges (streams) with a Dict
"""
"""Set the data (MassComposition) of network edges (streams) with a Dict"""
for stream_name, stream_data in stream_data.items():
stream_found = False
nodes_to_refresh = set()
for u, v, data in self.graph.edges(data=True):
if ('mc' in data.keys()) and (data['mc'].name == stream_name):
if 'mc' in data.keys() and (data['mc'].name if data['mc'] is not None else data['name']) == stream_name:
self._logger.info(f'Setting data on stream {stream_name}')
data['mc'] = stream_data
stream_found = True
# refresh the node status
for node in [u, v]:
self.graph.nodes[node]['mc'].inputs = [self.graph.get_edge_data(e[0], e[1])['mc'] for e in
self.graph.in_edges(node)]
self.graph.nodes[node]['mc'].outputs = [self.graph.get_edge_data(e[0], e[1])['mc'] for e in
self.graph.out_edges(node)]
nodes_to_refresh.update([u, v])
if not stream_found:
self._logger.warning(f'Stream {stream_name} not found in graph')
else:
# refresh the node status
for node in nodes_to_refresh:
self.graph.nodes[node]['mc'].inputs = [self.graph.get_edge_data(e[0], e[1])['mc'] for e in
self.graph.in_edges(node)]
self.graph.nodes[node]['mc'].outputs = [self.graph.get_edge_data(e[0], e[1])['mc'] for e in
self.graph.out_edges(node)]

def streams_to_dict(self) -> Dict[str, MC]:
"""Export the Stream objects to a Dict
Expand Down
77 changes: 77 additions & 0 deletions examples/03_flowsheet/02_flowsheet_from_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Flowsheet from File
===================

It is possible to create a Flowsheet object from a file. This example demonstrates how to create a Flowsheet object
from a file.

"""
from copy import deepcopy
from pathlib import Path
from typing import Dict

import pandas as pd
from matplotlib import pyplot as plt

from elphick.geomet import Stream, Flowsheet
from elphick.geomet.operation import OP, Operation
from elphick.geomet.utils.data import sample_data

# %%
#
# YAML file
# ---------
#
# The yaml file needs to follow this structure.

yaml_filepath: Path = Path('./../../elphick/geomet/config/flowsheet_example.yaml')
yaml_config: str = yaml_filepath.read_text()
yaml_config

# %%
# Create Flowsheet
# ----------------

fs: Flowsheet = Flowsheet.from_yaml(yaml_filepath)
fs

# %%
# The Flowsheet object can be visualized as a network.
# The streams are not yet populated, so are shown in red.
fs.plot()
plt

# %%
# Load the data
# -------------
# Load data onto two of the three streams.

df_feed: pd.DataFrame = sample_data()
obj_feed: Stream = Stream(df_feed, name='Feed')

# assume the Coarse stream is 0.4 of the Feed stream mass
df_coarse: pd.DataFrame = df_feed.copy()
df_coarse['wet_mass'] = df_coarse['wet_mass'] * 0.4
df_coarse['mass_dry'] = df_coarse['mass_dry'] * 0.4
obj_coarse: Stream = Stream(df_coarse, name='Coarse')

fs.set_stream_data(stream_data={'Feed': obj_feed, 'Coarse': obj_coarse})

# %%
fs.plot()
plt

# %%
# Solve the flowsheet
# -------------------

fs.solve()
fs.plot()
plt

# %%
# Plot the mass balance
# ---------------------

fig = fs.table_plot(plot_type='network')
fig.show()
Loading
Loading