well-log-toolkit 0.1.149__tar.gz → 0.1.150__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.
Files changed (20) hide show
  1. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/PKG-INFO +1 -1
  2. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/pyproject.toml +1 -1
  3. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/manager.py +110 -5
  4. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/property.py +10 -4
  5. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit.egg-info/PKG-INFO +1 -1
  6. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/README.md +0 -0
  7. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/setup.cfg +0 -0
  8. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/__init__.py +0 -0
  9. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/exceptions.py +0 -0
  10. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/las_file.py +0 -0
  11. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/operations.py +0 -0
  12. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/regression.py +0 -0
  13. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/statistics.py +0 -0
  14. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/utils.py +0 -0
  15. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/visualization.py +0 -0
  16. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit/well.py +0 -0
  17. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit.egg-info/SOURCES.txt +0 -0
  18. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit.egg-info/dependency_links.txt +0 -0
  19. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit.egg-info/requires.txt +0 -0
  20. {well_log_toolkit-0.1.149 → well_log_toolkit-0.1.150}/well_log_toolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.149
3
+ Version: 0.1.150
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "well-log-toolkit"
7
- version = "0.1.149"
7
+ version = "0.1.150"
8
8
  description = "Fast LAS file processing with lazy loading and filtering for well log analysis"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1445,12 +1445,23 @@ class _ManagerPropertyProxy:
1445
1445
  prop = prop.filter(filter_name, source=source_name)
1446
1446
 
1447
1447
  # Compute sums_avg
1448
- source_results[source_name] = prop.sums_avg(
1448
+ result = prop.sums_avg(
1449
1449
  weighted=weighted,
1450
1450
  arithmetic=arithmetic,
1451
1451
  precision=precision
1452
1452
  )
1453
1453
 
1454
+ # Add well-level thickness for this source if using filter_intervals
1455
+ if self._custom_intervals and result:
1456
+ well_thickness = 0.0
1457
+ for key, value in result.items():
1458
+ if isinstance(value, dict) and 'thickness' in value:
1459
+ well_thickness += value['thickness']
1460
+ if well_thickness > 0:
1461
+ result['thickness'] = round(well_thickness, precision)
1462
+
1463
+ source_results[source_name] = result
1464
+
1454
1465
  except (PropertyNotFoundError, PropertyTypeError, AttributeError, KeyError, ValueError):
1455
1466
  # Property or filter doesn't exist in this source, or filter isn't discrete - skip it
1456
1467
  pass
@@ -1476,12 +1487,23 @@ class _ManagerPropertyProxy:
1476
1487
  prop = prop.filter(filter_name)
1477
1488
 
1478
1489
  # Compute sums_avg
1479
- return prop.sums_avg(
1490
+ result = prop.sums_avg(
1480
1491
  weighted=weighted,
1481
1492
  arithmetic=arithmetic,
1482
1493
  precision=precision
1483
1494
  )
1484
1495
 
1496
+ # Add well-level thickness (sum of all zone thicknesses) if using filter_intervals
1497
+ if self._custom_intervals and result:
1498
+ well_thickness = 0.0
1499
+ for key, value in result.items():
1500
+ if isinstance(value, dict) and 'thickness' in value:
1501
+ well_thickness += value['thickness']
1502
+ if well_thickness > 0:
1503
+ result['thickness'] = round(well_thickness, precision)
1504
+
1505
+ return result
1506
+
1485
1507
  except PropertyNotFoundError as e:
1486
1508
  # Check if it's ambiguous (exists in multiple sources)
1487
1509
  if "ambiguous" in str(e).lower():
@@ -1512,6 +1534,16 @@ class _ManagerPropertyProxy:
1512
1534
  arithmetic=arithmetic,
1513
1535
  precision=precision
1514
1536
  )
1537
+
1538
+ # Add well-level thickness for this source if using filter_intervals
1539
+ if self._custom_intervals and result:
1540
+ well_thickness = 0.0
1541
+ for key, value in result.items():
1542
+ if isinstance(value, dict) and 'thickness' in value:
1543
+ well_thickness += value['thickness']
1544
+ if well_thickness > 0:
1545
+ result['thickness'] = round(well_thickness, precision)
1546
+
1515
1547
  source_results[source_name] = result
1516
1548
 
1517
1549
  except (PropertyNotFoundError, PropertyTypeError, AttributeError, KeyError, ValueError):
@@ -1632,7 +1664,7 @@ class _ManagerMultiPropertyProxy:
1632
1664
  PROPERTY_STATS = {'mean', 'median', 'mode', 'sum', 'std_dev', 'percentile', 'range'}
1633
1665
 
1634
1666
  # Stats that are common across properties (stay at group level)
1635
- COMMON_STATS = {'depth_range', 'samples', 'thickness', 'gross_thickness', 'thickness_fraction', 'calculation'}
1667
+ COMMON_STATS = {'depth_range', 'samples', 'thickness', 'thickness_fraction', 'calculation'}
1636
1668
 
1637
1669
  def __init__(
1638
1670
  self,
@@ -1849,7 +1881,17 @@ class _ManagerMultiPropertyProxy:
1849
1881
  return self._merge_flat_results(property_results)
1850
1882
 
1851
1883
  # Merge results: nest property-specific stats, keep common stats at group level
1852
- return self._merge_property_results(property_results)
1884
+ merged = self._merge_property_results(property_results)
1885
+
1886
+ # Add well-level thickness (sum of all zone thicknesses)
1887
+ if self._custom_intervals and merged:
1888
+ well_thickness = 0.0
1889
+ for key, value in merged.items():
1890
+ if isinstance(value, dict) and 'thickness' in value:
1891
+ well_thickness += value['thickness']
1892
+ merged['thickness'] = round(well_thickness, 6)
1893
+
1894
+ return merged
1853
1895
 
1854
1896
  def _apply_filter_intervals(self, prop, well):
1855
1897
  """
@@ -3108,7 +3150,70 @@ class WellDataManager:
3108
3150
  ['well_12_3_2_B', 'well_12_3_2_A']
3109
3151
  """
3110
3152
  return list(self._wells.keys())
3111
-
3153
+
3154
+ @property
3155
+ def saved_intervals(self) -> dict[str, list[str]]:
3156
+ """
3157
+ List saved interval names for all wells.
3158
+
3159
+ Returns
3160
+ -------
3161
+ dict[str, list[str]]
3162
+ Dictionary mapping well names to their saved interval names
3163
+
3164
+ Examples
3165
+ --------
3166
+ >>> manager.saved_intervals
3167
+ {'well_A': ['Reservoir_Zones', 'Slump_Zones'], 'well_B': ['Reservoir_Zones']}
3168
+ """
3169
+ result = {}
3170
+ for well_name, well in self._wells.items():
3171
+ if well.saved_intervals:
3172
+ result[well_name] = well.saved_intervals
3173
+ return result
3174
+
3175
+ def get_intervals(self, name: str) -> dict[str, list[dict]]:
3176
+ """
3177
+ Get saved filter intervals by name from all wells that have them.
3178
+
3179
+ Parameters
3180
+ ----------
3181
+ name : str
3182
+ Name of the saved filter intervals
3183
+
3184
+ Returns
3185
+ -------
3186
+ dict[str, list[dict]]
3187
+ Dictionary mapping well names to their interval definitions
3188
+
3189
+ Raises
3190
+ ------
3191
+ KeyError
3192
+ If no wells have intervals with the given name
3193
+
3194
+ Examples
3195
+ --------
3196
+ >>> manager.get_intervals("Slump_Zones")
3197
+ {'well_A': [{'name': 'Zone_A', 'top': 2500, 'base': 2650}],
3198
+ 'well_B': [{'name': 'Zone_A', 'top': 2600, 'base': 2750}]}
3199
+ """
3200
+ result = {}
3201
+ for well_name, well in self._wells.items():
3202
+ if name in well.saved_intervals:
3203
+ result[well_name] = well.get_intervals(name)
3204
+
3205
+ if not result:
3206
+ # Collect all available interval names for error message
3207
+ all_names = set()
3208
+ for well in self._wells.values():
3209
+ all_names.update(well.saved_intervals)
3210
+ raise KeyError(
3211
+ f"No wells have saved intervals named '{name}'. "
3212
+ f"Available: {sorted(all_names) if all_names else 'none'}"
3213
+ )
3214
+
3215
+ return result
3216
+
3112
3217
  def get_well(self, name: str) -> Well:
3113
3218
  """
3114
3219
  Get well by original or sanitized name.
@@ -1779,24 +1779,31 @@ class Property(PropertyOperationsMixin):
1779
1779
  # contributes to this zone (even boundary samples with partial intervals)
1780
1780
  interval_mask = zone_intervals > 0
1781
1781
 
1782
+ # Calculate zone thickness (sum of valid intervals within zone)
1783
+ valid_mask = interval_mask & ~np.isnan(self.values)
1784
+ zone_thickness = float(np.sum(zone_intervals[valid_mask]))
1785
+
1782
1786
  # If there are secondary properties, group within this interval
1783
1787
  if self.secondary_properties:
1784
- result[interval_name] = self._recursive_group(
1788
+ interval_result = self._recursive_group(
1785
1789
  0,
1786
1790
  interval_mask,
1787
1791
  weighted=weighted,
1788
1792
  arithmetic=arithmetic,
1789
- gross_thickness=gross_thickness,
1793
+ gross_thickness=zone_thickness, # Pass zone thickness as gross for children
1790
1794
  precision=precision,
1791
1795
  zone_intervals=zone_intervals
1792
1796
  )
1797
+ # Add zone-level thickness
1798
+ interval_result['thickness'] = round(zone_thickness, precision)
1799
+ result[interval_name] = interval_result
1793
1800
  else:
1794
1801
  # No secondary properties, compute stats directly for interval
1795
1802
  result[interval_name] = self._compute_stats(
1796
1803
  interval_mask,
1797
1804
  weighted=weighted,
1798
1805
  arithmetic=arithmetic,
1799
- gross_thickness=gross_thickness,
1806
+ gross_thickness=zone_thickness, # Use zone thickness for fraction calc
1800
1807
  precision=precision,
1801
1808
  zone_intervals=zone_intervals
1802
1809
  )
@@ -2416,7 +2423,6 @@ class Property(PropertyOperationsMixin):
2416
2423
  'depth_range': _round_value(depth_range_dict),
2417
2424
  'samples': int(len(valid)),
2418
2425
  'thickness': round(thickness, precision),
2419
- 'gross_thickness': round(gross_thickness, precision),
2420
2426
  'thickness_fraction': round(fraction, precision),
2421
2427
  'calculation': calc_method,
2422
2428
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.149
3
+ Version: 0.1.150
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT