| 511 if self.save_for_plot: |
518 if self.save_for_plot: |
| 512 self.plot_wp_bar = wp_bar |
519 self.plot_wp_bar = wp_bar |
| 513 self.plot_u_n_list = u_n_list |
520 self.plot_u_n_list = u_n_list |
| 514 self.plot_w_n_list = w_n_list |
521 self.plot_w_n_list = w_n_list |
| 515 self.plot_j_n_list = j |
522 self.plot_j_n_list = j |
| |
523 self.plot_aux = (k, c) |
| 516 |
524 |
| 517 return wp_bar, (expr2, expr3) |
525 return wp_bar, (expr2, expr3) |
| 518 |
526 |
| 519 def do_plot(self, iter, μ, frames, prefix): |
527 def do_plot(self, iter, μ, frames, prefix): |
| 520 n = self.num_steps |
528 n = self.num_steps |
| |
529 k, (c1, c2) = self.plot_aux |
| 521 np.savez_compressed( |
530 np.savez_compressed( |
| 522 "%s/res_%d.npz" % (prefix, iter), |
531 "%s/res_%d.npz" % (prefix, iter), |
| 523 wp_bar_array=self.plot_wp_bar.x.array, |
532 wp_bar_array=self.plot_wp_bar.x.array, |
| 524 u_n_list_array=[self.plot_u_n_list[i].x.array for i in frames], |
533 u_n_list_array=[self.plot_u_n_list[i].x.array for i in frames], |
| 525 w_n_list_array=[self.plot_w_n_list[i].x.array for i in frames], |
534 w_n_list_array=[self.plot_w_n_list[i].x.array for i in frames], |
| 526 j_n_list_array=[self.plot_j_n_list[i].x.array for i in frames], |
535 j_n_list_array=[self.plot_j_n_list[i].x.array for i in frames], |
| |
536 k=k, |
| |
537 c1=c1, |
| |
538 c2=c2, |
| 527 frames=frames, |
539 frames=frames, |
| 528 times=list(map(lambda i: self.T * i / (n - 1), frames)), |
540 times=list(map(lambda i: self.T * i / (n - 1), frames)), |
| 529 mu=[[point[0], point[1], alpha] for point, alpha in μ.iter_padded()], |
541 mu=[[point[0], point[1], alpha] for point, alpha in μ.iter_padded()], |
| 530 ) |
542 ) |
| 531 |
543 # print("Aux variables: k: %f, c1: %f, c2: %f" % (k, c1, c2)) |
| 532 def _stability_prefactor( |
544 |
| 533 self, *, C_stab=None, alpha=None, beta=None, T=None, C_emb=None, C_reg=None |
545 # def _stability_prefactor( |
| 534 ): |
546 # self, *, C_stab=None, alpha=None, beta=None, T=None, C_emb=None, C_reg=None |
| 535 """ |
547 # ): |
| 536 Return the stability prefactor |
548 # """ |
| 537 P := (C_stab/alpha) * (1 + sqrt(T)*(1 + sqrt(beta)/sqrt(alpha))) |
549 # Return the stability prefactor |
| 538 and the measure-to-functional constant L_e_mu := C_emb * C_reg * sqrt(T). |
550 # P := (C_stab/alpha) * (1 + sqrt(T)*(1 + sqrt(beta)/sqrt(alpha))) |
| 539 Return (P, L_e_mu): |
551 # and the measure-to-functional constant L_e_mu := C_emb * C_reg * sqrt(T). |
| 540 """ |
552 # Return (P, L_e_mu): |
| 541 # self._stability_prefactor(alpha=0.5, T=2.0) |
553 # """ |
| 542 |
554 # # self._stability_prefactor(alpha=0.5, T=2.0) |
| 543 # Read from arguments or instance attributes |
555 |
| 544 C_stab = C_stab if C_stab is not None else self.C_stab |
556 # # Read from arguments or instance attributes |
| 545 alpha = alpha if alpha is not None else self.alpha |
557 # C_stab = C_stab if C_stab is not None else self.C_stab |
| 546 beta = beta if beta is not None else self.beta |
558 # alpha = alpha if alpha is not None else self.alpha |
| 547 T = T if T is not None else self.T |
559 # beta = beta if beta is not None else self.beta |
| 548 C_emb = C_emb if C_emb is not None else self.C_emb |
560 # T = T if T is not None else self.T |
| 549 C_reg = C_reg if C_reg is not None else self.C_reg |
561 # C_emb = C_emb if C_emb is not None else self.C_emb |
| 550 |
562 # C_reg = C_reg if C_reg is not None else self.C_reg |
| 551 # Stability factor P |
563 |
| 552 |
564 # # Stability factor P |
| 553 P = (C_stab / alpha) * (1 + np.sqrt(T) * (1 + np.sqrt(beta) / np.sqrt(alpha))) |
565 |
| 554 L_e_mu = C_emb * C_reg * np.sqrt(T) |
566 # P = (C_stab / alpha) * (1 + np.sqrt(T) * (1 + np.sqrt(beta) / np.sqrt(alpha))) |
| 555 |
567 # L_e_mu = C_emb * C_reg * np.sqrt(T) |
| 556 return P, L_e_mu |
568 |
| 557 |
569 # return P, L_e_mu |
| 558 # Solution operator Lipschitz factor |
570 |
| 559 def lipschitz_factor(self): |
571 # # Solution operator Lipschitz factor |
| 560 """ |
572 # def lipschitz_factor(self, xbound=None): |
| 561 Conservative Lipschitz factor for the forward solution operator mapping |
573 # """ |
| 562 (k,c,mu) -> u. Returns a single scalar C_Lip such that |
574 # Conservative Lipschitz factor for the forward solution operator mapping |
| 563 ||u2 - u1|| <= C_Lip * (||k2-k1|| + ||c2-c1|| + ||mu2-mu1||_* ) |
575 # (k,c,mu) -> u. Returns a single scalar C_Lip such that |
| 564 (we use a conservative form combining the measure and (k,c) pieces). |
576 # ||u2 - u1|| <= C_Lip * (||k2-k1|| + ||c2-c1|| + ||mu2-mu1||_* ) |
| 565 """ |
577 # (we use a conservative form combining the measure and (k,c) pieces). |
| 566 P, L_e_mu = self._stability_prefactor() |
578 # """ |
| 567 # Conservative combined choice: multiply P by max(1, L_e_mu) |
579 # P, L_e_mu = self._stability_prefactor() |
| 568 C_Lip = P * max(1.0, L_e_mu) |
580 # # Conservative combined choice: multiply P by max(1, L_e_mu) |
| 569 return C_Lip |
581 # C_Lip = P * max(1.0, L_e_mu) |
| 570 |
582 # return C_Lip |
| 571 # Adjoint solution operator Lipschitz factor |
583 def diff_chain_lipschitz_factor(self, Delta_t=None): |
| 572 """ |
584 """ |
| 573 def diff_adj_lipschitz_factor(self): |
585 Compute a Lipschitz factor C_tot^Lip using locally accumulated bounds |
| 574 |
586 M_J = 1. |
| 575 Compute adjoint-solution Lipschitz factor A for 2D domain. |
587 """ |
| 576 |
588 |
| 577 For p = 2, A = 1. |
589 if self.override_lipschitz is not None: |
| 578 For p > 2, A = |Omega|^(1/2 - 1/p), where |Omega| is the mesh area. |
590 return self.override_lipschitz |
| 579 |
591 |
| 580 p = self.p |
592 # Use existing diff_bound3 |
| 581 omega_vol = self.domain_volume |
593 C_mu_adj, C_k_adj, C_c_adj = self.diff_bound3(Delta_t=Delta_t) |
| 582 return 1.0 if p == 2 else omega_vol ** (0.5 - 1.0 / p) |
594 |
| 583 """ |
595 if Delta_t is None: |
| 584 |
596 # fall back to the scheme used in diff_bound3 |
| 585 def diff_adj_lipschitz_factor(self): |
597 Delta_t = self.dt |
| 586 """ |
598 |
| 587 Always return 1 for p=2 |
599 N = int(np.ceil(self.T / Delta_t)) |
| 588 """ |
600 |
| 589 return 1.0 |
601 # Use local per‑step bounds |
| 590 |
602 C_adj_step = C_mu_adj / N |
| 591 def diff_bound(self, xbound=None): |
603 C_state_step = C_k_adj / N |
| 592 """ |
604 C_c_step = C_c_adj / N |
| 593 Differential bound for the solution operator derivative. |
605 |
| 594 |
606 # Per‑step "Lipschitz" |
| 595 Returns the tuple: (L_e_mu, L_e_k, L_e_c) |
607 l_e_local = C_adj_step + C_state_step + C_c_step |
| 596 |
608 |
| 597 where |
609 # Global Lipschitz constants |
| 598 L_e_k = ||∇u||_{L^2(Ω_T)} |
610 L_e = l_e_local * N |
| 599 L_e_c = ||∇u||_{L^2(Ω_T)} |
611 M_e = 1.2 * L_e |
| 600 L_e_mu = C_emb * C_reg * sqrt(T) |
612 L_A = 0.5 * C_state_step * N |
| 601 """ |
613 |
| 602 |
614 # PDE/coercivity |
| 603 # If grad_u_norm is None, compute it from u0 |
615 alpha = max(0.1, self.alpha) # guard α ≥ 0.1 |
| 604 grad_u_norm = getattr(self, "grad_u_norm", None) |
616 M_J = 1.0 |
| 605 if grad_u_norm is None: |
617 |
| 606 u = self.u0 |
618 # C_tot^Lip |
| 607 V = self.V |
619 inv_alpha = 1.0 / alpha |
| 608 # Compute L2 norm of gradient of u0 over the domain |
620 inv_alpha_sq = inv_alpha * inv_alpha |
| 609 grad_u = ufl.grad(u) |
621 C_tot = inv_alpha * (L_e + inv_alpha_sq * L_A * M_e) * M_J |
| 610 grad_u_sq = ufl.inner(grad_u, grad_u) |
622 return C_tot |
| 611 dx = ufl.Measure("dx", domain=self.domain) |
623 |
| 612 # Integral of |∇u|^2 over Ω |
624 def diff_chain_lipschitz_factor_pair(self, Delta_t=None): |
| 613 grad_u_norm = fem.assemble_scalar(fem.form(grad_u_sq * dx)) ** 0.5 |
625 """ |
| 614 # Optionally store it for later reuse |
626 Return (C^Lip_μ/M_J, C^Lip_aux/M_J) |
| 615 self.grad_u_norm = grad_u_norm |
627 """ |
| 616 |
628 |
| 617 return grad_u_norm |
629 if self.override_lipschitz_pair is not None: |
| 618 |
630 return self.override_lipschitz_pair |
| 619 def diff_bound_pair(self, xbound=None, Delta_t=None): |
631 |
| 620 if xbound is None: |
632 C_mu_adj, C_k_adj, C_c_adj = self.diff_bound3(Delta_t=Delta_t) |
| 621 return self.diff_bound(xbound=None) |
633 |
| 622 else: |
634 if Delta_t is None: |
| 623 xb_combined = xbound[0] + xbound[1] if len(xbound) > 1 else xbound[0] |
635 Delta_t = self.dt |
| 624 |
636 |
| 625 k_min = getattr(xb_combined, "k_min", self.alpha) |
637 N = int(np.ceil(self.T / Delta_t)) |
| 626 c_Linf = getattr(xb_combined, "c_Linf", np.sqrt(self.beta * k_min)) |
638 |
| 627 diam = getattr(xb_combined, "diam", self.diam) |
639 # Extract per-component per-step bounds (smaller scaling) |
| 628 T = getattr(xb_combined, "T", self.T) |
640 C_mu_step = C_mu_adj / N |
| 629 |
641 C_aux_step = max(C_k_adj, C_c_adj) / N |
| 630 Pe = c_Linf * diam / k_min |
642 |
| 631 gamma = c_Linf**2 / k_min |
643 # TINY Lipschitz factors |
| 632 # adaptive time step size Delta_t (Delta_t = 0.01 in the test) |
644 C_Lip_mu = C_mu_step * N |
| 633 if Delta_t is None: |
645 C_Lip_aux = C_aux_step * N |
| 634 Delta_t = min(0.1, k_min / c_Linf**2, 0.01 * T) # CFL-like |
646 |
| 635 |
647 # Light coercivity scaling |
| 636 # Accumulate N = T/Delta_t local steps |
648 alpha = self.alpha |
| 637 N = int(np.ceil(T / Delta_t)) |
649 inv_alpha = 1.0 / alpha |
| 638 |
650 |
| 639 # Per-step factors |
651 C_mu_tot = C_Lip_mu * inv_alpha |
| 640 exp_local = np.exp(0.5 * gamma * Delta_t) |
652 C_aux_tot = C_Lip_aux * inv_alpha |
| 641 C_adj_step = np.sqrt(Delta_t * (exp_local + Delta_t / k_min)) |
653 |
| 642 C_state_step = np.sqrt(Delta_t) * np.sqrt(1 + Pe**2) |
654 return C_mu_tot, C_aux_tot |
| 643 |
655 |
| 644 # Total accumulated bound (geometric sum <= N * max_step) |
656 def diff_bound3(self, Delta_t=None): |
| 645 C_adj_PDE = N * C_adj_step |
657 xbound = self.xbound |
| 646 C_state = N * C_state_step |
658 xb_combined = xbound # xbound[0] + xbound[1] if len(xbound) > 1 else xbound[0] |
| 647 |
659 |
| 648 # Use the embedding constant |
660 k_min = getattr(xb_combined, "k_min", self.alpha) |
| 649 C_mu_adj = self.C_emb * C_adj_PDE # ∫φ δμ |
661 c_Linf = getattr(xb_combined, "c_Linf", np.sqrt(self.beta * k_min)) |
| 650 C_k_adj = C_state * C_adj_PDE # ∫∇u·∇φ δk |
662 diam = getattr(xb_combined, "diam", self.diam) |
| 651 C_c1_adj = C_state * C_adj_PDE * c_Linf # ∫∂x u φ δc1 |
663 T = getattr(xb_combined, "T", self.T) |
| 652 C_c2_adj = C_state * C_adj_PDE * c_Linf # ∫∂y u φ δc2 |
664 |
| 653 |
665 Pe = c_Linf * diam / k_min |
| 654 return C_mu_adj, C_k_adj, C_c1_adj, C_c2_adj |
666 gamma = c_Linf**2 / k_min |
| |
667 # adaptive time step size Delta_t (Delta_t = 0.01 in the test) |
| |
668 if Delta_t is None: |
| |
669 Delta_t = min(0.1, k_min / c_Linf**2, 0.01 * T) # CFL-like |
| |
670 |
| |
671 # Accumulate N = T/Delta_t local steps |
| |
672 N = int(np.ceil(T / Delta_t)) |
| |
673 |
| |
674 # Per-step factors |
| |
675 exp_local = np.exp(0.5 * gamma * Delta_t) |
| |
676 C_adj_step = np.sqrt(Delta_t * (exp_local + Delta_t / k_min)) |
| |
677 C_state_step = np.sqrt(Delta_t) * np.sqrt(1 + Pe**2) |
| |
678 |
| |
679 # Total accumulated bound (geometric sum <= N * max_step) |
| |
680 C_adj_PDE = N * C_adj_step |
| |
681 C_state = N * C_state_step |
| |
682 |
| |
683 # Use the embedding constant |
| |
684 C_mu_adj = self.C_emb * C_adj_PDE # ∫φ δμ |
| |
685 C_k_adj = C_state * C_adj_PDE # ∫∇u·∇φ δk |
| |
686 C_c_adj = C_state * C_adj_PDE * c_Linf # ∫∂x u φ δc1 and c2 |
| |
687 |
| |
688 return C_mu_adj, C_k_adj, C_c_adj |
| 655 |
689 |
| 656 # ||S(x)||_Y→X ≤ C_state ||μ||_M(Ω) [time-independent μ] |
690 # ||S(x)||_Y→X ≤ C_state ||μ||_M(Ω) [time-independent μ] |
| 657 def codomain_bound(self, xbound=None): |
691 def codomain_bound(self): |
| 658 if xbound is None: |
692 xbound = self.xbound |
| 659 return 1.0 |
|
| 660 |
693 |
| 661 # Extract parameters |
694 # Extract parameters |
| 662 if hasattr(xbound, "k_min"): |
695 if hasattr(xbound, "k_min"): |
| 663 xb = xbound |
696 xb = xbound |
| 664 else: |
697 elif hasattr(xbound, "__len__") and len(xbound) > 0: |
| 665 if hasattr(xbound, "__len__") and len(xbound) > 0: |
698 xb = xbound[0] + xbound[1] if len(xbound) > 1 else xbound[0] |
| 666 xb = xbound[0] + xbound[1] if len(xbound) > 1 else xbound[0] |
699 else: |
| 667 else: |
700 xb = xbound |
| 668 xb = xbound |
701 |
| 669 k_min = getattr(xb, "k_min", self.alpha) |
702 k_min = getattr(xb, "k_min", self.alpha) |
| 670 c_Linf = getattr(xb, "c_Linf", np.sqrt(self.beta * k_min)) |
703 c_Linf = getattr(xb, "c_Linf", np.sqrt(self.beta * k_min)) |
| 671 mu_dual = getattr(xb, "mu_dual", 3.0) |
704 mu_dual = getattr(xb, "mu_dual", 3.0) |
| 672 diam = getattr(xb, "diam", self.diam) |
705 diam = getattr(xb, "diam", self.diam) |
| 673 T = getattr(xb, "T", self.T) |
706 T = getattr(xb, "T", self.T) |
| 674 |
707 |
| 675 Pe = c_Linf * diam / k_min |
708 Pe = c_Linf * diam / k_min |
| 676 if not hasattr(self, "Delta_t"): |
709 if not hasattr(self, "Delta_t"): |
| 677 Delta_t = min(0.1, k_min / c_Linf**2, 0.01 * T) |
710 Delta_t = min(0.1, k_min / c_Linf**2, 0.01 * T) |
| 678 else: |
711 else: |
| 878 vx *= β |
831 vx *= β |
| 879 return x |
832 return x |
| 880 |
833 |
| 881 def prox(self, τ, x): |
834 def prox(self, τ, x): |
| 882 """ |
835 """ |
| 883 Apply the proximal map of the quadratic regularisation fucntional to `x`. |
836 Apply the proximal map of the quadratic regularisation functional to `x`. |
| 884 WARNING: This function is unsafe. It may modify `x´. |
837 WARNING: This function is unsafe. It may modify `x´. |
| 885 That is ok for our purposes, as Rust, being a safe language, already needs to pass a |
838 That is ok for our purposes, as Rust, being a safe language, already needs to pass a |
| 886 copied or owned instance to Python. |
839 copied or owned instance to Python. |
| 887 """ |
840 """ |
| 888 k, (c1, c2) = x |
841 k, (c1, c2) = x |
| 889 (kb, (c1b, c2b)) = self._get_base() |
842 (kb, (c1b, c2b)) = self._get_base() |
| 890 |
843 |
| |
844 if isinstance(self.α, float): |
| |
845 αk, αc = self.α, self.α |
| |
846 else: |
| |
847 αk, αc = self.α |
| |
848 |
| 891 return ( |
849 return ( |
| 892 self._prox1(τ, k, kb), |
850 self._prox1(τ * αk, k, kb), |
| 893 (self._prox1(τ, c1, c1b), self._prox1(τ, c2, c2b)), |
851 (self._prox1(τ * αc, c1, c1b), self._prox1(τ * αc, c2, c2b)), |
| 894 ) |
852 ) |
| |
853 |
| |
854 |
| |
855 class LogPowerRegularisation: |
| |
856 """ |
| |
857 Logarithmic barrier + quadratic regularisation functional for the auxiliary variables; |
| |
858 $-α\\log(x) + ωx^2$. |
| |
859 """ |
| |
860 |
| |
861 def _own(self, x): |
| |
862 return x if isinstance(x, float) else own(x) |
| |
863 |
| |
864 def __init__(self, α, ω): |
| |
865 self.α = α |
| |
866 self.ω = ω |
| |
867 |
| |
868 def _apply1(self, x, α, ω): |
| |
869 if isinstance(x, float): |
| |
870 return -α * np.log(x) + ω * x**2 |
| |
871 else: |
| |
872 # Numpy is Matlab-grade junk. No reduce! Temporary arrays! In 2026! WTF?!?!?!? |
| |
873 return (-α * np.log(x) + ω * x**2).sum() |
| |
874 |
| |
875 def apply(self, x): |
| |
876 (k, (c1, c2)) = x |
| |
877 |
| |
878 if isinstance(self.α, float): |
| |
879 αk, αc = self.α, self.α |
| |
880 else: |
| |
881 αk, αc = self.α |
| |
882 if isinstance(self.ω, float): |
| |
883 ωk, ωc = self.ω, self.ω |
| |
884 else: |
| |
885 ωk, ωc = self.ω |
| |
886 |
| |
887 return ( |
| |
888 self._apply1(k, αk, ωk) |
| |
889 + self._apply1(c1, αc, ωc) |
| |
890 + self._apply1(c2, αc, ωc) |
| |
891 ) |
| |
892 |
| |
893 def _prox1(self, α, ω, x): |
| |
894 # OC: 0 = -α/z + 2ωz + z-x ⟺ -α + (2ω+1)z^2 - xz = 0. |
| |
895 # ⟺ z = -x ± √(x^2 + 4α(2ω+1))/(2(2ω+1)) |
| |
896 # Numpy is Matlab-grade junk. No reduce! Temporary arrays! In 2026! WTF?!?!?!? |
| |
897 return (x + np.sqrt(x**2 + 4 * α * (2 * ω + 1))) / (2 * (2 * ω + 1)) |
| |
898 |
| |
899 def prox(self, τ, x): |
| |
900 """ |
| |
901 Apply the proximal map of the logarithmic regularisation functional to `x`. |
| |
902 WARNING: This function is unsafe. It may modify `x´. |
| |
903 That is ok for our purposes, as Rust, being a safe language, already needs to pass a |
| |
904 copied or owned instance to Python. |
| |
905 """ |
| |
906 k, (c1, c2) = x |
| |
907 |
| |
908 if isinstance(self.α, float): |
| |
909 αk, αc = self.α, self.α |
| |
910 else: |
| |
911 αk, αc = self.α |
| |
912 if isinstance(self.ω, float): |
| |
913 ωk, ωc = self.ω, self.ω |
| |
914 else: |
| |
915 ωk, ωc = self.ω |
| |
916 |
| |
917 return ( |
| |
918 self._prox1(τ * αk, τ * ωk, k), |
| |
919 (self._prox1(τ * αc, τ * ωc, c1), self._prox1(τ * αc, τ * ωc, c2)), |
| |
920 ) |
| |
921 |
| |
922 |
| |
923 class BoxConstraints: |
| |
924 """ |
| |
925 Simple box constraints on the auxiliary variables |
| |
926 """ |
| |
927 |
| |
928 def _own(self, x): |
| |
929 return x if isinstance(x, float) else own(x) |
| |
930 |
| |
931 def __init__(self, α, ω): |
| |
932 self.α = α |
| |
933 self.ω = ω |
| |
934 |
| |
935 def _apply1(self, x, α, ω): |
| |
936 if isinstance(x, float): |
| |
937 if α <= x and x <= ω: |
| |
938 return 0.0 |
| |
939 else: |
| |
940 return np.inf |
| |
941 else: |
| |
942 raise Exception("Unimplemented") |
| |
943 |
| |
944 def apply(self, x): |
| |
945 (k, (c1, c2)) = x |
| |
946 |
| |
947 if isinstance(self.α, float): |
| |
948 αk, αc = self.α, self.α |
| |
949 else: |
| |
950 αk, αc = self.α |
| |
951 if isinstance(self.ω, float): |
| |
952 ωk, ωc = self.ω, self.ω |
| |
953 else: |
| |
954 ωk, ωc = self.ω |
| |
955 |
| |
956 return ( |
| |
957 self._apply1(k, αk, ωk) |
| |
958 + self._apply1(c1, αc, ωc) |
| |
959 + self._apply1(c2, αc, ωc) |
| |
960 ) |
| |
961 |
| |
962 def _prox1(self, α, ω, x): |
| |
963 # OC: 0 = -α/z + 2ωz + z-x ⟺ -α + (2ω+1)z^2 - xz = 0. |
| |
964 # ⟺ z = -x ± √(x^2 + 4α(2ω+1))/(2(2ω+1)) |
| |
965 # Numpy is Matlab-grade junk. No reduce! Temporary arrays! In 2026! WTF?!?!?!? |
| |
966 return max(min(ω, x), α) |
| |
967 |
| |
968 def prox(self, τ, x): |
| |
969 """ |
| |
970 Apply the proximal map of the box constraints to `x`. |
| |
971 WARNING: This function is unsafe. It may modify `x´. |
| |
972 That is ok for our purposes, as Rust, being a safe language, already needs to pass a |
| |
973 copied or owned instance to Python. |
| |
974 """ |
| |
975 k, (c1, c2) = x |
| |
976 |
| |
977 if isinstance(self.α, float): |
| |
978 αk, αc = self.α, self.α |
| |
979 else: |
| |
980 αk, αc = self.α |
| |
981 if isinstance(self.ω, float): |
| |
982 ωk, ωc = self.ω, self.ω |
| |
983 else: |
| |
984 ωk, ωc = self.ω |
| |
985 |
| |
986 return ( |
| |
987 self._prox1(αk, ωk, k), |
| |
988 (self._prox1(αc, ωc, c1), self._prox1(αc, ωc, c2)), |
| |
989 ) |
| |
990 |
| |
991 |
| |
992 """ |
| |
993 Quadratic regularisation with box contraints |
| |
994 """ |
| |
995 |
| |
996 |
| |
997 class BoxedQuadraticRegularisation: |
| |
998 def __init__(self, ω0, ω1, α, base=None): |
| |
999 self.box = BoxConstraints(ω0, ω1) |
| |
1000 self.quadratic = QuadraticRegularisation(α, base) |
| |
1001 |
| |
1002 def apply(self, x): |
| |
1003 return self.box.apply(x) + self.quadratic.apply(x) |
| |
1004 |
| |
1005 def prox(self, τ, x): |
| |
1006 return self.box.prox(1.0, self.quadratic.prox(τ, x)) |