Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/bartrs/compile_pymc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
join_nonshared_inputs,
make_shared_replacements,
)
from numba import carray, cfunc, extending, float64, types, njit
from numba import carray, cfunc, extending, float64, types, njit, int64
from numba.core import cgutils


Expand Down
21 changes: 19 additions & 2 deletions python/bartrs/pgbart.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ def __init__( # noqa: PLR0915

self.shape = 1 if len(shape) == 1 else shape[0]

self.bart.n_outputs = self.shape
type(self.bart).n_outputs = self.shape

self._non_tune_steps = 0
self._synced_all_trees = False

# Updated later in self.setup() by pymc.sample(...)
self.n_draws = 0
Expand Down Expand Up @@ -249,7 +252,7 @@ def __setstate__(self, state):
def astep(self, _):
t0 = perf_counter()
self.compiled_pymc_model.update_shared_arrays()
sum_trees, variable_inclusion, new_batch, new_baseline = self.pg_bart.step(self.tune)
sum_trees, variable_inclusion = self.pg_bart.step(self.tune)
t1 = perf_counter()

stats = {
Expand All @@ -260,6 +263,19 @@ def astep(self, _):
"time": t1 - t0,
}

if not self.tune:
self._non_tune_steps += 1

if (
not self._synced_all_trees
and self.n_draws > 0
and self._non_tune_steps == self.n_draws
):
batches = self.pg_bart.all_batches
baseline = self.pg_bart.baseline_forest
self.bart.all_trees.append((baseline, batches))
self._synced_all_trees = True

return sum_trees, [stats]


Expand All @@ -272,6 +288,7 @@ def competence(var, has_grad):
return Competence.INCOMPATIBLE

def setup(self, tune: int, draws: int) -> None:
self.n_draws = draws
self.pg_bart.reserve_draws(draws)

def calculate_max_tree_depth(alpha: float, beta: float, probs_leaf: float) -> int:
Expand Down
71 changes: 36 additions & 35 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,7 @@ impl PySampler {
&mut self,
py: Python<'py>,
tune: Option<bool>,
) -> PyResult<(
Bound<'py, PyArray2<f64>>,
Vec<u32>,
Option<(usize, Vec<TreeArrays>)>,
Option<Vec<TreeArrays>>,
)> {
) -> PyResult<(Bound<'py, PyArray2<f64>>, Vec<u32>)> {

let mut state = self
.state
Expand All @@ -258,20 +253,14 @@ impl PySampler {
}
let was_tune = state.tune;

// `new_baseline` mirrors what gets stored in `self.baseline_forest`, but only on the
// step where it's first computed, so callers can stream it back to Python exactly once.
let mut new_baseline: Option<Vec<TreeArrays>> = None;
if !was_tune && self.baseline_forest.is_none() {
let mut forest = state.forest.clone();
for t in &mut forest { t.leaf_indices = Vec::new(); }
self.baseline_forest = Some(forest.clone());
new_baseline = Some(forest);
self.baseline_forest = Some(forest);
}


let (new_state, info) = self.kernel.step(&mut self.rng, state);

let mut new_batch: Option<(usize, Vec<TreeArrays>)> = None;
if !was_tune {
let mut trees = Vec::with_capacity(info.batch_size);
for k in 0..info.batch_size {
Expand All @@ -280,22 +269,38 @@ impl PySampler {
t.leaf_indices = Vec::new();
trees.push(t);
}
self.all_trees.push(TreeBatch { start: info.batch_start, trees: trees.clone() });
new_batch = Some((info.batch_start, trees));
self.all_trees.push(TreeBatch { start: info.batch_start, trees });
}


// Return predictions as a 2-D array with shape (n_outputs, n_samples)
let result = numpy::PyArray2::from_owned_array(py, new_state.predictions.clone());

self.state = Some(new_state);

let variable_inclusion = self.state.as_ref().unwrap().get_variable_inclusion();

Ok((result, variable_inclusion, new_batch, new_baseline))
Ok((result, variable_inclusion))
}

#[getter]
fn n_draws(&self) -> usize {
self.all_trees.len()
}

#[getter]
fn all_batches(&self) -> Vec<(usize, Vec<TreeArrays>)> {
self.all_trees
.iter()
.map(|b| (b.start, b.trees.clone()))
.collect()
}

#[getter]
fn baseline_forest(&self) -> Option<Vec<TreeArrays>> {
self.baseline_forest.clone()
}

fn sample_posterior<'py>(&mut self, py: Python<'py>, x: PyReadonlyArray2<'py, f64>, samples: usize, excluded: Option<&Bound<'py, PyList>>) -> PyResult<Py<PyArray3<f64>>>{
fn sample_posterior<'py>(&mut self, py: Python<'py>, x: PyReadonlyArray2<'py, f64>, draw_indices: Vec<usize>, excluded: Option<&Bound<'py, PyList>>) -> PyResult<Py<PyArray3<f64>>>{

let data = x.as_array().to_owned();

Expand All @@ -306,22 +311,21 @@ impl PySampler {
None => Vec::new(),
};

let preds = self._sample_posterior(&data, samples, &excl).to_pyarray(py).unbind();
let preds = self._sample_posterior(&data, &draw_indices, &excl).to_pyarray(py).unbind();
return Ok(preds)
}
}

impl PySampler {
fn _sample_posterior(&mut self, x: &Array<f64, Ix2>, samples: usize, excluded: &Vec<usize>) -> Array<f64, Ix3> {
fn _sample_posterior(&mut self, x: &Array<f64, Ix2>, draw_indices: &[usize], excluded: &Vec<usize>) -> Array<f64, Ix3> {
predict_from_history(
&self.all_trees,
self.baseline_forest.as_deref(),
self.n_trees,
self.n_outputs,
x,
samples,
draw_indices,
excluded,
&mut self.rng,
)
}

Expand All @@ -334,23 +338,19 @@ fn predict_from_history(
n_trees: usize,
n_outputs: usize,
x: &Array<f64, Ix2>,
samples: usize,
draw_indices: &[usize],
excluded: &Vec<usize>,
rng: &mut SmallRng,
) -> Array<f64, Ix3> {
let n_data_samples: usize = x.nrows();
let n_draws = batches.len();
let samples = draw_indices.len();

assert!(n_draws > 0, "No posterior draws stored yet");

let random_samples: Vec<usize> = (0..samples)
.map(|_| rng.random_range(0..n_draws as u32) as usize)
.collect();

let mut predictions = Array3::zeros((samples, n_outputs, n_data_samples));

for posterior_sample_idx in 0..samples {
let draw_idx = random_samples[posterior_sample_idx];
let draw_idx = draw_indices[posterior_sample_idx];
let mut sample = predictions.slice_mut(s![posterior_sample_idx, .., ..]);

let mut covered = vec![false; n_trees];
Expand Down Expand Up @@ -391,7 +391,6 @@ struct PosteriorSampler {
baseline_forest: Option<Vec<TreeArrays>>,
n_trees: usize,
n_outputs: usize,
rng: SmallRng,
}

#[pymethods]
Expand All @@ -402,7 +401,6 @@ impl PosteriorSampler {
baseline_forest: Option<Vec<TreeArrays>>,
n_trees: usize,
n_outputs: usize,
seed: u64,
) -> Self {
let batches = batches
.into_iter()
Expand All @@ -413,7 +411,6 @@ impl PosteriorSampler {
baseline_forest,
n_trees,
n_outputs,
rng: SmallRng::seed_from_u64(seed),
}
}

Expand All @@ -422,7 +419,12 @@ impl PosteriorSampler {
self.n_outputs
}

fn sample_posterior<'py>(&mut self, py: Python<'py>, x: PyReadonlyArray2<'py, f64>, samples: usize, excluded: Option<&Bound<'py, PyList>>) -> PyResult<Py<PyArray3<f64>>> {
#[getter]
fn n_draws(&self) -> usize {
self.batches.len()
}

fn sample_posterior<'py>(&mut self, py: Python<'py>, x: PyReadonlyArray2<'py, f64>, draw_indices: Vec<usize>, excluded: Option<&Bound<'py, PyList>>) -> PyResult<Py<PyArray3<f64>>> {
let data = x.as_array().to_owned();

let excl = match excluded {
Expand All @@ -438,9 +440,8 @@ impl PosteriorSampler {
self.n_trees,
self.n_outputs,
&data,
samples,
&draw_indices,
&excl,
&mut self.rng,
).to_pyarray(py).unbind();

Ok(preds)
Expand Down