"""
Random parameter generation for PDE matrices
"""
import numpy as np
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
[docs]
@dataclass
class ParameterDistribution:
"""Parameter distribution specification"""
name: str
distribution: str = "uniform" # uniform, normal, lognormal
params: Dict = None # Distribution parameters
[docs]
def sample(self, seed: Optional[int] = None) -> float:
"""Sample from distribution"""
if seed is not None:
np.random.seed(seed)
if self.distribution == "uniform":
low = self.params.get('low', 0.0)
high = self.params.get('high', 1.0)
return np.random.uniform(low, high)
elif self.distribution == "normal":
mean = self.params.get('mean', 0.0)
std = self.params.get('std', 1.0)
return np.random.normal(mean, std)
elif self.distribution == "lognormal":
mean = self.params.get('mean', 0.0)
sigma = self.params.get('sigma', 1.0)
return np.random.lognormal(mean, sigma)
else:
raise ValueError(f"Unknown distribution: {self.distribution}")
[docs]
class RandomParameterGenerator:
"""
Generate random parameters for PDE problems
Examples:
>>> gen = RandomParameterGenerator()
>>> gen.add_parameter('diffusion', 'uniform', low=0.1, high=10.0)
>>> gen.add_parameter('velocity', 'normal', mean=1.0, std=0.5)
>>> params = gen.sample(n=100)
"""
[docs]
def __init__(self):
self.parameters: Dict[str, ParameterDistribution] = {}
[docs]
def add_parameter(
self,
name: str,
distribution: str = "uniform",
**dist_params
):
"""
Add a parameter to sample
Args:
name: Parameter name
distribution: Distribution type
**dist_params: Distribution parameters
"""
self.parameters[name] = ParameterDistribution(
name=name,
distribution=distribution,
params=dist_params
)
[docs]
def sample(
self,
n: int = 1,
seed: Optional[int] = None
) -> List[Dict[str, float]]:
"""
Sample n parameter sets
Args:
n: Number of samples
seed: Random seed
Returns:
List of parameter dictionaries
"""
samples = []
for i in range(n):
sample_seed = None if seed is None else seed + i
params = {}
for param_name, param_dist in self.parameters.items():
params[param_name] = param_dist.sample(sample_seed)
samples.append(params)
return samples
[docs]
class LatinHypercubeSampler:
"""
Latin Hypercube Sampling for better space coverage
Examples:
>>> sampler = LatinHypercubeSampler()
>>> sampler.add_parameter('diffusion', low=0.1, high=10.0)
>>> sampler.add_parameter('velocity', low=-1.0, high=1.0)
>>> samples = sampler.sample(n=50)
"""
[docs]
def __init__(self):
self.parameters: Dict[str, Tuple[float, float]] = {}
[docs]
def add_parameter(self, name: str, low: float, high: float):
"""Add parameter with range"""
self.parameters[name] = (low, high)
[docs]
def sample(
self,
n: int,
seed: Optional[int] = None
) -> List[Dict[str, float]]:
"""
Generate Latin Hypercube samples
Args:
n: Number of samples
seed: Random seed
Returns:
List of parameter dictionaries
"""
if seed is not None:
np.random.seed(seed)
n_params = len(self.parameters)
param_names = list(self.parameters.keys())
# Generate LHS samples in [0, 1]^d
lhs_samples = np.zeros((n, n_params))
for i in range(n_params):
# Divide [0, 1] into n intervals
intervals = np.arange(n) / n
# Sample within each interval
samples = intervals + np.random.uniform(0, 1/n, n)
# Randomly permute
lhs_samples[:, i] = np.random.permutation(samples)
# Scale to parameter ranges
result = []
for i in range(n):
params = {}
for j, param_name in enumerate(param_names):
low, high = self.parameters[param_name]
params[param_name] = low + (high - low) * lhs_samples[i, j]
result.append(params)
return result