Partition Models

Partition models, (a.k.a. partition curves) define the separation of a unit operation / process.

In the one dimensional case, the Partition Number (PN) is represents the probability that a particle will report to the defined reference stream.

Consider a desliming cyclone that aims to separate a slurry at 150 micron. The reference stream is defined as the Underflow (UF), since that is the “stream of value” in our simple example.

TODO

Add a reference to partition curves.

from functools import partial

import numpy as np
import pandas as pd
import plotly
import plotly.graph_objects as go
from scipy.interpolate import PchipInterpolator

from elphick.mass_composition import MassComposition
from elphick.mass_composition.datasets.sample_data import size_by_assay
from elphick.mass_composition.flowsheet import Flowsheet
from elphick.mass_composition.utils.partition import napier_munn
from elphick.mass_composition.utils.pd_utils import calculate_partition

# sphinx_gallery_thumbnail_number = -1

Create a mass-composition object

We get some demo data in the form of a pandas DataFrame

df_data: pd.DataFrame = size_by_assay()
mc_feed: MassComposition = MassComposition(df_data, name='size sample')
print(mc_feed)
size sample
<xarray.Dataset> Size: 336B
Dimensions:   (size: 6)
Coordinates:
  * size      (size) object 48B [0.85, 2.0) [0.5, 0.85) ... [0.0, 0.045)
Data variables:
    mass_wet  (size) float64 48B 3.3 9.9 26.5 2.5 8.8 49.0
    mass_dry  (size) float64 48B 3.3 9.9 26.5 2.5 8.8 49.0
    H2O       (size) float64 48B 0.0 0.0 0.0 0.0 0.0 0.0
    Fe        (size) float64 48B 64.15 64.33 64.52 62.65 62.81 55.95
    SiO2      (size) float64 48B 2.04 2.05 1.84 2.88 2.12 6.39
    Al2O3     (size) float64 48B 2.68 2.23 2.19 3.32 2.25 6.34
Attributes:
    mc_name:            size sample
    mc_vars_mass:       ['mass_wet', 'mass_dry']
    mc_vars_chem:       ['Fe', 'SiO2', 'Al2O3']
    mc_vars_attrs:      []
    mc_interval_edges:  {'size': {'left': 'retained', 'right': 'passing'}}

Define and Apply the Partition

We partially initialise the partition function The dim argument is added to inform the split method which dimension to apply the function/split to

part_cyclone = partial(napier_munn, d50=0.150, ep=0.1, dim='size')

Separate the object using the defined partitions. UF = Underflow, OF = Overflow

mc_uf, mc_of = mc_feed.split_by_partition(partition_definition=part_cyclone, name_1='underflow', name_2='overflow')
fs: Flowsheet = Flowsheet().from_streams([mc_feed, mc_uf, mc_of])

fig = fs.table_plot(table_pos='left',
                     sankey_color_var='Fe', sankey_edge_colormap='copper_r', sankey_vmin=50, sankey_vmax=70)
fig


We’ll now get the partition data from the objects

df_partition: pd.DataFrame = mc_feed.calculate_partition(ref=mc_uf)
df_partition
/home/runner/work/mass-composition/mass-composition/elphick/mass_composition/mass_composition.py:1386: FutureWarning:

The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
da PN
size
[0.85, 2.0) 1.303840 0.999997
[0.5, 0.85) 0.651920 0.995995
[0.15, 0.5) 0.273861 0.795960
[0.075, 0.15) 0.106066 0.381583
[0.045, 0.075) 0.058095 0.266972
[0.0, 0.045) 0.020855 0.194771


Create an interpolator from the data. As a Callable, the spline can be used to split a MassComposition object.

da = np.linspace(0.01, df_partition.index.right.max(), num=500)
spline_partition = PchipInterpolator(x=df_partition.sort_index()['da'], y=df_partition.sort_index()['PN'])
pn_extracted = spline_partition(da)

Plot the extracted data, and the spline on the input partition curve to visually validate.

pn_original = part_cyclone(da) / 100

fig = go.Figure(go.Scatter(x=da, y=pn_original, name='Input Partition', line=dict(width=5, color='DarkSlateGrey')))
fig.add_trace(go.Scatter(x=df_partition['da'], y=df_partition['PN'], name='Extracted Partition Data', mode='markers',
                         marker=dict(size=12, color='red', line=dict(width=2, color='DarkSlateGrey'))))
fig.add_trace(
    go.Scatter(x=da, y=pn_extracted, name='Extracted Partition Curve', line=dict(width=2, color='red', dash='dash')))

fig.update_xaxes(type="log")
fig.update_layout(title='Partition Round Trip Check', xaxis_title='da', yaxis_title='PN', yaxis_range=[0, 1.05])

# noinspection PyTypeChecker
plotly.io.show(fig)

There are differences in the re-created partition at the coarser sizes. It would be interesting to investigate if up-sampling in advance of partition generation would reduce this difference. Alternatively, the napier_munn parameteric partition function could be fitted to reduce the difference.

Pandas Function

The same functionality is available in pandas

df_partition_2: pd.DataFrame = mc_feed.data.to_dataframe().pipe(calculate_partition, df_ref=mc_uf.data.to_dataframe(),
                                                                col_mass_dry='mass_dry')
df_partition_2
da PN
size
[0.85, 2.0) 1.303840 0.999997
[0.5, 0.85) 0.651920 0.995995
[0.15, 0.5) 0.273861 0.795960
[0.075, 0.15) 0.106066 0.381583
[0.045, 0.075) 0.058095 0.266972
[0.0, 0.045) 0.020855 0.194771


pd.testing.assert_frame_equal(df_partition, df_partition_2)

Total running time of the script: ( 0 minutes 0.496 seconds)

Gallery generated by Sphinx-Gallery