Skip to main content

Overview

The Veydra Model Standard (VMS) is a set of conventions for building system dynamics models that are both executable and analyzable. VMS-compliant models enable automatic generation of stock-and-flow diagrams, causal loop diagrams, and other analytical visualizations through AST (Abstract Syntax Tree) parsing.

Executable

Models run in Python with numerical simulation via SciPy

Analyzable

AST parsing extracts structure for automatic diagram generation

Modular

Independent submodels can be composed into larger systems

Traceable

Explicit dependencies enable causal loop analysis

Core Architecture

VMS models consist of:
  • Submodels: Independent modules that inherit from Submodel base class
  • VARIABLES Dictionary: Module-level declaration of all model variables
  • SimulationContext: Runtime context for accessing stocks and parameters
  • Calculation Functions: Module-level functions for complex computations
from veydra_model_standard import Submodel, VeydraModelStandard, SimulationContext
import numpy as np

Submodel Structure

Every submodel follows this structure:
"""Module docstring describing the submodel's purpose."""

from veydra_model_standard import Submodel
import numpy as np

# 1. VMS Calculation Functions (optional, for complex logic)
def calc_something(input1, input2):
    """Docstring describing the calculation."""
    return input1 * input2

# 2. VARIABLES dictionary (REQUIRED - module level)
VARIABLES = {
    'namespace.s_stock_name': {...},
    'namespace.parameter_name': {...},
}

# 3. Submodel class
class MySubmodel(Submodel):
    """Class docstring."""
    
    def calculate_derivatives_and_flows(self, sim_context):
        # Implementation
        return derivatives, flows
Required Rules:
  • Do NOT override __init__ method
  • Do NOT override get_variables() method
  • VARIABLES dict MUST be at module level
  • Only implement calculate_derivatives_and_flows()

VARIABLES Dictionary

The VARIABLES dictionary declares all model variables at module level.

Stock Definition

'budget.s_government_budget': {
    'name': 'Government Budget',           # Display name
    'units': 'dollars',                    # Unit of measurement
    'description': 'Accumulated fund',     # Plain text description
    'default': 100000.0,                   # Initial value (THIS IS THE INITIAL VALUE)
    'min': -1000000.0,                     # Minimum allowed
    'max': 10000000.0,                     # Maximum allowed
    'type': 'slider',                      # UI control type
    'category': 'stock'                    # MUST be 'stock'
}

Parameter Definition

'budget.tax_rate': {
    'name': 'Tax Rate',
    'units': 'dimensionless',
    'description': 'Fraction collected as tax',
    'default': 0.02,
    'min': 0.0,
    'max': 0.15,
    'step': 0.005,                         # Step size for UI
    'type': 'slider',
    'category': 'parameter'                # MUST be 'parameter'
}

Naming Conventions

TypePrefix/SuffixExample
Stocks_ prefixbudget.s_government_budget
Parameternonebudget.tax_rate
Flow_flow suffixbudget.tax_revenue_flow
Critical Rule: The default field in a stock definition IS the initial value. Never create separate initial_* parameters.
# ❌ WRONG - Don't do this
'population.initial_population': {'default': 1000000, ...}  # FORBIDDEN

# ✅ CORRECT - Stock default IS the initial value
'population.s_population': {'default': 1000000, ...}  # default = initial value

Stock-and-Flow Modeling

Separate Flows by Direction

Each parameter affecting a stock must have its own distinct flow. Never collapse flows.
# Separate flows preserve directionality
deposit_flow = periodic_deposit_amount
withdrawal_flow = periodic_withdrawal_amount
interest_flow = balance * interest_rate

d_balance_dt = deposit_flow - withdrawal_flow + interest_flow

flows = {
    'account.deposit_flow': deposit_flow,
    'account.withdrawal_flow': withdrawal_flow,
    'account.interest_flow': interest_flow
}

Why This Matters

  • Individual flows enable proper stock-and-flow diagram generation
  • Collapsed flows obscure causal relationships
  • AST parser cannot trace through intermediate “net” variables

Direct Flow Reference Rule

Derivatives MUST directly reference the same variable names that appear in the flows dictionary. This enables the AST parser to trace stock-flow relationships for automatic diagram generation.
# Step 1: Define flow variables
government_tax_revenue_flow = tax_revenue
government_spending_flow = spending

# Step 2: Derivative DIRECTLY references flow variable names
d_government_budget_dt = government_tax_revenue_flow - government_spending_flow

# Step 3: flows dict uses the SAME variable names
flows = {
    'budget.government_tax_revenue_flow': government_tax_revenue_flow,  # ✅ Same name
    'budget.government_spending_flow': government_spending_flow         # ✅ Same name
}
Common Mistakes:
  • Using different variable names in derivative vs flows dict
  • Using intermediate “net” variables that hide individual flows

Calculation Function Convention

For complex intermediate calculations (conditionals, aggregations, multi-step computations), define module-level calculation functions where ALL dependencies are explicit in the function signature.

Why Use Calculation Functions

The AST parser extracts all function call arguments as dependencies. This makes complex logic fully traceable.
# AST parser sees: pool_frac_ill depends on all 5 arguments
pool_frac_ill = calc_pool_frac_ill(insured_pool, insured_healthy, frac_ill_healthy, insured_sickly, frac_ill_sickly)

Pattern

# Module-level calculation function with explicit dependencies
def calc_pool_frac_ill(insured_pool, insured_healthy, frac_ill_healthy, insured_sickly, frac_ill_sickly):
    """Calculate weighted average fraction ill across insured pool."""
    if insured_pool > 1.0:
        return (insured_healthy * frac_ill_healthy + insured_sickly * frac_ill_sickly) / insured_pool
    return frac_ill_healthy

# In calculate_derivatives_and_flows:
pool_frac_ill = calc_pool_frac_ill(insured_pool, insured_healthy, frac_ill_healthy, 
                                    insured_sickly, frac_ill_sickly)

Function Naming Convention

  • Use calc_ prefix: calc_pool_frac_ill, calc_total_income, calc_tax_revenue
  • Name should describe what is being calculated
  • Keep signatures to 6 or fewer parameters when possible

When to Use Calculation Functions

Use CaseExample
Conditional logicif pool > 0: ... else: ...
Weighted averages(a * weight_a + b * weight_b) / total
Multi-step computationsIntermediate results needed
Complex aggregationsSummations with conditions

Derivative Patterns

Dictionary Format (Required)

Always return derivatives as a dictionary mapping stock names to their rates of change:
derivatives = {
    'budget.s_government_budget': d_government_budget_dt,
    'budget.s_private_budget': d_private_budget_dt
}
Never use array format - it’s unclear which stock is which:
# ❌ WRONG
derivatives = [d_government_budget_dt, d_private_budget_dt]

Derivative Expression Pattern

Build derivatives from individual flows with explicit addition/subtraction:
# ✅ CORRECT - Explicit inflow/outflow pattern
d_stock_dt = inflow1 + inflow2 - outflow1 - outflow2

Accessing Stocks and Parameters

# ✅ CORRECT - Use getter methods
stock_value = sim_context.get_stock('namespace.s_stock_name', default_value)
param_value = sim_context.get_param('namespace.param_name', default_value)

# ❌ WRONG - Direct attribute access
stock_value = sim_context.stocks['namespace.s_stock_name']  # Don't do this
param_value = sim_context.params['namespace.param_name']    # Don't do this

AST Traceability

The VMS conventions enable automatic AST parsing for:
1

Stock-Flow Diagram Generation

Automatically creates visual diagrams showing stocks, flows, and their connections
2

Causal Loop Analysis

Traces parameter influences through the model to identify feedback loops
3

Dependency Extraction

Identifies all variable dependencies for each calculation

How AST Parsing Works

  1. Shallow Extraction: Parser extracts direct variable references from expressions
  2. Function Arguments: All arguments to calc_* functions are extracted as dependencies
  3. Flow Dictionary Keys: Flow names from the flows dict are matched to derivative expressions
ConventionEnables
Direct Flow ReferenceStock-to-flow connections in diagrams
Calculation FunctionsComplete dependency graphs
Separate FlowsIndividual causal links
VARIABLES dictParameter metadata extraction

Complete Example

Here’s a complete VMS-compliant submodel:
"""Budget submodel: government and private sector funding dynamics."""

from veydra_model_standard import Submodel
import numpy as np


# ═══════════════════════════════════════════════════════════════════════════════
# VMS CALCULATION FUNCTIONS - explicit dependency signatures for AST traceability
# ═══════════════════════════════════════════════════════════════════════════════

def calc_tax_revenue(total_income, tax_rate):
    """Calculate government tax revenue from total income."""
    return total_income * tax_rate


def calc_spending(investment_spending, split_fraction):
    """Calculate spending portion based on split."""
    return investment_spending * split_fraction


# ═══════════════════════════════════════════════════════════════════════════════
# VARIABLES - module-level declaration (stock defaults ARE initial values)
# ═══════════════════════════════════════════════════════════════════════════════

VARIABLES = {
    # Stocks
    'budget.s_government_budget': {
        'name': 'Government Budget',
        'units': 'dollars',
        'description': 'Accumulated government fund',
        'default': 100000.0,  # THIS IS THE INITIAL VALUE
        'min': -1000000.0,
        'max': 10000000.0,
        'type': 'slider',
        'category': 'stock'
    },
    
    # Parameters
    'budget.tax_rate': {
        'name': 'Tax Rate',
        'units': 'dimensionless',
        'description': 'Fraction collected as healthcare tax',
        'default': 0.02,
        'min': 0.0,
        'max': 0.15,
        'step': 0.005,
        'type': 'slider',
        'category': 'parameter'
    }
}


class BudgetSubmodel(Submodel):
    """Government budget dynamics with tax revenue and spending."""

    def calculate_derivatives_and_flows(self, sim_context):
        # ── Read stocks ───────────────────────────────────────────────────
        government_budget = sim_context.get_stock('budget.s_government_budget', 100000.0)

        # ── Read parameters ───────────────────────────────────────────────
        tax_rate = sim_context.get_param('budget.tax_rate', 0.02)
        
        # ── Read cross-submodel values ────────────────────────────────────
        total_income = sim_context.get_param('economy.total_income', 1000000.0)
        investment_spending = sim_context.get_param('capacity.investment_spending', 50000.0)

        # ── VMS Calculation Functions ─────────────────────────────────────
        tax_revenue = calc_tax_revenue(total_income, tax_rate)
        government_spending = calc_spending(investment_spending, 0.5)

        # ── Define flow variables (for direct reference rule) ─────────────
        tax_revenue_flow = tax_revenue
        spending_flow = government_spending

        # ── Derivative (directly references flow variable names) ──────────
        d_government_budget_dt = tax_revenue_flow - spending_flow

        # ── Return dictionaries ───────────────────────────────────────────
        derivatives = {
            'budget.s_government_budget': d_government_budget_dt
        }

        flows = {
            'budget.tax_revenue_flow': tax_revenue_flow,    # Inflow
            'budget.spending_flow': spending_flow           # Outflow
        }

        return derivatives, flows

Quick Reference Checklist

When writing VMS-compliant models, verify: