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