rmatqmcpy 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rmatqmcpy/__init__.py ADDED
@@ -0,0 +1,41 @@
1
+ __version__ = "0.1"
2
+
3
+ from .tf_On_Un_Spn import (
4
+ tf_On_QR,
5
+ tf_Un_QR,
6
+ tf_On_eig,
7
+ tf_Un_eig,
8
+ tf_Spn_SVD,
9
+ )
10
+
11
+ from .rand_On_Un_Spn import (
12
+ rand_On_QR,
13
+ rand_Un_QR,
14
+ rand_On_eig,
15
+ rand_Un_eig,
16
+ rand_Spn_SVD,
17
+ )
18
+
19
+ from .rand_Stiefel_Gr_flag_LGr import (
20
+ rand_flag_R,
21
+ rand_flag_C,
22
+ rand_flag_H,
23
+ rand_LGr_R,
24
+ rand_LGr_C,
25
+ rand_LGr_H,
26
+ rand_Stiefel_R,
27
+ rand_Stiefel_C,
28
+ rand_Stiefel_H,
29
+ rand_Gr_R,
30
+ rand_Gr_C,
31
+ rand_Gr_H,
32
+ )
33
+
34
+ from .kernels import (
35
+ KernelMatern,
36
+ KernelPoly,
37
+ )
38
+
39
+ from .opt import (
40
+ opt_weights_sum_1,
41
+ )
rmatqmcpy/kernels.py ADDED
@@ -0,0 +1,183 @@
1
+ import agsutil
2
+ import torch
3
+ import numpy as np
4
+
5
+ from .rand_Stiefel_Gr_flag_LGr import (
6
+ rand_flag_R,
7
+ rand_flag_C,
8
+ rand_flag_H,
9
+ rand_Stiefel_R,
10
+ rand_Stiefel_C,
11
+ rand_Stiefel_H,
12
+ rand_Gr_R,
13
+ rand_Gr_C,
14
+ rand_Gr_H,
15
+ rand_LGr_R,
16
+ rand_LGr_C,
17
+ rand_LGr_H,
18
+ )
19
+
20
+ class KernelMatern(object):
21
+ r"""
22
+ Matern chordal kernel.
23
+
24
+ Args:
25
+ nu (float): Smoothness parameter. Must be in `[1/2, 3/2, 5/2, 7/2, 9/2, 11/2, 13/2, np.inf]`.
26
+ sigma2 (float, optional): Variance parameter.
27
+ rho (float, optional): Length-scale parameter.
28
+
29
+ Examples:
30
+ >>> torch.set_default_dtype(torch.float64)
31
+
32
+ >>> kernel = KernelMatern(nu=1/2)
33
+
34
+ >>> x = rand_flag_R(3,7,seed=7)
35
+ >>> x.shape
36
+ torch.Size([3, 7, 7])
37
+ >>> k = kernel(x[:,None,:,:],x[None,:,:,:])
38
+ >>> k.shape
39
+ torch.Size([3, 3])
40
+ >>> k
41
+ tensor([[1.0000, 0.5458, 0.5388],
42
+ [0.5458, 1.0000, 0.5787],
43
+ [0.5388, 0.5787, 1.0000]])
44
+
45
+ >>> for rand_manifold in [
46
+ ... rand_flag_R,
47
+ ... rand_flag_C,
48
+ ... rand_flag_H,
49
+ ... rand_Stiefel_R,
50
+ ... rand_Stiefel_C,
51
+ ... rand_Stiefel_H,
52
+ ... rand_Gr_R,
53
+ ... rand_Gr_C,
54
+ ... rand_Gr_H,
55
+ ... rand_LGr_R,
56
+ ... rand_LGr_C,
57
+ ... rand_LGr_H,]:
58
+ ... x = rand_manifold(3,7,seed=7)
59
+ ... for nu in [1/2,3/2,5/2,7/2,9/2,11/2,13/2,np.inf]:
60
+ ... kernel = KernelMatern(nu=nu)
61
+ ... k = kernel(x[:,None,:,:],x[None,:,:,:])
62
+ ... assert k.shape==(3,3)
63
+ ... assert not k.isnan().any()
64
+ ... assert k.isfinite().all()
65
+ ... assert not k.is_complex()
66
+ """
67
+ SUPPORTED_NU = [1/2,3/2,5/2,7/2,9/2,11/2,13/2,np.inf]
68
+ def __init__(self, nu=5/2, sigma2=1, rho=1):
69
+ self.nu = nu
70
+ self.sigma2 = sigma2
71
+ self.rho = rho
72
+ assert self.nu in self.SUPPORTED_NU, "nu should be in %s"%str(self.SUPPORTED_NU)
73
+ if self.nu==1/2:
74
+ self.c = torch.tensor([1])
75
+ self.nu_str = r"$\nu = 1/2$"
76
+ elif self.nu==3/2:
77
+ self.c = torch.tensor([1,np.sqrt(3)])
78
+ self.nu_str = r"$\nu = 3/2$"
79
+ elif self.nu==5/2:
80
+ self.c = torch.tensor([1,np.sqrt(5),5/3])
81
+ self.nu_str = r"$\nu = 5/2$"
82
+ elif self.nu==7/2:
83
+ self.c = torch.tensor([1,np.sqrt(7),14/5,7*np.sqrt(7)/15])
84
+ self.nu_str = r"$\nu = 7/2$"
85
+ elif self.nu==9/2:
86
+ self.c = torch.tensor([1,3,27/7,18/7,27/35])
87
+ self.nu_str = r"$\nu = 9/2$"
88
+ elif self.nu==11/2:
89
+ self.c = torch.tensor([1,np.sqrt(11),44/9,11*np.sqrt(11)/9,121/63,121*np.sqrt(11)/945])
90
+ self.nu_str = r"$\nu = 11/2$"
91
+ elif self.nu==13/2:
92
+ self.c = torch.tensor([1,np.sqrt(13),65/11,52*np.sqrt(13)/33,338/99,169*np.sqrt(13)/495,2197/10395])
93
+ self.nu_str = r"$\nu = 13/2$"
94
+ elif self.nu==np.inf:
95
+ self.nu_str = r"$\nu = \infty$"
96
+ def __call__(self, x, y, dim=(-2,-1)):
97
+ r"""
98
+ Compute the Matern kernel between two batches of tensors.
99
+
100
+ Args:
101
+ x (torch.Tensor): First batch of tensors.
102
+ y (torch.Tensor): Second batch of tensors.
103
+ dim (tuple, optional): Dimensions over which to take the Frobenius distance..
104
+
105
+ Returns:
106
+ k (torch.Tensor): The computed Matern kernel values.
107
+ """
108
+ frob_dist = torch.linalg.norm(x-y,dim=dim).real
109
+ rscaled = frob_dist/self.rho
110
+ if self.nu==np.inf:
111
+ k = torch.exp(-(rscaled)**2/2)
112
+ else:
113
+ exp_term = torch.exp(-np.sqrt(2*self.nu)*rscaled)
114
+ powers = torch.arange(len(self.c),dtype=x.real.dtype,device=x.device)
115
+ poly_term = (self.c.to(x.device)*rscaled[...,None]**powers).sum(-1)
116
+ k = exp_term*poly_term
117
+ return self.sigma2*k
118
+
119
+ class KernelPoly(object):
120
+ r"""
121
+ Polynomial kernel.
122
+
123
+ Args:
124
+ r (int): Degree of the polynomial.
125
+ c (float): Constant offset.
126
+
127
+ Examples:
128
+ >>> torch.set_default_dtype(torch.float64)
129
+
130
+ >>> kernel = KernelPoly()
131
+
132
+ >>> x = rand_flag_R(3,7,seed=7)
133
+ >>> x.shape
134
+ torch.Size([3, 7, 7])
135
+ >>> k = kernel(x[:,None,:,:],x[None,:,:,:])
136
+ >>> k.shape
137
+ torch.Size([3, 3])
138
+ >>> k
139
+ tensor([[4.0000, 3.3004, 3.2718],
140
+ [3.3004, 4.0000, 3.4239],
141
+ [3.2718, 3.4239, 4.0000]])
142
+
143
+ >>> for rand_manifold in [
144
+ ... rand_flag_R,
145
+ ... rand_flag_C,
146
+ ... rand_flag_H,
147
+ ... rand_Stiefel_R,
148
+ ... rand_Stiefel_C,
149
+ ... rand_Stiefel_H,
150
+ ... rand_Gr_R,
151
+ ... rand_Gr_C,
152
+ ... rand_Gr_H,
153
+ ... rand_LGr_R,
154
+ ... rand_LGr_C,
155
+ ... rand_LGr_H,]:
156
+ ... x = rand_manifold(3,7,seed=7)
157
+ ... k = kernel(x[:,None,:,:],x[None,:,:,:])
158
+ ... assert k.shape==(3,3)
159
+ ... assert not k.isnan().any()
160
+ ... assert k.isfinite().all()
161
+ ... assert not k.is_complex()
162
+ """
163
+ def __init__(self, t=2, c=1):
164
+ assert t>=1
165
+ assert t%1==0
166
+ assert c>0
167
+ self.t = t
168
+ self.c = c
169
+ def __call__(self, x, y, dim=(-2,-1)):
170
+ r"""
171
+ Compute the polynomial kernel between two batches of tensors.
172
+
173
+ Args:
174
+ x (torch.Tensor): First batch of tensors.
175
+ y (torch.Tensor): Second batch of tensors.
176
+ dim (tuple, optional): Dimensions over which to take the Frobenius inner product.
177
+
178
+ Returns:
179
+ k (torch.Tensor): The computed polynomial kernel values.
180
+ """
181
+ frob_inner_prod = (x*y.conj()).sum(dim=dim).real
182
+ v = (self.c+frob_inner_prod)**self.t
183
+ return v
rmatqmcpy/opt.py ADDED
@@ -0,0 +1,90 @@
1
+ import torch
2
+ import agsutil
3
+
4
+ from .kernels import KernelMatern
5
+ from .rand_Stiefel_Gr_flag_LGr import rand_flag_R
6
+
7
+ def opt_weights_sum_1(x, kernel, dims=2, eps=1e-12, return_wce2_p_kmean=True, use_pcg=False, pcg_kwargs={"verbose":False}):
8
+ r"""
9
+ Compute the optimal weights for a kernel quadrature rule.
10
+
11
+ Args:
12
+ x (torch.Tensor): A batch of samples of shape `(..., N, *m)` where `len(m)==dims`.
13
+ kernel (callable): Kernel function.
14
+ dims (int): See description of `x`.
15
+ eps (float, optional): Regularization parameter for numerical stability.
16
+ return_wce2_p_kmean (bool): If True, return
17
+ $\mathrm{WCE}^2+\gamma = \boldsymbol{w}^\intercal \mathsf{K} \boldsymbol{w}$
18
+ where $\gamma = \int_\mathcal{M} K(\boldsymbol{x},\boldsymbol{y}) \mu(\mathrm{d} \boldsymbol{x})$
19
+ is assumed to be constant as a function of $\boldsymbol{x}$.
20
+ use_pcg (bool): If True, use preconditioned conjugate gradient (PCG) to solve for weights.
21
+ If `use_pcg` an int is passed in, then PCG is used whenever `N>=use_pcg`.
22
+ pcg_kwargs (dict): keyword arguments to pass to `agsutil.pcg`.
23
+
24
+ Returns:
25
+ w (torch.Tensor): Optimal weights of shape `(..., N)`.
26
+
27
+ Examples:
28
+ >>> torch.set_default_dtype(torch.float64)
29
+
30
+ >>> kernel = KernelMatern(nu=1/2)
31
+ >>> x = rand_flag_R(3,7,seed=7)
32
+ >>> x.shape
33
+ torch.Size([3, 7, 7])
34
+ >>> w,wce2_p_kmean = opt_weights_sum_1(x,kernel)
35
+ >>> w.shape
36
+ torch.Size([3])
37
+ >>> w.sum()
38
+ tensor(1.)
39
+ >>> w
40
+ tensor([0.3506, 0.3218, 0.3276])
41
+ >>> wce2_p_kmean
42
+ tensor(0.7027)
43
+
44
+ >>> x2 = rand_flag_R(3,7,seed=11)
45
+ >>> xfull = torch.stack([x,x2],dim=0)
46
+ >>> xfull.shape
47
+ torch.Size([2, 3, 7, 7])
48
+ >>> w_full,wce2_p_kmean_full = opt_weights_sum_1(xfull,kernel)
49
+ >>> w_full.shape
50
+ torch.Size([2, 3])
51
+ >>> w_full.sum(-1)
52
+ tensor([1., 1.])
53
+ >>> w_full
54
+ tensor([[0.3506, 0.3218, 0.3276],
55
+ [0.3015, 0.3055, 0.3931]])
56
+ >>> wce2_p_kmean_full.shape
57
+ torch.Size([2])
58
+ >>> wce2_p_kmean_full
59
+ tensor([0.7027, 0.7009])
60
+
61
+ >>> w_full_pcg,wce2_p_kmean_full_pcg = opt_weights_sum_1(xfull,kernel,use_pcg=True)
62
+ >>> torch.allclose(w_full,w_full_pcg)
63
+ True
64
+ >>> torch.allclose(wce2_p_kmean_full,wce2_p_kmean_full_pcg)
65
+ True
66
+ """
67
+ assert x.ndim>=(dims+1)
68
+ kmat = kernel(x.unsqueeze(-dims-1),x.unsqueeze(-dims-2),dim=tuple(j for j in range(-dims,0,1))) # (...,N,N)
69
+ N = kmat.size(-1)
70
+ b = torch.ones(kmat.shape[:-1],device=x.device) # (...,N)
71
+ if not isinstance(use_pcg,bool):
72
+ assert isinstance(use_pcg,int)
73
+ use_pcg = N>=use_pcg
74
+ assert isinstance(use_pcg,bool)
75
+ kmat_noisy = kmat+eps*torch.eye(N,device=x.device)
76
+ if use_pcg:
77
+ w = agsutil.pcg(
78
+ kmat_noisy,
79
+ b[...,None],
80
+ **pcg_kwargs,
81
+ )[...,0]
82
+ else:
83
+ L = torch.linalg.cholesky(kmat+eps*torch.eye(N,device=x.device),upper=False) # (...,N,N)
84
+ w = torch.cholesky_solve(b[...,None],L,upper=False)[...,0] # (...,N)
85
+ w = w/w.sum(-1,keepdim=True)
86
+ if return_wce2_p_kmean:
87
+ wce_squared_plus_kernel_integral = torch.einsum("...i,...ij,...j->...",w,kmat,w) # (...,)
88
+ return w,wce_squared_plus_kernel_integral
89
+ else:
90
+ return w
@@ -0,0 +1,286 @@
1
+ import agsutil
2
+ import torch
3
+ import numpy as np
4
+ import qmcpy as qp
5
+
6
+ from .tf_On_Un_Spn import (
7
+ tf_On_QR,
8
+ tf_On_eig,
9
+ tf_Un_QR,
10
+ tf_Un_eig,
11
+ tf_Spn_SVD,
12
+ )
13
+
14
+ def rand_On_QR(N, n, k, seed=None, device=None, qp_unif_gen=None):
15
+ r"""
16
+ Generate a batch of `N` random orthogonal matrices of size `(N, n, k)` using the QR decomposition.
17
+
18
+ Args:
19
+ N (int): Number of samples to generate.
20
+ n (int): Number of rows.
21
+ k (int): Number of columns with `k<=n`.
22
+ seed (int, optional): Random seed for reproducibility.
23
+ device (str, optional): Device to store the tensor on (e.g., "cpu", "cuda").
24
+ qp_unif_gen (qmcpy.DiscreteDistribution, optional): QMCPy distribution generator.
25
+
26
+ Returns:
27
+ x (torch.Tensor): A batch of `N` random orthogonal matrices of size `(N, n, k)`.
28
+
29
+ Examples:
30
+ >>> torch.set_default_dtype(torch.float64)
31
+
32
+
33
+ >>> q = rand_On_QR(2,3,3,seed=7)
34
+ >>> q.shape
35
+ torch.Size([2, 3, 3])
36
+ >>> q
37
+ tensor([[[-0.9577, -0.0582, -0.2820],
38
+ [ 0.2193, 0.4869, -0.8455],
39
+ [ 0.1865, -0.8715, -0.4535]],
40
+ <BLANKLINE>
41
+ [[-0.8182, -0.3566, -0.4510],
42
+ [-0.5690, 0.3904, 0.7237],
43
+ [ 0.0820, -0.8488, 0.5224]]])
44
+
45
+ >>> q = rand_On_QR(2,3,3,seed=7,qp_unif_gen=qp.Net)
46
+ >>> q.shape
47
+ torch.Size([2, 3, 3])
48
+ >>> q
49
+ tensor([[[-0.1323, -0.4299, 0.8931],
50
+ [-0.6249, 0.7356, 0.2615],
51
+ [-0.7694, -0.5236, -0.3660]],
52
+ <BLANKLINE>
53
+ [[ 0.8600, -0.4448, -0.2502],
54
+ [ 0.4598, 0.8880, 0.0014],
55
+ [ 0.2215, -0.1162, 0.9682]]])
56
+ """
57
+ if qp_unif_gen is None: qp_unif_gen = qp.IIDStdUniform
58
+ rng = agsutil.get_torch_rng(seed,device=device)
59
+ u = torch.from_numpy(qp_unif_gen(dimension=n*k,seed=seed)(N)).to(device)
60
+ q = tf_On_QR(u,n,k)
61
+ return q
62
+
63
+ def rand_Un_QR(N, n, k, seed=None, device=None, qp_unif_gen=None):
64
+ r"""
65
+ Generate a batch of `N` random unitary matrices of size `(N, n, k)` using the QR decomposition.
66
+
67
+ Args:
68
+ N (int): Number of samples to generate.
69
+ n (int): Number of rows.
70
+ k (int): Number of columns with `k<=n`.
71
+ seed (int, optional): Random seed for reproducibility.
72
+ device (str, optional): Device to store the tensor on (e.g., "cpu", "cuda").
73
+ qp_unif_gen (qmcpy.DiscreteDistribution, optional): QMCPy distribution generator.
74
+
75
+ Returns:
76
+ x (torch.Tensor): A batch of `N` random unitary matrices of size `(N, n, k)`.
77
+
78
+ Examples:
79
+ >>> torch.set_default_dtype(torch.float64)
80
+
81
+ >>> q = rand_Un_QR(2,3,3,seed=7)
82
+ >>> q.shape
83
+ torch.Size([2, 3, 3])
84
+ >>> q
85
+ tensor([[[-0.7005+0.0905j, 0.0129-0.0897j, 0.5259-0.4652j],
86
+ [ 0.1364-0.4500j, 0.2766-0.6973j, -0.2259-0.4062j],
87
+ [-0.4493+0.2795j, 0.6275-0.1878j, -0.2937+0.4525j]],
88
+ <BLANKLINE>
89
+ [[ 0.5150+0.6182j, -0.1275-0.0258j, -0.5254-0.2442j],
90
+ [ 0.1241+0.1499j, -0.6573-0.5715j, 0.3584+0.2739j],
91
+ [ 0.1027-0.5515j, -0.0820-0.4666j, -0.2046-0.6473j]]])
92
+
93
+ >>> q = rand_Un_QR(2,3,3,seed=7,qp_unif_gen=qp.Net)
94
+ >>> q.shape
95
+ torch.Size([2, 3, 3])
96
+ >>> q
97
+ tensor([[[ 0.1884+0.1952j, 0.2403+0.7558j, 0.1686-0.5187j],
98
+ [-0.0775+0.3625j, -0.0203-0.5851j, 0.2945-0.6581j],
99
+ [-0.8250-0.3293j, -0.0804+0.1475j, -0.2682-0.3329j]],
100
+ <BLANKLINE>
101
+ [[-0.6121-0.3989j, -0.2673-0.1013j, -0.6161-0.0701j],
102
+ [ 0.4590-0.0060j, 0.0882-0.5607j, -0.4561+0.5091j],
103
+ [ 0.1663+0.4774j, -0.7245-0.2670j, -0.0730-0.3782j]]])
104
+ """
105
+ if qp_unif_gen is None: qp_unif_gen = qp.IIDStdUniform
106
+ rng = agsutil.get_torch_rng(seed,device=device)
107
+ u = torch.from_numpy(qp_unif_gen(dimension=2*n*k,seed=seed)(N)).to(device)
108
+ q = tf_Un_QR(u,n,k)
109
+ return q
110
+
111
+ def rand_On_eig(N, n, k, seed=None, device=None, qp_unif_gen=None):
112
+ r"""
113
+ Generate a batch of `N` random orthogonal matrices of size `(N, n, k)` using the eigenvalue decomposition.
114
+
115
+ Args:
116
+ N (int): Number of samples to generate.
117
+ n (int): Number of rows.
118
+ k (int): Number of columns with `k<=n`.
119
+ seed (int, optional): Random seed for reproducibility.
120
+ device (str, optional): Device to store the tensor on (e.g., "cpu", "cuda").
121
+ qp_unif_gen (qmcpy.DiscreteDistribution, optional): QMCPy distribution generator.
122
+
123
+ Returns:
124
+ x (torch.Tensor): A batch of `N` random orthogonal matrices of size `(N, n, k)`.
125
+
126
+ Examples:
127
+ >>> torch.set_default_dtype(torch.float64)
128
+
129
+ >>> q = rand_On_eig(2,3,3,seed=7)
130
+ >>> q.shape
131
+ torch.Size([2, 3, 3])
132
+ >>> q
133
+ tensor([[[ 0.3748, 0.4469, -0.8123],
134
+ [-0.1178, -0.8461, -0.5199],
135
+ [ 0.9196, -0.2905, 0.2645]],
136
+ <BLANKLINE>
137
+ [[ 0.9026, -0.2322, -0.3624],
138
+ [-0.1140, -0.9409, 0.3190],
139
+ [ 0.4150, 0.2467, 0.8757]]])
140
+
141
+ >>> q = rand_On_eig(2,3,3,seed=7,qp_unif_gen=qp.Net)
142
+ >>> q.shape
143
+ torch.Size([2, 3, 3])
144
+ >>> q
145
+ tensor([[[-0.7446, -0.3508, 0.5679],
146
+ [-0.5217, 0.8366, -0.1671],
147
+ [-0.4165, -0.4207, -0.8060]],
148
+ <BLANKLINE>
149
+ [[-0.0434, -0.8815, 0.4702],
150
+ [-0.2524, 0.4650, 0.8486],
151
+ [ 0.9666, 0.0818, 0.2427]]])
152
+ """
153
+ if qp_unif_gen is None: qp_unif_gen = qp.IIDStdUniform
154
+ u = torch.from_numpy(qp_unif_gen(dimension=n*(n-1)//2+2*n,seed=seed)(N)).to(device)
155
+ q = tf_On_eig(u,n,k)
156
+ return q
157
+
158
+ def rand_Un_eig(N, n, k, seed=None, device=None, qp_unif_gen=None):
159
+ r"""
160
+ Generate a batch of `N` random unitary matrices of size `(N, n, k)` using the eigenvalue decomposition.
161
+
162
+ Args:
163
+ N (int): Number of samples to generate.
164
+ n (int): Number of rows.
165
+ k (int): Number of columns with `k<=n`.
166
+ seed (int, optional): Random seed for reproducibility.
167
+ device (str, optional): Device to store the tensor on (e.g., "cpu", "cuda").
168
+ qp_unif_gen (qmcpy.DiscreteDistribution, optional): QMCPy distribution generator.
169
+
170
+ Returns:
171
+ x (torch.Tensor): A batch of `N` random unitary matrices of size `(N, n, k)`.
172
+
173
+ Examples:
174
+ >>> torch.set_default_dtype(torch.float64)
175
+
176
+ >>> q = rand_Un_eig(2,3,3,seed=7)
177
+ >>> q.shape
178
+ torch.Size([2, 3, 3])
179
+ >>> q
180
+ tensor([[[-0.4009-0.1134j, 0.5152+0.3147j, -0.4627+0.4978j],
181
+ [-0.0383-0.0098j, -0.5419+0.5188j, 0.3717+0.5453j],
182
+ [-0.5107-0.7510j, -0.0557-0.2638j, 0.3130-0.0670j]],
183
+ <BLANKLINE>
184
+ [[ 0.1113+0.1290j, 0.0108-0.7796j, -0.3388+0.4983j],
185
+ [ 0.0135-0.9445j, 0.1002+0.0782j, -0.2065+0.2213j],
186
+ [ 0.1714+0.2221j, 0.3955+0.4685j, 0.1415+0.7248j]]])
187
+
188
+ >>> q = rand_Un_eig(2,3,3,seed=7,qp_unif_gen=qp.Net)
189
+ >>> q.shape
190
+ torch.Size([2, 3, 3])
191
+ >>> q
192
+ tensor([[[ 0.5988-0.2838j, -0.3694+0.2998j, -0.5360+0.2174j],
193
+ [ 0.0116-0.3634j, 0.6324-0.2327j, -0.5162-0.3838j],
194
+ [-0.6395+0.1405j, -0.2209+0.5204j, -0.4418-0.2376j]],
195
+ <BLANKLINE>
196
+ [[-0.6903+0.0581j, -0.1665+0.1366j, -0.4519-0.5192j],
197
+ [-0.6830+0.0339j, 0.0105-0.3853j, 0.3131+0.5346j],
198
+ [-0.2250-0.0432j, 0.2931+0.8480j, 0.3613+0.1091j]]])
199
+ """
200
+ if qp_unif_gen is None: qp_unif_gen = qp.IIDStdUniform
201
+ u = torch.from_numpy(qp_unif_gen(dimension=n**2+n,seed=seed)(N)).to(device)
202
+ q = tf_Un_eig(u,n,k)
203
+ return q
204
+
205
+ def rand_Spn_SVD(N, n, k, seed=None, device=None, qp_unif_gen=None):
206
+ r"""
207
+ Generate a batch of `N` random symplectic unitary matrices of size `(N, 2n, 2k)` using the SVD decomposition.
208
+
209
+ Args:
210
+ N (int): Number of samples to generate.
211
+ n (int): Half the number of rows.
212
+ k (int): Half the number of columns with `k<=n`.
213
+ seed (int, optional): Random seed for reproducibility.
214
+ device (str, optional): Device to store the tensor on (e.g., "cpu", "cuda").
215
+ qp_unif_gen (qmcpy.DiscreteDistribution, optional): QMCPy distribution generator.
216
+
217
+ Returns:
218
+ x (torch.Tensor): A batch of `N` random symplectic unitary matrices of size `(N, 2n, 2k)`.
219
+
220
+ Examples:
221
+ >>> torch.set_default_dtype(torch.float64)
222
+
223
+ >>> q = rand_Spn_SVD(2,3,3,seed=7)
224
+ >>> q.shape
225
+ torch.Size([2, 6, 6])
226
+ >>> q
227
+ tensor([[[-0.5379+0.0018j, 0.1665-0.3441j, 0.1465-0.4183j, -0.0826+0.2936j,
228
+ 0.0846-0.3585j, -0.3592-0.1015j],
229
+ [-0.2501+0.3772j, -0.4710+0.0421j, 0.1512+0.1012j, 0.1678-0.1855j,
230
+ 0.3810+0.2695j, -0.1729-0.4776j],
231
+ [ 0.0476+0.3640j, 0.4540-0.1432j, -0.1454-0.3261j, 0.1149-0.4497j,
232
+ 0.1691-0.1465j, 0.4496-0.2084j],
233
+ [ 0.0826+0.2936j, -0.0846-0.3585j, 0.3592-0.1015j, -0.5379-0.0018j,
234
+ 0.1665+0.3441j, 0.1465+0.4183j],
235
+ [-0.1678-0.1855j, -0.3810+0.2695j, 0.1729-0.4776j, -0.2501-0.3772j,
236
+ -0.4710-0.0421j, 0.1512-0.1012j],
237
+ [-0.1149-0.4497j, -0.1691-0.1465j, -0.4496-0.2084j, 0.0476-0.3640j,
238
+ 0.4540+0.1432j, -0.1454+0.3261j]],
239
+ <BLANKLINE>
240
+ [[-0.0798-0.5452j, -0.1923+0.2617j, 0.0815+0.2038j, -0.0359-0.4116j,
241
+ 0.2102-0.3235j, 0.4459+0.1562j],
242
+ [-0.3699+0.0262j, -0.2731-0.5066j, -0.1090-0.0730j, -0.2111-0.4440j,
243
+ -0.1848-0.1637j, -0.3600+0.2859j],
244
+ [ 0.1009+0.1924j, 0.1127-0.3529j, 0.5547+0.1064j, 0.2102+0.2346j,
245
+ -0.2066-0.4166j, 0.2735+0.3259j],
246
+ [ 0.0359-0.4116j, -0.2102-0.3235j, -0.4459+0.1562j, -0.0798+0.5452j,
247
+ -0.1923-0.2617j, 0.0815-0.2038j],
248
+ [ 0.2111-0.4440j, 0.1848-0.1637j, 0.3600+0.2859j, -0.3699-0.0262j,
249
+ -0.2731+0.5066j, -0.1090+0.0730j],
250
+ [-0.2102+0.2346j, 0.2066-0.4166j, -0.2735+0.3259j, 0.1009-0.1924j,
251
+ 0.1127+0.3529j, 0.5547-0.1064j]]])
252
+
253
+ >>> q = rand_Spn_SVD(2,3,3,seed=7,qp_unif_gen=qp.Net)
254
+ >>> q.shape
255
+ torch.Size([2, 6, 6])
256
+ >>> q
257
+ tensor([[[ 0.0024+0.0842j, 0.6149-0.0634j, -0.3828-0.0823j, -0.2519-0.3091j,
258
+ 0.3628+0.2602j, -0.1177+0.2920j],
259
+ [ 0.3461-0.6176j, 0.0761+0.1610j, -0.5182+0.0585j, 0.2130+0.0398j,
260
+ -0.1930-0.0418j, 0.3297+0.0230j],
261
+ [ 0.0650-0.1395j, 0.2626-0.2246j, 0.3886-0.2880j, 0.4926+0.1394j,
262
+ 0.4424+0.1808j, 0.3377-0.1361j],
263
+ [ 0.2519-0.3091j, -0.3628+0.2602j, 0.1177+0.2920j, 0.0024-0.0842j,
264
+ 0.6149+0.0634j, -0.3828+0.0823j],
265
+ [-0.2130+0.0398j, 0.1930-0.0418j, -0.3297+0.0230j, 0.3461+0.6176j,
266
+ 0.0761-0.1610j, -0.5182-0.0585j],
267
+ [-0.4926+0.1394j, -0.4424+0.1808j, -0.3377-0.1361j, 0.0650+0.1395j,
268
+ 0.2626+0.2246j, 0.3886+0.2880j]],
269
+ <BLANKLINE>
270
+ [[ 0.2988+0.4254j, -0.3412+0.0841j, 0.2553+0.2561j, 0.0461+0.1016j,
271
+ -0.1127-0.1724j, 0.5693-0.3106j],
272
+ [-0.1268+0.1878j, 0.2846+0.1790j, 0.2376+0.0494j, -0.3145-0.5206j,
273
+ 0.0981-0.6136j, -0.1342-0.0512j],
274
+ [ 0.4454-0.0571j, -0.0687+0.2563j, -0.5844+0.0523j, -0.0528+0.3026j,
275
+ 0.2235-0.4632j, -0.1256+0.0951j],
276
+ [-0.0461+0.1016j, 0.1127-0.1724j, -0.5693-0.3106j, 0.2988-0.4254j,
277
+ -0.3412-0.0841j, 0.2553-0.2561j],
278
+ [ 0.3145-0.5206j, -0.0981-0.6136j, 0.1342-0.0512j, -0.1268-0.1878j,
279
+ 0.2846-0.1790j, 0.2376-0.0494j],
280
+ [ 0.0528+0.3026j, -0.2235-0.4632j, 0.1256+0.0951j, 0.4454+0.0571j,
281
+ -0.0687-0.2563j, -0.5844-0.0523j]]])
282
+ """
283
+ if qp_unif_gen is None: qp_unif_gen = qp.IIDStdUniform
284
+ u = torch.from_numpy(qp_unif_gen(dimension=4*n**2,seed=seed)(N)).to(device)
285
+ q = tf_Spn_SVD(u,n,k)
286
+ return q