Data Module

The data module provides scenario-based data management capabilities for optimization problems. It includes data objects with multi-scenario support and type-safe database collections for organizing data.

Data Classes

Data Objects

class data.OXData(id: ~uuid.UUID = <factory>, class_name: str = '', active_scenario: str = 'Default', scenarios: dict[str, dict[str, ~typing.Any]] = <factory>)[source]

Bases: OXObject

A base class for data objects with scenario support.

This class provides a mechanism for storing different attribute values for different scenarios. When an attribute is accessed, the system first checks if it exists in the active scenario, and if not, falls back to the object’s own attribute.

active_scenario

The name of the currently active scenario. Defaults to “Default”.

Type:

str

scenarios

A dictionary mapping scenario names to dictionaries of attribute values.

Type:

dict[str, dict[str, Any]]

Examples

>>> data = OXData()
>>> data.value = 10
>>> data.create_scenario("Optimistic", value=20)
>>> data.create_scenario("Pessimistic", value=5)
>>> print(data.value)  # Default scenario
10
>>> data.active_scenario = "Optimistic"
>>> print(data.value)  # Optimistic scenario
20
>>> data.active_scenario = "Pessimistic"
>>> print(data.value)  # Pessimistic scenario
5
active_scenario: str = 'Default'
scenarios: dict[str, dict[str, Any]]
__getattribute__(item)[source]

Custom attribute access that checks the active scenario first.

When an attribute is accessed, this method first checks if it exists in the active scenario, and if not, falls back to the object’s own attribute.

Parameters:

item (str) – The name of the attribute to access.

Returns:

The value of the attribute in the active scenario, or the

object’s own attribute if not found in the active scenario.

Return type:

Any

create_scenario(scenario_name: str, **kwargs)[source]

Create a new scenario with the specified attribute values.

If the “Default” scenario doesn’t exist yet, it is created first, capturing the object’s current attribute values.

Parameters:
  • scenario_name (str) – The name of the new scenario.

  • **kwargs – Attribute-value pairs for the new scenario.

Raises:

OXception – If an attribute in kwargs doesn’t exist in the object.

Examples

>>> data = OXData()
>>> data.value = 10
>>> data.create_scenario("Optimistic", value=20)
>>> data.active_scenario = "Optimistic"
>>> print(data.value)
20
__init__(id: ~uuid.UUID = <factory>, class_name: str = '', active_scenario: str = 'Default', scenarios: dict[str, dict[str, ~typing.Any]] = <factory>) None

Database Collections

class data.OXDatabase(id: ~uuid.UUID = <factory>, class_name: str = '', objects: list[~base.OXObject.OXObject] = <factory>)[source]

Bases: OXObjectPot

A container for OXData objects.

This class extends OXObjectPot to provide a container specifically for OXData objects. It enforces type safety by ensuring that only OXData objects can be added to or removed from the database.

Examples

>>> db = OXDatabase()
>>> data1 = OXData()
>>> data2 = OXData()
>>> db.add_object(data1)
>>> db.add_object(data2)
>>> len(db)
2
>>> for data in db:
...     print(data.id)
12345678-1234-5678-1234-567812345678
87654321-4321-8765-4321-876543210987

See also

base.OXObjectPot.OXObjectPot data.OXData.OXData

add_object(obj: OXObject)[source]

Add an OXData object to the database.

Parameters:

obj (OXObject) – The object to add. Must be an instance of OXData.

Raises:

OXception – If the object is not an instance of OXData.

remove_object(obj: OXObject)[source]

Remove an OXData object from the database.

Parameters:

obj (OXObject) – The object to remove. Must be an instance of OXData.

Raises:
  • OXception – If the object is not an instance of OXData.

  • ValueError – If the object is not in the database.

__init__(id: ~uuid.UUID = <factory>, class_name: str = '', objects: list[~base.OXObject.OXObject] = <factory>) None

Constants

data.NON_SCENARIO_FIELDS = ['active_scenario', 'scenarios', 'id', 'class_name']

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

List of field names that are excluded from scenario management to prevent infinite loops and maintain object integrity. These fields are always accessed from the base object.

Examples

Basic Data Objects with Scenarios

from data import OXData

# Create a data object with base values
demand_data = OXData()
demand_data.quantity = 100
demand_data.cost = 50.0

# Create scenarios for sensitivity analysis
demand_data.create_scenario("High_Demand", quantity=150, cost=55.0)
demand_data.create_scenario("Low_Demand", quantity=75, cost=45.0)

# Access values in different scenarios
print(demand_data.quantity)  # 100 (Default scenario)

demand_data.active_scenario = "High_Demand"
print(demand_data.quantity)  # 150

demand_data.active_scenario = "Low_Demand"
print(demand_data.quantity)  # 75

Database Collections

from data import OXDatabase, OXData

# Create a database for organizing data objects
db = OXDatabase()

# Create multiple data objects
factory_a = OXData()
factory_a.location = "Factory_A"
factory_a.capacity = 500
factory_a.create_scenario("Expansion", capacity=750)

factory_b = OXData()
factory_b.location = "Factory_B"
factory_b.capacity = 300
factory_b.create_scenario("Expansion", capacity=450)

# Add objects to database
db.add_object(factory_a)
db.add_object(factory_b)

print(f"Total factories: {len(db)}")

# Iterate through all objects
for factory in db:
    print(f"Factory at {factory.location}: capacity {factory.capacity}")

# Switch to expansion scenario
factory_a.active_scenario = "Expansion"
factory_b.active_scenario = "Expansion"

# Check expanded capacities
for factory in db:
    print(f"Expanded {factory.location}: capacity {factory.capacity}")

Scenario Management for Optimization

from data import OXData

# Create demand data with multiple scenarios
demand = OXData()
demand.product = "Widget_A"
demand.base_demand = 1000
demand.seasonal_factor = 1.0

# Create scenarios for different market conditions
demand.create_scenario(
    "Optimistic",
    base_demand=1200,
    seasonal_factor=1.2
)

demand.create_scenario(
    "Pessimistic",
    base_demand=800,
    seasonal_factor=0.8
)

demand.create_scenario(
    "Realistic",
    base_demand=1000,
    seasonal_factor=1.0
)

# Function to calculate total demand
def calculate_total_demand(data, season_multiplier=1.0):
    return data.base_demand * data.seasonal_factor * season_multiplier

# Compare scenarios
scenarios = ["Default", "Optimistic", "Pessimistic", "Realistic"]

for scenario in scenarios:
    demand.active_scenario = scenario
    total = calculate_total_demand(demand)
    print(f"{scenario} scenario: {total} units")

Scenario-Based Sensitivity Analysis

def run_sensitivity_analysis(data_objects, scenarios, optimization_function):
    """Run optimization across multiple scenarios."""

    results = {}

    for scenario_name in scenarios:
        print(f"Running scenario: {scenario_name}")

        # Switch all data objects to the same scenario
        for data_obj in data_objects:
            if scenario_name in data_obj.scenarios:
                data_obj.active_scenario = scenario_name
            else:
                data_obj.active_scenario = "Default"

        # Run optimization with current scenario data
        result = optimization_function(data_objects)
        results[scenario_name] = result

    return results

# Usage example
def simple_profit_calculation(data_objects):
    total_profit = 0
    for obj in data_objects:
        if hasattr(obj, 'revenue') and hasattr(obj, 'cost'):
            total_profit += obj.revenue - obj.cost
    return total_profit

# Create test data
product1 = OXData()
product1.revenue = 100
product1.cost = 60
product1.create_scenario("HighPrice", revenue=120, cost=60)
product1.create_scenario("LowCost", revenue=100, cost=45)

product2 = OXData()
product2.revenue = 80
product2.cost = 50
product2.create_scenario("HighPrice", revenue=95, cost=50)
product2.create_scenario("LowCost", revenue=80, cost=40)

products = [product1, product2]
scenarios = ["Default", "HighPrice", "LowCost"]

results = run_sensitivity_analysis(products, scenarios, simple_profit_calculation)

for scenario, profit in results.items():
    print(f"{scenario}: ${profit} profit")

Working with Multiple Data Types

from data import OXData, OXDatabase

def create_supply_chain_data():
    """Create a multi-type supply chain dataset."""

    # Create suppliers with different scenarios
    supplier_db = OXDatabase()

    supplier_a = OXData()
    supplier_a.name = "Supplier_A"
    supplier_a.lead_time = 14
    supplier_a.reliability = 0.95
    supplier_a.cost_factor = 1.0
    supplier_a.create_scenario("Crisis", lead_time=21, reliability=0.85, cost_factor=1.3)

    supplier_b = OXData()
    supplier_b.name = "Supplier_B"
    supplier_b.lead_time = 7
    supplier_b.reliability = 0.98
    supplier_b.cost_factor = 1.1
    supplier_b.create_scenario("Crisis", lead_time=10, reliability=0.90, cost_factor=1.4)

    supplier_db.add_object(supplier_a)
    supplier_db.add_object(supplier_b)

    # Create demand points with scenarios
    demand_db = OXDatabase()

    region_1 = OXData()
    region_1.name = "Region_1"
    region_1.demand = 1000
    region_1.max_price = 50
    region_1.create_scenario("Growth", demand=1300, max_price=55)
    region_1.create_scenario("Recession", demand=700, max_price=45)

    region_2 = OXData()
    region_2.name = "Region_2"
    region_2.demand = 800
    region_2.max_price = 48
    region_2.create_scenario("Growth", demand=1100, max_price=52)
    region_2.create_scenario("Recession", demand=600, max_price=42)

    demand_db.add_object(region_1)
    demand_db.add_object(region_2)

    return supplier_db, demand_db

# Create the data
suppliers, demand_points = create_supply_chain_data()

# Analyze different scenarios
scenarios = ["Default", "Growth", "Crisis", "Recession"]

for scenario in scenarios:
    print(f"\n=== {scenario} Scenario ===")

    # Switch all objects to the scenario
    for supplier in suppliers:
        if scenario in supplier.scenarios:
            supplier.active_scenario = scenario
        else:
            supplier.active_scenario = "Default"

    for demand in demand_points:
        if scenario in demand.scenarios:
            demand.active_scenario = scenario
        else:
            demand.active_scenario = "Default"

    # Calculate scenario metrics
    total_demand = sum(d.demand for d in demand_points)
    avg_lead_time = sum(s.lead_time for s in suppliers) / len(suppliers)
    avg_reliability = sum(s.reliability for s in suppliers) / len(suppliers)

    print(f"Total demand: {total_demand}")
    print(f"Avg lead time: {avg_lead_time:.1f} days")
    print(f"Avg reliability: {avg_reliability:.2%}")

UUID-Based Object Access

from data import OXDatabase, OXData

# Create database with data objects
db = OXDatabase()

# Add several objects
for i in range(5):
    obj = OXData()
    obj.value = i * 10
    obj.category = f"Type_{i % 3}"
    db.add_object(obj)

print(f"Database contains {len(db)} objects")

# Access objects by UUID (inherited from OXObjectPot)
first_obj = list(db)[0]
found_obj = db.get_object_by_id(first_obj.id)
print(f"Found object with value: {found_obj.value}")

# Manual filtering using iteration
type_0_objects = [obj for obj in db if obj.category == "Type_0"]
print(f"Found {len(type_0_objects)} objects of Type_0")

# Remove objects
db.remove_object(first_obj)
print(f"After removal: {len(db)} objects")

Advanced Scenario Patterns

def create_monte_carlo_scenarios(base_data, num_scenarios=100):
    """Create multiple scenarios for Monte Carlo analysis."""
    import random

    for i in range(num_scenarios):
        # Create variations around base values
        demand_variation = random.uniform(0.8, 1.2)
        cost_variation = random.uniform(0.9, 1.1)

        scenario_name = f"MonteCarlo_{i+1:03d}"
        base_data.create_scenario(
            scenario_name,
            demand=int(base_data.demand * demand_variation),
            cost=base_data.cost * cost_variation
        )

# Create base data
product = OXData()
product.demand = 1000
product.cost = 50.0
product.revenue = 75.0

# Generate Monte Carlo scenarios
create_monte_carlo_scenarios(product, 10)  # Create 10 scenarios

# Run analysis across all scenarios
profits = []
for scenario_name in product.scenarios:
    product.active_scenario = scenario_name
    profit = product.revenue - product.cost
    profits.append((scenario_name, profit))

# Show results
for scenario, profit in sorted(profits, key=lambda x: x[1], reverse=True)[:5]:
    print(f"{scenario}: ${profit:.2f} profit")

Type Safety and Error Handling

from data import OXDatabase, OXData
from base import OXObject, OXception

# Demonstrate type safety
db = OXDatabase()
data_obj = OXData()

# This works - OXData is allowed
db.add_object(data_obj)
print("OXData object added successfully")

# This will fail - only OXData objects allowed
try:
    non_data_obj = OXObject()  # Base object, not OXData
    db.add_object(non_data_obj)
except OXception as e:
    print(f"Type safety error: {e}")

# Scenario error handling
try:
    data_obj.create_scenario("TestScenario", nonexistent_attr="value")
except OXception as e:
    print(f"Scenario creation error: {e}")

See Also

  • base - Base classes that data objects inherit from

  • Problem Module - Problem classes that use data objects

  • Variables Module - Variable creation that can be linked to data objects

  • ../user_guide/scenarios - Advanced scenario modeling guide