Skip to content

Commit ef1f2e4

Browse files
authored
Merge pull request #25 from zoj613/clean
2 parents 3811b77 + 41d8369 commit ef1f2e4

31 files changed

+195
-140
lines changed

DESCRIPTION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: htnorm
2-
Version: 0.2.0
2+
Version: 1.0.0
33
Title: Fast Simulation of Hyperplane-Truncated Multivariate Normal Distributions
44
Author: Zolisa Bleki
55
Maintainer: Zolisa Bleki <zolisa.bleki@gmail.com>

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ LIBS_DIR ?= /usr/lib
1616
override LIBS_DIR := -L$(LIBS_DIR)
1717
LIBS := -lm -lblas -llapack
1818

19-
SRCFILES = src/dist.c src/htnorm.c src/rng.c
19+
SRCFILES = src/htnorm_distributions.c src/htnorm.c src/htnorm_rng.c
2020

21-
OBJ = src/dist.o src/htnorm.o src/rng.o
21+
OBJ = src/htnorm_distributions.o src/htnorm.o src/htnorm_rng.o
2222

2323

2424
%.o: %.c

R/htnorm.r

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copyright (c) 2020, Zolisa Bleki
22
# SPDX-License-Identifier: BSD-3-Clause
33

4-
matrix_type <- c(0, 1, 2)
4+
matrix_type <- c("regular", "diagonal", "identity")
55

66

77
validate_output <- function(res) {
@@ -60,10 +60,20 @@ strprec_mvn <- function(rng, mean, a, phi, omega, str_mean, a_id, o_id, out) {
6060
)
6161

6262
if (!is.element(a_id, matrix_type) || !is.element(o_id, matrix_type))
63-
stop("`a_type` and `o_type` need to be one of {0, 1, 2}")
63+
stop("`a_type` and `o_type` need to be one of {'regular', 'diagonal', 'identity'}")
6464

65-
a_id <- as.integer(a_id)
66-
o_id <- as.integer(o_id)
65+
a_id <- switch(
66+
a_id,
67+
"regular" = as.integer(0),
68+
"diagonal" = as.integer(1),
69+
"identity" = as.integer(2)
70+
)
71+
o_id <- switch(
72+
o_id,
73+
"regular" = as.integer(0),
74+
"diagonal" = as.integer(1),
75+
"identity" = as.integer(2)
76+
)
6777

6878
if (is.null(out))
6979
out <- rep(0, length(mean))
@@ -118,7 +128,7 @@ strprec_mvn <- function(rng, mean, a, phi, omega, str_mean, a_id, o_id, out) {
118128
#' phi <- eig$vectors
119129
#' omega <- diag(eig$values)
120130
#' a <- diag(runif(length(mean)))
121-
#' rng$structured_precision_mvnorm(mean, a, phi, omega, a_type = 1, out = out)
131+
#' rng$structured_precision_mvnorm(mean, a, phi, omega, a_type = "diagonal", out = out)
122132
HTNGenerator <- function(seed = NULL, gen = "xrs128p") {
123133

124134
if (is.numeric(seed)) {
@@ -151,7 +161,7 @@ HTNGenerator <- function(seed = NULL, gen = "xrs128p") {
151161
}
152162

153163
res$structured_precision_mvnorm <- function(
154-
mean, a, phi, omega, str_mean = FALSE, a_type = 0, o_type = 0, out= NULL
164+
mean, a, phi, omega, str_mean = FALSE, a_type = "regular", o_type = "regular", out= NULL
155165
) {
156166
if (is.null(out)) {
157167
strprec_mvn(

README.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ interface is such that the code can be easily integrated into other existing lib
109109
Since `v1.0.0`, it supports passing a `numpy.random.Generator` instance as a parameter to aid reproducibility.
110110

111111
```python
112-
from pyhtnorm import hyperplane_truncated_mvnorm
112+
from pyhtnorm import hyperplane_truncated_mvnorm, structured_precision_mvnorm
113113
import numpy as np
114114

115115
rng = np.random.default_rng()
@@ -166,6 +166,13 @@ sum(samples)
166166
out <- rep(0, 1000)
167167
rng$hyperplane_truncated_mvnorm(mean, cov, G, r, out = out)
168168
sum(out) #verify
169+
170+
out <- rep(0, 1000)
171+
eig <- eigen(cov)
172+
phi <- eig$vectors
173+
omega <- diag(eig$values)
174+
a <- diag(runif(length(mean)))
175+
rng$structured_precision_mvnorm(mean, a, phi, omega, a_type = "diagonal", out = out)
169176
```
170177

171178
## Licensing
@@ -187,7 +194,7 @@ see the [LICENSE][6] file.
187194
[2]: https://www.pcg-random.org/
188195
[3]: https://en.wikipedia.org/wiki/Xoroshiro128%2B
189196
[4]: ./include/htnorm.h
190-
[5]: ./include/rng.h
197+
[5]: ./include/htnorm_rng.h
191198
[6]: ./LICENSE
192199
[7]: https://python-poetry.org/docs/pyproject/
193200
[8]: https://www.sciencedirect.com/science/article/abs/pii/S2211675317301574

build-wheels.sh

+16-13
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,38 @@ set -e -u -x
77
cd $(dirname $0)
88

99
bin_arr=(
10-
/opt/python/cp36-cp36m/bin
11-
/opt/python/cp37-cp37m/bin
10+
#/opt/python/cp37-cp37m/bin
1211
/opt/python/cp38-cp38/bin
13-
/opt/python/cp39-cp39/bin
12+
#/opt/python/cp39-cp39/bin
1413
)
1514

1615
# add python to image's path
17-
export PATH=/opt/python/cp38-cp38/bin/:$PATH
16+
export PATH=/opt/python/cp37-cp37m/bin/:$PATH
1817
# download install script
1918
curl -#sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py > get-poetry.py
2019
# install using local archive
21-
python get-poetry.py -y --file poetry-1.1.4-linux.tar.gz
20+
python get-poetry.py -y --file poetry-1.1.5-linux.tar.gz
2221
# install openblas
2322
yum install -y openblas-devel
2423

2524
function build_poetry_wheels
2625
{
27-
# build wheels for 3.6-3.9 with poetry
26+
# build wheels for 3.7-3.9 with poetry
2827
for BIN in "${bin_arr[@]}"; do
2928
rm -Rf build/*
3029
# install build deps
31-
"${BIN}/python" ${HOME}/.poetry/bin/poetry run pip install numpy
30+
"${BIN}/python" ${HOME}/.poetry/bin/poetry run pip install numpy==1.18.1
3231
BUILD_WHEELS=1 "${BIN}/python" ${HOME}/.poetry/bin/poetry build -f wheel
33-
auditwheel repair dist/*.whl --plat $1
34-
whl="$(basename dist/*.whl)"
35-
"${BIN}/python" -m pip install wheelhouse/"$whl"
36-
# test if installed wheel imports correctly
37-
"${BIN}/python" -c "from pyhtnorm import *"
38-
rm dist/*.whl
32+
mkdir -p ./tmp
33+
for whl in dist/*.whl; do
34+
auditwheel repair "$whl" --plat $1 -w "./tmp"
35+
whlname="$(basename "$(echo ./tmp/*.whl)")"
36+
"${BIN}/python" -m pip install ./tmp/"$whlname"
37+
# test if installed wheel imports correctly
38+
"${BIN}/python" -c "from pyhtnorm import *"
39+
mv ./tmp/*.whl wheelhouse/
40+
rm "$whl"
41+
done
3942
done
4043
}
4144

build.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
source_files = [
88
"pyhtnorm/_htnorm.c",
9-
"src/rng.c",
10-
"src/dist.c",
9+
"src/htnorm_rng.c",
10+
"src/htnorm_distributions.c",
1111
"src/htnorm.c"
1212
]
1313

@@ -32,7 +32,7 @@
3232
Extension(
3333
"_htnorm",
3434
source_files,
35-
include_dirs=[np.get_include(), './include'],
35+
include_dirs=[np.get_include(), './include', 'src'],
3636
library_dirs=library_dirs,
3737
libraries=libraries,
3838
define_macros=[('NPY_NO_DEPRECATED_API', 0)],

include/htnorm.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#include <stdbool.h>
2929
#include <stddef.h>
3030

31-
#include "rng.h"
31+
#include "htnorm_rng.h"
3232

3333

3434
typedef enum {NORMAL, DIAGONAL, IDENTITY} type_t;

include/rng.h include/htnorm_rng.h

File renamed without changes.

man/HTNGenerator.Rd

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyhtnorm/__init__.pxd

-1
This file was deleted.

pyhtnorm/_htnorm.pyx

+66-19
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,68 @@ References
3434
3535
"""
3636
from cpython.pycapsule cimport PyCapsule_GetPointer
37+
from libc.stdint cimport uint64_t
3738

38-
import numpy as np
3939
cimport numpy as np
4040
from numpy.random cimport BitGenerator, bitgen_t
4141

42-
from pyhtnorm.c_htnorm cimport *
42+
from numpy.random import default_rng
4343

4444

4545
np.import_array()
4646

47-
cdef extern from "../src/dist.h":
47+
cdef extern from "htnorm_distributions.h":
4848
int HTNORM_ALLOC_ERROR
4949

5050

51-
cdef inline void validate_return_info(info):
51+
cdef extern from "htnorm_rng.h" nogil:
52+
ctypedef struct rng_t:
53+
void* base
54+
uint64_t (*next_uint64)(void* state)
55+
double (*next_double)(void* state)
56+
57+
58+
cdef extern from "htnorm.h" nogil:
59+
ctypedef enum mat_type "type_t":
60+
NORMAL
61+
DIAGONAL
62+
IDENTITY
63+
64+
ctypedef struct ht_config_t:
65+
size_t gnrow
66+
size_t gncol
67+
const double* mean
68+
const double* cov
69+
const double* g
70+
const double* r
71+
bint diag
72+
73+
ctypedef struct sp_config_t:
74+
mat_type a_id
75+
mat_type o_id
76+
size_t pnrow
77+
size_t pncol
78+
const double* mean
79+
const double* a
80+
const double* phi
81+
const double* omega
82+
bint struct_mean
83+
84+
void init_ht_config(ht_config_t* conf, size_t gnrow, size_t gncol,
85+
const double* mean, const double* cov, const double* g,
86+
const double* r, bint diag)
87+
88+
void init_sp_config(sp_config_t* conf, size_t pnrow, size_t pncol,
89+
const double* mean, const double* a, const double* phi,
90+
const double* omega, bint struct_mean, mat_type a_id,
91+
mat_type o_id)
92+
93+
int htn_hyperplane_truncated_mvn(rng_t* rng, const ht_config_t* conf, double* out)
94+
95+
int htn_structured_precision_mvn(rng_t* rng, const sp_config_t* conf, double* out)
96+
97+
98+
cdef inline void validate_return_info(int info):
5299
if info == HTNORM_ALLOC_ERROR:
53100
raise MemoryError("Not enough memory to allocate resources.")
54101
elif info < 0:
@@ -62,13 +109,14 @@ cdef inline void validate_return_info(info):
62109
)
63110

64111

65-
cdef set _VALID_MATRIX_TYPES = {NORMAL, DIAGONAL, IDENTITY}
112+
cdef dict MAT_TYPE = {"regular": NORMAL, "diagonal": DIAGONAL, "identity": IDENTITY}
113+
cdef const char* BITGEN_NAME = "BitGenerator"
66114

67115

68-
cdef inline void initialize_rng(BitGenerator bitgenerator, rng_t* htnorm_rng):
116+
cdef inline void initialize_rng(object bitgenerator, rng_t* htnorm_rng):
69117
cdef bitgen_t* bitgen
70118

71-
bitgen = <bitgen_t*>PyCapsule_GetPointer(bitgenerator.capsule, "BitGenerator")
119+
bitgen = <bitgen_t*>PyCapsule_GetPointer(bitgenerator.capsule, BITGEN_NAME)
72120
htnorm_rng.base = bitgen.state
73121
htnorm_rng.next_uint64 = bitgen.next_uint64
74122
htnorm_rng.next_double = bitgen.next_double
@@ -135,7 +183,6 @@ def hyperplane_truncated_mvnorm(
135183
the algorithm could not successfully generate the samples.
136184
137185
"""
138-
cdef BitGenerator bitgenerator
139186
cdef rng_t rng
140187
cdef ht_config_t config
141188
cdef int info
@@ -152,7 +199,7 @@ def hyperplane_truncated_mvnorm(
152199
init_ht_config(&config, g.shape[0], g.shape[1], &mean[0],
153200
&cov[0, 0], &g[0, 0], &r[0], diag)
154201

155-
bitgenerator = np.random.default_rng(random_state)._bit_generator
202+
bitgenerator = default_rng(random_state)._bit_generator
156203
initialize_rng(bitgenerator, &rng)
157204

158205
with bitgenerator.lock, nogil:
@@ -169,14 +216,15 @@ def structured_precision_mvnorm(
169216
double[:,::1] phi,
170217
double[:,::1] omega,
171218
bint mean_structured=False,
172-
int a_type=0,
173-
int o_type=0,
219+
str a_type="regular",
220+
str o_type="regular",
174221
double[:] out=None,
175222
random_state=None
176223
):
177224
"""
178225
structured_precision_mvnorm(mean, a, phi, omega, mean_structured=False,
179-
a_type=0, o_type=0, out=None, random_state=None)
226+
a_type="regular", o_type="regular",
227+
out=None, random_state=None)
180228
181229
Sample from a MVN with a structured precision matrix :math:`\Lambda`
182230
.. math::
@@ -197,9 +245,9 @@ def structured_precision_mvnorm(
197245
such than ``mean = (precision)^-1 * phi^T * omega * t``. If this
198246
is set to True, then the `mean` parameter is assumed to contain the
199247
array ``t``.
200-
a_type : {0, 1, 2}, optional, default=0
248+
a_type : {"regular", "diagonal", "identity"}, optional, default="regular"
201249
Whether `a` ia a normal, diagonal or identity matrix.
202-
o_type : {0, 1, 2}, optional, default=0
250+
o_type : {"regular", "diagonal", "identity"}, optional, default="regular"
203251
Whether `omega` ia a normal, diagonal or identity matrix.
204252
out : 1d array, optional, default=None
205253
An array of the same shape as `mean` to store the samples. If not
@@ -228,7 +276,6 @@ def structured_precision_mvnorm(
228276
the algorithm could not successfully generate the samples.
229277
230278
"""
231-
cdef BitGenerator bitgenerator
232279
cdef rng_t rng
233280
cdef sp_config_t config
234281
cdef int info
@@ -238,8 +285,8 @@ def structured_precision_mvnorm(
238285
raise ValueError('`omega` and `a` both need to be square matrices')
239286
elif (phi.shape[0] != omega.shape[0]) or (phi.shape[1] != a.shape[0]):
240287
raise ValueError('Shapes of `phi`, `omega` and `a` are not consistent')
241-
elif not {a_type, o_type}.issubset(_VALID_MATRIX_TYPES):
242-
raise ValueError(f"`a_type` & `o_type` must be one of {_VALID_MATRIX_TYPES}")
288+
elif not {a_type, o_type}.issubset(MAT_TYPE):
289+
raise ValueError(f"`a_type` & `o_type` must be one of {set(MAT_TYPE)}")
243290
elif has_out and out.shape[0] != mean.shape[0]:
244291
raise ValueError("`out` must have the same size as the mean array.")
245292
elif not has_out:
@@ -248,9 +295,9 @@ def structured_precision_mvnorm(
248295

249296
init_sp_config(&config, phi.shape[0], phi.shape[1], &mean[0], &a[0, 0],
250297
&phi[0, 0], &omega[0, 0], mean_structured,
251-
<mat_type>a_type, <mat_type>o_type)
298+
MAT_TYPE[a_type], MAT_TYPE[o_type])
252299

253-
bitgenerator = np.random.default_rng(random_state)._bit_generator
300+
bitgenerator = default_rng(random_state)._bit_generator
254301
initialize_rng(bitgenerator, &rng)
255302

256303
with bitgenerator.lock, nogil:

0 commit comments

Comments
 (0)