@@ -576,72 +576,6 @@ def _compute_combined_mask(
576576 return ~ (x_points .isnull () | y_points .isnull ())
577577
578578
579- def _add_dpwl_sos2_core (
580- model : Model ,
581- name : str ,
582- x_expr : LinearExpression ,
583- target_expr : LinearExpression ,
584- x_points : DataArray ,
585- y_points : DataArray ,
586- lambda_mask : DataArray | None ,
587- active : LinearExpression | None = None ,
588- ) -> Constraint :
589- """
590- Core disjunctive SOS2 formulation with separate x/y points.
591-
592- When ``active`` is provided, the segment selection becomes
593- ``sum(z_k) == active`` instead of ``== 1``, forcing all segment
594- binaries, lambdas, and thus x and y to zero when ``active=0``.
595- """
596- binary_name = f"{ name } { PWL_BINARY_SUFFIX } "
597- select_name = f"{ name } { PWL_SELECT_SUFFIX } "
598- lambda_name = f"{ name } { PWL_LAMBDA_SUFFIX } "
599- convex_name = f"{ name } { PWL_CONVEX_SUFFIX } "
600- x_link_name = f"{ name } { PWL_X_LINK_SUFFIX } "
601- y_link_name = f"{ name } { PWL_Y_LINK_SUFFIX } "
602-
603- extra = _extra_coords (x_points , BREAKPOINT_DIM , SEGMENT_DIM )
604- lambda_coords = extra + [
605- pd .Index (x_points .coords [SEGMENT_DIM ].values , name = SEGMENT_DIM ),
606- pd .Index (x_points .coords [BREAKPOINT_DIM ].values , name = BREAKPOINT_DIM ),
607- ]
608- binary_coords = extra + [
609- pd .Index (x_points .coords [SEGMENT_DIM ].values , name = SEGMENT_DIM ),
610- ]
611-
612- binary_mask = (
613- lambda_mask .any (dim = BREAKPOINT_DIM ) if lambda_mask is not None else None
614- )
615-
616- binary_var = model .add_variables (
617- binary = True , coords = binary_coords , name = binary_name , mask = binary_mask
618- )
619-
620- # Segment selection: sum(z_k) == 1 or sum(z_k) == active
621- rhs = active if active is not None else 1
622- select_con = model .add_constraints (
623- binary_var .sum (dim = SEGMENT_DIM ) == rhs , name = select_name
624- )
625-
626- lambda_var = model .add_variables (
627- lower = 0 , upper = 1 , coords = lambda_coords , name = lambda_name , mask = lambda_mask
628- )
629-
630- model .add_sos_constraints (lambda_var , sos_type = 2 , sos_dim = BREAKPOINT_DIM )
631-
632- model .add_constraints (
633- lambda_var .sum (dim = BREAKPOINT_DIM ) == binary_var , name = convex_name
634- )
635-
636- x_weighted = (lambda_var * x_points ).sum (dim = [SEGMENT_DIM , BREAKPOINT_DIM ])
637- model .add_constraints (x_expr == x_weighted , name = x_link_name )
638-
639- y_weighted = (lambda_var * y_points ).sum (dim = [SEGMENT_DIM , BREAKPOINT_DIM ])
640- model .add_constraints (target_expr == y_weighted , name = y_link_name )
641-
642- return select_con
643-
644-
645579# ---------------------------------------------------------------------------
646580# Main entry point
647581# ---------------------------------------------------------------------------
@@ -983,6 +917,47 @@ def _add_disjunctive(
983917 "NaN values must only appear at the end of the breakpoint sequence."
984918 )
985919
986- return _add_dpwl_sos2_core (
987- model , name , x_expr , y_expr , x_points , y_points , mask , active
920+ binary_name = f"{ name } { PWL_BINARY_SUFFIX } "
921+ select_name = f"{ name } { PWL_SELECT_SUFFIX } "
922+ lambda_name = f"{ name } { PWL_LAMBDA_SUFFIX } "
923+ convex_name = f"{ name } { PWL_CONVEX_SUFFIX } "
924+ x_link_name = f"{ name } { PWL_X_LINK_SUFFIX } "
925+ y_link_name = f"{ name } { PWL_Y_LINK_SUFFIX } "
926+
927+ extra = _extra_coords (x_points , BREAKPOINT_DIM , SEGMENT_DIM )
928+ lambda_coords = extra + [
929+ pd .Index (x_points .coords [SEGMENT_DIM ].values , name = SEGMENT_DIM ),
930+ pd .Index (x_points .coords [BREAKPOINT_DIM ].values , name = BREAKPOINT_DIM ),
931+ ]
932+ binary_coords = extra + [
933+ pd .Index (x_points .coords [SEGMENT_DIM ].values , name = SEGMENT_DIM ),
934+ ]
935+
936+ binary_mask = mask .any (dim = BREAKPOINT_DIM ) if mask is not None else None
937+
938+ binary_var = model .add_variables (
939+ binary = True , coords = binary_coords , name = binary_name , mask = binary_mask
940+ )
941+
942+ rhs = active if active is not None else 1
943+ select_con = model .add_constraints (
944+ binary_var .sum (dim = SEGMENT_DIM ) == rhs , name = select_name
945+ )
946+
947+ lambda_var = model .add_variables (
948+ lower = 0 , upper = 1 , coords = lambda_coords , name = lambda_name , mask = mask
988949 )
950+
951+ model .add_sos_constraints (lambda_var , sos_type = 2 , sos_dim = BREAKPOINT_DIM )
952+
953+ model .add_constraints (
954+ lambda_var .sum (dim = BREAKPOINT_DIM ) == binary_var , name = convex_name
955+ )
956+
957+ x_weighted = (lambda_var * x_points ).sum (dim = [SEGMENT_DIM , BREAKPOINT_DIM ])
958+ model .add_constraints (x_expr == x_weighted , name = x_link_name )
959+
960+ y_weighted = (lambda_var * y_points ).sum (dim = [SEGMENT_DIM , BREAKPOINT_DIM ])
961+ model .add_constraints (y_expr == y_weighted , name = y_link_name )
962+
963+ return select_con
0 commit comments