Getting Started

Welcome to pollywog! This guide will help you install the library, understand the basics, run your first calculation, and explore further resources.

Note

New to Python? If you’re a geologist or mining engineer with limited programming experience, see Tutorial for Resource Geologists for a comprehensive step-by-step introduction designed specifically for you.

What You Need to Know

Before using pollywog, it’s helpful to understand:

  • Python basics: Functions, lists, dictionaries, and imports

  • Leapfrog concepts: Block models, drillholes, calculations

  • .lfcalc files: Leapfrog calculation set files

You don’t need to be a Python expert—pollywog is designed to be accessible to geologists and mining engineers with basic programming knowledge.

Installation

Install from PyPI

The easiest way to install pollywog:

pip install lf_pollywog

Install with Optional Dependencies

For machine learning model conversion (scikit-learn):

pip install lf_pollywog[conversion]

For development (includes testing tools):

pip install lf_pollywog[dev]

Install from GitHub

To get the latest development version:

pip install git+https://github.com/endarthur/pollywog.git

Verify Installation

Check that pollywog is installed correctly:

import pollywog as pw
print(pw.__version__)  # Should print version number

Try in Your Browser

Don’t want to install yet? Try pollywog in your browser using JupyterLite:

Note

JupyterLite saves files in browser memory—download your work regularly to avoid losing it!

Basic Concepts

CalcSet

A CalcSet is a collection of calculation items that can be exported to a .lfcalc file:

from pollywog.core import CalcSet, Number

calcset = CalcSet([
    Number("Au_clean", "clamp([Au], 0)"),
    Number("Au_log", "log([Au_clean] + 1e-6)"),
])

Number

Number represents a numeric calculation (integers or floats):

from pollywog.core import Number

# Simple calculation
grade_calc = Number("Au_final", "[Au] * 0.95")

# With comment
grade_calc = Number(
    "Au_final",
    "[Au] * 0.95",
    comment_equation="Apply 5% dilution factor"
)

Category

Category represents a categorical/text calculation:

from pollywog.core import Category, If

# Categorical output
ore_type = Category(name="material_class", expression=[
    If("[Au] >= 0.5", "'ore'", "'waste'")
])

Variable

Variable represents intermediate calculations that are visible in Leapfrog’s calculator UI but NOT available outside the calculator (can’t visualize in 3D, can’t export, can’t use in other Leapfrog tools).

Use Variables for intermediate steps you need for calculations but won’t visualize or export:

from pollywog.core import CalcSet, Variable, Number

calcset = CalcSet([
    # Intermediate cleaning steps (calculator only)
    Variable("Au_clean", "clamp([Au], 0)"),
    Variable("Au_capped", "clamp([Au_clean], 0, 100)"),

    # Final result (available everywhere in Leapfrog)
    Number("Au_final", "[Au_capped] * 0.95",
           comment_equation="Prepared for kriging")
])

Why use Variables?

Variables keep your Leapfrog interface clean by hiding intermediate calculations that users don’t need to see or export. They’re visible in the calculator (so you can reference them and verify logic), but they won’t clutter your block model variable list or visualization options.

When to use Variable vs Number vs Category:

  • Variable: Intermediate steps, won’t visualize/export

  • Number: Final numeric outputs you’ll visualize or export

  • Category: Final categorical outputs you’ll visualize or export

        flowchart TD
   Q1{What's the output?}
   Q1 -->|Numeric| Q2{Will you visualize it?}
   Q1 -->|Text/Category| Q3{Will you visualize it?}
   Q1 -->|Boolean/Filter| F[Filter]

   Q2 -->|Yes| N[Number]
   Q2 -->|No - just intermediate| V1[Variable]

   Q3 -->|Yes| C[Category]
   Q3 -->|No - just intermediate| V2[Variable]

   style N fill:#4a90e2,stroke:#333,stroke-width:2px,color:#fff
   style C fill:#f4a261,stroke:#333,stroke-width:2px,color:#fff
   style V1 fill:#999,stroke:#333,stroke-width:2px,color:#fff
   style V2 fill:#999,stroke:#333,stroke-width:2px,color:#fff
   style F fill:#9b59b6,stroke:#333,stroke-width:2px,color:#fff
    

Filter

Filter represents boolean conditions that restrict which data is included in calculations. Filters evaluate to true/false and are used to exclude data from processing.

from pollywog.core import CalcSet, Filter, Number

calcset = CalcSet([
    # Only include high-grade samples
    Filter("is_ore", "[Au] > 0.5"),

    # Only include complete data
    Filter("has_density", "is_normal([density])"),

    # Calculations on filtered data
    Number("ore_tonnage", "[volume] * [density]")
])

Filters are less commonly used in block models (where you typically calculate everything and classify later), but are useful for drillhole data processing and conditional workflows.

Expressions and Syntax

In pollywog, expressions use the same syntax as Leapfrog’s calculator. Variables are referenced with square brackets [variable_name], and you can use mathematical operations and functions.

Quick example:

Number("precious_metals", "[Au] + [Ag]")
Number("density_calc", "[block_density] * [block_volume]")

See also

For complete expression syntax, functions, and operators, see Leapfrog Expression Syntax Guide

Understanding the Expression Parameter

The expression parameter accepts either a string or a list:

  • Use a string for simple expressions (recommended for most cases):

    Number("doubled", "[x] * 2")
    Category("rock_type", "'granite'")
    
  • Use a list when including If objects for conditional logic:

    Number("result", [If("[x] > 0", "[x]", "0")])
    

Why can expression be a list?

If statements are separate Python objects that cannot be embedded in expression strings. Since the Leapfrog calculation format supports conditional logic, pollywog needs to handle these If objects alongside string expressions. That’s why the parameter accepts lists.

For convenience, single strings are automatically wrapped in a list internally, so you don’t need to write expression=["[x] * 2"] - just use expression="[x] * 2" or the even simpler positional form: Number("doubled", "[x] * 2").

If vs IfRow:

If accepts condition-value pairs as tuples (recommended for most users):

If([
    ("[Au] > 1", "[Au] * 1.1"),
    ("[Au] <= 1", "[Au] * 0.9")
], otherwise=["[Au]"])

IfRow is a lower-level class representing a single condition-value pair, used internally by If. Most users won’t need to use IfRow directly - stick with tuple notation in If for clarity.

Your First Calculation

Let’s create a simple calculation set step by step:

Step 1: Import pollywog

from pollywog.core import CalcSet, Number

Step 2: Create Calculations

# Create individual calculations
au_clean = Number(
    "Au_clean",
    "clamp([Au], 0)",
    comment_equation="Remove negative values"
)

au_scaled = Number(
    "Au_scaled",
    "[Au_clean] * 0.95",
    comment_equation="Apply 95% factor"
)

Step 3: Build a CalcSet

# Combine into a CalcSet
calcset = CalcSet([au_clean, au_scaled])

Step 4: Export to Leapfrog

# Export to .lfcalc file
calcset.to_lfcalc("my_calculations.lfcalc")

print(f"Exported {len(calcset.items)} calculations")

Note

In JupyterLite and Jupyter Notebooks, Pollywog provides an extension that makes a download button appear below the cell when you export with to_lfcalc. To enable this feature, first run %load_ext pollywog.magics in a cell, then %pollywog autodownload on. When you export, simply click the button to save the file to your computer.

Step 5: Use in Leapfrog

  1. Open Leapfrog

  2. Navigate to your block model, drillhole, or mesh

  3. Right-click on “Evaluations” or “Numeric” section

  4. Select “Import” → “From File”

  5. Choose your my_calculations.lfcalc file

  6. The calculations will appear in your evaluation tree

Common Workflows

Drillhole Preprocessing

Clean and transform drillhole assay data:

from pollywog.core import CalcSet, Number

preprocessing = CalcSet([
    # Remove outliers
    Number("Au_capped", "clamp([Au], 0, 100)",
           comment_equation="Cap gold at 100 g/t"),
    Number("Cu_capped", "clamp([Cu], 0, 5)",
           comment_equation="Cap copper at 5%"),

    # Log transforms for kriging
    Number("Au_log", "log([Au_capped] + 0.01)"),
    Number("Cu_log", "log([Cu_capped] + 0.01)"),
])

preprocessing.to_lfcalc("drillhole_preprocessing.lfcalc")

Block Model Postprocessing

Process estimated grades in a block model:

from pollywog.core import CalcSet, Number
from pollywog.helpers import WeightedAverage

postprocessing = CalcSet([
    # Back-transform from log space
    Number("Au_est", "exp([Au_log_kriged]) - 0.01"),

    # Apply dilution
    Number("Au_diluted", "[Au_est] * 0.95"),

    # Apply recovery
    Number("Au_recovered", "[Au_diluted] * 0.88"),
])

postprocessing.to_lfcalc("block_postprocessing.lfcalc")

Domain Weighting

Combine estimates from multiple domains:

from pollywog.helpers import WeightedAverage
from pollywog.core import CalcSet

domain_weighted = CalcSet([
    WeightedAverage(
        variables=["Au_oxide", "Au_sulfide", "Au_transition"],
        weights=["prop_oxide", "prop_sulfide", "prop_transition"],
        name="Au_composite"
    )
])

domain_weighted.to_lfcalc("domain_weighted.lfcalc")

Working with Helpers

Pollywog provides helper functions to simplify common patterns:

from pollywog.helpers import Sum, Product, Scale, CategoryFromThresholds
from pollywog.core import CalcSet

helpers_example = CalcSet([
    # Sum multiple Au assays from different labs/methods
    Sum(["Au_fire_assay", "Au_screen_assay", "Au_leach"], name="Au_total"),

    # Calculate tonnage: volume × density
    Product(["block_volume", "density"], name="tonnage"),

    # Apply dilution factor
    Scale("Au", 0.95, name="Au_diluted"),

    # Categorize by grade thresholds
    CategoryFromThresholds(
        variable="Au",
        thresholds=[0.5, 2.0],
        categories=["low_grade", "medium_grade", "high_grade"],
        name="ore_class"
    ),
])

helpers_example.to_lfcalc("helpers_example.lfcalc")

Visualization in Jupyter

Display calculation sets as interactive HTML in Jupyter notebooks:

from pollywog.display import display_calcset, display_item, set_theme

# Set theme (optional)
set_theme("light")  # or "dark"

# Display the calcset
display_calcset(calcset)

# Display individual items
display_item(my_number)
display_item(my_category)

Items and CalcSets also have automatic rich display in Jupyter notebooks. Simply evaluating them in a cell will show their rich HTML representation:

# Automatic rich display (no display_* function needed)
calcset  # Shows interactive tree view

my_number  # Shows formatted item with equation

This creates an interactive tree view showing:

  • Calculation names

  • Expressions

  • Dependencies between calculations

  • Comments and metadata

Reading Existing Files

Load and modify existing .lfcalc files:

from pollywog.core import CalcSet

# Read existing file
existing = CalcSet.read_lfcalc("existing_calculations.lfcalc")

# View contents
print(f"Loaded {len(existing.items)} calculations")
for item in existing.items:
    print(f"  - {item.name}")

# Modify
from pollywog.core import Number
existing.items.append(
    Number("new_calc", "[existing_var] * 2")
)

# Save modified version
existing.to_lfcalc("modified_calculations.lfcalc")

Tips for Success

  1. Start Simple: Begin with basic calculations and add complexity incrementally

  2. Use Comments: Document your logic with comment_equation parameter

  3. Test Small: Export small test files and verify in Leapfrog before scaling up

  4. Check Dependencies: Use item.dependencies to see what variables are referenced

  5. Organize Code: Keep related calculations together in the same CalcSet

  6. Version Control: Store your Python scripts in Git for traceability

  7. Validate Data: Use clamp() to handle invalid inputs (negatives, outliers)

  8. Break It Down: Split complex expressions into multiple simple calculations

Common Pitfalls

Forgetting Square Brackets

# Wrong - Au is treated as undefined variable
Number("result", "Au * 2")

# Correct - Au is a reference to existing variable
Number("result", "[Au] * 2")

Division by Zero

# Risky
Number("ratio", "[a] / [b]")

# Safe
Number("ratio", "[a] / ([b] + 1e-10)")
Number("ratio", "[a] / clamp([b], 0.001)")

Log of Zero

# Risky
Number("Au_log", "log([Au])")

# Safe
Number("Au_log", "log([Au] + 1e-6)")

Missing Parentheses

# Ambiguous - may not compute as intended
Number("result", "[a] + [b] * [c]")

# Clear
Number("result", "[a] + ([b] * [c])")

Next Steps

Now that you understand the basics, explore:

Additional Resources

Getting Help

If you encounter issues:

  1. Check the documentation for similar examples

  2. Review the example notebooks in the repository

  3. Search existing GitHub issues

  4. Open a new issue with a minimal reproducible example