mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-05-31 08:33:36 +00:00
feature: refactored demand splitting and implementation
This commit is contained in:
@@ -1,30 +1,39 @@
|
|||||||
from sys import platform
|
from sys import platform
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .lib.demand import generate_demand, estimate_demand
|
from .lib.demand import generate_demand_for_actor, estimate_demand
|
||||||
from .lib.behavior import sample_behavior
|
from .lib.behavior import sample_behavior
|
||||||
from logging import INFO, getLogger
|
from logging import INFO, getLogger
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
logger.setLevel(INFO)
|
logger.setLevel(INFO)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MarketEngine():
|
class MarketEngine():
|
||||||
|
"""implements separate demand distributions for humans and agents per Section 3.1.1"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
alpha = 0.5,
|
alpha: float,
|
||||||
N = 100,
|
N: int,
|
||||||
demand_distribution = (50, 10),
|
human_params: tuple,
|
||||||
demand_sampling_function = np.random.normal):
|
agent_params: tuple,
|
||||||
|
demand_distribution = np.random.normal,
|
||||||
|
noise_std: float = 1.0):
|
||||||
|
# no defaults for D_H, D_A - force explicit experiment design
|
||||||
|
self.alpha = alpha
|
||||||
self.Nagents = int(N * alpha)
|
self.Nagents = int(N * alpha)
|
||||||
self.Nhumans = int(N * (1 - alpha))
|
self.Nhumans = int(N * (1 - alpha))
|
||||||
self.demand = (demand_sampling_function, demand_distribution)
|
self.human_params = human_params
|
||||||
|
self.agent_params = agent_params
|
||||||
|
self.noise_std = noise_std
|
||||||
|
self.demand_dist = demand_distribution
|
||||||
|
|
||||||
def act(self, prices):
|
def act(self, prices):
|
||||||
demand = generate_demand(prices, *self.demand)
|
# generate separate demands d() per actor type
|
||||||
sample_n = lambda n, human: [sample_behavior(demand, human=human) for _ in range(n)]
|
demand_h = generate_demand_for_actor(prices, self.human_params, self.noise_std, distribution_method = self.demand_dist)
|
||||||
human_t, agent_t = sample_n(self.Nhumans, True), sample_n(self.Nagents, False)
|
demand_a = generate_demand_for_actor(prices, self.agent_params, self.noise_std, distribution_method = self.demand_dist)
|
||||||
trajectories = human_t + agent_t
|
# sample behavior trajectories from each demand distribution
|
||||||
demand_estimate = estimate_demand(trajectories)
|
human_t = [sample_behavior(demand_h, human=True) for _ in range(self.Nhumans)]
|
||||||
return demand_estimate
|
agent_t = [sample_behavior(demand_a, human=False) for _ in range(self.Nagents)]
|
||||||
|
return estimate_demand(human_t + agent_t)
|
||||||
|
|
||||||
def measure(self):
|
def measure(self):
|
||||||
pass
|
pass
|
||||||
@@ -60,7 +69,7 @@ class Limbo():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
platform = PricingEngine()
|
platform = PricingEngine()
|
||||||
market = MarketEngine()
|
market = MarketEngine(alpha=0.3, N=100, human_params=(50, 10), agent_params=(45, 15))
|
||||||
limbo = Limbo(platform, market)
|
limbo = Limbo(platform, market)
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
limbo.step()
|
limbo.step()
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .demand import generate_demand, estimate_demand
|
from .demand import estimate_demand, generate_demand_for_actor
|
||||||
from .behavior import sample_behavior
|
from .behavior import sample_behavior
|
||||||
from .render import DashboardRenderer, style_axis
|
from .render import DashboardRenderer, style_axis
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from sim.rl.behavior_loader.models import BehaviorModel, AgentBehaviorModel, aggregate_event_transitions
|
from sim.rl.behavior_loader.models import BehaviorModel, AgentBehaviorModel, aggregate_event_transitions
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .demand import generate_demand
|
from .demand import generate_demand_for_actor
|
||||||
|
|
||||||
base_dir = "/home/velocitatem/Documents/Projects/PHANTOM/experiments"
|
base_dir = "/home/velocitatem/Documents/Projects/PHANTOM/experiments"
|
||||||
human_dir, agent_dir = f"{base_dir}/collected_data/", f"{base_dir}/agents/collected_data/"
|
human_dir, agent_dir = f"{base_dir}/collected_data/", f"{base_dir}/agents/collected_data/"
|
||||||
@@ -41,7 +41,7 @@ def sample_behavior(condition, human=True, max_len=40):
|
|||||||
return trajectory
|
return trajectory
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
t=sample_behavior(generate_demand(np.array([10,20,30])), human=True)
|
t=sample_behavior(generate_demand_for_actor(np.array([10,20,30])), human=True)
|
||||||
print(t)
|
print(t)
|
||||||
t=sample_behavior(generate_demand(np.array([10,20,30])), human=False)
|
t=sample_behavior(generate_demand_for_actor(np.array([10,20,30])), human=False)
|
||||||
print(t)
|
print(t)
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import numpy as np
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
def generate_demand(prices, distribution_method = np.random.normal, distribution_params = (50.0, 10.0)):
|
def generate_demand_for_actor(prices: np.ndarray, params: tuple, noise_std: float = 1.0, distribution_method=np.random.normal) -> np.ndarray:
|
||||||
# assumption 1: each product has an intrinsic valuation drawn from a normal distribution centered at 50
|
"""d(p;0) = max(0, valuation - price) + epsi for single actor type
|
||||||
product_valuations = distribution_method(*distribution_params, size=len(prices))
|
params: (mean, std) for valuation distribution D_H or D_A"""
|
||||||
# assumption 2: demand decreases as price increases, following a simple linear model
|
val = distribution_method(*params, size=len(prices))
|
||||||
demand = np.maximum(0, product_valuations - prices) # demand cannot be negative
|
noise = distribution_method(0, noise_std, len(prices))
|
||||||
|
demand = np.maximum(0, val - prices + noise)
|
||||||
total = np.sum(demand)
|
total = np.sum(demand)
|
||||||
demand = demand / total * 100 if total > 0 else demand # normalize to percentage, avoid div by zero
|
return demand / total * 100 if total > 0 else demand
|
||||||
logger.info(f"Generated demand for prices {prices}: {demand} with valuations from distribution {distribution_params}")
|
|
||||||
return demand
|
|
||||||
|
|
||||||
def estimate_demand(trajectories):
|
def estimate_demand(trajectories):
|
||||||
demand_estimate = {}
|
demand_estimate = {}
|
||||||
@@ -29,17 +29,16 @@ def estimate_demand(trajectories):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
np.random.seed(42)
|
np.random.seed(42)
|
||||||
prices = np.array([20.0, 35.0, 50.0, 65.0])
|
prices = np.array([20.0, 35.0, 50.0, 65.0])
|
||||||
demand = generate_demand(prices)
|
# demo actor-specific demands
|
||||||
print("Generated Demand:", demand)
|
human_params, agent_params = (50, 10), (45, 15)
|
||||||
|
demand_h = generate_demand_for_actor(prices, human_params)
|
||||||
|
demand_a = generate_demand_for_actor(prices, agent_params)
|
||||||
|
print("Human Demand:", demand_h)
|
||||||
|
print("Agent Demand:", demand_a)
|
||||||
from .behavior import sample_behavior
|
from .behavior import sample_behavior
|
||||||
N, alphat =200, 0.1
|
N, alpha = 200, 0.3
|
||||||
trajectories = []
|
n_h, n_a = int(N * (1 - alpha)), int(N * alpha)
|
||||||
for _ in range(int(N*(1 - alphat))):
|
human_t = [sample_behavior(demand_h, human=True) for _ in range(n_h)]
|
||||||
trajectories.append(sample_behavior(demand, human=True))
|
agent_t = [sample_behavior(demand_a, human=False) for _ in range(n_a)]
|
||||||
for _ in range(int(N*alphat)):
|
demand_estimate = estimate_demand(human_t + agent_t)
|
||||||
trajectories.append(sample_behavior(demand, human=False))
|
|
||||||
demand_estimate = estimate_demand(trajectories)
|
|
||||||
print("Estimated Demand from Behavior:", demand_estimate)
|
print("Estimated Demand from Behavior:", demand_estimate)
|
||||||
delta = {k: demand_estimate.get(k, 0) - demand[i] for i, k in enumerate(range(len(prices)))}
|
|
||||||
delta = np.mean([np.abs(v) for v in delta.values()])
|
|
||||||
print("Demand Delta:", delta)
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ class PHANTOM(gym.Env):
|
|||||||
n_products: int = 10,
|
n_products: int = 10,
|
||||||
alpha: float = 0.3,
|
alpha: float = 0.3,
|
||||||
N: int = 100,
|
N: int = 100,
|
||||||
|
human_params: tuple = (50.0, 10.0),
|
||||||
|
agent_params: tuple = (45.0, 15.0),
|
||||||
|
noise_std: float = 1.0,
|
||||||
price_bounds: tuple = (10.0, 150.0),
|
price_bounds: tuple = (10.0, 150.0),
|
||||||
lambda_coi: float = 0.1,
|
lambda_coi: float = 0.1,
|
||||||
coi_window: int = 10,
|
coi_window: int = 10,
|
||||||
@@ -26,8 +29,13 @@ class PHANTOM(gym.Env):
|
|||||||
self.render_mode = render_mode
|
self.render_mode = render_mode
|
||||||
self.alpha = alpha
|
self.alpha = alpha
|
||||||
self.N = N
|
self.N = N
|
||||||
|
self.human_params = human_params
|
||||||
|
self.agent_params = agent_params
|
||||||
|
|
||||||
self.market = MarketEngine(alpha=alpha, N=N)
|
self.market = MarketEngine(
|
||||||
|
alpha=alpha, N=N,
|
||||||
|
human_params=human_params, agent_params=agent_params, noise_std=noise_std
|
||||||
|
)
|
||||||
self._platform_stub = PricingEngine()
|
self._platform_stub = PricingEngine()
|
||||||
self._limbo = Limbo(self._platform_stub, self.market)
|
self._limbo = Limbo(self._platform_stub, self.market)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user