Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9042914
WIP: ProbabilisticLabelling
JeffersonYeh Sep 29, 2025
7bb56b1
adjust ProductProcess to generalize to probabilistic label mapping ma…
JeffersonYeh Sep 29, 2025
7f36b6e
WIP: bellman for ProductProcess with Probabilistic Labelling
JeffersonYeh Sep 30, 2025
c5a64ab
WIP
Zinoex Oct 1, 2025
26661b9
WIP
Zinoex Oct 1, 2025
45f5cf5
restructure Labelling classes and ran formatter
JeffersonYeh Oct 1, 2025
9821670
Fix labelling tests
JeffersonYeh Oct 1, 2025
792fb2a
Merge branch 'stochastic-labelling' of github.com:Zinoex/IntervalMDP.…
Zinoex Oct 1, 2025
128f01a
Merge branch 'fm/fimdp' into stochastic-labelling
Zinoex Oct 1, 2025
6921802
Merge branch 'fm/fimdp' into stochastic-labelling
Zinoex Oct 1, 2025
264b2d1
Merge branch 'fm/fimdp' into stochastic-labelling
Zinoex Oct 1, 2025
48cb07b
minor changes from review
JeffersonYeh Oct 3, 2025
a3f1bae
Add tests for ProbabilisticLabelling and minor updates to other label…
JeffersonYeh Oct 3, 2025
62d8800
Update docs for DeterministicLabelling
JeffersonYeh Oct 29, 2025
57a9f39
Merge branch 'fm/fimdp' into stochastic-labelling
JeffersonYeh Oct 30, 2025
6ff4c0b
Add Probabilistically Labelled prodIMDP tests for bellman (over all s…
JeffersonYeh Nov 2, 2025
fb97189
Improve strategy cache initialization in product process bellman tests
JeffersonYeh Nov 3, 2025
e45b292
Merge branch 'fm/fimdp' into stochastic-labelling
Zinoex Nov 3, 2025
28fe368
Fix show test for ProductProcess
Zinoex Nov 3, 2025
3617936
Add product process bellman tests with optimal and nonoptimal strateg…
JeffersonYeh Nov 4, 2025
a0db4b2
Add probabilistic labelling docs
Zinoex Nov 4, 2025
da06562
Merge branch 'main' into stochastic-labelling
Zinoex Nov 4, 2025
11bfb4c
Format workspace
Zinoex Nov 4, 2025
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
6 changes: 3 additions & 3 deletions docs/src/reference/systems.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ num_labels(tf::TransitionFunction)

### Labelling of IMDP states to Automaton alphabet
```@docs
LabellingFunction
mapping(labelling_func::LabellingFunction)
num_labels(labelling_func::LabellingFunction)
DeterministicLabelling
mapping(dl::DeterministicLabelling)
num_labels(dl::DeterministicLabelling)
```
2 changes: 1 addition & 1 deletion docs/src/specifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ mdp = IntervalMarkovDecisionProcess(transition_probs, istates)

```@example product_process_example
map = [1, 2, 3] # "", "a", "b"
lf = LabellingFunction(map)
lf = DeterministicLabelling(map)

product_process = ProductProcess(mdp, dfa, lf)
```
Expand Down
69 changes: 69 additions & 0 deletions src/bellman.jl
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,30 @@ function bellman!(
lf = labelling_function(model)
dfa = automaton(model)

return _bellman_helper!(
workspace,
strategy_cache,
Vres,
V,
dfa,
mp,
lf,
upper_bound,
maximize,
)
end

function _bellman_helper!(
workspace::ProductWorkspace,
strategy_cache::AbstractStrategyCache,
Vres,
V,
dfa::DFA,
mp::IntervalMarkovProcess,
lf::DeterministicLabelling,
upper_bound = false,
maximize = true,
)
W = workspace.intermediate_values

@inbounds for state in dfa
Expand Down Expand Up @@ -244,6 +268,51 @@ function bellman!(
return Vres
end

function _bellman_helper!(
workspace::ProductWorkspace,
strategy_cache::AbstractStrategyCache,
Vres,
V::AbstractArray{R},
dfa::DFA,
mp::IntervalMarkovProcess,
lf::ProbabilisticLabelling,
upper_bound = false,
maximize = true,
) where {R}
W = workspace.intermediate_values

@inbounds for state in dfa
local_strategy_cache = localize_strategy_cache(strategy_cache, state)

# Select the value function for the current DFA state
# according to the appropriate DFA transition function
map!(W, CartesianIndices(state_values(mp))) do idx
v = zero(R)

for (label, prob) in enumerate(lf[idx])
new_dfa_state = dfa[state, label]
v += prob * V[idx, new_dfa_state]
end

return v
end

# For each state in the product process, compute the Bellman operator
# for the corresponding Markov process
bellman!(
workspace.underlying_workspace,
local_strategy_cache,
selectdim(Vres, ndims(Vres), state),
W,
mp;
upper_bound = upper_bound,
maximize = maximize,
)
end

return Vres
end

function localize_strategy_cache(strategy_cache::NoStrategyCache, dfa_state)
return strategy_cache
end
Expand Down
2 changes: 1 addition & 1 deletion src/models/ProductProcess.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function checkproduct(
)

# check labelling states (input) match MDP states
if size(labelling_func) != state_values(mdp)
if state_values(labelling_func) != state_values(mdp)
throw(
DimensionMismatch(
"The mapped states $(size(labelling_func)) in the labelling function is not equal the fRMDP state variables $(state_values(mdp)).",
Expand Down
84 changes: 84 additions & 0 deletions src/probabilities/DeterministicLabelling.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
struct DeterministicLabelling{
T <: Integer,
AT <: AbstractArray{T}
}

A type representing the labelling of IMDP states into DFA inputs.

Formally, let ``L : S \\to 2^{AP}`` be a labelling function, where
- ``S`` is the set of IMDP states, and
- ``2^{AP}`` is the power set of atomic propositions

Then the ```DeterministicLabelling``` type is defined as vector which stores the mapping.

### Fields
- `map::AT`: mapping function where indices are (factored) IMDP states and stored values are DFA inputs.
- `num_outputs::Int32`: number of labels accounted for in mapping.

"""
struct DeterministicLabelling{T <: Integer, AT <: AbstractArray{T}} <: AbstractLabelling
map::AT
num_outputs::Int32

function DeterministicLabelling(map::AT) where {T <: Integer, AT <: AbstractArray{T}}
num_outputs = checklabelling(map)

return new{T, AT}(map, Int32(num_outputs))
end
end

function checklabelling(map::AbstractArray{<:Integer})
labels = unique(map)

if any(labels .< 1)
throw(ArgumentError("Labelled state index cannot be less than 1"))
end

# Check that labels are consecutive integers
sort!(labels)
if any(diff(labels) .!= 1)
throw(ArgumentError("Labelled state indices must be consecutive integers"))
end

return last(labels)
end

"""
mapping(dl::DeterministicLabelling)

Return the mapping array of the labelling function.
"""
mapping(dl::DeterministicLabelling) = dl.map

"""
size(dl::DeterministicLabelling)

Returns the shape of the input range of the labeling function ``L : S \\to 2^{AP}``, which can be multiple dimensions in case of factored IMDPs.
"""
Base.size(dl::DeterministicLabelling) = size(dl.map)

"""
num_labels(dl::DeterministicLabelling)
Return the number of labels (DFA inputs) in the labelling function.
"""
num_labels(dl::DeterministicLabelling) = dl.num_outputs

"""
state_values(dl::DeterministicLabelling)
Return a tuple with the number of states for each state variable of the labeling function ``L : S \\to 2^{AP}``, which can be multiple dimensions in case of factored IMDPs.
"""
state_values(dl::DeterministicLabelling) = size(dl.map)

"""
num_states(dl::DeterministicLabelling)
Return the number of states of the labeling function ``L : S \\to 2^{AP}``
"""
num_states(dl::DeterministicLabelling) = prod(state_values(dl))

"""
getindex(dl::DeterministicLabelling, s...)

Return the label for state s.
"""
Base.getindex(dl::DeterministicLabelling, s...) = dl.map[s...]
74 changes: 3 additions & 71 deletions src/probabilities/Labelling.jl
Original file line number Diff line number Diff line change
@@ -1,74 +1,6 @@
abstract type AbstractLabelling end

"""
struct LabellingFunction{
T <: Integer,
AT <: AbstractArray{T}
}

A type representing the labelling of IMDP states into DFA inputs.

Formally, let ``L : S \\to 2^{AP}`` be a labelling function, where
- ``S`` is the set of IMDP states, and
- ``2^{AP}`` is the power set of atomic propositions

Then the ```LabellingFunction``` type is defined as vector which stores the mapping.

### Fields
- `map::AT`: mapping function where indices are (factored) IMDP states and stored values are DFA inputs.
- `num_inputs::Int32`: number of IMDP states accounted for in mapping.

"""
struct LabellingFunction{T <: Integer, AT <: AbstractArray{T}} <: AbstractLabelling
map::AT
num_outputs::Int32
end

function LabellingFunction(map::AT) where {T <: Integer, AT <: AbstractArray{T}}
num_outputs = checklabelling(map)

return LabellingFunction(map, Int32(num_outputs))
end

function checklabelling(map::AbstractArray{<:Integer})
labels = unique(map)

if any(labels .< 1)
throw(ArgumentError("Labelled state index cannot be less than 1"))
end

# Check that labels are consecutive integers
sort!(labels)
if any(diff(labels) .!= 1)
throw(ArgumentError("Labelled state indices must be consecutive integers"))
end

return last(labels)
end

"""
mapping(labelling_func::LabellingFunction)

Return the mapping array of the labelling function.
"""
mapping(labelling_func::LabellingFunction) = labelling_func.map

"""
size(labelling_func::LabellingFunction)

Returns the shape of the input range of the labeling function ``L : S \\to 2^{AP}``, which can be multiple dimensions in case of factored IMDPs.
"""
Base.size(labelling_func::LabellingFunction) = size(labelling_func.map)

"""
num_labels(labelling_func::LabellingFunction)
Return the number of labels (DFA inputs) in the labelling function.
"""
num_labels(labelling_func::LabellingFunction) = labelling_func.num_outputs
AbstractLabelling

An abstract type for labelling functions.
"""
getindex(lf::LabellingFunction, s...)

Return the label for state s.
"""
Base.getindex(lf::LabellingFunction, s...) = lf.map[s...]
abstract type AbstractLabelling end
84 changes: 84 additions & 0 deletions src/probabilities/ProbabilisticLabelling.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
struct ProbabilisticLabelling{
R <: Real,
MR <: AbstractMatrix{R}
}

A type representing the Probabilistic labelling of IMDP states into DFA inputs. Each labelling is assigned a probability.

Formally, let ``L : S \\times 2^{AP} \\to [0, 1]`` be a labelling function, where
- ``S`` is the set of IMDP states, and
- ``2^{AP}`` is the power set of atomic propositions

Then the ```ProbabilisticLabelling``` type is defined as matrix which stores the mapping.

### Fields
- `map::MT`: mapping function encoded as matrix with labels on the rows, IMDP states on the columns, and valid probability values for the destination.

The choice to have labels on the rows is due to the column-major storage of matrices in Julia and the fact that we want the inner loop over DFA target states
in the Bellman operator `bellman!`.

"""
struct ProbabilisticLabelling{R <: Real, MR <: AbstractMatrix{R}} <: AbstractLabelling
map::MR

function ProbabilisticLabelling(map::MR) where {R <: Real, MR <: AbstractMatrix{R}}
checklabellingprobs(map)

return new{R, MR}(map)
end
end

function checklabellingprobs(map::AbstractMatrix{<:Real})

# check for each state, all the labels probabilities sum to 1
if any(sum(map; dims = 1) .!= one(eltype(map)))
throw(
ArgumentError(
"For each IMDP state, probabilities over label states must sum to 1",
),
)
end
end

"""
mapping(pl::ProbabilisticLabelling)

Return the mapping matrix of the probabilistic labelling function.
"""
mapping(pl::ProbabilisticLabelling) = pl.map

Base.size(pl::ProbabilisticLabelling) = size(pl.map)
Base.size(pl::ProbabilisticLabelling, i) = size(pl.map, i)

"""
getindex(pl::ProbabilisticLabelling, s, l)

Return the probabilities for labelling l from state s.
"""
Base.getindex(pl::ProbabilisticLabelling, l, s) = pl.map[l, s]

"""
getindex(pl::ProbabilisticLabelling, s)

Return the probabilities over labels from state s.
"""
Base.getindex(pl::ProbabilisticLabelling, s) = @view(pl.map[:, s])

"""
num_labels(pl::ProbabilisticLabelling)
Return the number of labels (DFA inputs) in the probabilistic labelling function.
"""
num_labels(pl::ProbabilisticLabelling) = size(pl.map, 1)

"""
state_values(pl::ProbabilisticLabelling)
Return a tuple with the number of states for each state variable of the labeling function ``L : S \\to 2^{AP}``, which can be multiple dimensions in case of factored IMDPs.
"""
state_values(pl::ProbabilisticLabelling) = Base.tail(size(pl.map))

"""
num_states(pl::ProbabilisticLabelling)
Return the number of states of the labeling function ``L : S \\to 2^{AP}``
"""
num_states(pl::ProbabilisticLabelling) = prod(state_values(pl))
7 changes: 6 additions & 1 deletion src/probabilities/probabilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ include("TransitionFunction.jl")
export TransitionFunction, transition

include("Labelling.jl")
export LabellingFunction, mapping, num_labels

include("DeterministicLabelling.jl")
export DeterministicLabelling, mapping, num_labels, state_values, num_states

include("ProbabilisticLabelling.jl")
export ProbabilisticLabelling, mapping, num_labels, state_values, num_states
Loading
Loading