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
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,12 @@ OpinionT LongShortTermMemory<OpinionT>::add(const OpinionT& new_opinion)
if (conflict > threshold_)
{
last_conflicted_pair_ = { get_short_opinion(), get_long_opinion() };
reset_long_memory();
last_conflicted_ = true;
if (handle_st_conflict_)
{
handle_internal_conflict();
}
reset_long_memory();
}

return output;
Expand Down Expand Up @@ -370,7 +370,7 @@ void LongShortTermMemory<OpinionT>::handle_internal_conflict()

auto it = std::max_element(rel_conflicts.begin(), rel_conflicts.end());
std::size_t argmax = std::distance(rel_conflicts.begin(), it);
k_star = argmax < 2 ? 0 : argmax - 2;
k_star = argmax < 1 ? 0 : argmax - 1;
}
else
{
Expand All @@ -389,11 +389,12 @@ void LongShortTermMemory<OpinionT>::handle_internal_conflict()
}
// copy over new short memory
auto it = std::max_element(conflicts.begin(), conflicts.end());
k_star = std::distance(conflicts.begin(), it);
k_star = std::distance(conflicts.begin(), it) + 1;
}

short_term_memory_ = { linear_buffer.begin(), linear_buffer.begin() + k_star };
short_term_memory_.assign(linear_buffer.rend() - k_star, linear_buffer.rend());
current_size_ = short_term_memory_.size();
current_short_ring_idx_ = 0;
}

} // namespace subjective_logic::container
} // namespace subjective_logic::container
6 changes: 2 additions & 4 deletions library/src/bindings/template_combination_helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ template <template <std::size_t, typename> typename loader,
std::size_t number,
std::size_t... numbers,
typename... TYPES,
template <typename...>
typename List>
template <typename...> typename List>
constexpr void loadNumbers(T& nb_mod, const List<TYPES...>& types)
{
loadTypes<loader, T, number, TYPES...>(nb_mod);
Expand Down Expand Up @@ -66,8 +65,7 @@ template <typename T, std::size_t>
using RepeatType = T;

template <typename T, std::size_t... INDICES>
auto make_tuple_with_type_and_length(std::index_sequence<INDICES...>)
-> std::tuple<RepeatType<T, INDICES>...>;
auto make_tuple_with_type_and_length(std::index_sequence<INDICES...>) -> std::tuple<RepeatType<T, INDICES>...>;

template <typename T, std::size_t N>
using TupleWithTypeAnLength = decltype(make_tuple_with_type_and_length<T>(std::make_index_sequence<N>{}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ TYPED_TEST(LSTMemoryTest, ShortTermReset)
{
return;
}
std::size_t expected_ST_size = idx - num_ops;
std::size_t expected_ST_size = idx - num_ops + 1;

EXPECT_EQ(expected_ST_size, mem.get_short_size());
// sanity check reset location
Expand Down
3 changes: 1 addition & 2 deletions publications/2026_mfi/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
tqdm
numba
tqdm
16 changes: 12 additions & 4 deletions publications/2026_mfi/reset_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

Note: For high noise, some runs do not result in a reset.
"""

import numpy as np
import subjective_logic as sl
import tqdm
Expand All @@ -18,9 +19,14 @@ def run_simulation(ops: list[sl.Opinion]) -> None:

for idx, op in enumerate(ops):
# Jump will happen at IDX = 25 (24 still old process, 25 new process)
# We know detection of jump, e.g. idx = 29 -> ST = (25, 26, 27, 28)
# Thus, we expect the ST size to be idx - 25 (jump location) = 4
expected_ST_size = idx - JUMP_LOCATION
# We know detection of jump, e.g. idx = 29 -> ST = (25, 26, 27, 28, 29)
# Thus, we expect the ST size to be idx - 25 + 1 (jump location) = 5
# IMPORTANT: the expected_size changed between review and final submission
# the reported percentages of early / late / correct are not affected because
# the change was applied to both sides of the evaluation.
# However, due to the longer ST window, the number of additional resets increased
# but the previous trends still holds.
expected_ST_size = idx - JUMP_LOCATION + 1

for name, ltst in ltsts.items():
ltst.add(op)
Expand Down Expand Up @@ -82,7 +88,9 @@ def run_simulation(ops: list[sl.Opinion]) -> None:

for name, res in results.items():
if sum(res[:3]) != NUM_RUNS:
print(f"\tSome runs did not result in a reset - can happen for high noise! Found {sum(res[:3])} valid resets.")
print(
f"\tSome runs did not result in a reset - can happen for high noise! Found {sum(res[:3])} valid resets."
)
print(
f"\t{name}: correct: {res[0]} ({res[0] / NUM_RUNS * 100:.2f}%) "
f"early: {res[1]} ({res[1] / NUM_RUNS * 100:.2f}%) "
Expand Down
47 changes: 44 additions & 3 deletions publications/2026_mfi/tef.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@
from util import generate_scenario


class EWMA:

def __init__(self, alpha) -> None:
self.alpha = alpha
self.value = None

def __call__(self, value):
if isinstance(value, float):
if self.value is None:
self.value = value
else:
self.value = self.alpha * value + (1 - self.alpha) * self.value
elif isinstance(value, sl.Opinion2d):
if self.value is None:
self.value = value
else:
belief = self.alpha * value.belief() + (1 - self.alpha) * self.value.belief()
disbelief = self.alpha * value.disbelief() + (1 - self.alpha) * self.value.disbelief()
self.value = sl.Opinion2d(belief, disbelief)
else:
raise NotImplementedError("Currently only float is supported")
return self.value


@numba.njit(cache=True)
def dirichlet_multinomial_ll(alphas, evidence) -> float:
alpha_0 = sum(alphas)
Expand Down Expand Up @@ -50,7 +74,7 @@ def monte_carlo_ll(long_op: sl.Opinion, short_op: sl.Opinion, num_simulations: i
synthetic_counts = rng.multinomial(sum(evidence_st), ps)
mc_log_likelihoods[idx] = dirichlet_multinomial_ll(alphas_lt, synthetic_counts)

# count more extreme outcomes
# count more extreme outcomes
tail_events = np.sum(mc_log_likelihoods <= log_likelihood)
pval = 1 - tail_events / num_simulations
mc_sim.append(pval)
Expand All @@ -73,6 +97,13 @@ def post_hoc_conflict_detection(memory) -> None:
dcs.append(short_op.degree_of_conflict(long_op))


def sliding_window(arr: np.ndarray, window_size) -> np.ndarray:
moving_sum = np.convolve(arr, np.ones(window_size), mode="full")[: len(arr)]
# sliding window only contains single value at start
divisors = np.minimum(np.arange(1, len(arr) + 1), window_size)
return moving_sum / divisors


def plot(segments: list[dict[str, any]]) -> None:
def plot_ppu(axis, data: np.array, **kwargs) -> None:
axis.plot(data[:, 0], **kwargs)
Expand All @@ -99,6 +130,8 @@ def plot_ppu(axis, data: np.array, **kwargs) -> None:

plot_ppu(axes[1], inp, color="tab:blue", label="input")
plot_ppu(axes[1], buffer, color="tab:orange", label="buffer")
plot_ppu(axes[1], ewma_ppu, color="tab:green", label="ewma")
axes[1].plot(sliding_window_pp, color="tab:purple", label="window")
axes[1].vlines(np.argwhere(resets), ymin=0.0, ymax=1.0, linestyles=":", color="tab:red")

axes[1].set_ylim(-0.05, 1.05)
Expand All @@ -122,8 +155,8 @@ def plot_ppu(axis, data: np.array, **kwargs) -> None:

def write_csv(base_path="/tmp") -> None:
os.makedirs(base_path, exist_ok=True)
headers = ["inp-pp", "inp-u", "buffer-pp", "buffer-u", "resets", "dc", "pval"]
np_arrs_to_write = [inp, buffer, resets.astype(int), dcs, mc_sim]
headers = ["inp-pp", "inp-u", "buffer-pp", "buffer-u", "resets", "dc", "pval", "ewma-pp", "ewma-u", "window-pp"]
np_arrs_to_write = [inp, buffer, resets.astype(int), dcs, mc_sim, ewma_ppu]
cols = []

for arr in np_arrs_to_write:
Expand Down Expand Up @@ -155,6 +188,11 @@ def run_simulation(ops: list[sl.Opinion], ltst: sl.LongShortTermMemory) -> None:
resets[idx] = ltst.is_last_conflicted()
post_hoc_conflict_detection(ltst)

ewma_op = ewma(op)
ewma_ppu[idx, :] = (ewma_op.getBinomialProjection(), ewma_op.uncertainty())

sliding_window_pp[:] = sliding_window(inp[:, 0], window_size=int(2 / (1 - DISCOUNT) - 1))


SEED = 42
rng = np.random.default_rng(seed=SEED)
Expand Down Expand Up @@ -206,10 +244,13 @@ def run_simulation(ops: list[sl.Opinion], ltst: sl.LongShortTermMemory) -> None:
buffer = np.zeros((len(ops), 2))
resets = np.zeros(len(ops))
mc_sim, dcs = [], []
ewma_ppu = np.zeros((len(ops), 2))
sliding_window_pp = np.zeros(len(ops))

ltst = sl.LongShortTermMemory2d(
SHORT_WINDOW_SIZE, THRESHOLD, DISCOUNT, FUSION_TYPE, HANDLE_ST_CONFLICT, AVG_DC_CONFLICT_HANDLING
)
ewma = EWMA(1 - DISCOUNT)

run_simulation(ops, ltst)
plot(segments)
Expand Down
Loading