Helper Functions Guide

Pollywog provides a collection of helper functions to simplify common calculation patterns. These functions can generate either complete Number or Category objects for your CalcSet, or return just the expression/logic for use in more complex calculations.

Why Use Helpers?

Helper functions offer several advantages:

  1. Readability: Sum(["Au_assay1", "Au_assay2", "Au_assay3"]) is clearer than manually writing the expression

  2. Consistency: Helpers ensure correct syntax and parentheses

  3. Error Prevention: Reduce mistakes in complex expressions

  4. Maintainability: Easier to update and refactor calculations

  5. Self-Documentation: Helper names clearly indicate intent

  6. Flexibility: Can return either complete calculations or just expressions for composition

All helper functions are available from the pollywog.helpers module:

from pollywog.helpers import (
    Sum, Product, Average, WeightedSum, WeightedAverage,
    Scale, Normalize, NumberFromThresholds, CategoryFromThresholds
)

How Helpers Work: Two Modes

All helper functions have two modes of operation:

  1. With ``name`` parameter: Returns a complete Number or Category object ready to add to a CalcSet

  2. Without ``name`` parameter: Returns just the expression (string) or logic (If block) for use in more complex calculations

This flexibility allows you to use helpers both for simple standalone calculations and as building blocks for more complex logic.

from pollywog.helpers import Sum, Product
from pollywog.core import Number

# Mode 1: Complete calculation (with name)
# Example: Average of multiple assay results
avg_Au = Sum(["Au_lab1", "Au_lab2", "Au_lab3"], name="Au_total_for_avg")
# Returns a Number object ready for CalcSet

# Mode 2: Expression only (without name)
# Example: Calculate block volume
volume_expr = Product(["block_x", "block_y", "block_z"])
# Returns: "([block_x] * [block_y] * [block_z])"
# Use this in more complex expressions:
tonnage_calc = Number(
    name="tonnage",
    expression=[f"{volume_expr} * [density]"]
)

When to use each mode:

  • With name: Simple, standalone calculations

  • Without name: Building blocks for complex logic, nested expressions, or conditional calculations

Mathematical Helpers

Sum

Add multiple variables together.

from pollywog.helpers import Sum

# With name: Returns a Number
total = Sum(["Au_assay1", "Au_assay2", "Au_assay3"], name="Au_composite")
# Generates: Number(name="Au_composite", expression=["([Au_assay1] + [Au_assay2] + [Au_assay3])"])

# Without name: Returns expression string
sum_expr = Sum(["length", "width", "height"])
# Returns: "([length] + [width] + [height])"

# Use expression in complex logic
from pollywog.core import Number
value_calc = Number(
    name="total_dimension",
    expression=[f"{sum_expr} * [scale_factor]"]
)

Parameters:

  • variables: List of variable names as strings

  • name (optional): Name for output variable. If None, returns expression string

  • comment (optional): Custom comment for the calculation

Returns: Number object when name is provided, or expression string when name is None

Product

Multiply multiple variables together.

from pollywog.helpers import Product

# With name: Returns a Number
payable = Product(["grade", "recovery", "tonnes"], name="payable_metal")
# Generates: Number(name="payable_metal", expression=["([grade] * [recovery] * [tonnes])"])

# Without name: Returns expression string
prod_expr = Product(["grade", "recovery"])
# Returns: "([grade] * [recovery])"

# Use in complex calculation
from pollywog.core import Number
metal_value = Number(
    name="metal_value_usd",
    expression=[f"{prod_expr} * [tonnes] * [price_per_oz]"]
)

Parameters:

  • variables: List of variable names as strings

  • name (optional): Name for output variable. If None, returns expression string

  • comment (optional): Custom comment for the calculation

Returns: Number object when name is provided, or expression string when name is None

Average

Calculate the arithmetic mean of multiple variables.

from pollywog.helpers import Average

# With name: Returns a Number
avg_grade = Average(["Au_kriging", "Au_idw", "Au_nn"], name="Au_average")
# Generates: ([Au_kriging] + [Au_idw] + [Au_nn]) / 3

# Without name: Returns expression string for reuse
avg_expr = Average(["est1", "est2", "est3"])
# Returns: "([est1] + [est2] + [est3]) / 3"

Parameters:

  • variables: List of variable names as strings

  • name (optional): Name for output variable. If None, returns expression string

  • comment (optional): Custom comment for the calculation

Returns: Number object when name is provided, or expression string when name is None

WeightedAverage

Calculate a weighted average of variables. Essential for domain proportion calculations.

from pollywog.helpers import WeightedAverage

# Weighted by domain proportions
weighted_grade = WeightedAverage(
    variables=["Au_oxide", "Au_sulfide", "Au_transition"],
    weights=["prop_oxide", "prop_sulfide", "prop_transition"],
    name="Au_composite"
)
# Generates: ([Au_oxide] * [prop_oxide] + [Au_sulfide] * [prop_sulfide] +
#             [Au_transition] * [prop_transition]) /
#            ([prop_oxide] + [prop_sulfide] + [prop_transition])

# With constant weights
method_blend = WeightedAverage(
    variables=["Au_kriging", "Au_idw"],
    weights=[0.7, 0.3],
    name="Au_blended"
)
# Generates: ([Au_kriging] * 0.7 + [Au_idw] * 0.3) / (0.7 + 0.3)

Parameters:

  • variables: List of variable names to average

  • weights: List of weights (can be variable names or numeric constants)

  • name (optional): Name for output variable (default: wavg_<var1>_<var2>_...)

  • comment (optional): Custom comment for the calculation

Returns: Number object

Important: Weights are automatically summed in the denominator, so they don’t need to sum to 1.

WeightedSum

Calculate a weighted sum of variables (without dividing by the sum of weights).

from pollywog.helpers import WeightedSum

# Weighted contribution from multiple metals
metal_value = WeightedSum(
    variables=["Au_composite", "Ag_composite"],
    weights=[1800, 22],
    name="metal_value_per_tonne"
)
# Generates: ([Au_composite] * 1800 + [Ag_composite] * 22)

# Without name: Returns expression string
ws_expr = WeightedSum(["Au", "Ag"], [0.7, 0.3])
# Returns: "([Au] * 0.7 + [Ag] * 0.3)"

Parameters:

  • variables: List of variable names to sum

  • weights: List of weights (can be variable names or numeric constants)

  • name (optional): Name for output variable. If None, returns expression string

  • comment (optional): Custom comment for the calculation

Returns: Number object when name is provided, or expression string when name is None

Note: Unlike WeightedAverage, this does not divide by the sum of weights. Use this when you want the raw weighted total (e.g., revenue calculations).

Scale

Multiply a variable by a scaling factor.

from pollywog.helpers import Scale

# Apply dilution factor
diluted = Scale("Au_est", 0.95, name="Au_diluted")
# Generates: [Au_est] * 0.95

# Scale by another variable
adjusted = Scale("grade", "adjustment_factor", name="grade_adjusted")
# Generates: [grade] * [adjustment_factor]

Parameters:

  • variable: Variable name to scale

  • factor: Scaling factor (number or variable name)

  • name (optional): Name for output variable (default: scale_<variable>)

  • comment (optional): Custom comment for the calculation

Returns: Number object

Normalize

Normalize a variable to the range [0, 1] given min and max values.

from pollywog.helpers import Normalize

# Normalize grade to 0-1 range
norm_grade = Normalize("Au", min_value=0, max_value=10, name="Au_normalized")
# Generates: ([Au] - 0) / (10 - 0)

# Normalize depth
norm_depth = Normalize("depth", min_value=0, max_value=500, name="depth_norm")

Parameters:

  • variable: Variable name to normalize

  • min_value: Minimum value for normalization

  • max_value: Maximum value for normalization

  • name (optional): Name for output variable (default: norm_<variable>)

  • comment (optional): Custom comment for the calculation

Returns: Number object

Note: If the variable equals min_value, result is 0. If it equals max_value, result is 1.

Classification Helpers

CategoryFromThresholds

Create categorical classifications based on numeric thresholds. Perfect for grade shells, ore types, and quality classifications.

from pollywog.helpers import CategoryFromThresholds

# Classify ore grade
ore_class = CategoryFromThresholds(
    variable="Au",
    thresholds=[0.3, 1.0, 3.0],
    categories=["waste", "low_grade", "medium_grade", "high_grade"],
    name="Au_class"
)
# Creates an If statement:
# If Au <= 0.3: "waste"
# Else if Au <= 1.0: "low_grade"
# Else if Au <= 3.0: "medium_grade"
# Else: "high_grade"

# Rock quality designation
rqd_class = CategoryFromThresholds(
    variable="RQD",
    thresholds=[25, 50, 75, 90],
    categories=["very_poor", "poor", "fair", "good", "excellent"],
    name="rock_quality",
    comment="RQD classification per Deere, 1989"
)

Parameters:

  • variable: Variable to threshold

  • thresholds: List of threshold values (must be sorted in ascending order)

  • categories: List of category labels (length must be len(thresholds) + 1)

  • name (optional): Name for output category (default: class_<variable>)

  • comment (optional): Custom comment for the calculation

Returns: Category object

Important: - Thresholds must be in ascending order - Number of categories must be one more than number of thresholds - Categories below or equal to first threshold get first label - Categories above last threshold get last label

NumberFromThresholds

Create numeric value assignments based on thresholds. The numeric counterpart to CategoryFromThresholds — assigns numbers instead of category labels.

from pollywog.helpers import NumberFromThresholds

# Assign numeric scores based on grade ranges
grade_score = NumberFromThresholds(
    variable="Au",
    thresholds=[0.5, 1.0, 2.0],
    values=[0, 1, 2, 3],
    name="Au_score"
)
# Creates an If statement:
# If Au <= 0.5: 0
# Else if Au <= 1.0: 1
# Else if Au <= 2.0: 2
# Else: 3

# Recovery factors by grade range
recovery = NumberFromThresholds(
    variable="Au",
    thresholds=[0.5, 2.0, 5.0],
    values=[0.75, 0.82, 0.88, 0.92],
    name="Au_recovery",
    comment="Grade-dependent recovery factor"
)

# Without name: Returns If block for composition
score_if = NumberFromThresholds("RQD", [25, 50, 75], [1, 2, 3, 4])
# Returns an If object for use in more complex expressions

Parameters:

  • variable: Variable to threshold

  • thresholds: List of threshold values (must be sorted in ascending order)

  • values: List of numeric values (length must be len(thresholds) + 1)

  • name (optional): Name for output variable. If None, returns the If block

  • comment (optional): Custom comment for the calculation

Returns: Number object when name is provided, or If block when name is None

Important: - Thresholds must be in ascending order - Number of values must be one more than number of thresholds - Values below or equal to first threshold get first value - Values above last threshold get last value

Using Helpers in Workflows

Combining Multiple Helpers

Helpers work well together to build complex calculations:

from pollywog.core import CalcSet
from pollywog.helpers import WeightedAverage, Product, CategoryFromThresholds

# Multi-metal resource estimation
domains = ["oxide", "transition", "sulfide"]

calcset = CalcSet([
    # Weighted grades by domain
    WeightedAverage(
        variables=[f"Au_{d}" for d in domains],
        weights=[f"prop_{d}" for d in domains],
        name="Au_composite"
    ),
    WeightedAverage(
        variables=[f"Cu_{d}" for d in domains],
        weights=[f"prop_{d}" for d in domains],
        name="Cu_composite"
    ),

    # Payable metal
    Product("Au_composite", "recovery_Au", "tonnes", name="Au_payable_oz"),
    Product("Cu_composite", "recovery_Cu", "tonnes", name="Cu_payable_lbs"),

    # Classification
    CategoryFromThresholds(
        variable="Au_composite",
        thresholds=[0.5, 1.5],
        categories=["low", "medium", "high"],
        name="grade_class"
    ),
])

calcset.to_lfcalc("multi_metal_workflow.lfcalc")

Programmatic Generation

Use Python loops to generate many similar calculations:

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

metals = ["Au", "Ag", "Cu", "Pb", "Zn"]
domains = ["high_grade", "medium_grade", "low_grade"]

calculations = []

# Weighted average for each metal
for metal in metals:
    calculations.append(
        WeightedAverage(
            variables=[f"{metal}_{d}" for d in domains],
            weights=[f"prop_{d}" for d in domains],
            name=f"{metal}_composite",
            comment=f"Domain-weighted {metal} grade"
        )
    )

# Apply recovery factor to each metal
recovery_factors = {
    "Au": 0.88, "Ag": 0.75, "Cu": 0.85, "Pb": 0.80, "Zn": 0.82
}

for metal in metals:
    calculations.append(
        Scale(
            f"{metal}_composite",
            recovery_factors[metal],
            name=f"{metal}_recovered",
            comment=f"{metal} grade after {recovery_factors[metal]*100:.0f}% recovery"
        )
    )

# Create and export calcset
calcset = CalcSet(calculations)
calcset.to_lfcalc("automated_workflow.lfcalc")

When to Use Helpers vs. Manual Expressions

Use Helpers When:

  • The calculation matches a helper pattern (sum, average, product, etc.)

  • You want readable, self-documenting code

  • You’re generating many similar calculations programmatically

  • You want to minimize syntax errors

Use Manual Expressions When:

  • The calculation is unique or complex

  • You need fine control over the expression

  • The calculation doesn’t fit any helper pattern

  • You need to use specific Leapfrog functions (clamp, log, etc.)

Example comparing both approaches:

from pollywog.core import CalcSet, Number
from pollywog.helpers import Sum, Product

# Using helpers with name (returns Number)
helper_approach = CalcSet([
    # Sum multiple Au assays for composite
    Sum(["Au_north", "Au_south", "Au_center"], name="Au_total"),
    # Calculate block tonnage
    Product(["block_volume", "density"], name="tonnage"),
])

# Using manual expressions (needed for complex logic)
manual_approach = CalcSet([
    Number(name="complex_calc", expression=[
        "log([Au] + 0.01) * clamp([recovery], 0.5, 1.0) + [base_adjustment]"
    ]),
])

# Combining helpers without name for complex expressions
from pollywog.helpers import WeightedAverage, Product
mixed_approach = CalcSet([
    # Simple standalone: average multiple Au estimates
    Sum(["Au_kriging", "Au_idw", "Au_nn"], name="Au_sum_for_avg"),
    # Complex: Calculate NSR using helper composition
    Number(
        name="NSR_Au",
        expression=[
            # Gold revenue: grade * price * recovery * conversion
            f"{Product(['Au_composite', '31.1035'])} * [Au_price_per_oz] * [Au_recovery]"
        ]
    ),
])

Advanced Usage: Composing Helpers

The real power of helpers returning expressions (when name=None) is the ability to compose them into more complex calculations.

Nested Helper Expressions

from pollywog.core import Number, CalcSet
from pollywog.helpers import Sum, Product, Average, WeightedAverage

# Build complex expression from helper building blocks
calcset = CalcSet([
    # Calculate total block dimensions for geometric checks
    Number(
        name="total_block_dimension",
        expression=[
            f"({Sum(['block_x', 'block_y', 'block_z'])})"
        ]
    ),

    # Average of multiple Au estimates times tonnage
    Number(
        name="Au_metal_content",
        expression=[
            f"{Average(['Au_kriging', 'Au_idw', 'Au_nn'])} * "
            f"{Product(['block_volume', 'density'])}"
        ]
    ),

    # Weighted average grade with clamping for safety
    Number(
        name="Au_composite_safe",
        expression=[
            f"clamp({WeightedAverage(['Au_oxide', 'Au_sulfide'], ['prop_oxide', 'prop_sulfide'])}, 0, 100)"
        ]
    ),
])

Using Helper Expressions in Conditional Logic

from pollywog.core import Number, If, CalcSet
from pollywog.helpers import Product, CategoryFromThresholds

# Use helper expressions in If conditions
# Example: Adjust dilution based on Au grade
calcset = CalcSet([
    Number(
        name="dilution_factor",
        expression=[
            If([
                ("[Au_composite] > 5", "1.05"),  # High grade: 5% dilution
                ("[Au_composite] > 2", "1.10"),  # Medium grade: 10% dilution
            ], otherwise=["1.15"])  # Low grade: 15% dilution
        ]
    ),

    # Calculate metal content with grade-dependent recovery
    Number(
        name="Au_recovered_oz",
        expression=[
            If([
                ("[Au_composite] > 3",
                 f"{Product(['Au_composite', '31.1035', 'tonnage', '0.92'])}"),  # High grade: 92% recovery
                ("[Au_composite] > 1",
                 f"{Product(['Au_composite', '31.1035', 'tonnage', '0.88'])}"),  # Medium: 88% recovery
            ], otherwise=[f"{Product(['Au_composite', '31.1035', 'tonnage', '0.82'])}"])  # Low: 82% recovery
        ]
    ),
])

Real-World Example: Multi-Metal Resource Model

from pollywog.core import CalcSet, Number
from pollywog.helpers import WeightedAverage, Sum, Product, Scale

metals = ["Au", "Ag", "Cu", "Pb", "Zn"]
domains = ["oxide", "transition", "sulfide"]

calculations = []

# Step 1: Domain-weighted composites (with name)
for metal in metals:
    calculations.append(
        WeightedAverage(
            variables=[f"{metal}_{d}" for d in domains],
            weights=[f"prop_{d}" for d in domains],
            name=f"{metal}_composite"
        )
    )

# Step 2: Revenue calculation with nested helpers - each metal priced individually
prices = {"Au": 1800, "Ag": 22, "Cu": 3.5, "Pb": 0.9, "Zn": 1.2}
recoveries = {"Au": 0.88, "Ag": 0.75, "Cu": 0.85, "Pb": 0.80, "Zn": 0.82}

revenue_terms = []
for metal in metals:
    # Use Scale without name to get expression
    recovered_expr = Scale(f"{metal}_composite", recoveries[metal])
    # Use Product without name to get expression
    value_expr = Product([recovered_expr, str(prices[metal])])
    revenue_terms.append(value_expr)

# Sum all revenue terms
calculations.append(
    Number(
        name="total_revenue_per_tonne",
        expression=[Sum(revenue_terms)],
        comment_equation="Total revenue from all metals after recovery"
    )
)

calcset = CalcSet(calculations)
calcset.to_lfcalc("complex_resource_model.lfcalc")
This example shows how helpers with name=None enable building complex calculations while keeping code readable and maintainable.

“[precious] * clamp([price_ratio], 0, 5) + [premium]”

]),

])

Custom Helper Functions

You can create your own helper functions following the same pattern:

from pollywog.core import Number

def GradeThickness(grade_var, thickness_var, name=None, comment=None):
    """
    Calculate grade-thickness product (common in vein modeling).
    """
    if name is None:
        name = f"GT_{grade_var}"
    expr = f"[{grade_var}] * [{thickness_var}]"
    return Number(
        name,
        [expr],
        comment_equation=comment or f"Grade-thickness product for {grade_var}"
    )

def MetalValue(grade_var, price, recovery=1.0, name=None, comment=None):
    """
    Calculate metal value per tonne.
    """
    if name is None:
        name = f"value_{grade_var}"
    expr = f"[{grade_var}] * {price} * {recovery} / 31.1035"
    return Number(
        name,
        [expr],
        comment_equation=comment or f"Metal value for {grade_var} at ${price}/oz"
    )

# Use your custom helpers
from pollywog.core import CalcSet

calcset = CalcSet([
    GradeThickness("Au", "vein_width", name="Au_GT"),
    MetalValue("Au", 1800, recovery=0.88, name="Au_value_per_tonne"),
])

Helper Function Reference

Quick reference table:

Helper

Returns

Use Case

Sum

Number

Add multiple variables

Product

Number

Multiply multiple variables

Average

Number

Arithmetic mean

WeightedSum

Number

Weighted total (revenue, scoring)

WeightedAverage

Number

Weighted mean (domain proportions)

Scale

Number

Multiply by factor (dilution, recovery)

Normalize

Number

Scale to [0, 1] range

NumberFromThresholds

Number

Assign numeric values by thresholds

CategoryFromThresholds

Category

Classify by numeric thresholds

Common Patterns

Domain Proportion Weighting

from pollywog.helpers import WeightedAverage

domains = ["dom1", "dom2", "dom3"]
metals = ["Au", "Ag", "Cu"]

calcs = [
    WeightedAverage(
        variables=[f"{metal}_{d}" for d in domains],
        weights=[f"prop_{d}" for d in domains],
        name=f"{metal}_final"
    )
    for metal in metals
]

Multi-Step Calculations

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

CalcSet([
    # Step 1: Apply dilution
    Scale("Au_est", 0.95, name="Au_diluted"),

    # Step 2: Apply recovery
    Scale("Au_diluted", 0.88, name="Au_recovered"),

    # Step 3: Calculate metal content
    Product("Au_recovered", "tonnes", name="Au_ounces_total"),
])

Economic Calculations

from pollywog.core import CalcSet, Number
from pollywog.helpers import Sum, Product

CalcSet([
    # Revenue per tonne for each metal
    Product("Au_recovered", "Au_price", name="Au_revenue_per_oz"),
    Product("Cu_recovered", "Cu_price", name="Cu_revenue_per_lb"),

    # Convert to $/tonne
    Number(name="Au_value", expression=["[Au_revenue_per_oz] / 31.1035"]),
    Number(name="Cu_value", expression=["[Cu_revenue_per_lb] * 22.046"]),

    # Total revenue
    Sum("Au_value", "Cu_value", name="total_revenue_per_tonne"),
])

See Also