geometallurgy 0.4.6__tar.gz → 0.4.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/PKG-INFO +1 -1
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/base.py +115 -3
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/config/mc_config.yml +1 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/pyproject.toml +1 -1
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/LICENSE +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/README.md +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/__init__.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/block_model.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/config/__init__.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/config/config_read.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/config/flowsheet_example.yaml +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/data/downloader.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/data/register.csv +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/datasets/__init__.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/datasets/datasets.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/datasets/downloader.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/datasets/register.csv +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/datasets/sample_data.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/extras.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/flowsheet/__init__.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/flowsheet/flowsheet.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/flowsheet/loader.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/flowsheet/operation.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/flowsheet/stream.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/interval_sample.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/io.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/plot.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/profile.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/sample.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/__init__.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/amenability.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/block_model_converter.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/components.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/data.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/interp.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/layout.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/moisture.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/pandas.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/parallel.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/partition.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/sampling.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/size.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/timer.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/utils/viz.py +0 -0
- {geometallurgy-0.4.6 → geometallurgy-0.4.8}/elphick/geomet/validate.py.hide +0 -0
|
@@ -4,10 +4,12 @@ import logging
|
|
|
4
4
|
import re
|
|
5
5
|
from abc import ABC
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Optional, Union, Literal, TypeVar, TYPE_CHECKING
|
|
7
|
+
from typing import Optional, Union, Literal, TypeVar, TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
import pandas as pd
|
|
11
|
+
import plotly.express as px
|
|
12
|
+
import plotly.graph_objects as go
|
|
11
13
|
|
|
12
14
|
from elphick.geomet.config import read_yaml
|
|
13
15
|
from elphick.geomet.utils.components import get_components, is_compositional
|
|
@@ -17,8 +19,6 @@ from elphick.geomet.utils.sampling import random_int
|
|
|
17
19
|
from elphick.geomet.utils.timer import log_timer
|
|
18
20
|
from .config.config_read import get_column_config
|
|
19
21
|
from .plot import parallel_plot, comparison_plot
|
|
20
|
-
import plotly.express as px
|
|
21
|
-
import plotly.graph_objects as go
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from elphick.geomet.flowsheet.stream import Stream
|
|
@@ -138,6 +138,22 @@ class MassComposition(ABC):
|
|
|
138
138
|
# Recalculate the aggregate whenever the data changes
|
|
139
139
|
self.aggregate = self.weight_average()
|
|
140
140
|
|
|
141
|
+
def get_mass_data(self, include_moisture: bool = True) -> pd.DataFrame:
|
|
142
|
+
"""Get the mass data
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
include_moisture: If True (and moisture is in scope), include the moisture mass column
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
if include_moisture and self.moisture_in_scope:
|
|
151
|
+
moisture_mass = self._mass_data[self.mass_wet_var] - self._mass_data[self.mass_dry_var]
|
|
152
|
+
mass_data: pd.DataFrame = self._mass_data.copy()
|
|
153
|
+
mass_data.insert(loc=2, column=self.moisture_column, value=moisture_mass)
|
|
154
|
+
return mass_data
|
|
155
|
+
return self._mass_data
|
|
156
|
+
|
|
141
157
|
@property
|
|
142
158
|
def aggregate(self) -> pd.DataFrame:
|
|
143
159
|
if self._aggregate is None and self._mass_data is not None:
|
|
@@ -235,6 +251,49 @@ class MassComposition(ABC):
|
|
|
235
251
|
|
|
236
252
|
return self
|
|
237
253
|
|
|
254
|
+
def clip_recovery(self, other: MC, recovery_bounds: tuple[float, float] = (0.01, 0.99),
|
|
255
|
+
allow_moisture_coercion: bool = True) -> MC:
|
|
256
|
+
"""Clip the recovery to the specified bounds and recalculate the estimate.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
other: The other MassComposition object, from which the recovery of self is calculated.
|
|
260
|
+
recovery_bounds: The bounds for the recovery between 0.0 and 1.0
|
|
261
|
+
allow_moisture_coercion: if True, allow the wet mass to be modified to maintain the moisture (in the
|
|
262
|
+
case that dry mass is clipped to manage recovery)
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
The MassComposition object with the recovery clipped to the bounds.
|
|
266
|
+
"""
|
|
267
|
+
recovery: pd.DataFrame = (self.get_mass_data(include_moisture=False) /
|
|
268
|
+
other.get_mass_data(include_moisture=False))
|
|
269
|
+
|
|
270
|
+
# Limit the recovery to the bounds
|
|
271
|
+
before_clip = recovery.copy()
|
|
272
|
+
recovery = recovery.clip(lower=recovery_bounds[0], upper=recovery_bounds[1]).fillna(0.0)
|
|
273
|
+
|
|
274
|
+
# Check if any records were affected
|
|
275
|
+
affected_indexes = set(recovery.index[np.any(before_clip != recovery, axis=1)])
|
|
276
|
+
if affected_indexes:
|
|
277
|
+
# Recalculate the estimate from the bound recovery
|
|
278
|
+
new_mass: pd.DataFrame = recovery * other.get_mass_data(include_moisture=False)[recovery.columns]
|
|
279
|
+
|
|
280
|
+
if self.moisture_in_scope and allow_moisture_coercion:
|
|
281
|
+
# Calculate the moisture from the new mass
|
|
282
|
+
new_mass[self.mass_wet_var] = solve_mass_moisture(mass_dry=new_mass[self.mass_dry_var],
|
|
283
|
+
moisture=self.data[self.moisture_column])
|
|
284
|
+
|
|
285
|
+
# Log the top 50 records affected by the recovery coercion
|
|
286
|
+
affected_indexes_list = sorted(affected_indexes)[:50]
|
|
287
|
+
self._logger.info(f"Recovery coercion affected {len(affected_indexes)} records. "
|
|
288
|
+
f"Affected indexes (first 50): {affected_indexes_list}")
|
|
289
|
+
|
|
290
|
+
# Update the mass data of self
|
|
291
|
+
self.update_mass_data(new_mass)
|
|
292
|
+
else:
|
|
293
|
+
self._logger.info("Recovery coercion did not affect any records.")
|
|
294
|
+
|
|
295
|
+
return self
|
|
296
|
+
|
|
238
297
|
def set_moisture(self, moisture: Union[pd.Series, float, int], mass_to_adjust: Literal['wet', 'dry'] = 'wet') -> MC:
|
|
239
298
|
"""Set the moisture to the specified value
|
|
240
299
|
|
|
@@ -268,6 +327,45 @@ class MassComposition(ABC):
|
|
|
268
327
|
|
|
269
328
|
return self
|
|
270
329
|
|
|
330
|
+
def clip_composition(self, ranges: Optional[dict[str, list[float]]] = None) -> MC:
|
|
331
|
+
"""Clip the components
|
|
332
|
+
|
|
333
|
+
Clip to the components to within the range provided or the default range for each component.
|
|
334
|
+
This method does not clip moisture - see set_moisture and solve_moisture for that.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
ranges: An optional dict defining a list of [lo, hi] floats for each component. If not provided,
|
|
338
|
+
the default range from the config file will be used.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
The object with clipped composition.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
# load the default ranges from the config file
|
|
345
|
+
component_ranges: dict = self._get_component_ranges(ranges)
|
|
346
|
+
|
|
347
|
+
# define a small value to ensure the clipped values lie marginally inside the specified range.
|
|
348
|
+
epsilon: float = 0.0 # 1.0e-05
|
|
349
|
+
# clip the components
|
|
350
|
+
affected_indexes = set()
|
|
351
|
+
for component, component_range in component_ranges.items():
|
|
352
|
+
before_clip = self._mass_data[component].copy()
|
|
353
|
+
# define the component mass that aligns with the lower and upper bounds
|
|
354
|
+
component_mass_limits = self._mass_data[self.mass_dry_var].values[:, np.newaxis] * np.array(
|
|
355
|
+
component_range) / self.composition_factor
|
|
356
|
+
# apply the clip to the mass data
|
|
357
|
+
self._mass_data[component] = self._mass_data[component].clip(lower=component_mass_limits[:, 0] + epsilon,
|
|
358
|
+
upper=component_mass_limits[:, 1] - epsilon)
|
|
359
|
+
affected_indexes.update(self._mass_data.index[before_clip != self._mass_data[component]])
|
|
360
|
+
|
|
361
|
+
# log the action, including the first 50 indexes affected
|
|
362
|
+
affected_indexes_list = sorted(affected_indexes)[:50]
|
|
363
|
+
self._logger.info(
|
|
364
|
+
f"{len(affected_indexes)} records where composition has been clipped to the range: {component_ranges}."
|
|
365
|
+
f" Affected indexes (first 50): {affected_indexes_list}")
|
|
366
|
+
|
|
367
|
+
return self
|
|
368
|
+
|
|
271
369
|
def plot_parallel(self, color: Optional[str] = None,
|
|
272
370
|
vars_include: Optional[list[str]] = None,
|
|
273
371
|
vars_exclude: Optional[list[str]] = None,
|
|
@@ -804,6 +902,20 @@ class MassComposition(ABC):
|
|
|
804
902
|
|
|
805
903
|
return res
|
|
806
904
|
|
|
905
|
+
def _get_component_ranges(self, ranges: dict[str, list]) -> dict[str, list]:
|
|
906
|
+
|
|
907
|
+
d_ranges: dict = get_column_config(config_dict=self.config, var_map=self.variable_map,
|
|
908
|
+
config_key='range')
|
|
909
|
+
# filter to include only components
|
|
910
|
+
d_ranges = {k: v for k, v in d_ranges.items() if k in self.composition_columns}
|
|
911
|
+
|
|
912
|
+
# modify the default dict based on any user passed constraints
|
|
913
|
+
if ranges:
|
|
914
|
+
for k, v in ranges.items():
|
|
915
|
+
d_ranges[k] = v
|
|
916
|
+
|
|
917
|
+
return d_ranges
|
|
918
|
+
|
|
807
919
|
|
|
808
920
|
class OutOfRangeStatus:
|
|
809
921
|
"""A class to check and report out-of-range records in an MC object."""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "geometallurgy"
|
|
3
3
|
packages = [{ include = "elphick/geomet" }]
|
|
4
|
-
version = "0.4.
|
|
4
|
+
version = "0.4.8"
|
|
5
5
|
description = "Tools for the geometallurgist"
|
|
6
6
|
authors = ["Greg <11791585+elphick@users.noreply.github.com>"]
|
|
7
7
|
repository = "https://github.com/elphick/geometallurgy"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|