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
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd"
CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[weakdeps]
Expand All @@ -25,5 +26,6 @@ DocStringExtensions = "0.9"
JuMP = "1"
OptimalControl = "2.2"
OrderedCollections = "1, 2"
SHA = "0.7, 1"
TOML = "1"
julia = "1.10"
50 changes: 45 additions & 5 deletions src/OptimalControlProblems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ using CTBase: CTBase
using CTModels: CTModels
using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES
using OrderedCollections: OrderedDict
using SHA: SHA
using TOML: TOML

# Accessors shared with OptimalControl, which reexports these very functions: binding them
Expand Down Expand Up @@ -75,6 +76,38 @@ function _problem_exists(problem::Symbol, spec)
return isfile(_problem_file(problem, spec))
end

# The files that define `problem`: its metadata, its description and its model in each
# modeling where it exists. Its reference objectives are not part of it: they are computed
# from its results.
function _problem_files(problem::Symbol)
ext = joinpath(dirname(@__DIR__), "ext")
files = [
joinpath(ext, "MetaData", "$(problem).jl"),
joinpath(ext, "Descriptions", "$(problem).md"),
]
for spec in MODELING_SPECS
_problem_exists(problem, spec) && push!(files, _problem_file(problem, spec))
end
return files
end

"""
Fingerprint of `problem` (#314): the SHA-256 of the files that define it (metadata,
description, model files), recorded with each result and compared by the documentation to
tell whether a result is up to date. The same on every platform: paths are relative to the
package and written with `/`, and line endings are read as `\n`.
"""
_fingerprint(problem::Symbol) = _fingerprint(_problem_files(problem), dirname(@__DIR__))

function _fingerprint(files, root::AbstractString)
ctx = SHA.SHA256_CTX()
for file in files
SHA.update!(ctx, codeunits(join(splitpath(relpath(file, root)), "/") * "\0"))
SHA.update!(ctx, codeunits(replace(read(file, String), "\r\n" => "\n") * "\0"))
end
return bytes2hex(SHA.digest!(ctx))
end

function _warn_if_draft(problem::Symbol, modeling::Symbol)
data = METADATA_STORAGE[problem]
statuses = data[:status]
Expand Down Expand Up @@ -138,7 +171,10 @@ end
# at load time and by the consistency tests.
const METADATA_SCHEMA = (
required=(
grid_size=Int, parameters=Union{Nothing,NamedTuple}, status=NamedTuple, init=Function
grid_size=Int,
parameters=Union{Nothing,NamedTuple},
status=NamedTuple,
init=Function,
),
optional=(notes=AbstractString, reference_solution=Function),
generated=(reference_objective=NamedTuple,),
Expand Down Expand Up @@ -175,15 +211,19 @@ _relative_gap(a, b) = abs(a - b) / (iszero(b) ? 1 : abs(b))
# a new directory changes `METADATA_PATH`, already a dependency
isdir(REFERENCES_PATH) && Base.include_dependency(REFERENCES_PATH)

function _references(problem::Symbol, file::AbstractString=joinpath(REFERENCES_PATH, "$(problem).toml"))
function _references(
problem::Symbol, file::AbstractString=joinpath(REFERENCES_PATH, "$(problem).toml")
)
isfile(file) || return nothing
Base.include_dependency(file)
entries = Pair{Symbol,NamedTuple}[]
for (scheme, table) in TOML.parsefile(file)
table isa AbstractDict || error("Reference objective $(scheme) of $(problem): not a table")
table isa AbstractDict ||
error("Reference objective $(scheme) of $(problem): not a table")
values = map(keys(REFERENCE_FIELDS)) do key
haskey(table, string(key)) ||
error("Missing field $(key) in the reference objective $(scheme) of $(problem)")
haskey(table, string(key)) || error(
"Missing field $(key) in the reference objective $(scheme) of $(problem)",
)
return REFERENCE_FIELDS[key](table[string(key)])
end
push!(entries, Symbol(scheme) => NamedTuple{keys(REFERENCE_FIELDS)}(values))
Expand Down
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
Expand All @@ -9,6 +10,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[compat]
Aqua = "0.8"
CTBase = "0.30"
InteractiveUtils = "1"
Pkg = "1"
REPL = "1"
TOML = "1"
Expand Down
Loading
Loading