|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import torch
|
|
|
|
|
|
import torch.nn as nn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""用于数值试井双对数曲线预测的正演代理模型。
|
|
|
|
|
|
|
|
|
|
|
|
模型将变换后的物理参数和编码后的流量制度映射为 log_pressure /
|
|
|
|
|
|
log_derivative 响应。它不是反演模型:PSO 最终的 pbest/gbest 仍由
|
|
|
|
|
|
数值求解器的真实评价结果更新。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_mlp(
|
|
|
|
|
|
in_dim: int,
|
|
|
|
|
|
hidden_dims: list[int],
|
|
|
|
|
|
out_dim: int,
|
|
|
|
|
|
dropout: float = 0.0,
|
|
|
|
|
|
) -> nn.Sequential:
|
|
|
|
|
|
layers: list[nn.Module] = []
|
|
|
|
|
|
prev = in_dim
|
|
|
|
|
|
for h in hidden_dims:
|
|
|
|
|
|
layers.append(nn.Linear(prev, h))
|
|
|
|
|
|
layers.append(nn.ReLU())
|
|
|
|
|
|
if dropout > 0:
|
|
|
|
|
|
layers.append(nn.Dropout(dropout))
|
|
|
|
|
|
prev = h
|
|
|
|
|
|
layers.append(nn.Linear(prev, out_dim))
|
|
|
|
|
|
return nn.Sequential(*layers)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScheduleEncoder(nn.Module):
|
|
|
|
|
|
"""编码固定长度的流量制度向量。"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, schedule_dim: int, hidden_dim: int, dropout: float = 0.0):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.net = nn.Sequential(
|
|
|
|
|
|
nn.Linear(schedule_dim, hidden_dim),
|
|
|
|
|
|
nn.ReLU(),
|
|
|
|
|
|
nn.Dropout(dropout) if dropout > 0 else nn.Identity(),
|
|
|
|
|
|
nn.Linear(hidden_dim, hidden_dim),
|
|
|
|
|
|
nn.ReLU(),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
|
|
|
|
return self.net(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ParamEncoder(nn.Module):
|
|
|
|
|
|
"""编码经过变换的物理参数,例如 log10(k) 和 asinh(skin)。"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, param_dim: int, hidden_dim: int, dropout: float = 0.0):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.net = nn.Sequential(
|
|
|
|
|
|
nn.Linear(param_dim, hidden_dim),
|
|
|
|
|
|
nn.ReLU(),
|
|
|
|
|
|
nn.Dropout(dropout) if dropout > 0 else nn.Identity(),
|
|
|
|
|
|
nn.Linear(hidden_dim, hidden_dim),
|
|
|
|
|
|
nn.ReLU(),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
|
|
|
|
return self.net(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ForwardSurrogate(nn.Module):
|
|
|
|
|
|
"""双分支正演代理模型,并分别设置压力和导数输出头。"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
param_dim: int,
|
|
|
|
|
|
schedule_dim: int,
|
|
|
|
|
|
curve_dim: int,
|
|
|
|
|
|
hidden_dim: int = 128,
|
|
|
|
|
|
fusion_hidden_dims: list[int] | None = None,
|
|
|
|
|
|
dropout: float = 0.0,
|
|
|
|
|
|
use_schedule: bool = True,
|
|
|
|
|
|
):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
|
|
if curve_dim % 3 != 0:
|
|
|
|
|
|
raise ValueError(f"curve_dim={curve_dim} 不能被 3 整除;期望为 pressure/derivative/slope 三段")
|
|
|
|
|
|
|
|
|
|
|
|
if fusion_hidden_dims is None:
|
|
|
|
|
|
fusion_hidden_dims = [256, 256]
|
|
|
|
|
|
|
|
|
|
|
|
self.curve_dim = curve_dim
|
|
|
|
|
|
self.part_dim = curve_dim // 3
|
|
|
|
|
|
self.use_schedule = bool(use_schedule)
|
|
|
|
|
|
|
|
|
|
|
|
# 参数和流量制度的物理含义与尺度差异较大,因此采用两个分支分别编码。
|
|
|
|
|
|
self.param_encoder = ParamEncoder(param_dim, hidden_dim, dropout=dropout)
|
|
|
|
|
|
|
|
|
|
|
|
if self.use_schedule:
|
|
|
|
|
|
self.schedule_encoder = ScheduleEncoder(schedule_dim, hidden_dim, dropout=dropout)
|
|
|
|
|
|
trunk_in_dim = hidden_dim * 2
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.schedule_encoder = None
|
|
|
|
|
|
trunk_in_dim = hidden_dim
|
|
|
|
|
|
|
|
|
|
|
|
trunk_out_dim = fusion_hidden_dims[-1]
|
|
|
|
|
|
self.trunk = build_mlp(
|
|
|
|
|
|
in_dim=trunk_in_dim,
|
|
|
|
|
|
hidden_dims=fusion_hidden_dims,
|
|
|
|
|
|
out_dim=trunk_out_dim,
|
|
|
|
|
|
dropout=dropout,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 压力曲线拆成 level + centered shape:
|
|
|
|
|
|
# level 学习整体纵向偏移,shape 学习局部曲线形态。
|
|
|
|
|
|
self.pressure_level_head = build_mlp(
|
|
|
|
|
|
in_dim=trunk_out_dim,
|
|
|
|
|
|
hidden_dims=[128],
|
|
|
|
|
|
out_dim=1,
|
|
|
|
|
|
dropout=dropout,
|
|
|
|
|
|
)
|
|
|
|
|
|
self.pressure_shape_head = build_mlp(
|
|
|
|
|
|
in_dim=trunk_out_dim,
|
|
|
|
|
|
hidden_dims=[128],
|
|
|
|
|
|
out_dim=self.part_dim,
|
|
|
|
|
|
dropout=dropout,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 导数曲线同样拆分为 level + shape,因为平台、谷值和过渡段
|
|
|
|
|
|
# 对自动拟合筛选非常重要。
|
|
|
|
|
|
self.derivative_level_head = build_mlp(
|
|
|
|
|
|
in_dim=trunk_out_dim,
|
|
|
|
|
|
hidden_dims=[128],
|
|
|
|
|
|
out_dim=1,
|
|
|
|
|
|
dropout=dropout,
|
|
|
|
|
|
)
|
|
|
|
|
|
self.derivative_shape_head = build_mlp(
|
|
|
|
|
|
in_dim=trunk_out_dim,
|
|
|
|
|
|
hidden_dims=[128],
|
|
|
|
|
|
out_dim=self.part_dim,
|
|
|
|
|
|
dropout=dropout,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# slope 是辅助输出,主要用于保持数据布局兼容。
|
|
|
|
|
|
self.slope_head = build_mlp(
|
|
|
|
|
|
in_dim=trunk_out_dim,
|
|
|
|
|
|
hidden_dims=[128],
|
|
|
|
|
|
out_dim=self.part_dim,
|
|
|
|
|
|
dropout=dropout,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def center_shape(x: torch.Tensor) -> torch.Tensor:
|
|
|
|
|
|
"""去除每个样本的均值,避免 shape 分支重复学习整体 level。"""
|
|
|
|
|
|
return x - x.mean(dim=1, keepdim=True)
|
|
|
|
|
|
|
|
|
|
|
|
def forward(self, params_x: torch.Tensor, schedule_x: torch.Tensor | None = None) -> torch.Tensor:
|
|
|
|
|
|
p = self.param_encoder(params_x)
|
|
|
|
|
|
|
|
|
|
|
|
if self.use_schedule:
|
|
|
|
|
|
if schedule_x is None:
|
|
|
|
|
|
raise ValueError("use_schedule=True,但 forward 没有传入 schedule_x")
|
|
|
|
|
|
s = self.schedule_encoder(schedule_x)
|
|
|
|
|
|
fused = torch.cat([p, s], dim=-1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
fused = p
|
|
|
|
|
|
|
|
|
|
|
|
trunk_feat = self.trunk(fused)
|
|
|
|
|
|
|
|
|
|
|
|
pressure_level = self.pressure_level_head(trunk_feat) # [B, 1]
|
|
|
|
|
|
pressure_shape = self.pressure_shape_head(trunk_feat) # [B, T]
|
|
|
|
|
|
pressure_shape = self.center_shape(pressure_shape)
|
|
|
|
|
|
pressure_pred = pressure_level + pressure_shape
|
|
|
|
|
|
|
|
|
|
|
|
derivative_level = self.derivative_level_head(trunk_feat) # [B, 1]
|
|
|
|
|
|
derivative_shape = self.derivative_shape_head(trunk_feat) # [B, T]
|
|
|
|
|
|
derivative_shape = self.center_shape(derivative_shape)
|
|
|
|
|
|
derivative_pred = derivative_level + derivative_shape
|
|
|
|
|
|
|
|
|
|
|
|
slope_pred = self.slope_head(trunk_feat) # [B, T]
|
|
|
|
|
|
|
|
|
|
|
|
curve_pred = torch.cat([pressure_pred, derivative_pred, slope_pred], dim=1)
|
|
|
|
|
|
return curve_pred
|