RDSS/core/simulator.py

1100 lines
52 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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