Skip to content

Add ImportanceSamplingForwardPass #848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
76 changes: 76 additions & 0 deletions src/plugins/forward_passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,79 @@
end
return pass
end

struct ImportanceSamplingForwardPass <: AbstractForwardPass end

function forward_pass(

Check warning on line 404 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L404

Added line #L404 was not covered by tests
model::PolicyGraph{T},
options::Options,
pass::ImportanceSamplingForwardPass,
) where {T}
@assert isempty(model.belief_partition)
scenario_path = Tuple{T,Any}[]
sampled_states = Dict{Symbol,Float64}[]
cumulative_value = 0.0
incoming_state_value = copy(options.initial_state)
node_index = sample_noise(model.root_children)
node = model[node_index]
noise = sample_noise(node.noise_terms)
while node_index !== nothing
node = model[node_index]
lock(node.lock)
try
push!(scenario_path, (node_index, noise))
subproblem_results = solve_subproblem(

Check warning on line 422 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L409-L422

Added lines #L409 - L422 were not covered by tests
model,
node,
incoming_state_value,
noise,
scenario_path;
duality_handler = nothing,
)
cumulative_value += subproblem_results.stage_objective
incoming_state_value = copy(subproblem_results.state)
push!(sampled_states, incoming_state_value)
if isempty(node.bellman_function.local_thetas)

Check warning on line 433 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L430-L433

Added lines #L430 - L433 were not covered by tests
# First iteration with no multi-cuts, or a node with no children
node_index = SDDP.sample_noise(node.children)
if node_index !== nothing
new_node = model[node_index]
noise = SDDP.sample_noise(new_node.noise_terms)

Check warning on line 438 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L435-L438

Added lines #L435 - L438 were not covered by tests
end
else
objectives = map(node.bellman_function.local_thetas) do t
return JuMP.value(t.theta)

Check warning on line 442 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L441-L442

Added lines #L441 - L442 were not covered by tests
end
adjusted_probability = fill(NaN, length(objectives))
nominal_probability = Float64[]
support = Any[]
for child in node.children
for noise in model[child.term].noise_terms
push!(nominal_probability, child.probability * noise.probability)
push!(support, (child.term, noise.term))
end
end
@assert length(nominal_probability) == length(objectives)
_ = SDDP.adjust_probability(

Check warning on line 454 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L444-L454

Added lines #L444 - L454 were not covered by tests
options.risk_measures[node_index],
adjusted_probability,
nominal_probability,
support,
objectives,
model.objective_sense == MOI.MIN_SENSE,
)
terms = SDDP.Noise.(support, adjusted_probability)
node_index, noise = SDDP.sample_noise(terms)

Check warning on line 463 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L462-L463

Added lines #L462 - L463 were not covered by tests
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Danjiang2000 it's a complete implementation. The two for-loops build the nominal probability pmf, then we use SDDP.adjust_probability to get the risk-adjusted probabilities, and then we sample a new node and noise from that distribution.

Copy link

@Danjiang2000 Danjiang2000 Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Oscar,

Thanks for your last answer, it is really helpful.

I am 'drafting' to modify this forward_pass function so that it can better serve the purpose of an importance sampling experiment.
In the current implementation, we sample noise using adjusted pmf, and we can find the cumulative cost along the path we sample. To do the importance sampling, I also want to know the weight adjustment for each path, which means I need to multiply the weight adjustment on each path together.
I am not sure if there is a better way to suggest code modification I want to make, so I will write the code in this comment:

At line 413,
cumulative_weight = 1.0

At line 464,
sel = (node_index, noise)
idx = findfirst(x -> x == sel, support)
ratio = nominal_probability[idx] / adjusted_probability[idx]
cumulative_weight *= ratio

At line 475,
cumulative_weight = cumulative_weight,

Then, to simulate via importance sampling, I can write another function that repeat this modified forward pass multiple times. If I want to have a point estimate for entropic cost, I can find point estimate for exponential cost by sample averaging the product of cumulative weight and exponential of cumulative cost.
Does this sound like a good plan if I want to implement a complete importance sampling experiment?

image

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this sound like a good plan if I want to implement a complete importance sampling experiment?

Yes, I think at least for now.

What this is really asking for the ability to sample with the risk-adjusted probabilities in SDDP.simulate instead of SDDP.train. We either need to write something separate for SDDP.simulate, or change SDDP.simulate to use the AbstractForwardPass machinery.

end
finally
unlock(node.lock)

Check warning on line 466 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L466

Added line #L466 was not covered by tests
end
end
return (

Check warning on line 469 in src/plugins/forward_passes.jl

View check run for this annotation

Codecov / codecov/patch

src/plugins/forward_passes.jl#L468-L469

Added lines #L468 - L469 were not covered by tests
scenario_path = scenario_path,
sampled_states = sampled_states,
objective_states = NTuple{0,Float64}[],
belief_states = Tuple{Int,Dict{T,Float64}}[],
cumulative_value = cumulative_value,
)
end
Loading