Skip to content

Commit f6f04be

Browse files
authored
Typed objects block model (#216)
This PR brings block models under the same typed object envolope asrecently added objects. Key parts of this pull requets: * BLock models are now an OPTIONAL dependency for objects. **We want to have a single point of entry** and block models on evo are not just references from the perspective of the user - they are actual block models that the user expects to interact with. If the user does not install block models, they can interact with block model metadata handling the current dependency in a similar way to pandas and numpy. Evo-sdk user will get the whole package, sneaky developer who only cares about very thin set of changes - can get evo-objects with cut down dependencies. * Block model dependencies were adjusted so that by default it can be used just to parse metadata. Interaction with data is now an optional dependency, this way for basic services that don't care about interacting with evo-data and that user evo-objects there is no extra dependency added. * Block models support dataframe conversions, pretty printing via widgets, report creation, and automated mapping/creation from `object_from_reference/path`. * I have shared typed definitions in Common, without going as far as adding shared functionality that relies on numpy to avoid new depenency on common. * Block model in objects acts as a wrapper for a fully featured typed objects in evo-block models that can exist by itself. * I have not added any converters to and from block model and 3D grid... but it is not that hard as interfaces are similar though not identical and because the objects are different enough - we do not want to have more shared interfaces at this stage other than common base blocks. * OCTREE and FULLY SUBBLOCKED modes support was left out for now (via typed Objects) - PR is large already
1 parent f5cabe1 commit f6f04be

33 files changed

Lines changed: 6763 additions & 152 deletions

File tree

code-samples/blockmodels/reports.ipynb

Lines changed: 478 additions & 0 deletions
Large diffs are not rendered by default.

code-samples/geoscience-objects/running-kriging-compute/running-kriging-compute.ipynb

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -539,20 +539,33 @@
539539
"source": [
540540
"---\n",
541541
"\n",
542-
"## WIP: Creating Kriging and Compute\n",
542+
"## 8. Create Target Block Model\n",
543543
"\n",
544-
"The following sections demonstrate how to run kriging estimation using Evo Compute.\n",
545-
"\n",
546-
"**Note:** This functionality is under development. The code below shows the expected API pattern."
544+
"Create a Block Model to hold the kriging results. The block model defines the estimation grid."
547545
]
548546
},
549547
{
550-
"cell_type": "markdown",
548+
"cell_type": "code",
549+
"execution_count": null,
551550
"metadata": {},
551+
"outputs": [],
552552
"source": [
553-
"### WIP: Create Target Block Model\n",
553+
"from evo.blockmodels import Point3, RegularBlockModel, RegularBlockModelData, Size3d, Size3i, Units\n",
554+
"\n",
555+
"# Define block model covering the drill hole extent\n",
556+
"bm_data = RegularBlockModelData(\n",
557+
" name=\"CU Kriging Estimate\",\n",
558+
" description=\"Block model with kriged copper grades\",\n",
559+
" origin=Point3(x=444750, y=492850, z=2350),\n",
560+
" n_blocks=Size3i(nx=50, ny=75, nz=45), # 50x75x45 blocks\n",
561+
" block_size=Size3d(dx=20.0, dy=20.0, dz=20.0), # 20m blocks\n",
562+
" coordinate_reference_system=\"EPSG:32650\",\n",
563+
" size_unit_id=Units.METRES,\n",
564+
")\n",
554565
"\n",
555-
"Create a Block Model to hold the kriging results. The block model defines the estimation grid."
566+
"block_model = await RegularBlockModel.create(manager, bm_data)\n",
567+
"print(f\"Created Block Model: {block_model.name}\")\n",
568+
"print(f\"Block Model ID: {block_model.id}\")"
556569
]
557570
},
558571
{
@@ -561,32 +574,15 @@
561574
"metadata": {},
562575
"outputs": [],
563576
"source": [
564-
"# WIP: Create target block model for kriging estimation\n",
565-
"#\n",
566-
"# from evo.objects.typed import BlockModel, RegularBlockModelData, Point3, Size3i, Size3d\n",
567-
"# from evo.blockmodels import Units\n",
568-
"#\n",
569-
"# # Define block model covering the downhole extent\n",
570-
"# bm_data = RegularBlockModelData(\n",
571-
"# name=\"CU Kriging Estimate\",\n",
572-
"# description=\"Block model with kriged copper grades\",\n",
573-
"# origin=Point3(x=444750, y=492850, z=2350),\n",
574-
"# n_blocks=Size3i(nx=50, ny=75, nz=45), # 50x75x45 blocks\n",
575-
"# block_size=Size3d(dx=20.0, dy=20.0, dz=20.0), # 20m blocks\n",
576-
"# crs=\"EPSG:32650\",\n",
577-
"# size_unit_id=Units.METRES,\n",
578-
"# )\n",
579-
"#\n",
580-
"# block_model = await BlockModel.create_regular(manager, bm_data)\n",
581-
"# print(f\"Created Block Model: {block_model.name}\")\n",
582-
"# print(f\"Bounding Box: {block_model.bounding_box}\")"
577+
"# Display the block model metadata\n",
578+
"block_model.version"
583579
]
584580
},
585581
{
586582
"cell_type": "markdown",
587583
"metadata": {},
588584
"source": [
589-
"### WIP: Define Kriging Parameters\n",
585+
"## WIP. Define Kriging Parameters\n",
590586
"\n",
591587
"Configure the kriging search neighborhood and estimation parameters."
592588
]
@@ -597,15 +593,13 @@
597593
"metadata": {},
598594
"outputs": [],
599595
"source": [
600-
"# WIP: Define kriging parameters\n",
601-
"#\n",
602596
"# from evo.compute.tasks import SearchNeighborhood\n",
603597
"# from evo.compute.tasks.kriging import KrigingParameters\n",
604598
"#\n",
605599
"# # Use the search ellipsoid we created earlier (2x variogram range)\n",
606600
"# params = KrigingParameters(\n",
607601
"# source=pointset.attributes[\"CU_pct\"], # Source attribute\n",
608-
"# target=block_model.attributes[\"CU_estimate\"], # Target attribute\n",
602+
"# target=block_model.attributes[f\"CU_samples_{max_samples}\"]\n",
609603
"# variogram=variogram,\n",
610604
"# search=SearchNeighborhood(\n",
611605
"# ellipsoid=search_ellipsoid,\n",
@@ -614,15 +608,15 @@
614608
"# ),\n",
615609
"# )\n",
616610
"#\n",
617-
"# print(f\"Kriging source: {params.source}\")\n",
611+
"# print(f\"Kriging source: CU_pct from pointset\")\n",
618612
"# print(f\"Search ellipsoid: major={search_ellipsoid.ranges.major}m\")"
619613
]
620614
},
621615
{
622616
"cell_type": "markdown",
623617
"metadata": {},
624618
"source": [
625-
"### WIP: Run Kriging Task\n",
619+
"## WIP. Run Kriging Task\n",
626620
"\n",
627621
"Submit and run the kriging task using Evo Compute."
628622
]
@@ -633,23 +627,21 @@
633627
"metadata": {},
634628
"outputs": [],
635629
"source": [
636-
"# WIP: Run kriging task\n",
637-
"#\n",
638630
"# from evo.compute.tasks import run\n",
639631
"#\n",
640632
"# # Submit kriging task (progress feedback is shown by default)\n",
641633
"# print(\"Submitting kriging task...\")\n",
642634
"# results = await run(manager, [params])\n",
643635
"#\n",
644636
"# print(f\"Kriging complete!\")\n",
645-
"# print(f\"Result: {results[0].message}\")"
637+
"# print(f\"Result: {results[0].status}\")"
646638
]
647639
},
648640
{
649641
"cell_type": "markdown",
650642
"metadata": {},
651643
"source": [
652-
"### WIP: View Kriging Results\n",
644+
"## WIP. View Kriging Results\n",
653645
"\n",
654646
"Refresh the block model and view the estimated grades."
655647
]
@@ -660,13 +652,11 @@
660652
"metadata": {},
661653
"outputs": [],
662654
"source": [
663-
"# WIP: View kriging results\n",
664-
"#\n",
665-
"# # Refresh block model to see new attributes\n",
666-
"# block_model = await block_model.refresh()\n",
667-
"#\n",
668-
"# # Display the block model (pretty-printed with Portal/Viewer links)\n",
669-
"# block_model"
655+
"# Refresh block model to see new attributes\n",
656+
"await block_model.refresh()\n",
657+
"\n",
658+
"# Display the block model version (shows updated columns)\n",
659+
"block_model.version"
670660
]
671661
},
672662
{
@@ -675,21 +665,19 @@
675665
"metadata": {},
676666
"outputs": [],
677667
"source": [
678-
"# WIP: Query estimated values\n",
679-
"#\n",
680-
"# # Get the kriged values as a DataFrame\n",
681-
"# results_df = await block_model.to_dataframe(columns=[\"CU_estimate\"])\n",
682-
"#\n",
683-
"# print(f\"Estimated {len(results_df)} blocks\")\n",
684-
"# print(f\"\\nStatistics:\")\n",
685-
"# print(results_df[\"CU_estimate\"].describe())"
668+
"# Get the kriged values as a DataFrame\n",
669+
"results_df = block_model.cell_data\n",
670+
"\n",
671+
"print(f\"Estimated {len(results_df)} blocks\")\n",
672+
"print(\"\\nStatistics for CU_estimate:\")\n",
673+
"print(results_df[\"CU_estimate\"].describe())"
686674
]
687675
},
688676
{
689677
"cell_type": "markdown",
690678
"metadata": {},
691679
"source": [
692-
"### WIP: Running Multiple Kriging Scenarios\n",
680+
"## WIP. Running Multiple Kriging Scenarios\n",
693681
"\n",
694682
"Run multiple kriging tasks concurrently to compare different parameters."
695683
]
@@ -700,8 +688,6 @@
700688
"metadata": {},
701689
"outputs": [],
702690
"source": [
703-
"# WIP: Multiple kriging scenarios with different max_samples\n",
704-
"#\n",
705691
"# max_samples_values = [5, 10, 15, 20]\n",
706692
"#\n",
707693
"# # Create parameter sets for each scenario\n",
@@ -721,7 +707,7 @@
721707
"# # Run all scenarios in parallel\n",
722708
"# print(f\"Submitting {len(parameter_sets)} kriging tasks...\")\n",
723709
"# results = await run(manager, parameter_sets)\n",
724-
"# print(f\"All {len(results)} scenarios completed!\")"
710+
"# print(f\"All {len(results)} scenarios completed!\")\n"
725711
]
726712
}
727713
],

packages/evo-blockmodels/pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "evo-blockmodels"
33
description = "Python SDK for using the Seequent Evo Geoscience Block Model API"
4-
version = "0.1.1"
4+
version = "0.2.0"
55
requires-python = ">=3.10"
66
license-files = ["LICENSE.md"]
77
dynamic = ["readme"]
@@ -21,14 +21,14 @@ Homepage = "https://www.seequent.com/"
2121
Documentation = "https://developer.seequent.com/"
2222

2323
[project.optional-dependencies]
24-
pyarrow = ["pyarrow>=19.0.0"]
24+
utils = ["pyarrow>=19.0.0", "pandas>=2.0.0"]
2525
aiohttp = ["evo-sdk-common[aiohttp]>=0.1.0"]
2626
notebooks = ["evo-sdk-common[notebooks]>=0.1.0"]
2727

2828
[dependency-groups]
2929
# Dev dependencies. The version is left unspecified so the latest is installed.
3030
test = [
31-
"evo-blockmodels[aiohttp,pyarrow]",
31+
"evo-blockmodels[aiohttp,utils]",
3232
"parameterized==0.9.0",
3333
"pytest",
3434
]
@@ -40,7 +40,7 @@ dev = [
4040
{include-group = "test"}
4141
]
4242
notebooks = [
43-
"evo-blockmodels[aiohttp,notebooks,pyarrow]",
43+
"evo-blockmodels[aiohttp,notebooks,utils]",
4444
"jupyter",
4545
]
4646

packages/evo-blockmodels/src/evo/blockmodels/client.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
Version,
3434
)
3535
from .endpoints import models
36-
from .endpoints.api import ColumnOperationsApi, JobsApi, MetadataApi, OperationsApi, VersionsApi
36+
from .endpoints.api import ColumnOperationsApi, JobsApi, MetadataApi, OperationsApi, ReportsApi, VersionsApi
3737
from .endpoints.models import (
3838
AnyUrl,
3939
BBox,
@@ -127,6 +127,7 @@ def __init__(self, environment: Environment, connector: APIConnector, cache: ICa
127127
self._operations_api = OperationsApi(connector)
128128
self._column_operations_api = ColumnOperationsApi(connector)
129129
self._metadata_api = MetadataApi(connector)
130+
self._reports_api = ReportsApi(connector)
130131
self._cache = cache
131132

132133
@classmethod
@@ -311,7 +312,6 @@ async def _upload_data(self, bm_id: uuid.UUID, job_id: uuid.UUID, upload_url: st
311312

312313
cache_location = get_cache_location_for_upload(self._cache, self._environment, job_id)
313314
pyarrow.parquet.write_table(data, cache_location)
314-
315315
# Upload the data
316316
upload = BlockModelUpload(self._connector, self._environment, bm_id, job_id, upload_url)
317317
await upload.upload_from_path(cache_location, self._connector.transport)
@@ -368,6 +368,19 @@ async def list_block_models(self) -> list[BlockModel]:
368368

369369
return [self._bm_from_model(m) for m in response.results]
370370

371+
async def get_block_model(self, bm_id: UUID) -> BlockModel:
372+
"""Get a block model by ID.
373+
374+
:param bm_id: The ID of the block model to retrieve.
375+
:return: The BlockModel metadata.
376+
"""
377+
response = await self._metadata_api.retrieve_block_model(
378+
bm_id=str(bm_id),
379+
workspace_id=str(self._environment.workspace_id),
380+
org_id=str(self._environment.org_id),
381+
)
382+
return self._bm_from_model(response)
383+
371384
async def list_all_block_models(self, page_limit: int | None = 100) -> list[BlockModel]:
372385
"""Return all block models for the current workspace, following paginated responses.
373386
@@ -527,6 +540,26 @@ async def create_block_model(
527540
version = await self._add_new_columns(create_result.bm_uuid, initial_data, units, geometry_change)
528541
return self._bm_from_model(create_result), version
529542

543+
async def add_new_subblocked_columns(
544+
self,
545+
bm_id: UUID,
546+
data: Table,
547+
units: dict[str, str] | None = None,
548+
):
549+
"""Add new columns to an existing sub-blocked block model. This will not change the sub-blocking structure, thus the provided data must match existing sub-blocks in the model.
550+
551+
Units for the columns can be provided in the `units` dictionary.
552+
553+
This method requires the `pyarrow` package to be installed, and the 'cache' parameter to be set in the constructor.
554+
555+
:param bm_id: The ID of the block model to add columns to.
556+
:param data: The data containing the new columns to add.
557+
:param units: A dictionary mapping column names within `data` to units.
558+
:raises CacheNotConfiguredException: If the cache is not configured.
559+
:return: The new version of the block model with the added columns.
560+
"""
561+
return await self._add_new_columns(bm_id, data, units, geometry_change=False)
562+
530563
async def _add_new_columns(
531564
self,
532565
bm_id: UUID,
@@ -580,26 +613,6 @@ async def _add_new_columns(
580613
version = await self._upload_data(bm_id, update_response.job_uuid, str(update_response.upload_url), data)
581614
return _version_from_model(version)
582615

583-
async def add_new_subblocked_columns(
584-
self,
585-
bm_id: UUID,
586-
data: Table,
587-
units: dict[str, str] | None = None,
588-
):
589-
"""Add new columns to an existing sub-blocked block model. This will not change the sub-blocking structure, thus the provided data must match existing sub-blocks in the model.
590-
591-
Units for the columns can be provided in the `units` dictionary.
592-
593-
This method requires the `pyarrow` package to be installed, and the 'cache' parameter to be set in the constructor.
594-
595-
:param bm_id: The ID of the block model to add columns to.
596-
:param data: The data containing the new columns to add.
597-
:param units: A dictionary mapping column names within `data` to units.
598-
:raises CacheNotConfiguredException: If the cache is not configured.
599-
:return: The new version of the block model with the added columns.
600-
"""
601-
return await self._add_new_columns(bm_id, data, units, geometry_change=False)
602-
603616
async def add_new_columns(
604617
self,
605618
bm_id: UUID,

packages/evo-blockmodels/src/evo/blockmodels/data.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,16 @@ class Version:
230230
"""
231231
Columns within this version
232232
"""
233+
234+
def __repr__(self) -> str:
235+
"""Return a concise string representation of the version."""
236+
col_names = [c.title for c in self.columns]
237+
bbox_str = ""
238+
if self.bbox:
239+
bbox_str = f", bbox=i[{self.bbox.i_minmax.min}-{self.bbox.i_minmax.max}] j[{self.bbox.j_minmax.min}-{self.bbox.j_minmax.max}] k[{self.bbox.k_minmax.min}-{self.bbox.k_minmax.max}]"
240+
return (
241+
f"Version(id={self.version_id}, "
242+
f"created={self.created_at.strftime('%Y-%m-%d %H:%M:%S')}, "
243+
f"by={self.created_by.name or self.created_by.email}{bbox_str}, "
244+
f"columns={col_names})"
245+
)

0 commit comments

Comments
 (0)