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
19 changes: 12 additions & 7 deletions rustuna_core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ pub trait Storage: Send + Sync {
// references without relying on unsafe patterns.
fn get_studies(&mut self) -> Result<&Vec<PersistedStudy>>;
fn get_study(&mut self, study_id: u32) -> Result<&PersistedStudy>;
fn get_trials(&mut self, study_id: u32) -> Result<&Vec<PersistedTrial>>;
fn get_trial(&mut self, trial_id: u32) -> Result<&PersistedTrial>;
// Design Note:
// `no_sync` avoids refreshing or syncing from the backend and reads from the
// current in-memory cache. Implementations may return TrialNotFound or StudyNotFound
// if the requested data is not cached yet.
fn get_trials(&mut self, study_id: u32, no_sync: bool) -> Result<&Vec<PersistedTrial>>;
fn get_trial(&mut self, trial_id: u32, no_sync: bool) -> Result<&PersistedTrial>;
// get_cached_* methods always return references from the in-memory cache without
// synchronizing with backends. These methods are separate from get_* to allow
// callers to use read locks for cache-only reads.
fn get_cached_trial(&self, trial_id: u32) -> Result<&PersistedTrial>;
// Design Note:
// Category labels are stored in study system attrs internally, but exposed via dedicated
// APIs for caching efficiency. Since category labels cannot be overwritten once set for
Expand Down Expand Up @@ -304,11 +305,15 @@ impl Storage for InMemoryStorage {
Ok(study)
}

fn get_trials(&mut self, study_id: u32, _no_sync: bool) -> Result<&Vec<PersistedTrial>> {
fn get_trials(&mut self, study_id: u32) -> Result<&Vec<PersistedTrial>> {
get_trials_by_study_id(&self.trials, study_id)
}

fn get_trial(&mut self, trial_id: u32, _no_sync: bool) -> Result<&PersistedTrial> {
fn get_trial(&mut self, trial_id: u32) -> Result<&PersistedTrial> {
self.get_cached_trial(trial_id)
}

fn get_cached_trial(&self, trial_id: u32) -> Result<&PersistedTrial> {
let (study_id, trial_number) =
get_study_id_trial_number_by_trial_id(&self.trial_id_to_study_number, trial_id)?;
let trial = get_trials_by_study_id(&self.trials, study_id)?
Expand Down
6 changes: 3 additions & 3 deletions rustuna_core/src/study.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl Study {
.storage
.write()
.map_err(|_| Error::new(ErrorKind::StorageError))?;
let trials = guard.get_trials(self.id, false)?;
let trials = guard.get_trials(self.id)?;
Ok(trials.clone())
}

Expand Down Expand Up @@ -278,7 +278,7 @@ pub fn get_best_trial(study: &Study) -> Result<u32> {
.storage
.write()
.map_err(|_| Error::new(ErrorKind::StorageError))?;
let trials = guard.get_trials(study.id, false)?;
let trials = guard.get_trials(study.id)?;

let best_trial = trials
.iter()
Expand Down Expand Up @@ -313,7 +313,7 @@ pub fn get_pareto_front(study: &Study) -> Result<Vec<u32>> {
.write()
.map_err(|_| Error::new(ErrorKind::StorageError))?;
let trials = guard
.get_trials(study.id, false)?
.get_trials(study.id)?
.iter()
.filter(|t| matches!(t.state_values, TrialStateValues::Complete(ref _v)))
.collect::<Vec<_>>();
Expand Down
2 changes: 1 addition & 1 deletion rustuna_core/src/trial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ mod tests {
guard.set_trial_attrs(trial.id, attrs, false)?;

// Check the attributes
let trial = guard.get_trial(trial.id, false)?;
let trial = guard.get_trial(trial.id)?;
assert_eq!(
trial.attrs.get(&AttrKey::User("key".into())),
Some(&"user".to_string())
Expand Down
2 changes: 1 addition & 1 deletion rustuna_importance/src/fanova.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub fn get_param_importance(study: &Study) -> Result<Vec<Vec<f64>>> {
.map_err(|_e| Error::new(ErrorKind::Unexpected))?;
// TODO(c-bata): Avoid to clone trials.
let completed_trials: Vec<PersistedTrial> = guard
.get_trials(study.id, false)?
.get_trials(study.id)?
.clone()
.into_iter()
.filter(|t| matches!(t.state_values, TrialStateValues::Complete(_)))
Expand Down
2 changes: 1 addition & 1 deletion rustuna_js/src/study.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl JsStudy {
JsError::new(&format!("Failed to acquire the storage guard: {e:?}"))
})?;
let trials = guard
.get_trials(self.0.id, false)
.get_trials(self.0.id)
.map_err(|e| JsError::new(&format!("Failed to get trials: {:?}", e.kind)))?
.clone();
let study_attrs = guard
Expand Down
20 changes: 14 additions & 6 deletions rustuna_pyo3/rustuna/_rustuna.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,9 @@ class StorageProtocol(Protocol):
) -> None: ...
def get_studies(self) -> list[PersistedStudy]: ...
def get_study(self, study_id: int) -> PersistedStudy: ...
def get_trials(
self, study_id: int, *, no_sync: bool = ...
) -> list[PersistedTrial]: ...
def get_trial(self, trial_id: int, *, no_sync: bool = ...) -> PersistedTrial: ...
def get_trials(self, study_id: int) -> list[PersistedTrial]: ...
def get_trial(self, trial_id: int) -> PersistedTrial: ...
def get_cached_trial(self, trial_id: int) -> PersistedTrial: ...
def get_trial_id_from_study_id_trial_number(
self, study_id: int, trial_number: int
) -> int: ...
Expand Down Expand Up @@ -544,7 +543,7 @@ class Storage:
Returns:
The study.
"""
def get_trials(self, study_id: int, *, no_sync: bool = ...) -> list[PersistedTrial]:
def get_trials(self, study_id: int) -> list[PersistedTrial]:
"""Get all trials in a study.

Args:
Expand All @@ -562,9 +561,18 @@ class Storage:
Returns:
Number of trials.
"""
def get_trial(self, trial_id: int, *, no_sync: bool = ...) -> PersistedTrial:
def get_trial(self, trial_id: int) -> PersistedTrial:
"""Get a trial by ID.

Args:
trial_id: ID of the trial.

Returns:
The trial.
"""
def get_cached_trial(self, trial_id: int) -> PersistedTrial:
"""Get a cached trial by ID without synchronizing with backends.

Args:
trial_id: ID of the trial.

Expand Down
11 changes: 5 additions & 6 deletions rustuna_pyo3/rustuna/converter/_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ def get_study(self, study_id: int) -> rustuna.PersistedStudy:
return to_persisted_study(s)
raise KeyError(f"Study {study_id} not found")

def get_trials(
self, study_id: int, *, no_sync: bool = False
) -> list[rustuna.PersistedTrial]:
def get_trials(self, study_id: int) -> list[rustuna.PersistedTrial]:
frozen_trials = self._storage.get_all_trials(study_id)

persisted_trials: list[rustuna.PersistedTrial] = []
Expand All @@ -113,9 +111,7 @@ def get_trials(
self._trial_id_to_study_id[t._trial_id] = study_id
return persisted_trials

def get_trial(
self, trial_id: int, *, no_sync: bool = False
) -> rustuna.PersistedTrial:
def get_trial(self, trial_id: int) -> rustuna.PersistedTrial:
with self._lock:
study_id = self._trial_id_to_study_id.get(trial_id, -1)
if study_id == -1:
Expand All @@ -126,6 +122,9 @@ def get_trial(
frozen_trial = self._storage.get_trial(trial_id)
return to_persisted_trial(frozen_trial, study_id=study_id)

def get_cached_trial(self, trial_id: int) -> rustuna.PersistedTrial:
return self.get_trial(trial_id)

def delete_study(self, study_id: int) -> None:
self._storage.delete_study(study_id)

Expand Down
29 changes: 17 additions & 12 deletions rustuna_pyo3/src/pyobject_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ impl PyObjectStorage {
) -> rustuna_core::Result<()> {
let cache_n_trials = match cache_n_trials {
Some(n) => n,
None => self.cache.get_trials(cache_study_id, false)?.len() as u32,
None => self.cache.get_trials(cache_study_id)?.len() as u32,
};
match src_trial.number.cmp(&cache_n_trials) {
std::cmp::Ordering::Equal => {
Expand All @@ -273,7 +273,7 @@ impl PyObjectStorage {
}
}

let cache_trial = self.cache.get_trial(src_trial.id, false)?.clone();
let cache_trial = self.cache.get_trial(src_trial.id)?.clone();
if cache_trial.is_finished() {
return Ok(());
}
Expand Down Expand Up @@ -315,10 +315,10 @@ impl PyObjectStorage {
.map_err(|_| rustuna_core::Error::new(rustuna_core::ErrorKind::StorageError))?;
src_trials.sort_by_key(|trial| trial.number);

let mut cache_n_trials = self.cache.get_trials(cache_study_id, false)?.len() as u32;
let mut cache_n_trials = self.cache.get_trials(cache_study_id)?.len() as u32;
for src_trial in src_trials {
self.sync_trial(cache_study_id, src_trial, Some(cache_n_trials))?;
cache_n_trials = self.cache.get_trials(cache_study_id, false)?.len() as u32;
cache_n_trials = self.cache.get_trials(cache_study_id)?.len() as u32;
}
Ok(())
}
Expand Down Expand Up @@ -385,15 +385,15 @@ impl Storage for PyObjectStorage {
let src_trial_id = src_trial.id;
if self.is_distributed {
self.sync_trials(study_id)?;
return self.cache.get_trial(src_trial_id, false);
return self.cache.get_trial(src_trial_id);
}
let cached_n_trials = self.cache.get_trials(study_id, false)?.len() as u32;
let cached_n_trials = self.cache.get_trials(study_id)?.len() as u32;
if src_trial.number != cached_n_trials {
self.sync_trials(study_id)?;
return self.cache.get_trial(src_trial_id, false);
return self.cache.get_trial(src_trial_id);
}
self.sync_trial(study_id, src_trial, Some(cached_n_trials))?;
self.cache.get_trial(src_trial_id, false)
self.cache.get_trial(src_trial_id)
}

fn set_trial_param(
Expand Down Expand Up @@ -459,17 +459,22 @@ impl Storage for PyObjectStorage {
fn get_trials(
&mut self,
study_id: u32,
_no_sync: bool,
) -> rustuna_core::Result<&Vec<rustuna_core::trial::PersistedTrial>> {
self.cache.get_trials(study_id, false)
self.cache.get_trials(study_id)
}

fn get_trial(
&mut self,
trial_id: u32,
_no_sync: bool,
) -> rustuna_core::Result<&rustuna_core::trial::PersistedTrial> {
self.cache.get_trial(trial_id, false)
self.cache.get_trial(trial_id)
}

fn get_cached_trial(
&self,
trial_id: u32,
) -> rustuna_core::Result<&rustuna_core::trial::PersistedTrial> {
self.cache.get_cached_trial(trial_id)
}

fn get_category_labels(
Expand Down
29 changes: 16 additions & 13 deletions rustuna_pyo3/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl PyStorage {
.write()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
guard
.get_trial(trial_id, false)
.get_trial(trial_id)
.map_err(err_to_exceptions)?
.study_id
};
Expand Down Expand Up @@ -249,34 +249,26 @@ impl PyStorage {
Ok(study.clone().into())
}

#[pyo3(signature = (study_id, *, no_sync = false))]
fn get_trials(&mut self, study_id: u32, no_sync: bool) -> PyResult<Vec<PyPersistedTrial>> {
fn get_trials(&mut self, study_id: u32) -> PyResult<Vec<PyPersistedTrial>> {
let mut guard = self
.storage
.write()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
let trials = guard
.get_trials(study_id, no_sync)
.map_err(err_to_exceptions)?;
let trials = guard.get_trials(study_id).map_err(err_to_exceptions)?;
let py_trials: Vec<PyPersistedTrial> = trials
.iter()
.map(|t| PyPersistedTrial::from_storage(self.storage.clone(), t))
.collect();
Ok(py_trials)
}

#[pyo3(signature = (trial_id, *, no_sync = false))]
fn get_trial(&mut self, trial_id: u32, no_sync: bool) -> PyResult<PyPersistedTrial> {
fn get_trial(&mut self, trial_id: u32) -> PyResult<PyPersistedTrial> {
let mut guard = self
.storage
.write()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
if no_sync {
let trial = guard.get_trial(trial_id, true).map_err(err_to_exceptions)?;
return Ok(PyPersistedTrial::from_storage(self.storage.clone(), trial));
}
let trial = guard
.get_trial(trial_id, false)
.get_trial(trial_id)
.map_err(err_to_exceptions)?
.clone();
let study_attrs = guard
Expand All @@ -287,6 +279,17 @@ impl PyStorage {
Ok(PyPersistedTrial::new(trial, study_attrs))
}

fn get_cached_trial(&self, trial_id: u32) -> PyResult<PyPersistedTrial> {
let guard = self
.storage
.read()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
let trial = guard
.get_cached_trial(trial_id)
.map_err(err_to_exceptions)?;
Ok(PyPersistedTrial::from_storage(self.storage.clone(), trial))
}

fn get_trial_id_from_study_id_trial_number(
&mut self,
study_id: u32,
Expand Down
6 changes: 3 additions & 3 deletions rustuna_pyo3/src/study.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ impl PyStudy {
PyRuntimeError::new_err(format!("Failed to get trial id: {:?}", e.kind))
})?;
let trial = guard
.get_trial(trial_id, false)
.get_trial(trial_id)
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get trial: {:?}", e.kind)))?;
Ok(PyPersistedTrial::from_storage(
self.study.storage.clone(),
Expand All @@ -325,7 +325,7 @@ impl PyStudy {
.write()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
let trials_vec = guard
.get_trials(self.study.id, false)
.get_trials(self.study.id)
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get trials: {:?}", e.kind)))?;
let trials: Vec<PyPersistedTrial> = trials_vec
.iter()
Expand Down Expand Up @@ -361,7 +361,7 @@ impl PyStudy {
.write()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
let trials_vec = guard
.get_trials(self.study.id, false)
.get_trials(self.study.id)
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get trials: {:?}", e.kind)))?;
let best_trials = pareto_front_numbers
.iter()
Expand Down
6 changes: 3 additions & 3 deletions rustuna_pyo3/src/trial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,11 @@ impl PyPersistedTrial {
PyPersistedTrialSource::StorageBacked {
storage, trial_id, ..
} => {
let mut guard = storage
.write()
let guard = storage
.read()
.map_err(|_| PyRuntimeError::new_err("Failed to acquire the storage guard"))?;
let trial = guard
.get_trial(*trial_id, true)
.get_cached_trial(*trial_id)
.map_err(err_to_exceptions)?;
f(trial)
}
Expand Down
4 changes: 2 additions & 2 deletions rustuna_samplers/src/nsgaii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ impl NSGAIISampler {
let mut guard = storage
.write()
.map_err(|_e| Error::new(ErrorKind::Unexpected))?;
let trials = guard.get_trials(ctx.study_id, false)?;
let trials = guard.get_trials(ctx.study_id)?;
let population_params = trials
.iter()
.filter(|trial| population_numbers.contains(&trial.number))
Expand Down Expand Up @@ -220,7 +220,7 @@ impl Sampler for NSGAIISampler {
let mut guard = storage
.write()
.map_err(|_e| Error::new(ErrorKind::Unexpected))?;
let trials = guard.get_trials(ctx.study_id, false)?;
let trials = guard.get_trials(ctx.study_id)?;
let (parent_generation, parent_population_numbers) =
self.get_parent_population_numbers(ctx, trials)?;
let child_generation = u32::try_from(parent_generation + 1).unwrap();
Expand Down
6 changes: 3 additions & 3 deletions rustuna_samplers/src/tpe/sampler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl TpeSampler {
let mut guard = storage
.write()
.map_err(|_e| Error::new(ErrorKind::Unexpected))?;
let trials = guard.get_trials(ctx.study_id, false)?.clone();
let trials = guard.get_trials(ctx.study_id)?.clone();
drop(guard);

let complete_trials: Vec<_> = trials.into_iter().filter(|t| t.is_finished()).collect();
Expand Down Expand Up @@ -396,7 +396,7 @@ impl Sampler for TpeSampler {
let mut guard = storage
.write()
.map_err(|_e| Error::new(ErrorKind::Unexpected))?;
let trials = guard.get_trials(ctx.study_id, false)?.clone();
let trials = guard.get_trials(ctx.study_id)?.clone();
drop(guard);

let complete_trials: Vec<_> = trials.into_iter().filter(|t| t.is_finished()).collect();
Expand Down Expand Up @@ -425,7 +425,7 @@ impl Sampler for TpeSampler {
let mut guard = storage
.write()
.map_err(|_e| Error::new(ErrorKind::Unexpected))?;
let trials = guard.get_trials(ctx.study_id, false)?.clone();
let trials = guard.get_trials(ctx.study_id)?.clone();
drop(guard);

let complete_trials: Vec<_> = trials.into_iter().filter(|t| t.is_finished()).collect();
Expand Down
Loading