Skip to content

Commit

Permalink
Merge pull request #18 from CliMA/orad/refactor-state-env
Browse files Browse the repository at this point in the history
Refactor of Storage and Environments
  • Loading branch information
odunbar authored Dec 12, 2024
2 parents 0c45862 + fc09ed6 commit 52f95ed
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 93 deletions.
30 changes: 21 additions & 9 deletions examples/routing/gamma_IRF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ river_model = HillslopeChannelRiverModel(hillslope, channel)
# build environment
data_file_path = joinpath(@__DIR__, "..", "..", "data", "routing")

# build static environment
# files for static environment
@info "reading data files from $(data_file_path)"
graph_dict =
JSON.parsefile(joinpath(data_file_path, "graphs", "graph_lv05.json"))
basins_dir = joinpath(data_file_path, "routing_lvs", "routing_lvs_lv05")
attributes_dir = joinpath(data_file_path, "attributes", "attributes_lv05")
static_env = StaticEnvironment(basins_dir, attributes_dir, graph_dict)
graph_file = joinpath(data_file_path, "graphs", "graph_lv05.json")
basin_id_file = joinpath(
data_file_path,
"routing_lvs",
"routing_lvs_lv05",
"all_basin_ids.txt",
)
attributes_file =
joinpath(data_file_path, "attributes", "attributes_lv05", "attributes.csv")

# build dynamic environment
# files for dynamic environment
forcing_timeseries_dir =
joinpath(data_file_path, "timeseries", "timeseries_lv05")
output_dir =
Expand All @@ -30,9 +34,17 @@ output_dir =
if !isdir(output_dir)
mkpath(output_dir)
end
dynamic_env = DynamicEnvironment(forcing_timeseries_dir, output_dir)

env = Environment(static_env, dynamic_env)
# build environment
env = Environment(
basin_ids_file = basin_ids_file,
attributes_file = attributes_file,
graph_file = graph_file,
forcing_timeseries_dir = forcing_timeseries_dir,
output_dir = output_dir,
forcing_timeseries_file_prefix = "basin_",
)


## evolutionary model, evolving a state over time
model_types = ["instant"]
Expand Down
31 changes: 21 additions & 10 deletions examples/routing/mini_gamma_IRF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,37 @@ river_model = HillslopeChannelRiverModel(hillslope, channel)
# build environment
data_file_path = joinpath(@__DIR__, "..", "..", "mini_data", "routing")

# build static environment
# files for static environment
@info "reading data files from $(data_file_path)"
graph_dict =
JSON.parsefile(joinpath(data_file_path, "graphs", "graph_lv05.json"))
basins_dir = joinpath(data_file_path, "routing_lvs", "routing_lvs_lv05")
attributes_dir = joinpath(data_file_path, "attributes", "attributes_lv05")
static_env = StaticEnvironment(basins_dir, attributes_dir, graph_dict)
graph_file = joinpath(data_file_path, "graphs", "graph_lv05.json")
basin_ids_file = joinpath(
data_file_path,
"routing_lvs",
"routing_lvs_lv05",
"all_basin_ids.txt",
)
attributes_file =
joinpath(data_file_path, "attributes", "attributes_lv05", "attributes.csv")

# build dynamic environment
# files for dynamic environment
forcing_timeseries_dir =
joinpath(data_file_path, "timeseries", "timeseries_lv05")
output_dir =
joinpath(data_file_path, "simulations", "simulations_lv05", "gamma_IRF")
@info "creating output"
@info "creating output path"
if !isdir(output_dir)
mkpath(output_dir)
end
dynamic_env = DynamicEnvironment(forcing_timeseries_dir, output_dir)

env = Environment(static_env, dynamic_env)
# build environment
env = Environment(
basin_ids_file = basin_ids_file,
attributes_file = attributes_file,
graph_file = graph_file,
forcing_timeseries_dir = forcing_timeseries_dir,
output_dir = output_dir,
forcing_timeseries_file_prefix = "basin_",
)

## evolutionary model, evolving a state over time
model_types = ["instant"]
Expand Down
209 changes: 199 additions & 10 deletions src/Environments.jl
Original file line number Diff line number Diff line change
@@ -1,27 +1,216 @@
# Contains the structs that define the static and dynamic environment that configures/forces the river model.
using JSON, CSV, DataFrames
export StaticEnvironment, DynamicEnvironment, Environment

## Auxiliary functions
# function for reading basins from txt file into Vector{Int}
function get_basin_list(basins_file::String)
basins_list = Int64[]
file = open(joinpath(basins_file))
for line in eachline(file)
push!(basins_list, parse(Int64, line))
end
close(file)

return basins_list
end


# Static Data Objects
struct StaticEnvironment
"Directory containing list of all basins"
basins_dir::String
"Directory containing attribute csv"
attributes_dir::String
"Basin mapping dictionary"
"""
$(TYPEDEF)
Stores the static features that describe the river basin network.
$(TYPEDFIELDS)
"""
struct StaticEnvironment{AV <: AbstractVector}
"Vector of basin identifiers"
basin_ids::AV
"Dataframe of static basin attributes"
attributes::DataFrame
"Dictionary of pairs `(basin_id => basin_ids of direct upstream neighbours)`"
graph_dict::Dict
end

"""
$(TYPEDSIGNATURES)
Constructor of `StaticEnvironment` from three strings holding files for basin id (txt), attributes (csv) and the graph (JSON).
"""
function StaticEnvironment(
basin_ids_file::AS1,
attributes_file::AS2,
graph_file::AS3,
) where {AS1 <: AbstractString, AS2 <: AbstractString, AS3 <: AbstractString}

# create basin
basin_ids = get_basin_list(basin_ids_file)

# create attributes
attributes = CSV.read(attributes_file, DataFrame)

# create graph
graph_dict = JSON.parsefile(graph_file)

return StaticEnvironment(basin_ids, attributes, graph_dict)
end

# Dynamic Data Objects
"""
$(TYPEDEF)
Stores the dynamic features that apply to the river network over a dated time period.
$(TYPEDFIELDS)
"""
struct DynamicEnvironment
"Directory containing forcing timeseries csv"
forcing_timeseries_dir::String
"Dictionary of pairs `(basin_id => forcing timeseries [DataFrame] at basin_id)`"
forcing_timeseries::Dict
"Directory to store simulation results"
output_dir::String
end

"""
$(TYPEDSIGNATURES)
Constructor of `DynamicEnvironment` from a vector of `basin_id`s, and a vector of `forcing_timeseries_files` files (CSV).
"""
function DynamicEnvironment(
basin_ids::AV1,
forcing_timeseries_files::AV2,
output_dir::String,
) where {AV1 <: AbstractVector, AV2 <: AbstractVector}

# build forcing timeseries
forcing_timeseries_array = []
for file in forcing_timeseries_files
push!(forcing_timeseries_array, CSV.read(file, DataFrame))
end
forcing_timeseries = Dict(eachrow([basin_ids forcing_timeseries_array])) # creates id => timeseries dictionary

return DynamicEnvironment(forcing_timeseries, output_dir)
end


"""
$(TYPEDEF)
Stores both the static and dynamic environments
$(TYPEDFIELDS)
"""
struct Environment{SE <: StaticEnvironment, DE <: DynamicEnvironment}
"Static data objects"
"StaticEnvironment data objects"
static_env::SE
"Dynamic data objects"
"DynamicEnvironment data objects"
dynamic_env::DE
end

"""
$(TYPEDSIGNATURES)
Constructor of `Enviroment` using a list of forcing timeseries files. See constructors for StaticEnvironment and DynamicEnvironment for more details on other inputs.
"""
function Environment(
basin_ids_file::AS1,
attributes_file::AS2,
graph_file::AS3,
forcing_timeseries_files::AV,
output_dir::AS4;
) where {
AS1 <: AbstractString,
AS2 <: AbstractString,
AS3 <: AbstractString,
AS4 <: AbstractString,
AV <: AbstractVector,
}

static_env = StaticEnvironment(basin_ids_file, attributes_file, graph_file)
basin_ids = static_env.basin_ids
dynamic_env =
DynamicEnvironment(basin_ids, forcing_timeseries_files, output_dir)

return Environment(static_env, dynamic_env)

end

"""
$(TYPEDSIGNATURES)
Constructor of `Enviroment` using a directory of the forcing timeseries, and inferring the files as
```forcing_timeseries_dir/forcing_timeseries_file_prefix*\$(basin_id).csv```.
See constructors for StaticEnvironment and DynamicEnvironment for more details on other inputs.
"""
function Environment(
basin_ids_file::AS1,
attributes_file::AS2,
graph_file::AS3,
forcing_timeseries_dir::AS4,
output_dir::AS5;
forcing_timeseries_file_prefix = "basin_",
) where {
AS1 <: AbstractString,
AS2 <: AbstractString,
AS3 <: AbstractString,
AS4 <: AbstractString,
AS5 <: AbstractString,
}
static_env = StaticEnvironment(basin_ids_file, attributes_file, graph_file)

basin_ids = static_env.basin_ids
forcing_timeseries_files = [
joinpath(
forcing_timeseries_dir,
forcing_timeseries_file_prefix * "$(id).csv",
) for id in basin_ids
]
dynamic_env =
DynamicEnvironment(basin_ids, forcing_timeseries_files, output_dir)

return Environment(static_env, dynamic_env)

end

"""
$(TYPEDSIGNATURES)
Constructor based on keywords, where users must provide either `forcing_timeseries_dir` or `forcing_timeseries_files`.
"""
function Environment(;
basin_ids_file::Union{AS1, Nothing} = nothing,
attributes_file::Union{AS2, Nothing} = nothing,
graph_file::Union{AS3, Nothing} = nothing,
forcing_timeseries_dir::Union{AS4, Nothing} = nothing,
output_dir::Union{AS5, Nothing} = nothing,
forcing_timeseries_file_prefix::AS6 = "basin_",
forcing_timeseries_files::Union{AS7, Nothing} = nothing,
) where {
AS1 <: AbstractString,
AS2 <: AbstractString,
AS3 <: AbstractString,
AS4 <: AbstractString,
AS5 <: AbstractString,
AS6 <: AbstractString,
AS7 <: AbstractString,
}
if isnothing(forcing_timeseries_dir)
return Environment(
basin_ids_file,
attributes_file,
graph_file,
forcing_timeseries_files,
output_dir,
)
elseif isnothing(forcing_timeseries_files)
return Environment(
basin_ids_file,
attributes_file,
graph_file,
forcing_timeseries_dir,
output_dir,
forcing_timeseries_file_prefix = forcing_timeseries_file_prefix,
)
end

end
32 changes: 14 additions & 18 deletions src/RiverModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,16 @@ struct HillslopeChannelRiverModel{
channel_model::CH
end

# Auxiliary function
## function for reading basins from txt file into Vector{Int}
function get_basin_list(basins_file::String)
basins_list = Int64[]
file = open(joinpath(basins_file))
for line in eachline(file)
push!(basins_list, parse(Int64, line))
end
close(file)

return basins_list
end

function calculate_streamflow(
function compute_streamflow(
river_state::RS,
env::E,
) where {RS <: RiverState, E <: Environment}
static_env::SE,
dynamic_env::DE,
) where {RS <: RiverState, SE <: StaticEnvironment, DE <: DynamicEnvironment}

output_dir = env.dynamic_env.output_dir
output_dir = dynamic_env.output_dir

all_basin_ids =
get_basin_list(joinpath(env.static_env.basins_dir, "all_basin_ids.txt"))
all_basin_ids = static_env.basin_ids

for basin_id in all_basin_ids
channel_file = joinpath(output_dir, "channel_basin_$basin_id.csv")
Expand Down Expand Up @@ -70,5 +58,13 @@ function calculate_streamflow(
end
end

function compute_streamflow(
river_state::RS,
env::E,
) where {RS <: RiverState, E <: Environment}
return compute_streamflow(river_state, env.static_env, env.dynamic_env)
end


# Specific hillslope-channel models loaded here:
# include("MizurouteV1.jl")
Loading

0 comments on commit 52f95ed

Please sign in to comment.