RDSS/core/simulator.py

1100 lines
52 KiB
Python
Raw Normal View History

2026-03-13 14:16:00 +08:00
#!/usr/bin/env python3
"""
事件模拟器核心类
"""
import numpy as np
import logging
from core.encoder import BitFieldEncoder
# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class SampleSpaceGenerator:
"""样本空间生成器能量单位keV"""
@staticmethod
def generate_from_distribution(dist_config: dict, size: int) -> np.ndarray:
"""根据配置生成样本能量单位keV"""
dist_type = dist_config['type']
params = {k: v for k, v in dist_config.items() if k != 'type'}
if dist_type == 'normal':
return np.random.normal(params.get('mean', 0), params.get('std', 1), size)
elif dist_type == 'uniform':
return np.random.uniform(params.get('low', 0), params.get('high', 1), size)
elif dist_type == 'exponential':
# 指数分布scale是尺度参数loc是位置参数延迟/偏移)
return np.random.exponential(params.get('scale', 1), size) + params.get('loc', 0)
elif dist_type == 'gamma':
return np.random.gamma(params.get('shape', 1), params.get('scale', 1), size)
elif dist_type == 'poisson':
return np.random.poisson(params.get('lam', 1), size)
elif dist_type == 'lognormal':
return np.random.lognormal(params.get('mean', 0), params.get('sigma', 1), size)
elif dist_type == 'constant':
return np.full(size, params.get('value', 1000.0))
else:
raise ValueError(f"不支持的分布类型: {dist_type}")
def generate_sample_space(self, detector_config: dict) -> np.ndarray:
"""生成样本空间能量单位keV"""
size = detector_config['sample_space_size']
# 生成能量样本 (keV)
energies = self.generate_from_distribution(detector_config['energy_distribution'], size)
energies = np.abs(energies)
# 生成时间戳样本 (us)
timestamps = self.generate_from_distribution(detector_config['timestamp_distribution'], size)
timestamps = np.abs(timestamps)
# 组合成二维向量
sample_space = np.column_stack((energies, timestamps))
logger.info(f"{detector_config['name']}生成了{size}个样本 (能量单位: keV)")
return sample_space
class DetectorSampler:
"""探测器采样器"""
def __init__(self, sample_space: np.ndarray, allow_replacement: bool = True):
self.sample_space = sample_space
self.size = len(sample_space)
self.allow_replacement = allow_replacement
def sample(self, num_samples: int) -> np.ndarray:
"""从样本空间抽样"""
if num_samples <= 0:
return np.array([])
if self.allow_replacement:
indices = np.random.choice(self.size, num_samples, replace=True)
else:
if num_samples > self.size:
logger.warning(f"请求样本数({num_samples})超过样本空间大小({self.size})")
num_samples = self.size
indices = np.random.choice(self.size, num_samples, replace=False)
return self.sample_space[indices].copy()
class EventSimulatorV4:
"""事件模拟器 V4支持三种事件生成模式"""
def __init__(self, config: dict, pre_generated_sample_spaces: dict = None):
self.config = config
self.sample_generator = SampleSpaceGenerator()
self.encoder = BitFieldEncoder()
# 获取事件生成模式
self.event_generation_mode = config['simulation'].get('event_generation_mode', 'random')
logger.info(f"使用事件生成模式: {self.event_generation_mode}")
# 获取能量转换参数
energy_conversion = config['simulation'].get('energy_conversion', {})
self.energy_K = energy_conversion.get('K', 1.0)
self.energy_B = energy_conversion.get('B', 0.0)
logger.info(f"能量转换参数: K={self.energy_K}, B={self.energy_B} (mV = keV * K + B)")
# 初始化探测器配置(所有模式都需要)
self.detectors = self._initialize_detectors()
# 对于随机模式,需要样本空间和采样器
if self.event_generation_mode == 'random':
# 使用预生成的样本空间(如果提供),否则生成新的
if pre_generated_sample_spaces:
logger.info("使用预生成的样本空间")
self.sample_spaces = pre_generated_sample_spaces
else:
logger.info("生成新的样本空间")
self.sample_spaces = self._generate_sample_spaces()
self.samplers = {}
for name, sample_space in self.sample_spaces.items():
allow_replacement = self.detectors[name]['allow_replacement']
self.samplers[name] = DetectorSampler(sample_space, allow_replacement)
else:
# 固定模式和脉冲模式不需要样本空间
self.sample_spaces = {}
self.samplers = {}
# 加载固定事件配置
self.fixed_events_config = config.get('fixed_events', {})
# 加载脉冲信号配置
self.pulse_signals_config = config.get('pulse_signals', {})
def _initialize_detectors(self) -> dict:
"""初始化探测器配置"""
detectors = {}
for det_name, det_config in self.config['detectors'].items():
detectors[det_name] = det_config
return detectors
def _generate_sample_spaces(self) -> dict:
"""为所有探测器生成样本空间(仅随机模式使用)"""
sample_spaces = {}
for name, detector_config in self.detectors.items():
sample_spaces[name] = self.sample_generator.generate_sample_space(detector_config)
return sample_spaces
def generate_event_random(self, event_id: int) -> list:
"""
随机模式生成一个随机事件
返回:
事件信号列表
"""
sampling_config = self.config['simulation']['sampling']
require_signal = sampling_config.get('require_signal', True)
# 为每个探测器随机确定信号数量
num_signals = {
name: np.random.randint(
sampling_config['min_signals_per_detector'],
sampling_config['max_signals_per_detector'] + 1
)
for name in self.detectors.keys()
}
# 从每个探测器抽样
detector_signals = {}
for name, sampler in self.samplers.items():
samples = sampler.sample(num_signals[name])
if len(samples) > 0:
sorted_indices = np.argsort(samples[:, 1])
detector_signals[name] = samples[sorted_indices]
else:
detector_signals[name] = np.array([])
# 合并所有探测器信号并编码为64位整数
encoded_events = self._merge_and_encode_signals(detector_signals, require_signal)
# 获取最大时间戳用于事件结束标记
max_timestamp = 0
if encoded_events:
for encoded in encoded_events:
decoded = self.encoder.decode(encoded)
if decoded['timestamp'] > max_timestamp:
max_timestamp = decoded['timestamp']
# 设置最后一个信号的事件结束标志位
if encoded_events:
encoded_events[-1] = self.encoder.set_event_end_flag(encoded_events[-1])
else:
# 如果没有信号,创建一个结束标记信号
end_signal = self.encoder.encode(max_timestamp, [0, 0, 0], event_end=True)
encoded_events.append(end_signal)
return encoded_events
def generate_event_fixed(self, event_id: int) -> list:
"""
固定模式生成一个固定模式的事件
返回:
事件信号列表
"""
config = self.fixed_events_config
# 获取配置参数
num_signals = config.get('num_signals_per_event', 5)
energy_levels = config.get('energy_levels', {})
timestamps = config.get('timestamps', [])
repeat_pattern = config.get('repeat_pattern', True)
# 准备能量数据
det1_energies = energy_levels.get('detector1', [1000] * num_signals)
det2_energies = energy_levels.get('detector2', [800] * num_signals)
det3_energies = energy_levels.get('detector3', [500] * num_signals)
# 准备时间戳
if repeat_pattern and len(timestamps) > 0:
# 重复时间戳模式
timestamps_cycle = timestamps * ((num_signals // len(timestamps)) + 1)
timestamps_used = timestamps_cycle[:num_signals]
else:
# 使用递增时间戳
if len(timestamps) >= num_signals:
timestamps_used = timestamps[:num_signals]
else:
base_timestamp = 10
interval = 50
timestamps_used = [base_timestamp + i * interval for i in range(num_signals)]
# 确保数组长度一致
det1_energies = self._ensure_length(det1_energies, num_signals, 1000)
det2_energies = self._ensure_length(det2_energies, num_signals, 800)
det3_energies = self._ensure_length(det3_energies, num_signals, 500)
# 编码信号
encoded_signals = []
for i in range(num_signals):
timestamp = timestamps_used[i]
energies = [det1_energies[i], det2_energies[i], det3_energies[i]]
try:
encoded = self.encoder.encode(timestamp, energies, event_end=False)
encoded_signals.append(encoded)
except Exception as e:
logger.warning(f"固定模式编码信号时出错: {e}")
# 创建一个默认信号
default_signal = self.encoder.encode(timestamp, [0, 0, 0], event_end=False)
encoded_signals.append(default_signal)
# 设置最后一个信号的事件结束标志位
if encoded_signals:
encoded_signals[-1] = self.encoder.set_event_end_flag(encoded_signals[-1])
logger.info(f"固定模式生成事件 {event_id+1}: {num_signals}个信号")
return encoded_signals
def generate_event_pulse(self, event_id: int) -> list:
"""
脉冲模式生成一个脉冲信号事件
返回:
事件信号列表
"""
config = self.pulse_signals_config
# 获取配置参数
pulse_count = config.get('pulse_count', 10)
pulse_interval = config.get('pulse_interval', 100)
pulse_width = config.get('pulse_width', 5)
energy_levels = config.get('energy_levels', {})
jitter = config.get('jitter', 10.0)
energy_noise = config.get('energy_noise', 100.0)
# 获取各探测器基础能量
det1_energy = energy_levels.get('detector1', 1500)
det2_energy = energy_levels.get('detector2', 1200)
det3_energy = energy_levels.get('detector3', 900)
# 生成脉冲信号
encoded_signals = []
for pulse_idx in range(pulse_count):
# 计算脉冲起始时间
base_time = pulse_idx * pulse_interval
# 生成脉冲内的多个信号
for sub_idx in range(pulse_width):
# 添加时间抖动
time_jitter = np.random.uniform(-jitter, jitter) if jitter > 0 else 0
timestamp = base_time + sub_idx * (pulse_interval / pulse_width) + time_jitter
timestamp = max(0, timestamp)
# 添加能量噪声
det1_noisy = det1_energy + np.random.uniform(-energy_noise, energy_noise) if energy_noise > 0 else det1_energy
det2_noisy = det2_energy + np.random.uniform(-energy_noise, energy_noise) if energy_noise > 0 else det2_energy
det3_noisy = det3_energy + np.random.uniform(-energy_noise, energy_noise) if energy_noise > 0 else det3_energy
# 确保能量非负
det1_noisy = max(0, det1_noisy)
det2_noisy = max(0, det2_noisy)
det3_noisy = max(0, det3_noisy)
energies = [det1_noisy, det2_noisy, det3_noisy]
try:
encoded = self.encoder.encode(int(timestamp), energies, event_end=False)
encoded_signals.append(encoded)
except Exception as e:
logger.warning(f"脉冲模式编码信号时出错: {e}")
# 创建一个默认信号
default_signal = self.encoder.encode(int(timestamp), [0, 0, 0], event_end=False)
encoded_signals.append(default_signal)
# 设置最后一个信号的事件结束标志位
if encoded_signals:
encoded_signals[-1] = self.encoder.set_event_end_flag(encoded_signals[-1])
logger.info(f"脉冲模式生成事件 {event_id+1}: {len(encoded_signals)}个信号")
return encoded_signals
def _ensure_length(self, array: list, target_length: int, default_value: float) -> list:
"""确保数组达到目标长度,不足时用默认值填充"""
if len(array) >= target_length:
return array[:target_length]
else:
return array + [default_value] * (target_length - len(array))
def _merge_and_encode_signals(self, detector_signals: dict, require_signal: bool = True) -> list:
"""
合并探测器信号并编码为64位整数
参数:
detector_signals: 各探测器的信号数组
require_signal: 是否只输出有信号的探测器
返回:
编码后的64位整数列表
"""
time_signal_map = {}
for det_idx, det_name in enumerate(['detector1', 'detector2', 'detector3']):
signals = detector_signals.get(det_name, np.array([]))
for signal in signals:
if len(signal) == 2:
energy_kev = signal[0]
timestamp_us = signal[1]
try:
# 能量转换: mV = keV * K + B结果取整
energy_mv = int(round(energy_kev * self.energy_K + self.energy_B))
# 确保能量非负
if energy_mv < 0:
energy_mv = 0
# 检查溢出
if energy_mv > ((1 << 14) - 1):
logger.warning(f"能量溢出,放弃信号: {energy_mv}mV (原始: {energy_kev}keV)")
continue
if timestamp_us > ((1 << 18) - 1):
logger.warning(f"时间戳溢出,放弃信号: {timestamp_us}us")
continue
# 添加到时间映射
timestamp_int = int(round(timestamp_us))
if timestamp_int not in time_signal_map:
time_signal_map[timestamp_int] = {
'timestamp': timestamp_int,
'energies': [0, 0, 0],
'has_signal': [False, False, False]
}
time_signal_map[timestamp_int]['energies'][det_idx] = energy_mv
time_signal_map[timestamp_int]['has_signal'][det_idx] = True
except Exception as e:
logger.warning(f"编码信号时出错,放弃: {e}")
continue
sorted_timestamps = sorted(time_signal_map.keys())
encoded_events = []
for timestamp in sorted_timestamps:
data = time_signal_map[timestamp]
energies = data['energies']
has_signal = data['has_signal']
if require_signal and not any(has_signal):
continue
try:
encoded = self.encoder.encode(timestamp, energies, event_end=False)
encoded_events.append(encoded)
except Exception as e:
logger.warning(f"编码时间点{timestamp}信号时出错,放弃: {e}")
continue
return encoded_events
def generate_event(self, event_id: int) -> list:
"""
根据当前模式生成一个事件
返回:
事件信号列表
"""
if self.event_generation_mode == 'random':
return self.generate_event_random(event_id)
elif self.event_generation_mode == 'fixed':
return self.generate_event_fixed(event_id)
elif self.event_generation_mode == 'pulse':
return self.generate_event_pulse(event_id)
else:
logger.warning(f"未知的事件生成模式: {self.event_generation_mode},使用随机模式")
return self.generate_event_random(event_id)
def simulate_events(self, num_events: int = None) -> list:
"""
模拟多个事件根据配置的模式生成
参数:
num_events: 事件数量覆盖配置
返回:
所有事件的列表
"""
if num_events is None:
num_events = self.config['simulation']['num_events']
all_events = []
print(f"开始模拟事件...")
print(f" 模式: {self.event_generation_mode}")
print(f" 数量: {num_events}个事件")
for i in range(num_events):
if self.event_generation_mode == 'random':
event = self.generate_event_random(i)
valid_signals = sum(1 for sig in event if sig != 0)
logger.info(f"已生成随机事件 {i + 1}/{num_events} - 有效信号: {valid_signals}")
elif self.event_generation_mode == 'fixed':
event = self.generate_event_fixed(i)
logger.info(f"已生成固定事件 {i + 1}/{num_events} - 信号数: {len(event)}")
elif self.event_generation_mode == 'pulse':
event = self.generate_event_pulse(i)
logger.info(f"已生成脉冲事件 {i + 1}/{num_events} - 信号数: {len(event)}")
else:
event = self.generate_event_random(i)
valid_signals = sum(1 for sig in event if sig != 0)
logger.info(f"已生成事件 {i + 1}/{num_events} - 有效信号: {valid_signals}")
all_events.append(event)
# 汇总统计信息
total_signals = sum(len(event) for event in all_events)
total_valid = sum(sum(1 for sig in event if sig != 0) for event in all_events)
print(f"\n模拟完成!")
print(f" 总事件数: {len(all_events)}")
print(f" 总信号数: {total_signals}")
print(f" 有效信号数: {total_valid}")
print(f" 平均每事件信号数: {total_signals/len(all_events):.1f}")
return all_events
class TimeSeriesGenerator:
"""时间序列生成器 - 根据工作模式和同步脉冲配置生成时间序列"""
def __init__(self, work_mode: str, sync_pulses: list, sample_spaces: dict,
samplers: dict, detectors: dict = None, energy_K: float = 1.0, energy_B: float = 0.0,
sync_pulse_signals: dict = None):
"""
初始化时间序列生成器
参数:
work_mode: 工作模式 ("CO", "Sigma", "Combo")
sync_pulses: 同步脉冲配置列表每个元素为 {"count": int, "period": int}
sample_spaces: 各探测器的样本空间
samplers: 各探测器的采样器
detectors: 各探测器的配置包含每探测器信号数
energy_K: 能量转换系数K
energy_B: 能量转换系数B
sync_pulse_signals: 每个同步脉冲的探测器信号数配置
格式: {"pulse1": {"detector1": {"min": 1, "max": 10}, ...}, ...}
"""
self.work_mode = work_mode
self.sync_pulses = sync_pulses
self.sample_spaces = sample_spaces
self.samplers = samplers
self.detectors = detectors or {}
self.energy_K = energy_K
self.energy_B = energy_B
self.sync_pulse_signals = sync_pulse_signals or {}
self.encoder = BitFieldEncoder()
logger.info(f"时间序列生成器初始化 - 工作模式: {work_mode}")
for i, pulse in enumerate(sync_pulses):
logger.info(f" 同步脉冲{i+1}: 个数={pulse['count']}, 周期={pulse['period']} (10ns)")
def generate_time_series(self) -> dict:
"""
生成时间序列
返回:
包含各探测器时间序列的字典
{
"detector1": [(timestamp, energy), ...],
"detector2": [(timestamp, energy), ...],
"detector3": [(timestamp, energy), ...],
"total_duration": 总时长(10ns)
}
"""
time_series = {
"detector1": [],
"detector2": [],
"detector3": [],
"total_duration": 0
}
if self.work_mode == "CO":
time_series = self._generate_co_mode()
elif self.work_mode == "Sigma":
time_series = self._generate_sigma_mode()
elif self.work_mode == "Combo":
time_series = self._generate_combo_mode()
else:
logger.warning(f"未知的工作模式: {self.work_mode}使用CO模式")
time_series = self._generate_co_mode()
total_signals = sum(len(time_series[det]) for det in ["detector1", "detector2", "detector3"])
logger.info(f"时间序列生成完成 - 总时长: {time_series['total_duration']} (10ns), 总信号数: {total_signals}")
return time_series
def _generate_co_mode(self) -> dict:
"""生成CO模式时间序列"""
time_series = {
"detector1": [],
"detector2": [],
"detector3": [],
"total_duration": 0
}
pulse1_count = self.sync_pulses[0]["count"]
pulse1_period = self.sync_pulses[0]["period"]
pulse2_count = self.sync_pulses[1]["count"]
pulse2_period = self.sync_pulses[1]["period"]
pulse3_count = self.sync_pulses[2]["count"]
pulse3_period = self.sync_pulses[2]["period"]
pulse4_count = self.sync_pulses[3]["count"]
pulse4_period = self.sync_pulses[3]["period"]
# 计算总时长:串行计算所有脉冲序列的总时间
total_duration = (
pulse1_count * pulse1_period +
pulse2_count * pulse2_period +
pulse3_count * pulse3_period +
pulse4_count * pulse4_period
)
time_series["total_duration"] = total_duration
# 获取同步脉冲信号数配置
def get_signal_count(pulse_key, det_name, default_min=1, default_max=10):
"""获取指定同步脉冲和探测器的信号数配置"""
if pulse_key in self.sync_pulse_signals:
if det_name in self.sync_pulse_signals[pulse_key]:
config = self.sync_pulse_signals[pulse_key][det_name]
min_val = config.get("min", default_min)
max_val = config.get("max", default_max)
return np.random.randint(min_val, max_val + 1)
return np.random.randint(default_min, default_max + 1)
# 对每个同步脉冲触发采样
# 同步脉冲1
for pulse_idx in range(pulse1_count):
# 计算脉冲时间位置10ns单位
pulse_start_time = pulse_idx * pulse1_period
# 同步脉冲1的时间窗口0-160微秒
max_time_us = 160
# 用于记录当前同步脉冲内每个探测器已使用的时间戳(单个探测器内去重)
det_timestamps = {
"detector1": set(),
"detector2": set(),
"detector3": set()
}
for det_name in ["detector1", "detector2", "detector3"]:
if det_name in self.samplers:
# 在最小和最大信号数之间随机抽取使用同步脉冲1的配置
num_signals = get_signal_count("pulse1", det_name)
sampler = self.samplers[det_name]
sample_space = self.sample_spaces[det_name]
if len(sample_space) > 0:
# 循环抽样,确保在同一个同步脉冲内,同一个探测器的时间戳不重复
signals_collected = 0
max_attempts = num_signals * 10 # 最多尝试次数
attempts = 0
while signals_collected < num_signals and attempts < max_attempts:
# 从样本空间中抽取一个样本
sample = sampler.sample(1)[0]
energy_kev = sample[0]
sample_time_us = sample[1]
# 检查时间戳是否在同步脉冲时间窗口内
if sample_time_us <= max_time_us:
# 转换为10ns单位确保为整数
sample_time_10ns = int(sample_time_us * 100) # us -> 10ns
timestamp_10ns = pulse_start_time + sample_time_10ns
# 检查该时间戳是否已经被当前探测器在当前同步脉冲内使用(单个探测器内去重)
if timestamp_10ns not in det_timestamps[det_name]:
# 能量转换并确保非负
energy_mv = int(energy_kev * self.energy_K + self.energy_B)
energy_mv = max(0, energy_mv)
time_series[det_name].append((timestamp_10ns, energy_mv))
det_timestamps[det_name].add(timestamp_10ns)
signals_collected += 1
else:
logger.debug(f"重复时间戳: 探测器={det_name}, 时间戳={timestamp_10ns}, 能量={energy_kev}keV")
attempts += 1
# 同步脉冲2
for pulse_idx in range(pulse2_count):
# 计算脉冲时间位置10ns单位
# 同步脉冲2的起始时间是同步脉冲1的结束时间
pulse_start_time = pulse1_count * pulse1_period + pulse_idx * pulse2_period
# 同步脉冲2的时间窗口0-1000微秒
max_time_us = 1000
# 用于记录当前同步脉冲内每个探测器已使用的时间戳(单个探测器内去重)
det_timestamps = {
"detector1": set(),
"detector2": set(),
"detector3": set()
}
for det_name in ["detector1", "detector2", "detector3"]:
if det_name in self.samplers:
# 在最小和最大信号数之间随机抽取使用同步脉冲2的配置
num_signals = get_signal_count("pulse2", det_name)
sampler = self.samplers[det_name]
sample_space = self.sample_spaces[det_name]
if len(sample_space) > 0:
# 循环抽样,确保在同一个同步脉冲内,同一个探测器的时间戳不重复
signals_collected = 0
max_attempts = num_signals * 10 # 最多尝试次数
attempts = 0
while signals_collected < num_signals and attempts < max_attempts:
# 从样本空间中抽取一个样本
sample = sampler.sample(1)[0]
energy_kev = sample[0]
sample_time_us = sample[1]
# 检查时间戳是否在同步脉冲时间窗口内
if sample_time_us <= max_time_us:
# 转换为10ns单位确保为整数
sample_time_10ns = int(sample_time_us * 100) # us -> 10ns
timestamp_10ns = pulse_start_time + sample_time_10ns
# 检查该时间戳是否已经被当前探测器在当前同步脉冲内使用(单个探测器内去重)
if timestamp_10ns not in det_timestamps[det_name]:
# 能量转换并确保非负
energy_mv = int(energy_kev * self.energy_K + self.energy_B)
energy_mv = max(0, energy_mv)
time_series[det_name].append((timestamp_10ns, energy_mv))
det_timestamps[det_name].add(timestamp_10ns)
signals_collected += 1
attempts += 1
# 同步脉冲3
for pulse_idx in range(pulse3_count):
# 计算脉冲时间位置10ns单位
# 同步脉冲3的起始时间是同步脉冲2的结束时间
pulse_start_time = pulse1_count * pulse1_period + pulse2_count * pulse2_period + pulse_idx * pulse3_period
# 同步脉冲3的时间窗口0-4000微秒
max_time_us = 4000
# 用于记录当前同步脉冲内每个探测器已使用的时间戳(单个探测器内去重)
det_timestamps = {
"detector1": set(),
"detector2": set(),
"detector3": set()
}
for det_name in ["detector1", "detector2", "detector3"]:
if det_name in self.samplers:
# 在最小和最大信号数之间随机抽取使用同步脉冲3的配置
num_signals = get_signal_count("pulse3", det_name)
sampler = self.samplers[det_name]
sample_space = self.sample_spaces[det_name]
if len(sample_space) > 0:
# 循环抽样,确保在同一个同步脉冲内,同一个探测器的时间戳不重复
signals_collected = 0
max_attempts = num_signals * 10 # 最多尝试次数
attempts = 0
while signals_collected < num_signals and attempts < max_attempts:
# 从样本空间中抽取一个样本
sample = sampler.sample(1)[0]
energy_kev = sample[0]
sample_time_us = sample[1]
# 检查时间戳是否在同步脉冲时间窗口内
if sample_time_us <= max_time_us:
# 转换为10ns单位确保为整数
sample_time_10ns = int(sample_time_us * 100) # us -> 10ns
timestamp_10ns = pulse_start_time + sample_time_10ns
# 检查该时间戳是否已经被当前探测器在当前同步脉冲内使用(单个探测器内去重)
if timestamp_10ns not in det_timestamps[det_name]:
# 能量转换并确保非负
energy_mv = int(energy_kev * self.energy_K + self.energy_B)
energy_mv = max(0, energy_mv)
time_series[det_name].append((timestamp_10ns, energy_mv))
det_timestamps[det_name].add(timestamp_10ns)
signals_collected += 1
attempts += 1
# 对每个探测器的信号按时间戳排序
for det_name in ["detector1", "detector2", "detector3"]:
time_series[det_name].sort(key=lambda x: x[0])
return time_series
def _generate_sigma_mode(self) -> dict:
"""生成Sigma模式时间序列"""
time_series = {
"detector1": [],
"detector2": [],
"detector3": [],
"total_duration": 0
}
pulse1_count = self.sync_pulses[0]["count"]
pulse1_period = self.sync_pulses[0]["period"]
total_duration = pulse1_count * pulse1_period
time_series["total_duration"] = total_duration
# 对每个同步脉冲触发采样
for pulse_idx in range(pulse1_count):
# 计算脉冲时间位置10ns单位
pulse_start_time = pulse_idx * pulse1_period
# 用于记录当前同步脉冲内每个探测器已使用的时间戳(单个探测器内去重)
det_timestamps = {
"detector1": set(),
"detector2": set(),
"detector3": set()
}
for det_name in ["detector1", "detector2", "detector3"]:
if det_name in self.samplers:
sampler = self.samplers[det_name]
sample_space = self.sample_spaces[det_name]
if len(sample_space) > 0:
# 循环抽样,确保在同一个同步脉冲内,同一个探测器的时间戳不重复
num_samples = np.random.randint(1, 4)
signals_collected = 0
max_attempts = num_samples * 10 # 最多尝试次数
attempts = 0
while signals_collected < num_samples and attempts < max_attempts:
# 从样本空间中抽取一个样本
sample = sampler.sample(1)[0]
energy_kev = sample[0]
sample_time_us = sample[1]
# 计算最终时间戳:脉冲时间 + 样本时间使用10ns单位整数运算避免浮点精度问题
sample_time_10ns = int(sample_time_us * 100) # us -> 10ns
timestamp_10ns = pulse_start_time + sample_time_10ns
# 检查该时间戳是否已经被当前探测器在当前同步脉冲内使用(单个探测器内去重)
if timestamp_10ns not in det_timestamps[det_name]:
# 能量转换并确保非负
energy_mv = int(energy_kev * self.energy_K + self.energy_B)
energy_mv = max(0, energy_mv)
time_series[det_name].append((timestamp_10ns, energy_mv))
det_timestamps[det_name].add(timestamp_10ns)
signals_collected += 1
else:
logger.debug(f"重复时间戳: 探测器={det_name}, 脉冲={pulse_idx}, 时间戳={timestamp_10ns}, 样本时间={sample_time_us}us")
attempts += 1
return time_series
def _generate_combo_mode(self) -> dict:
"""生成Combo模式时间序列"""
time_series = {
"detector1": [],
"detector2": [],
"detector3": [],
"total_duration": 0
}
pulse1_count = self.sync_pulses[0]["count"]
pulse1_period = self.sync_pulses[0]["period"]
pulse2_count = self.sync_pulses[1]["count"]
pulse2_period = self.sync_pulses[1]["period"]
pulse3_count = self.sync_pulses[2]["count"]
pulse3_period = self.sync_pulses[2]["period"]
# 计算总时长:取最长的脉冲序列结束时间
total_duration = max(
pulse1_count * pulse1_period,
pulse2_count * pulse2_period,
pulse3_count * pulse3_period
)
time_series["total_duration"] = total_duration
# 对每个同步脉冲触发采样
# 同步脉冲1
for pulse_idx in range(pulse1_count):
# 计算脉冲时间位置10ns单位
pulse_start_time = pulse_idx * pulse1_period
# 用于记录当前同步脉冲内每个探测器已使用的时间戳(单个探测器内去重)
det_timestamps = {
"detector1": set(),
"detector2": set(),
"detector3": set()
}
for det_name in ["detector1", "detector2", "detector3"]:
if det_name in self.samplers:
sampler = self.samplers[det_name]
sample_space = self.sample_spaces[det_name]
if len(sample_space) > 0:
# 循环抽样,确保在同一个同步脉冲内,同一个探测器的时间戳不重复
num_samples = np.random.randint(1, 4)
signals_collected = 0
max_attempts = num_samples * 10 # 最多尝试次数
attempts = 0
while signals_collected < num_samples and attempts < max_attempts:
# 从样本空间中抽取一个样本
sample = sampler.sample(1)[0]
energy_kev = sample[0]
sample_time_us = sample[1]
# 计算最终时间戳:脉冲时间 + 样本时间使用10ns单位整数运算避免浮点精度问题
sample_time_10ns = int(sample_time_us * 100) # us -> 10ns
timestamp_10ns = pulse_start_time + sample_time_10ns
# 检查该时间戳是否已经被当前探测器在当前同步脉冲内使用(单个探测器内去重)
if timestamp_10ns not in det_timestamps[det_name]:
# 能量转换并确保非负
energy_mv = int(energy_kev * self.energy_K + self.energy_B)
energy_mv = max(0, energy_mv)
time_series[det_name].append((timestamp_10ns, energy_mv))
det_timestamps[det_name].add(timestamp_10ns)
signals_collected += 1
attempts += 1
# 同步脉冲3
for pulse_idx in range(pulse3_count):
# 计算脉冲时间位置10ns单位
pulse_start_time = pulse_idx * pulse3_period
# 转换为微秒单位
pulse_start_time_us = pulse_start_time / 100 # 10ns = 0.01us
# 用于记录当前同步脉冲内每个探测器已使用的时间戳(单个探测器内去重)
det_timestamps = {
"detector1": set(),
"detector2": set(),
"detector3": set()
}
for det_name in ["detector1", "detector2", "detector3"]:
if det_name in self.samplers:
sampler = self.samplers[det_name]
sample_space = self.sample_spaces[det_name]
if len(sample_space) > 0:
# 循环抽样,确保在同一个同步脉冲内,同一个探测器的时间戳不重复
num_samples = np.random.randint(1, 4)
signals_collected = 0
max_attempts = num_samples * 10 # 最多尝试次数
attempts = 0
while signals_collected < num_samples and attempts < max_attempts:
# 从样本空间中抽取一个样本
sample = sampler.sample(1)[0]
energy_kev = sample[0]
sample_time_us = sample[1]
# 计算最终时间戳:脉冲时间 + 样本时间使用10ns单位整数运算避免浮点精度问题
sample_time_10ns = int(sample_time_us * 100) # us -> 10ns
timestamp_10ns = pulse_start_time + sample_time_10ns
# 检查该时间戳是否已经被当前探测器在当前同步脉冲内使用(单个探测器内去重)
if timestamp_10ns not in det_timestamps[det_name]:
# 能量转换并确保非负
energy_mv = int(energy_kev * self.energy_K + self.energy_B)
energy_mv = max(0, energy_mv)
time_series[det_name].append((timestamp_10ns, energy_mv))
det_timestamps[det_name].add(timestamp_10ns)
signals_collected += 1
attempts += 1
return time_series
def encode_time_series(self, time_series: dict) -> list:
"""
将时间序列编码为64位整数列表
参数:
time_series: 时间序列字典
返回:
编码后的64位整数列表
"""
# 计算同步脉冲时间点
sync_pulse_times = []
current_time = 0
for pulse_config in self.sync_pulses:
count = pulse_config["count"]
period = pulse_config["period"]
for i in range(count):
sync_pulse_times.append(current_time)
current_time += period
logger.info(f"同步脉冲时间点: {len(sync_pulse_times)}")
# 为每个同步脉冲创建一个事件
event_signals = []
# 标记已分配的信号
assigned_timestamps = set()
for pulse_idx, pulse_time in enumerate(sync_pulse_times):
# 找到该脉冲时间窗口内的信号
# 时间范围:从当前脉冲时间到下一个脉冲时间
next_pulse_time = sync_pulse_times[pulse_idx + 1] if pulse_idx + 1 < len(sync_pulse_times) else None
# 按相对时间戳分组信号(合并相同时间戳的信号)
relative_time_signal_map = {}
# 收集当前脉冲时间窗口内的信号
for det_idx, det_name in enumerate(['detector1', 'detector2', 'detector3']):
signals = time_series.get(det_name, [])
for signal in signals:
if len(signal) == 2:
timestamp = signal[0]
energy = signal[1]
# 检查信号是否在当前脉冲时间窗口内且未被分配
if timestamp >= pulse_time and timestamp not in assigned_timestamps:
if next_pulse_time is None or timestamp < next_pulse_time:
# 计算相对时间戳(相对于同步脉冲时间)
relative_timestamp = timestamp - pulse_time
# 转换为微秒单位(编码器期望的单位)
relative_timestamp_us = relative_timestamp // 100 # 10ns -> us使用整数除法避免浮点精度问题
# 按相对时间戳分组,确保同一事件内同一探测器的时间戳不重复
if relative_timestamp_us not in relative_time_signal_map:
relative_time_signal_map[relative_timestamp_us] = [0, 0, 0]
# 如果该探测器在这个相对时间戳上已经有信号,使用第一个信号(去重)
if relative_time_signal_map[relative_timestamp_us][det_idx] == 0:
relative_time_signal_map[relative_timestamp_us][det_idx] = energy
else:
logger.debug(f"重复相对时间戳: 事件={pulse_idx}, 时间戳={relative_timestamp_us}us, 探测器={det_name}, 旧能量={relative_time_signal_map[relative_timestamp_us][det_idx]}, 新能量={energy}")
# 统计合并后的信号
merged_count = 0
for timestamp, energies in relative_time_signal_map.items():
active_dets = sum(1 for e in energies if e > 0)
if active_dets > 1:
merged_count += 1
logger.debug(f"事件 {pulse_idx} - 时间戳 {timestamp}us 合并了 {active_dets} 个探测器的信号: {energies}")
# 按相对时间戳排序
sorted_relative_timestamps = sorted(relative_time_signal_map.keys())
pulse_signals = []
for relative_timestamp_us in sorted_relative_timestamps:
energies = relative_time_signal_map[relative_timestamp_us]
try:
encoded = self.encoder.encode(relative_timestamp_us, energies, event_end=False)
pulse_signals.append(encoded)
except Exception as e:
logger.warning(f"编码信号时出错: {e}")
default_signal = self.encoder.encode(relative_timestamp_us, [0, 0, 0], event_end=False)
pulse_signals.append(default_signal)
# 标记当前脉冲时间窗口内的信号为已分配
for det_idx, det_name in enumerate(['detector1', 'detector2', 'detector3']):
signals = time_series.get(det_name, [])
for signal in signals:
if len(signal) == 2:
timestamp = signal[0]
if timestamp >= pulse_time and timestamp not in assigned_timestamps:
if next_pulse_time is None or timestamp < next_pulse_time:
assigned_timestamps.add(timestamp)
# 如果有信号,设置最后一个信号为事件结束
if pulse_signals:
# 设置最后一个信号的事件结束标志
original_val = pulse_signals[-1]
pulse_signals[-1] = self.encoder.set_event_end_flag(pulse_signals[-1])
logger.debug(f"事件 {pulse_idx}: 设置event_end, 原始值={hex(original_val)}, 新值={hex(pulse_signals[-1])}")
event_signals.extend(pulse_signals)
logger.info(f"时间序列编码完成 - 编码信号数: {len(event_signals)}, 事件数: {len(sync_pulse_times)}")
return event_signals
def _merge_and_encode_signals(self, detector_signals: dict, require_signal: bool = True) -> list:
"""
合并探测器信号并编码为64位整数
参数:
detector_signals: 各探测器的信号数组
require_signal: 是否只输出有信号的探测器
返回:
编码后的64位整数列表
"""
time_signal_map = {}
for det_idx, det_name in enumerate(['detector1', 'detector2', 'detector3']):
signals = detector_signals.get(det_name, [])
for signal in signals:
if len(signal) == 2:
# 注意这里的信号来自sampler.sample(),时间戳是微秒单位
energy_kev = signal[0]
timestamp_us = signal[1]
try:
# 能量转换: mV = keV * K + B结果取整
energy_mv = int(round(energy_kev * self.energy_K + self.energy_B))
# 确保能量非负
if energy_mv < 0:
energy_mv = 0
# 检查溢出
if energy_mv > ((1 << 14) - 1):
logger.warning(f"能量溢出,放弃信号: {energy_mv}mV (原始: {energy_kev}keV)")
continue
if timestamp_us > ((1 << 18) - 1):
logger.warning(f"时间戳溢出,放弃信号: {timestamp_us}us")
continue
# 使用微秒单位的时间戳作为键
timestamp_int = int(round(timestamp_us))
if timestamp_int not in time_signal_map:
time_signal_map[timestamp_int] = [0, 0, 0]
# 只保留第一个信号(去重)
if time_signal_map[timestamp_int][det_idx] == 0:
time_signal_map[timestamp_int][det_idx] = energy_mv
else:
logger.debug(f"重复时间戳: 时间戳={timestamp_int}us, 探测器={det_name}, 旧能量={time_signal_map[timestamp_int][det_idx]}, 新能量={energy_mv}")
except Exception as e:
logger.warning(f"编码信号时出错,放弃: {e}")
continue
encoded_signals = []
sorted_timestamps = sorted(time_signal_map.keys())
for timestamp in sorted_timestamps:
energies = time_signal_map[timestamp]
if require_signal:
if all(e == 0 for e in energies):
continue
try:
# 直接使用微秒单位的时间戳(编码器期望的单位)
encoded = self.encoder.encode(timestamp, energies, event_end=False)
encoded_signals.append(encoded)
except Exception as e:
logger.warning(f"编码时间序列信号时出错: {e}")
default_signal = self.encoder.encode(timestamp, [0, 0, 0], event_end=False)
encoded_signals.append(default_signal)
return encoded_signals