diff --git a/deepmd/dpmodel/descriptor/dpa3.py b/deepmd/dpmodel/descriptor/dpa3.py index 3cf08c14ee..b2f27195a8 100644 --- a/deepmd/dpmodel/descriptor/dpa3.py +++ b/deepmd/dpmodel/descriptor/dpa3.py @@ -123,6 +123,14 @@ class RepFlowArgs: smooth_edge_update : bool, optional Whether to make edge update smooth. If True, the edge update from angle message will not use self as padding. + use_exp_switch : bool, optional + Whether to use an exponential switch function instead of a polynomial one in the neighbor update. + The exponential switch function ensures neighbor contributions smoothly diminish as the interatomic distance + `r` approaches the cutoff radius `rcut`. Specifically, the function is defined as: + s(r) = \\exp(-\\exp(20 * (r - rcut_smth) / rcut_smth)) for 0 < r \\leq rcut, and s(r) = 0 for r > rcut. + Here, `rcut_smth` is an adjustable smoothing factor and `rcut_smth` should be chosen carefully + according to `rcut`, ensuring s(r) approaches zero smoothly at the cutoff. + Typical recommended values are `rcut_smth` = 5.3 for `rcut` = 6.0, and 3.5 for `rcut` = 4.0. use_dynamic_sel : bool, optional Whether to dynamically select neighbors within the cutoff radius. If True, the exact number of neighbors within the cutoff radius is used @@ -162,6 +170,7 @@ def __init__( skip_stat: bool = False, optim_update: bool = True, smooth_edge_update: bool = False, + use_exp_switch: bool = False, use_dynamic_sel: bool = False, sel_reduce_factor: float = 10.0, ) -> None: @@ -190,6 +199,7 @@ def __init__( self.a_compress_use_split = a_compress_use_split self.optim_update = optim_update self.smooth_edge_update = smooth_edge_update + self.use_exp_switch = use_exp_switch self.use_dynamic_sel = use_dynamic_sel self.sel_reduce_factor = sel_reduce_factor @@ -223,6 +233,7 @@ def serialize(self) -> dict: "fix_stat_std": self.fix_stat_std, "optim_update": self.optim_update, "smooth_edge_update": self.smooth_edge_update, + "use_exp_switch": self.use_exp_switch, "use_dynamic_sel": self.use_dynamic_sel, "sel_reduce_factor": self.sel_reduce_factor, } @@ -321,6 +332,7 @@ def init_subclass_params(sub_data, sub_class): fix_stat_std=self.repflow_args.fix_stat_std, optim_update=self.repflow_args.optim_update, smooth_edge_update=self.repflow_args.smooth_edge_update, + use_exp_switch=self.repflow_args.use_exp_switch, use_dynamic_sel=self.repflow_args.use_dynamic_sel, sel_reduce_factor=self.repflow_args.sel_reduce_factor, exclude_types=exclude_types, diff --git a/deepmd/dpmodel/descriptor/repflows.py b/deepmd/dpmodel/descriptor/repflows.py index ea82678064..df0b81d9d2 100644 --- a/deepmd/dpmodel/descriptor/repflows.py +++ b/deepmd/dpmodel/descriptor/repflows.py @@ -125,6 +125,14 @@ class DescrptBlockRepflows(NativeOP, DescriptorBlock): smooth_edge_update : bool, optional Whether to make edge update smooth. If True, the edge update from angle message will not use self as padding. + use_exp_switch : bool, optional + Whether to use an exponential switch function instead of a polynomial one in the neighbor update. + The exponential switch function ensures neighbor contributions smoothly diminish as the interatomic distance + `r` approaches the cutoff radius `rcut`. Specifically, the function is defined as: + s(r) = \\exp(-\\exp(20 * (r - rcut_smth) / rcut_smth)) for 0 < r \\leq rcut, and s(r) = 0 for r > rcut. + Here, `rcut_smth` is an adjustable smoothing factor and `rcut_smth` should be chosen carefully + according to `rcut`, ensuring s(r) approaches zero smoothly at the cutoff. + Typical recommended values are `rcut_smth` = 5.3 for `rcut` = 6.0, and 3.5 for `rcut` = 4.0. use_dynamic_sel : bool, optional Whether to dynamically select neighbors within the cutoff radius. If True, the exact number of neighbors within the cutoff radius is used @@ -185,6 +193,7 @@ def __init__( fix_stat_std: float = 0.3, optim_update: bool = True, smooth_edge_update: bool = False, + use_exp_switch: bool = False, use_dynamic_sel: bool = False, sel_reduce_factor: float = 10.0, seed: Optional[Union[int, list[int]]] = None, @@ -218,6 +227,7 @@ def __init__( self.a_compress_use_split = a_compress_use_split self.optim_update = optim_update self.smooth_edge_update = smooth_edge_update + self.use_exp_switch = use_exp_switch self.use_dynamic_sel = use_dynamic_sel self.sel_reduce_factor = sel_reduce_factor if self.use_dynamic_sel and not self.smooth_edge_update: @@ -290,10 +300,16 @@ def __init__( wanted_shape = (self.ntypes, self.nnei, 4) self.env_mat_edge = EnvMat( - self.e_rcut, self.e_rcut_smth, protection=self.env_protection + self.e_rcut, + self.e_rcut_smth, + protection=self.env_protection, + use_exp_switch=self.use_exp_switch, ) self.env_mat_angle = EnvMat( - self.a_rcut, self.a_rcut_smth, protection=self.env_protection + self.a_rcut, + self.a_rcut_smth, + protection=self.env_protection, + use_exp_switch=self.use_exp_switch, ) self.mean = np.zeros(wanted_shape, dtype=PRECISION_DICT[self.precision]) self.stddev = np.ones(wanted_shape, dtype=PRECISION_DICT[self.precision]) @@ -647,6 +663,7 @@ def serialize(self): "precision": self.precision, "fix_stat_std": self.fix_stat_std, "optim_update": self.optim_update, + "use_exp_switch": self.use_exp_switch, "smooth_edge_update": self.smooth_edge_update, "use_dynamic_sel": self.use_dynamic_sel, "sel_reduce_factor": self.sel_reduce_factor, diff --git a/deepmd/dpmodel/utils/env_mat.py b/deepmd/dpmodel/utils/env_mat.py index 6c17ad7702..ee11678d3a 100644 --- a/deepmd/dpmodel/utils/env_mat.py +++ b/deepmd/dpmodel/utils/env_mat.py @@ -35,6 +35,24 @@ def compute_smooth_weight( return vv +@support_array_api(version="2023.12") +def compute_exp_sw( + distance: np.ndarray, + rmin: float, + rmax: float, +): + """Compute the exponential switch function for neighbor update.""" + if rmin >= rmax: + raise ValueError("rmin should be less than rmax.") + xp = array_api_compat.array_namespace(distance) + distance = xp.clip(distance, min=0.0, max=rmax) + C = 20 + a = C / rmin + b = rmin + exp_sw = xp.exp(-xp.exp(a * (distance - b))) + return exp_sw + + def _make_env_mat( nlist, coord, @@ -42,6 +60,7 @@ def _make_env_mat( ruct_smth: float, radial_only: bool = False, protection: float = 0.0, + use_exp_switch: bool = False, ): """Make smooth environment matrix.""" xp = array_api_compat.array_namespace(nlist) @@ -66,7 +85,11 @@ def _make_env_mat( length = length + xp.astype(~xp.expand_dims(mask, axis=-1), length.dtype) t0 = 1 / (length + protection) t1 = diff / (length + protection) ** 2 - weight = compute_smooth_weight(length, ruct_smth, rcut) + weight = ( + compute_smooth_weight(length, ruct_smth, rcut) + if not use_exp_switch + else compute_exp_sw(length, ruct_smth, rcut) + ) weight = weight * xp.astype(xp.expand_dims(mask, axis=-1), weight.dtype) if radial_only: env_mat = t0 * weight @@ -81,10 +104,12 @@ def __init__( rcut, rcut_smth, protection: float = 0.0, + use_exp_switch: bool = False, ) -> None: self.rcut = rcut self.rcut_smth = rcut_smth self.protection = protection + self.use_exp_switch = use_exp_switch def call( self, @@ -142,6 +167,7 @@ def _call(self, nlist, coord_ext, radial_only): self.rcut_smth, radial_only=radial_only, protection=self.protection, + use_exp_switch=self.use_exp_switch, ) return em, diff, ww @@ -151,6 +177,8 @@ def serialize( return { "rcut": self.rcut, "rcut_smth": self.rcut_smth, + "protection": self.protection, + "use_exp_switch": self.use_exp_switch, } @classmethod diff --git a/deepmd/pd/model/descriptor/dpa1.py b/deepmd/pd/model/descriptor/dpa1.py index 6af494d5b4..6942b096c9 100644 --- a/deepmd/pd/model/descriptor/dpa1.py +++ b/deepmd/pd/model/descriptor/dpa1.py @@ -506,7 +506,9 @@ def serialize(self) -> dict: "precision": RESERVED_PRECISION_DICT[obj.prec], "embeddings": obj.filter_layers.serialize(), "attention_layers": obj.dpa1_attention.serialize(), - "env_mat": DPEnvMat(obj.rcut, obj.rcut_smth).serialize(), + "env_mat": DPEnvMat( + obj.rcut, obj.rcut_smth, obj.env_protection + ).serialize(), "type_embedding": self.type_embedding.embedding.serialize(), "exclude_types": obj.exclude_types, "env_protection": obj.env_protection, diff --git a/deepmd/pd/model/descriptor/se_a.py b/deepmd/pd/model/descriptor/se_a.py index 8d84620caa..7b70a742ce 100644 --- a/deepmd/pd/model/descriptor/se_a.py +++ b/deepmd/pd/model/descriptor/se_a.py @@ -337,7 +337,9 @@ def serialize(self) -> dict: # make deterministic "precision": RESERVED_PRECISION_DICT[obj.prec], "embeddings": obj.filter_layers.serialize(), - "env_mat": DPEnvMat(obj.rcut, obj.rcut_smth).serialize(), + "env_mat": DPEnvMat( + obj.rcut, obj.rcut_smth, obj.env_protection + ).serialize(), "exclude_types": obj.exclude_types, "env_protection": obj.env_protection, "@variables": { diff --git a/deepmd/pt/model/descriptor/dpa1.py b/deepmd/pt/model/descriptor/dpa1.py index fae50a138c..9c1e144f48 100644 --- a/deepmd/pt/model/descriptor/dpa1.py +++ b/deepmd/pt/model/descriptor/dpa1.py @@ -508,7 +508,9 @@ def serialize(self) -> dict: "precision": RESERVED_PRECISION_DICT[obj.prec], "embeddings": obj.filter_layers.serialize(), "attention_layers": obj.dpa1_attention.serialize(), - "env_mat": DPEnvMat(obj.rcut, obj.rcut_smth).serialize(), + "env_mat": DPEnvMat( + obj.rcut, obj.rcut_smth, obj.env_protection + ).serialize(), "type_embedding": self.type_embedding.embedding.serialize(), "exclude_types": obj.exclude_types, "env_protection": obj.env_protection, diff --git a/deepmd/pt/model/descriptor/dpa3.py b/deepmd/pt/model/descriptor/dpa3.py index 0d2ba060df..de7b25749d 100644 --- a/deepmd/pt/model/descriptor/dpa3.py +++ b/deepmd/pt/model/descriptor/dpa3.py @@ -150,6 +150,7 @@ def init_subclass_params(sub_data, sub_class): fix_stat_std=self.repflow_args.fix_stat_std, optim_update=self.repflow_args.optim_update, smooth_edge_update=self.repflow_args.smooth_edge_update, + use_exp_switch=self.repflow_args.use_exp_switch, use_dynamic_sel=self.repflow_args.use_dynamic_sel, sel_reduce_factor=self.repflow_args.sel_reduce_factor, exclude_types=exclude_types, diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index dc7142249a..c57ae209fd 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -3,6 +3,7 @@ import torch from deepmd.pt.utils.preprocess import ( + compute_exp_sw, compute_smooth_weight, ) @@ -14,6 +15,7 @@ def _make_env_mat( ruct_smth: float, radial_only: bool = False, protection: float = 0.0, + use_exp_switch: bool = False, ): """Make smooth environment matrix.""" bsz, natoms, nnei = nlist.shape @@ -33,7 +35,11 @@ def _make_env_mat( length = length + ~mask.unsqueeze(-1) t0 = 1 / (length + protection) t1 = diff / (length + protection) ** 2 - weight = compute_smooth_weight(length, ruct_smth, rcut) + weight = ( + compute_smooth_weight(length, ruct_smth, rcut) + if not use_exp_switch + else compute_exp_sw(length, ruct_smth, rcut) + ) weight = weight * mask.unsqueeze(-1) if radial_only: env_mat = t0 * weight @@ -52,6 +58,7 @@ def prod_env_mat( rcut_smth: float, radial_only: bool = False, protection: float = 0.0, + use_exp_switch: bool = False, ): """Generate smooth environment matrix from atom coordinates and other context. @@ -64,6 +71,7 @@ def prod_env_mat( - rcut_smth: Smooth hyper-parameter for pair force & energy. - radial_only: Whether to return a full description or a radial-only descriptor. - protection: Protection parameter to prevent division by zero errors during calculations. + - use_exp_switch: Whether to use the exponential switch function. Returns ------- @@ -76,6 +84,7 @@ def prod_env_mat( rcut_smth, radial_only, protection=protection, + use_exp_switch=use_exp_switch, ) # shape [n_atom, dim, 4 or 1] t_avg = mean[atype] # [n_atom, dim, 4 or 1] t_std = stddev[atype] # [n_atom, dim, 4 or 1] diff --git a/deepmd/pt/model/descriptor/repflows.py b/deepmd/pt/model/descriptor/repflows.py index fe55b2d1c2..1486ee358a 100644 --- a/deepmd/pt/model/descriptor/repflows.py +++ b/deepmd/pt/model/descriptor/repflows.py @@ -136,6 +136,14 @@ class DescrptBlockRepflows(DescriptorBlock): smooth_edge_update : bool, optional Whether to make edge update smooth. If True, the edge update from angle message will not use self as padding. + use_exp_switch : bool, optional + Whether to use an exponential switch function instead of a polynomial one in the neighbor update. + The exponential switch function ensures neighbor contributions smoothly diminish as the interatomic distance + `r` approaches the cutoff radius `rcut`. Specifically, the function is defined as: + s(r) = \\exp(-\\exp(20 * (r - rcut_smth) / rcut_smth)) for 0 < r \\leq rcut, and s(r) = 0 for r > rcut. + Here, `rcut_smth` is an adjustable smoothing factor and `rcut_smth` should be chosen carefully + according to `rcut`, ensuring s(r) approaches zero smoothly at the cutoff. + Typical recommended values are `rcut_smth` = 5.3 for `rcut` = 6.0, and 3.5 for `rcut` = 4.0. use_dynamic_sel : bool, optional Whether to dynamically select neighbors within the cutoff radius. If True, the exact number of neighbors within the cutoff radius is used @@ -198,6 +206,7 @@ def __init__( precision: str = "float64", fix_stat_std: float = 0.3, smooth_edge_update: bool = False, + use_exp_switch: bool = False, use_dynamic_sel: bool = False, sel_reduce_factor: float = 10.0, optim_update: bool = True, @@ -232,6 +241,7 @@ def __init__( self.a_compress_use_split = a_compress_use_split self.optim_update = optim_update self.smooth_edge_update = smooth_edge_update + self.use_exp_switch = use_exp_switch self.use_dynamic_sel = use_dynamic_sel self.sel_reduce_factor = sel_reduce_factor if self.use_dynamic_sel and not self.smooth_edge_update: @@ -425,6 +435,7 @@ def forward( self.e_rcut, self.e_rcut_smth, protection=self.env_protection, + use_exp_switch=self.use_exp_switch, ) nlist_mask = nlist != -1 sw = torch.squeeze(sw, -1) @@ -446,6 +457,7 @@ def forward( self.a_rcut, self.a_rcut_smth, protection=self.env_protection, + use_exp_switch=self.use_exp_switch, ) a_nlist_mask = a_nlist != -1 a_sw = torch.squeeze(a_sw, -1) diff --git a/deepmd/pt/model/descriptor/se_a.py b/deepmd/pt/model/descriptor/se_a.py index 9d3b850d0f..fc3e14bd25 100644 --- a/deepmd/pt/model/descriptor/se_a.py +++ b/deepmd/pt/model/descriptor/se_a.py @@ -381,7 +381,9 @@ def serialize(self) -> dict: # make deterministic "precision": RESERVED_PRECISION_DICT[obj.prec], "embeddings": obj.filter_layers.serialize(), - "env_mat": DPEnvMat(obj.rcut, obj.rcut_smth).serialize(), + "env_mat": DPEnvMat( + obj.rcut, obj.rcut_smth, obj.env_protection + ).serialize(), "exclude_types": obj.exclude_types, "env_protection": obj.env_protection, "@variables": { diff --git a/deepmd/pt/model/descriptor/se_t.py b/deepmd/pt/model/descriptor/se_t.py index fb444c3d13..6e075a04e4 100644 --- a/deepmd/pt/model/descriptor/se_t.py +++ b/deepmd/pt/model/descriptor/se_t.py @@ -415,7 +415,9 @@ def serialize(self) -> dict: "activation_function": obj.activation_function, "precision": RESERVED_PRECISION_DICT[obj.prec], "embeddings": obj.filter_layers.serialize(), - "env_mat": DPEnvMat(obj.rcut, obj.rcut_smth).serialize(), + "env_mat": DPEnvMat( + obj.rcut, obj.rcut_smth, obj.env_protection + ).serialize(), "exclude_types": obj.exclude_types, "env_protection": obj.env_protection, "type_map": self.type_map, diff --git a/deepmd/pt/utils/preprocess.py b/deepmd/pt/utils/preprocess.py index 8ab489dede..7161bac692 100644 --- a/deepmd/pt/utils/preprocess.py +++ b/deepmd/pt/utils/preprocess.py @@ -15,3 +15,15 @@ def compute_smooth_weight(distance, rmin: float, rmax: float): uu2 = uu * uu vv = uu2 * uu * (-6 * uu2 + 15 * uu - 10) + 1 return vv + + +def compute_exp_sw(distance, rmin: float, rmax: float): + """Compute the exponential switch function for neighbor update.""" + if rmin >= rmax: + raise ValueError("rmin should be less than rmax.") + distance = torch.clamp(distance, min=0.0, max=rmax) + C = 20 + a = C / rmin + b = rmin + exp_sw = torch.exp(-torch.exp(a * (distance - b))) + return exp_sw diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 7554abc7e6..6e9663592f 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -1497,6 +1497,15 @@ def dpa3_repflow_args(): "Whether to make edge update smooth. " "If True, the edge update from angle message will not use self as padding." ) + doc_use_exp_switch = ( + "Whether to use an exponential switch function instead of a polynomial one in the neighbor update. " + "The exponential switch function ensures neighbor contributions smoothly diminish as the interatomic distance " + "`r` approaches the cutoff radius `rcut`. Specifically, the function is defined as: " + "s(r) = \\exp(-\\exp(20 * (r - rcut_smth) / rcut_smth)) for 0 < r \\leq rcut, and s(r) = 0 for r > rcut. " + "Here, `rcut_smth` is an adjustable smoothing factor and should be chosen carefully according to `rcut`, " + "ensuring s(r) approaches zero smoothly at the cutoff. " + "Typical recommended values are `rcut_smth` = 5.3 for `rcut` = 6.0, and 3.5 for `rcut` = 4.0." + ) doc_use_dynamic_sel = ( "Whether to dynamically select neighbors within the cutoff radius. " "If True, the exact number of neighbors within the cutoff radius is used " @@ -1611,6 +1620,14 @@ def dpa3_repflow_args(): default=False, # For compatability. This will be True in the future doc=doc_smooth_edge_update, ), + Argument( + "use_exp_switch", + bool, + optional=True, + default=False, + alias=["use_env_envelope"], + doc=doc_use_exp_switch, + ), Argument( "use_dynamic_sel", bool, diff --git a/source/tests/consistent/descriptor/test_dpa3.py b/source/tests/consistent/descriptor/test_dpa3.py index 0a68b3fc5f..2647da52b3 100644 --- a/source/tests/consistent/descriptor/test_dpa3.py +++ b/source/tests/consistent/descriptor/test_dpa3.py @@ -65,6 +65,7 @@ (1, 2), # a_compress_e_rate (True,), # a_compress_use_split (True, False), # optim_update + (True, False), # use_exp_switch (True, False), # use_dynamic_sel (0.3, 0.0), # fix_stat_std (1, 2), # n_multi_edge_message @@ -81,6 +82,7 @@ def data(self) -> dict: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, @@ -105,6 +107,7 @@ def data(self) -> dict: "a_compress_e_rate": a_compress_e_rate, "a_compress_use_split": a_compress_use_split, "optim_update": optim_update, + "use_exp_switch": use_exp_switch, "use_dynamic_sel": use_dynamic_sel, "smooth_edge_update": True, "fix_stat_std": fix_stat_std, @@ -134,6 +137,7 @@ def skip_pt(self) -> bool: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, @@ -151,13 +155,17 @@ def skip_pd(self) -> bool: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, precision, ) = self.param return ( - not INSTALLED_PD or precision == "bfloat16" or use_dynamic_sel + not INSTALLED_PD + or precision == "bfloat16" + or use_exp_switch + or use_dynamic_sel ) # not supported yet @property @@ -170,6 +178,7 @@ def skip_dp(self) -> bool: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, @@ -187,6 +196,7 @@ def skip_tf(self) -> bool: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, @@ -246,6 +256,7 @@ def setUp(self) -> None: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, @@ -326,6 +337,7 @@ def rtol(self) -> float: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, @@ -349,6 +361,7 @@ def atol(self) -> float: a_compress_e_rate, a_compress_use_split, optim_update, + use_exp_switch, use_dynamic_sel, fix_stat_std, n_multi_edge_message, diff --git a/source/tests/universal/dpmodel/descriptor/test_descriptor.py b/source/tests/universal/dpmodel/descriptor/test_descriptor.py index d973f45ca6..08708c5924 100644 --- a/source/tests/universal/dpmodel/descriptor/test_descriptor.py +++ b/source/tests/universal/dpmodel/descriptor/test_descriptor.py @@ -482,6 +482,7 @@ def DescriptorParamDPA3( a_compress_use_split=False, optim_update=True, smooth_edge_update=False, + use_exp_switch=False, fix_stat_std=0.3, use_dynamic_sel=False, precision="float64", @@ -495,15 +496,20 @@ def DescriptorParamDPA3( "a_dim": 8, "nlayers": 2, "e_rcut": rcut, - "e_rcut_smth": rcut_smth, + "e_rcut_smth": rcut_smth + if not use_exp_switch + else (rcut - 1.0), # suitable for ut "e_sel": sum(sel), "a_rcut": rcut / 2, - "a_rcut_smth": rcut_smth / 2, + "a_rcut_smth": rcut_smth / 2 + if not use_exp_switch + else (rcut - 1.0) / 2, # suitable for ut "a_sel": sum(sel) // 4, "a_compress_rate": a_compress_rate, "a_compress_e_rate": a_compress_e_rate, "a_compress_use_split": a_compress_use_split, "optim_update": optim_update, + "use_exp_switch": use_exp_switch, "smooth_edge_update": smooth_edge_update, "fix_stat_std": fix_stat_std, "n_multi_edge_message": n_multi_edge_message, @@ -543,6 +549,7 @@ def DescriptorParamDPA3( "a_compress_use_split": (True,), "optim_update": (True, False), "smooth_edge_update": (True,), + "use_exp_switch": (True, False), "fix_stat_std": (0.3,), "n_multi_edge_message": (1, 2), "use_dynamic_sel": (True, False),