text:
export AbstractPDDL,
        PDDL, goal_test, execute_action,
        AbstractPlanningAction, PlanningAction,
        substitute, check_precondition,
        air_cargo_pddl, air_cargo_pddl_goal_test,
        spare_tire_pddl, spare_tire_pddl_goal_test,
        three_block_tower_pddl, three_block_tower_pddl_goal_test,
        have_cake_and_eat_cake_too_pddl, have_cake_and_eat_cake_too_pddl_goal_test,
        PlanningLevel,
        find_mutex_links, build_level_links, build_level_links_permute_arguments, perform_actions,
        planning_combinations,
        PlanningGraph, expand_graph, non_mutex_goals,
        GraphPlanProblem, check_level_off, extract_solution,
        graphplan,
        doubles_tennis_pddl, doubles_tennis_pddl_goal_test,
        PlanningHighLevelAction, check_resource, check_job_order,
        HighLevelPDDL, refinements, hierarchical_search,
        job_shop_scheduling_pddl, job_shop_scheduling_pddl_goal_test;

abstract type AbstractPDDL end;

abstract type AbstractPlanningAction end;

#=

    PlanningAction is an action schema defined by the action's name, preconditions, and effects.

    Preconditions and effects consists of either positive and negated literals.

=#
struct PlanningAction <: AbstractPlanningAction
    name::String
    arguments::Tuple
    precondition_positive::Array{Expression, 1}
    precondition_negated::Array{Expression, 1}
    effect_add_list::Array{Expression, 1}
    effect_delete_list::Array{Expression, 1}

    function PlanningAction(action::Expression, precondition::Tuple{Vararg{Array, 2}}, effect::Tuple{Vararg{Array, 2}})
        return new(action.operator, action.arguments, precondition[1], precondition[2], effect[1], effect[2]);
    end
end

function substitute(action::T, e::Expression, arguments::Tuple{Vararg{Expression}}) where {T <: AbstractPlanningAction}
    local new_arguments::AbstractVector = collect(e.arguments);
    for (index_1, argument) in enumerate(e.arguments)
        for index_2 in 1:length(action.arguments)
            if (action.arguments[index_2] == argument)
                new_arguments[index_1] = arguments[index_2];
            end
        end
    end
    return Expression(e.operator, Tuple((new_arguments...,)));
end

function check_precondition(action::T1, kb::T2, arguments::Tuple) where {T1 <: AbstractPlanningAction, T2 <: AbstractKnowledgeBase}
    # Check for positive clauses.
    for clause in action.precondition_positive
        if (!(substitute(action, clause, arguments) in kb.clauses))
            return false;
        end
    end
    # Check for negated clauses.
    for clause in action.precondition_negated
        if (substitute(action, clause, arguments) in kb.clauses)
            return false;
        end
    end
    return true;
end

"""
    execute_action(action, kb, arguments)

Execute the given action 'action' on the knowledge base of the planning problem.
"""
function execute_action(action::T1, kb::T2, arguments::Tuple) where {T1 <: AbstractPlanningAction, T2 <: AbstractKnowledgeBase}
    if (!(check_precondition(action, kb, arguments)))
        error(@sprintf("execute_action(): Action \"%s\" preconditions are not satisfied!", action.name));
    end
    # Retract negated literals to knowledge base 'kb'.
    for clause in action.effect_delete_list
        retract(kb, substitute(action, clause, arguments));
    end
    # Add positive literals to knowledge base 'kb'.
    for clause in action.effect_add_list
        tell(kb, substitute(action, clause, arguments));
    end
    nothing;
end

#=

    The Planning Domain Definition Language (PDDL) is used to define a search problem.

    The states (starting from the initial state) are represented as the conjunction of

    the statements in 'kb' (a FirstOrderLogicKnowledgeBase). The actions are described

    by 'actions' (an array of action schemas). The 'goal_test' is a function that checks

    if the current state of the problem is at the goal state.

=#
struct PDDL <: AbstractPDDL
    kb::FirstOrderLogicKnowledgeBase
    actions::Array{PlanningAction, 1}
    goal_test::Function

    function PDDL(initial_state::Array{Expression, 1}, actions::Array{PlanningAction, 1}, goal_test::Function)
        return new(FirstOrderLogicKnowledgeBase(initial_state), actions, goal_test);
    end
end

function goal_test(plan::T) where {T <: AbstractPDDL}
    return plan.goal_test(plan.kb);
end

"""
    execute_action(plan, action)

Execute the first relevant PlanningAction associated with action 'action'
on the given planning problem's knowledgebase.
"""
function execute_action(plan::T, action::Expression) where {T <: AbstractPDDL}
    local action_name::String = action.operator;
    local arguments::Tuple = action.arguments;
    local relevant_actions::AbstractVector = collect(a for a in plan.actions if (a.name == action_name));
    if (length(relevant_actions) == 0)
        error(@sprintf("execute_action(): Action \"%s\" not found!", action_name));
    else
        local first_relevant_action::PlanningAction = relevant_actions[1];
        if (!check_precondition(first_relevant_action, plan.kb, arguments))
            error(@sprintf("execute_action(): Action \"%s\" preconditions are not satisfied!", repr(action)));
        else
            execute_action(first_relevant_action, plan.kb, arguments);
        end
    end
    nothing;
end

function air_cargo_pddl_goal_test(kb::FirstOrderLogicKnowledgeBase)
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   # length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(kb, q) for q in (expr("At(C1, JFK)"), expr("At(C2, SFO)"))));
end

"""
    air_cargo_pddl()

Return a PDDL representing the air cargo transportation planning problem (Fig. 10.1).
"""
function air_cargo_pddl()
    local initial::Array{Expression, 1} = map(expr, ["At(C1, SFO)",
                                                "At(C2, JFK)",
                                                "At(P1, SFO)",
                                                "At(P2, JFK)",
                                                "Cargo(C1)",
                                                "Cargo(C2)",
                                                "Plane(P1)",
                                                "Plane(P2)",
                                                "Airport(JFK)",
                                                "Airport(SFO)"]);
    # Load Action Schema
    local precondition_positive::Array{Expression, 1} = map(expr, ["At(c, a)",
                                                            "At(p, a)",
                                                            "Cargo(c)",
                                                            "Plane(p)",
                                                            "Airport(a)"]);
    local precondition_negated::Array{Expression, 1} = [];
    local effect_add_list::Array{Expression, 1} = [expr("In(c, p)")];
    local effect_delete_list::Array{Expression, 1} = [expr("At(c, a)")];
    local load::PlanningAction = PlanningAction(expr("Load(c, p, a)"),
                                                (precondition_positive, precondition_negated),
                                                (effect_add_list, effect_delete_list));
    # Unload Action Schema
    precondition_positive = map(expr, ["In(c, p)", "At(p, a)", "Cargo(c)", "Plane(p)", "Airport(a)"]);
    precondition_negated = [];
    effect_add_list = [expr("At(c, a)")];
    effect_delete_list = [expr("In(c, p)")];
    local unload::PlanningAction = PlanningAction(expr("Unload(c, p, a)"),
                                                (precondition_positive, precondition_negated),
                                                (effect_add_list, effect_delete_list));
    # Fly Action Schema
    precondition_positive = map(expr, ["At(p, f)", "Plane(p)", "Airport(f)", "Airport(to)"]);
    precondition_negated = [];
    effect_add_list = [expr("At(p, to)")];
    effect_delete_list = [expr("At(p, f)")];
    local fly::PlanningAction = PlanningAction(expr("Fly(p, f, to)"),
                                                (precondition_positive, precondition_negated),
                                                (effect_add_list, effect_delete_list));
    return PDDL(initial, [load, unload, fly], air_cargo_pddl_goal_test);
end

function spare_tire_pddl_goal_test(kb::FirstOrderLogicKnowledgeBase)
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   # length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(kb, q) for q in (expr("At(Spare, Axle)"),)));
end

"""
    spare_tire_pddl()

Return a PDDL representing the spare tire planning problem (Fig. 10.2).
"""
function spare_tire_pddl()
    local initial::Array{Expression, 1} = map(expr, ["Tire(Flat)",
                                                    "Tire(Spare)",
                                                    "At(Flat, Axle)",
                                                    "At(Spare, Trunk)"]);
    # Remove Action Schema
    local precondition_positive::Array{Expression, 1} = [expr("At(obj, loc)")];
    local precondition_negated::Array{Expression, 1} = [];
    local effect_add_list::Array{Expression, 1} = [expr("At(obj, Ground)")];
    local effect_delete_list::Array{Expression, 1} = [expr("At(obj, loc)")];
    local remove::PlanningAction = PlanningAction(expr("Remove(obj, loc)"),
                                                (precondition_positive, precondition_negated),
                                                (effect_add_list, effect_delete_list));
    # PutOn Action Schema
    precondition_positive = map(expr, ["Tire(t)", "At(t, Ground)"]);
    precondition_negated = [expr("At(Flat, Axle)")];
    effect_add_list = [expr("At(t, Axle)")];
    effect_delete_list = [expr("At(t, Ground)")];
    local put_on::PlanningAction = PlanningAction(expr("PutOn(t, Axle)"),
                                                    (precondition_positive, precondition_negated),
                                                    (effect_add_list, effect_delete_list));
    # LeaveOvernight Action Schema
    precondition_positive = [];
    precondition_negated = [];
    effect_add_list = [];
    effect_delete_list = map(expr, ["At(Spare, Ground)", "At(Spare, Axle)", "At(Spare, Trunk)",
                                    "At(Flat, Ground)", "At(Flat, Axle)", "At(Flat, Trunk)"]);
    local leave_overnight::PlanningAction = PlanningAction(expr("LeaveOvernight"),
                                                            (precondition_positive, precondition_negated),
                                                            (effect_add_list, effect_delete_list));
    return PDDL(initial, [remove, put_on, leave_overnight], spare_tire_pddl_goal_test);
end

function three_block_tower_pddl_goal_test(kb::FirstOrderLogicKnowledgeBase)
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   #length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(kb, q) for q in (expr("On(A, B)"), expr("On(B, C)"))));
end

"""
    three_block_tower_pddl()

Return a PDDL representing the building of a three-block tower planning problem (Fig. 10.3).
"""
function three_block_tower_pddl()
    local initial::Array{Expression, 1} = map(expr, ["On(A, Table)",
                                                    "On(B, Table)",
                                                    "On(C, A)",
                                                    "Block(A)",
                                                    "Block(B)",
                                                    "Block(C)",
                                                    "Clear(B)",
                                                    "Clear(C)"]);
    # Move Action Schema
    local precondition_positive::Array{Expression, 1} = map(expr, ["On(b, x)", "Clear(b)", "Clear(y)", "Block(b)", "Block(y)"]);
    local precondition_negated::Array{Expression, 1} = [];
    local effect_add_list::Array{Expression, 1} = [expr("On(b, y)"), expr("Clear(x)")];
    local effect_delete_list::Array{Expression, 1} = [expr("On(b, x)"), expr("Clear(y)")];
    local move::PlanningAction = PlanningAction(expr("Move(b, x, y)"),
                                                (precondition_positive, precondition_negated),
                                                (effect_add_list, effect_delete_list));
    # MoveToTable Action Schema
    precondition_positive = map(expr, ["On(b, x)", "Clear(b)", "Block(b)"]);
    precondition_negated = [];
    effect_add_list = [expr("On(b, Table)"), expr("Clear(x)")];
    effect_delete_list = [expr("On(b, x)")];
    local move_to_table::PlanningAction = PlanningAction(expr("MoveToTable(b, x)"),
                                                        (precondition_positive, precondition_negated),
                                                        (effect_add_list, effect_delete_list));
    return PDDL(initial, [move, move_to_table], three_block_tower_pddl_goal_test);
end

function have_cake_and_eat_cake_too_pddl_goal_test(kb::FirstOrderLogicKnowledgeBase)
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   # length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(kb, q) for q in (expr("Have(Cake)"), expr("Eaten(Cake)"))));
end

"""
    have_cake_and_eat_cake_too_pddl()

Return a PDDL representing the 'have cake and eat cake too' planning problem (Fig. 10.7).
"""
function have_cake_and_eat_cake_too_pddl()
    local initial::Array{Expression, 1} = [expr("Have(Cake)")];
    # Eat Cake Action Schema
    local precondition_positive::Array{Expression, 1} = [expr("Have(Cake)")];
    local precondition_negated::Array{Expression, 1} = [];
    local effect_add_list::Array{Expression, 1} = [expr("Eaten(Cake)")];
    local effect_delete_list::Array{Expression, 1} = [expr("Have(Cake)")];
    local eat_cake::PlanningAction = PlanningAction(expr("Eat(Cake)"),
                                                    (precondition_positive, precondition_negated),
                                                    (effect_add_list, effect_delete_list));
    # Bake Cake Action Schema
    precondition_positive = [];
    precondition_negated = [expr("Have(Cake)")];
    effect_add_list = [expr("Have(Cake)")];
    effect_delete_list = [];
    local bake_cake::PlanningAction = PlanningAction(expr("Bake(Cake)"),
                                                    (precondition_positive, precondition_negated),
                                                    (effect_add_list, effect_delete_list));
    return PDDL(initial, [eat_cake, bake_cake], have_cake_and_eat_cake_too_pddl_goal_test);
end

mutable struct PlanningLevel
    positive_kb::FirstOrderLogicKnowledgeBase
    current_state_positive::Array{Expression, 1}    #current state of the planning problem
    current_state_negated::Array{Expression, 1}     #current state of the planning problem
    current_action_links_positive::Dict             #current actions to current state link
    current_action_links_negated::Dict              #current actions to current state link
    current_state_links_positive::Dict              #current state to action link
    current_state_links_negated::Dict               #current state to action link
    next_action_links::Dict                         #current action to next state link
    next_state_links_positive::Dict                 #next state to current action link
    next_state_links_negated::Dict                  #next state to current action link
    mutex_links::Array{Set, 1}                      #each mutex relation is a Set of 2 actions/literals

    function PlanningLevel(p_kb::FirstOrderLogicKnowledgeBase, n_kb::FirstOrderLogicKnowledgeBase)
        return new(p_kb, p_kb.clauses, n_kb.clauses, Dict(), Dict(), Dict(), Dict(), Dict(), Dict(), Dict(), []);
    end
end

function find_mutex_links(level::PlanningLevel)
    # Inconsistent effects condition between 2 action schemas at a given level
    for positive_effect in level.next_state_links_positive
        negated_effect = positive_effect;
        if (haskey(level.next_state_links_negated, negated_effect))
            for a in level.next_state_links_positive[positive_effect]
                for b in level.next_state_links_negated[negated_effect]
                    if (!(Set([a, b]) in level.mutex_links))
                        push!(level.mutex_links, Set([a, b]));
                    end
                end
            end
        end
    end
    # Inference condition between 2 action schemas at a given level
    for positive_precondition in level.current_state_links_positive
        negated_effect = positive_precondition;
        if (haskey(level.next_state_links_negated, negated_effect))
            for a in level.current_state_links_positive[positive_precondition]
                for b in level.next_state_links_negated[negated_effect]
                    if (!(Set([a, b]) in level.mutex_links))
                        push!(level.mutex_links, Set([a, b]));
                    end
                end
            end
        end
    end
    for negated_precondition in level.current_state_links_negated
        positive_effect = negated_precondition;
        if (haskey(level.next_state_links_positive, positive_effect))
            for a in level.next_state_links_positive[positive_effect]
                for b in level.current_state_links_negated[negated_precondition]
                    if (!(Set([a, b]) in level.mutex_links))
                        push!(level.mutex_links, Set([a, b]));
                    end
                end
            end
        end
    end
    # Competing needs condition between 2 action schemas
    for positive_precondition in level. current_state_links_positive
        negated_precondition = positive_precondition;
        if (haskey(level.current_state_links_negated, negated_precondition))
            for a in level.current_state_links_positive[positive_precondition]
                for b in level.current_state_links_negated[negated_precondition]
                    if (!(Set([a, b]) in level.mutex_links))
                        push!(level.mutex_links, Set([a, b]));
                    end
                end
            end
        end
    end
    # Inconsistent support condition
    local state_mutex_links::AbstractVector = [];
    for pair in level.mutex_links
        collected_pair::AbstractVector = collect(pair);
        next_state_1 = level.next_action_links[collected_pair[1]];
        if (length(sorted_pair) == 2)
            next_state_2 = level.next_action_links[collected_pair[2]];
        else
            next_state_2 = level.next_action_links[collected_pair[1]];
        end
        if ((length(next_state_1) == 1) && (length(next_state_2) == 1))
            push!(state_mutex_links, Set([next_state_1[1], next_state_2[1]]));
        end
    end
    level.mutex_links = vcat(level.mutex_links, state_mutex_links);
    nothing;
end

function build_level_links_permute_arguments(depth::Int64, objects::AbstractVector, current_permutation::Tuple, permutations_array::AbstractVector)
    if (depth == 0)
        push!(permutations_array, current_permutation);
    elseif (depth < 0)
        error("build_level_links_permute_arguments(): Found negative depth!");
    else
        for (i, item) in enumerate(objects)
            build_level_links_permute_arguments((depth - 1),
                                                Tuple((objects[1:(i - 1)]..., objects[(i + 1):end]...,)),
                                                Tuple((current_permutation..., item)),
                                                permutations_array)
        end
    end
end

function build_level_links_permute_arguments(depth::Int64, objects::Tuple, current_permutation::Tuple, permutations_array::AbstractVector)
    if (depth == 0)
        push!(permutations_array, current_permutation);
    elseif (depth < 0)
        error("build_level_links_permute_arguments(): Found negative depth!");
    else
        for (i, item) in enumerate(objects)
            build_level_links_permute_arguments((depth - 1),
                                                Tuple((objects[1:(i - 1)]..., objects[(i + 1):end]...,)),
                                                Tuple((current_permutation..., item)),
                                                permutations_array)
        end
    end
end

function build_level_links(level::PlanningLevel, actions::AbstractVector, objects::Set)
    # Create persistence actions for positive states
    for clause in level.current_state_positive
        level.current_action_links_positive[Expression("Persistence", clause)] = [clause];
        level.next_action_links[Expression("Persistence", clause)] = [clause];
        level.current_state_links_positive[clause] = [Expression("Persistence", clause)];
        level.next_state_links_positive[clause] = [Expression("Persistence", clause)];
    end
    # Create persistence actions for negated states
    for clause in level.current_state_negated
        not_expression = Expression("not"*clause.operator, clause.arguments);
        level.current_action_links_negated[Expression("Persistence", not_expression)] = [clause];
        level.next_action_links[Expression("Persistence", not_expression)] = [clause];
        level.current_state_links_negated[clause] = [Expression("Persistence", not_expression)];
        level.next_state_links_negated[clause] = [Expression("Persistence", not_expression)];
    end
    # Recursively collect num_arg depth, collecting a Tuple of Tuples
    for action in actions
        local num_arguments::Int64 = length(action.arguments);
        local possible_arguments::AbstractVector = [];
        build_level_links_permute_arguments(num_arguments, collect(objects), (), possible_arguments);
        for argument in possible_arguments
            if (check_precondition(action, level.positive_kb, argument))
                for (number, symbol) in enumerate(action.arguments)
                    if (!all(islowercase, symbol.operator))
                        argument = Tuple((argument[1:(number - 1)]..., symbol, argument[(number + 1):end]...,));
                    end
                end
                local new_action::Expression = substitute(action, Expression(action.name, action.arguments), argument);
                level.current_action_links_positive[new_action] = [];
                level.current_action_links_negated[new_action] = [];
                local new_clause::Expression;
                for clause in action.precondition_positive
                    new_clause = substitute(action, clause, argument);
                    push!(level.current_action_links_positive[new_action], new_clause);
                    if (haskey(level.current_state_links_positive, new_clause))
                        push!(level.current_state_links_positive[new_clause], new_action);
                    else
                        level.current_state_links_positive[new_clause] = [new_action];
                    end
                end
                for clause in action.precondition_negated
                    new_clause = substitute(action, clause, argument);
                    push!(level.current_action_links_negated[new_action], new_clause);
                    if (haskey(level.current_state_links_negated, new_clause))
                        push!(level.current_state_links_negated[new_clause], new_action);
                    else
                        level.current_state_links_negated[new_clause] = [new_action];
                    end
                end
                level.next_action_links[new_action] = [];
                for clause in action.effect_add_list
                    new_clause = substitute(action, clause, argument);
                    push!(level.next_action_links[new_action], new_clause);
                    if (haskey(level.next_state_links_positive, new_clause))
                        push!(level.next_state_links_positive[new_clause], new_action);
                    else
                        level.next_state_links_positive[new_clause] = [new_action];
                    end
                end
                for clause in action.effect_delete_list
                    new_clause = substitute(action, clause, argument);
                    push!(level.next_action_links[new_action], new_clause);
                    if (haskey(level.next_state_links_negated, new_clause))
                        push!(level.next_state_links_negated[new_clause], new_action);
                    else
                        level.next_state_links_negated[new_clause] = [new_action];
                    end
                end
            end
        end
    end
    nothing;
end

function perform_actions(level::PlanningLevel)
    local new_kb_positive::FirstOrderLogicKnowledgeBase = FirstOrderLogicKnowledgeBase(Array{Expression, 1}(collect(Set(collect(keys(level.next_state_links_positive))))));
    local new_kb_negated::FirstOrderLogicKnowledgeBase = FirstOrderLogicKnowledgeBase(Array{Expression, 1}(collect(Set(collect(keys(level.next_state_links_negated))))));
    return PlanningLevel(new_kb_positive, new_kb_negated);
end

function planning_combinations(items::AbstractVector) #ordered permutations of length 2
    local combinations::AbstractVector = [];
    for (i, a) in enumerate(items)
        for b in items[(i + 1):end]
            push!(combinations, (a, b));
        end
    end
    return combinations;
end

#=

    PlanningGraph is an implementation of a planning graph data structure.

    The planning graph is organized into levels based on the given PDDL

    and negated knowledge base.

=#
struct PlanningGraph
    pddl::AbstractPDDL
    levels::Array{PlanningLevel, 1}
    objects::Set{Expression}

    function PlanningGraph(pddl::T, n_kb::FirstOrderLogicKnowledgeBase) where {T <: AbstractPDDL}
        return new(pddl, [PlanningLevel(pddl.kb, n_kb)], Set(collect(arg for clause in vcat(pddl.kb.clauses, n_kb.clauses) for arg in clause.arguments)));
    end
end

function expand_graph(pg::PlanningGraph)
    local last_level = pg.levels[length(pg.levels)];
    build_level_links(last_level, pg.pddl.actions, pg.objects);
    find_mutex_links(last_level);
    push!(pg.levels, perform_actions(last_level));
    nothing;
end

function non_mutex_goals(pg::PlanningGraph, goals::AbstractVector, index::Int64)
    local goal_combinations::AbstractVector = planning_combinations(goals);
    for goal in goal_combinations
        if (index < 0)
            if (Set(collect(goal)) in reverse(pg.levels)[abs(index)].mutex_links)
                return false;
            end
        else
            error("non_mutex_goals(): Expected negative index, got ", index, "!");
        end
    end
    return true;
end

#=

    GraphPlanProblem is a data structure that stores the 'graph', 'nogoods',

    and 'solution' variables of a given Graphplan planning problem.

=#
struct GraphPlanProblem
    graph::PlanningGraph
    nogoods::AbstractVector
    solution::AbstractVector

    function GraphPlanProblem(pddl::AbstractPDDL, n_kb::FirstOrderLogicKnowledgeBase)
        return new(PlanningGraph(pddl, n_kb), [], []);
    end
end

function check_level_off(gpp::GraphPlanProblem)
    local first_check::Bool = (Set(reverse(gpp.graph.levels)[1].current_state_positive) == Set(reverse(gpp.graph.levels)[2].current_state_positive));
    local second_check::Bool = (Set(reverse(gpp.graph.levels)[1].current_state_negated) == Set(reverse(gpp.graph.levels)[2].current_state_negated));
    return (first_check && second_check);
end

function extract_solution(gpp::GraphPlanProblem, goals_positive::AbstractVector, goals_negated::AbstractVector, index::Int64)
    local level::PlanningLevel;
    if (index < 0)
        level = reverse(gpp.graph.levels)[abs(index)];
    else
        error("extract_solution(): Expected negative index, got ", index, "!");
    end
    if (!non_mutex_goals(gpp.graph, vcat(goals_positive, goals_negated), index))
        push!(gpp.nogoods, (level, goals_positive, goals_negated));
        return nothing;
    end
    level = reverse(gpp.graph.levels)[abs(index - 1)];
    local actions::AbstractVector = [];
    for goal in goals_positive
        push!(actions, level.next_state_links_positive[goal]);
    end
    for goal in goals_negated
        push!(actions, level.next_state_links_negated[goal]);
    end

    # Create all possible combinations of actions by using finding the cartesian product.
    local action_combinations::AbstractVector = iterable_cartesian_product(actions);
    # Remove action combinations that contain mutexes.
    local non_mutex_actions::AbstractVector = [];
    for action_list in action_combinations
        action_pairs = planning_combinations(collect(Set(action_list)));
        push!(non_mutex_actions, collect(Set(action_list)));
        for pair in action_pairs
            if (Set(collect(pair)) in level.mutex_links)
                pop!(non_mutex_actions);
                break;
            end
        end
    end

    for action_list in non_mutex_actions
        if (!([action_list, index] in gpp.solution))
            push!(gpp.solution, [action_list, index]);
            local new_goals_positive::AbstractVector = [];
            local new_goals_negated::AbstractVector = [];
            for action in Set(action_list)
                if (haskey(level.current_action_links_positive, action))
                    new_goals_positive = vcat(new_goals_positive, level.current_action_links_positive[action]);
                end
                if (haskey(level.current_action_links_negated, action))
                    new_goals_negated = vcat(new_goals_negated, level.current_action_links_negated[action]);
                end
            end
            if ((abs(index) + 1) == length(gpp.graph.levels))
                return nothing;
            elseif ((level, new_goals_positive, new_goals_negated) in gpp.nogoods)
                return nothing;
            else
                extract_solution(gpp, new_goals_positive, new_goals_negated, (index - 1))
            end
        end
    end
    local solution::AbstractVector = [];
    for item in gpp.solution
        if (item[2] == -1)
            push!(solution, [item[1]]);
        else
            push!(reverse(solution)[1], item[1]);
        end
    end
    for (i, item) in enumerate(solution)
        solution[i] = reverse(item);
    end
    return solution;
end

function goal_test(gpp::GraphPlanProblem, goals::AbstractVector)    #goal_test() for graphplan()
    local positive_kb::FirstOrderLogicKnowledgeBase = reverse(gpp.graph.levels)[1].positive_kb;
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   # length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(positive_kb, goal) for goal in goals));
end

"""
    graphplan(gpp::GraphPlanProblem, goals::Tuple)

Apply the Graphplan algorithm (Fig. 10.9) to the given planning problem 'gpp' and goal state 'goals'.
Return the solution or 'nothing' on failure.
"""
function graphplan(gpp::GraphPlanProblem, goals::Tuple)
    local goals_positive::AbstractVector = goals[1];
    local goals_negated::AbstractVector = goals[2];
    while (true)
        if (goal_test(gpp, goals_positive) && non_mutex_goals(gpp.graph, vcat(goals_positive, goals_negated), -1))
            solution = extract_solution(gpp, goals_positive, goals_negated, -1);
            if (!(typeof(solution) <: Nothing))
                return solution;
            end
        end
        expand_graph(gpp.graph);
        if ((length(gpp.graph.levels) > 1) && check_level_off(gpp))
            return nothing;
        end
    end
    return nothing;
end

function doubles_tennis_pddl_goal_test(kb::FirstOrderLogicKnowledgeBase)
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   #length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(kb, q) for q in map(expr, ["Returned(Ball)", "At(a, RightNet)", "At(a, LeftNet)"])));
end

"""
    doubles_tennis_pddl()

Return a PDDL representing the doubles tennis planning problem (Fig. 11.10).

The PlanningActions 'hit' and 'go' both take 3 arguments because their
preconditions will not be satisfied without the 'loc' variable.
This occurs when substituting propositional logic variables within
the check_precondition() function.
"""
function doubles_tennis_pddl()
    local initial::Array{Expression, 1} = map(expr, ["At(A, LeftBaseLine)",
                                                    "At(B, RightNet)",
                                                    "Approaching(Ball, RightBaseLine)",
                                                    "Partner(A, B)",
                                                    "Partner(B, A)"]);
    # Hit Action Schema
    local precondition_positive::Array{Expression, 1} = map(expr, ["Approaching(Ball, loc)", "At(actor, loc)"]);
    local precondition_negated::Array{Expression, 1} = [];
    local effect_add_list::Array{Expression, 1} = [expr("Returned(Ball)")];
    local effect_delete_list::Array{Expression, 1} = [];
    local hit::PlanningAction = PlanningAction(expr("Hit(actor, loc, Ball)"),
                                                (precondition_positive, precondition_negated),
                                                (effect_add_list, effect_delete_list));
    # Go Action Schema
    precondition_positive = [expr("At(actor, loc)")];
    precondition_negated = [];
    effect_add_list = [expr("At(actor, to)")];
    effect_delete_list = [expr("At(actor, loc)")];
    local go::PlanningAction = PlanningAction(expr("Go(actor, loc, to)"),
                                            (precondition_positive, precondition_negated),
                                            (effect_add_list, effect_delete_list));
    return PDDL(initial, [hit, go], doubles_tennis_pddl_goal_test);
end

#=

    PlanningHighLevelAction (HLA) is an action schema with resource constraints and refinements.

=#
mutable struct PlanningHighLevelAction <: AbstractPlanningAction
    name::String
    arguments::Tuple
    precondition_positive::Array{Expression, 1}
    precondition_negated::Array{Expression, 1}
    effect_add_list::Array{Expression, 1}
    effect_delete_list::Array{Expression, 1}
    duration::Int64
    consumes::Dict
    uses::Dict
    completed::Bool
    priority::Int64     #undefined field used to schedule multiple HLAs
    job_group::Int64    #undefined field used to schedule multiple HLAs

    function PlanningHighLevelAction(action::Expression,
                                    precondition::Tuple{Vararg{Array{Expression, 1}, 2}},
                                    effect::Tuple{Vararg{Array{Expression, 1}, 2}};
                                    duration::Int64=0,
                                    consumes::Dict=Dict(),
                                    uses::Dict=Dict())
        return new(action.operator, action.arguments, precondition[1], precondition[2], effect[1], effect[2], duration, consumes, uses, false);
    end
end

"""
    execute_action(hla, job_order, available_resources, kb, arguments)

Execute the given action 'hla' on the knowledge base of the planning problem.
This function checks for resources and job order execution required for the action to be executed.
"""
function execute_action(hla::PlanningHighLevelAction, job_order::Array{Array{PlanningHighLevelAction, 1}, 1}, available_resources::Dict, kb::AbstractKnowledgeBase, arguments::Tuple)
    if (!check_consumes_resource(hla, available_resources))
        error(@sprintf("execute_action(): High-level action \"%s\"'s consumable constraint was not satisfied!", hla.name));
    elseif (!check_uses_resource(hla, available_resources))
        error(@sprintf("execute_action(): High-level action \"%s\"'s reusable constraint was not satisfied!", hla.name));
    elseif (!check_job_order(hla, job_order))
        error(@sprintf("execute_action(): High-level action \"%s\" is not scheduled to execute! All scheduled unexecuted actions should run first!", hla.name));
    end

    # Execute 'hla' like a primitive action within this method to avoid confusion of
    # calling execute_action() within execute_action().

    if (!(check_precondition(hla, kb, arguments)))
        error(@sprintf("execute_action(): High-level action \"%s\" preconditions are not satisfied!", hla.name));
    end
    # Retract negated literals to knowledge base 'kb'.
    for clause in hla.effect_delete_list
        retract(kb, substitute(hla, clause, arguments));
    end
    # Add positive literals to knowledge base 'kb'.
    for clause in hla.effect_add_list
        tell(kb, substitute(hla, clause, arguments));
    end

    for resource in keys(hla.consumes)
        available_resources[resource] = available_resources[resource] - hla.consumes[resource];
    end

    # Update current high-level action task as completed.
    hla.completed = true;
    nothing;
end

function check_consumes_resource(hla::PlanningHighLevelAction, available_resources::Dict)
    for resource in keys(hla.consumes)
        if (!haskey(available_resources, resource))
            return false;
        elseif (available_resources[resource] < (hla.consumes)[resource])
            return false;
        end
    end
    return true;
end

function check_uses_resource(hla::PlanningHighLevelAction, available_resources::Dict)
    for resource in keys(hla.uses)
        if (!haskey(available_resources, resource))
            return false;
        elseif (available_resources[resource] < (hla.uses)[resource])
            return false;
        end
    end
    return true;
end

function check_job_order(hla::PlanningHighLevelAction, job_order::Array{Array{PlanningHighLevelAction, 1}, 1})
    for jobs in job_order
        if (hla in jobs)
            for job in jobs
                if (job == hla)
                    return true;
                elseif (!job.completed)
                    return false;
                end
            end
        end
    end
    return true;
end

#=

    HighLevelPDDL is a high-level Planning Domain Definition Language (PDDL) and is used to

    define a search problem. The HighLevelPDDL includes job scheduling and resource constraints

    in addition to the states, action schemas, and the goal test found in the PDDL datatype.

=#
struct HighLevelPDDL <: AbstractPDDL
    kb::FirstOrderLogicKnowledgeBase
    actions::Array{PlanningHighLevelAction, 1}
    goal_test::Function
    jobs::Array{Array{PlanningHighLevelAction, 1}, 1}
    resources::Dict

    function HighLevelPDDL(initial_state::Array{Expression, 1}, actions::Array{PlanningHighLevelAction, 1}, goal_test::Function; jobs::Array{Array{PlanningHighLevelAction, 1}, 1}=Array{Array{PlanningHighLevelAction, 1}, 1}(), resources::Dict=Dict())
        return new(FirstOrderLogicKnowledgeBase(initial_state), actions, goal_test, jobs, resources);
    end
end

"""
    execute_action(plan, action)

Execute the first relevant PlanningHighLevelAction associated with action 'action'
on the given planning problem's knowledge base.
"""
function execute_action(plan::HighLevelPDDL, action::PlanningHighLevelAction)
    local arguments::Tuple = action.arguments;
    local relevant_actions::AbstractVector = collect(a for a in plan.actions if (a.name == action.name));
    if (length(relevant_actions) == 0)
        error(@sprintf("execute_action(): High-level action \"%s\" not found!", action.name));
    else
        local first_relevant_action::PlanningHighLevelAction = relevant_actions[1];
        execute_action(first_relevant_action, plan.jobs, plan.resources, plan.kb, arguments);
    end
    nothing;
end

"""
    get_result(plan, action)

Return the planning problem after executing the given high-level action 'action'.
"""
function get_result(plan::HighLevelPDDL, action::PlanningHighLevelAction)
    execute_action(plan, action);
    return plan;
end

function get_result(plan::HighLevelPDDL, action::Nothing)
    return plan;
end

"""
    refinements(hla, state, dict)

Return a action sequence such that each refinement's preconditions of 'hla' are satisfied by
the given state of the planning problem 'state'.
"""
function refinements(hla::PlanningHighLevelAction, state::HighLevelPDDL, dict::Dict)
    local solution::Array{PlanningHighLevelAction, 1} = Array{PlanningHighLevelAction, 1}();
    local indices::AbstractVector = collect(i for (i, x) in enumerate(dict["HLA"]) if (expr(x).operator == hla.name));
    for i in indices
        local action::PlanningHighLevelAction = PlanningHighLevelAction(expr(dict["steps"][i][1]),
                                                                        (collect(expr(x) for x in dict["precondition_positive"][i]), collect(expr(x) for x in dict["precondition_negated"][i])),
                                                                        (collect(expr(x) for x in dict["effect_add_list"][i]), collect(expr(x) for x in dict["effect_delete_list"][i])));
        if (check_precondition(hla, state.kb, action.arguments))
            push!(solution, action);
        end
    end
    return solution;
end

"""
    hierarchical_search(problem, hierarchy)

Use the breadth-first implementation of the Hierarchical Search algorithm (Fig. 11.5) to the
given high-level planning problem 'problem' and 'hierarchy'. Return the solution or 'nothing'
on failure.
"""
function hierarchical_search(problem::HighLevelPDDL, hierarchy)
    local act::Node = Node(problem.actions[1]);
    local frontier::FIFOQueue();
    push!(frontier, act);
    while (true)
        if (length(frontier) == 0)
            return nothing;
        end
        local plan = pop!(frontier);
        hla = plan.state;
        if (!(plan.parent === nothing))
            prefix = plan.parent.action;
        else
            prefix = nothing;
        end
        local outcome::HighLevelPDDL = get_result(problem, prefix);
        if (typeof(hla) <: Nothing)
            if (goal_test(outcome))
                return path(plan);
            end
        else
            for sequence in refinements(hla, outcome, hierachy)
                push!(frontier, Node(plan.state, parent=plan.parent, action=sequence));
            end
        end
    end
    nothing;
end

function job_shop_scheduling_pddl_goal_test(kb::FirstOrderLogicKnowledgeBase)
    return all((function(ans)
                    if (typeof(ans) <: Bool)
                        return ans;
                    else
                        if (length(ans) == 0)   # length of Tuple
                            return false;
                        else
                            return true;
                        end
                    end
                end),
                collect(ask(kb, q) for q in map(expr, ["Has(C1, W1)", "Has(C1, E1)", "Inspected(C1)",
                                                        "Has(C2, W2)", "Has(C2, E2)", "Inspected(C2)",
                                                        "Car(C1)", "Car(C2)",
                                                        "Wheels(W1)", "Wheels(W2)",
                                                        "Engine(E1)", "Engine(E2)"])));
end

"""
    job_shop_scheduling_pddl()

Return a HighLevelPDDL representing the job-shop scheduling planning problem (Fig. 11.1).
"""
function job_shop_scheduling_pddl()
    local initial::Array{Expression, 1} = map(expr, ["Car(C1)", "Car(C2)",
                                                    "Wheels(W1)", "Wheels(W2)",
                                                    "Engine(E1)", "Engine(E2)"]);
    local resources::Dict = Dict([Pair("EngineHoists", 1),
                                Pair("WheelStations", 2),
                                Pair("Inspectors", 2),
                                Pair("LugNuts", 500)]);
    # AddEngine1 high-level action schema
    local precondition_positive::Array{Expression, 1} = [];
    local precondition_negated::Array{Expression, 1} = [expr("Has(C1, E1)")];
    local effect_add_list::Array{Expression, 1} = [expr("Has(C1, E1)")];
    local effect_delete_list::Array{Expression, 1} = [];
    local add_engine_1::PlanningHighLevelAction = PlanningHighLevelAction(expr("AddEngine1"),
                                                                        (precondition_positive, precondition_negated),
                                                                        (effect_add_list, effect_delete_list),
                                                                        duration=30, uses=Dict([Pair("EngineHoists", 1)]));
    # AddEngine2 high-level action schema
    precondition_positive = [];
    precondition_negated = [expr("Has(C2, E2)")];
    effect_add_list = [expr("Has(C2, E2)")];
    effect_delete_list = [];
    local add_engine_2::PlanningHighLevelAction = PlanningHighLevelAction(expr("AddEngine2"),
                                                                        (precondition_positive, precondition_negated),
                                                                        (effect_add_list, effect_delete_list),
                                                                        duration=60, uses=Dict([Pair("EngineHoists", 1)]));
    # AddWheels1 high-level action schema
    precondition_positive = [];
    precondition_negated = [expr("Has(C1, W1)")];
    effect_add_list = [expr("Has(C1, W1)")];
    effect_delete_list = [];
    local add_wheels_1::PlanningHighLevelAction = PlanningHighLevelAction(expr("AddWheels1"),
                                                                        (precondition_positive, precondition_negated),
                                                                        (effect_add_list, effect_delete_list),
                                                                        duration=30, consumes=Dict([Pair("LugNuts", 20)]), uses=Dict([Pair("WheelStations", 1)]));
    # AddWheels2 high-level action schema
    precondition_positive = [];
    precondition_negated = [expr("Has(C2, W2)")];
    effect_add_list = [expr("Has(C2, W2)")];
    effect_delete_list = [];
    local add_wheels_2::PlanningHighLevelAction = PlanningHighLevelAction(expr("AddWheels2"),
                                                                        (precondition_positive, precondition_negated),
                                                                        (effect_add_list, effect_delete_list),
                                                                        duration=15, consumes=Dict([Pair("LugNuts", 20)]), uses=Dict([Pair("WheelStations", 1)]));
    # Inspect1 high-level action schema
    precondition_positive = [];
    precondition_negated = [expr("Inspected(C1)")];
    effect_add_list = [expr("Inspected(C1)")];
    effect_delete_list = [];
    local inspect_1::PlanningHighLevelAction = PlanningHighLevelAction(expr("Inspect1"),
                                                                        (precondition_positive, precondition_negated),
                                                                        (effect_add_list, effect_delete_list),
                                                                        duration=10, uses=Dict([Pair("Inspectors", 1)]));
    # Inspect2 high-level action schema
    precondition_positive = [];
    precondition_negated = [expr("Inspected(C2)")];
    effect_add_list = [expr("Inspected(C2)")];
    effect_delete_list = [];
    local inspect_2::PlanningHighLevelAction = PlanningHighLevelAction(expr("Inspect2"),
                                                                        (precondition_positive, precondition_negated),
                                                                        (effect_add_list, effect_delete_list),
                                                                        duration=10, uses=Dict([Pair("Inspectors", 1)]));
    local job_group_1::Array{PlanningHighLevelAction, 1} = [add_engine_1, add_wheels_1, inspect_1];
    local job_group_2::Array{PlanningHighLevelAction, 1} = [add_engine_2, add_wheels_2, inspect_2];
    return HighLevelPDDL(initial, [add_engine_1, add_engine_2, add_wheels_1, add_wheels_2, inspect_1, inspect_2], job_shop_scheduling_pddl_goal_test, jobs=[job_group_1, job_group_2], resources=resources);
end

max_stars_repo_name: mikhail-j/aima-julia

max_issues_repo_name: mikhail-j/aima-julia

max_forks_repo_name: mikhail-j/aima-julia


text:
using Test
using BinaryBuilderBase
using BinaryBuilderBase: coerce_source, sourcify, SetupSource, setup
using JSON

@testset "Sources" begin
    @test ArchiveSource("https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz", "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e").unpack_target == ""
    @test ArchiveSource("https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz", "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"; unpack_target = "wget").unpack_target == "wget"
    @test GitSource("https://github.com/jedisct1/libsodium.git", "5b2ea7d73d3ffef2fb93b82b9f112f009d54c6e6").unpack_target == ""
    @test GitSource("https://github.com/jedisct1/libsodium.git", "5b2ea7d73d3ffef2fb93b82b9f112f009d54c6e6"; unpack_target = "libs").unpack_target == "libs"
    @test FileSource("https://curl.haxx.se/ca/cacert-2020-01-01.pem", "adf770dfd574a0d6026bfaa270cb6879b063957177a991d453ff1d302c02081f").filename == "cacert-2020-01-01.pem"
    @test FileSource("https://curl.haxx.se/ca/cacert-2020-01-01.pem", "adf770dfd574a0d6026bfaa270cb6879b063957177a991d453ff1d302c02081f"; filename="cacert.pem").filename == "cacert.pem"

    @test SetupSource("https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz", "", "", "") isa SetupSource{ArchiveSource}
    @test SetupSource("https://ftp.gnu.org/gnu/wget/wget-1.20.3.zip", "", "", "")    isa SetupSource{ArchiveSource}
    @test SetupSource("https://github.com/jedisct1/libsodium.git", "", "", "")       isa SetupSource{GitSource}
    @test SetupSource("https://curl.haxx.se/ca/cacert-2020-01-01.pem", "", "", "")   isa SetupSource{FileSource}

    @testset "Download and setup" begin
        mktempdir() do dir
            cd(dir) do
                as = ArchiveSource("https://github.com/ralna/ARCHDefs/archive/v2.0.3x.tar.gz", "6583e27f84338447767bbdf4335514c8836ae4ad54f5e66280307e8b57189cff")
                # Download the source
                sas = @test_logs (:info, r"Downloading .* to.*") download_source(as; verbose = true, downloads_dir = dir)
                # Check that the cache is found
                @test @test_logs (:info, r"Cached file found in .*") download_source(as; verbose = true, downloads_dir = dir) == sas
                fs = FileSource("https://github.com/ralna/ARCHDefs/archive/v2.0.3x.tar.gz", "6583e27f84338447767bbdf4335514c8836ae4ad54f5e66280307e8b57189cff"; filename = "file-source.tar.gz")
                # Re-fetch the same tarball, as a `FileSource` this time
                sfs = @test_logs (:info, r"Cached file found in .*") download_source(fs; verbose = true, downloads_dir = dir)
                gs = GitSource("https://github.com/ralna/ARCHDefs.git", "fc8c5960c3a6d26970ab245241cfc067fe4ecfdd")
                # Clone the repo once
                sgs = @test_logs (:info, r"^Cloning") download_source(gs; verbose = true, downloads_dir = dir)
                # Fetch again the repo to make sure cache works
                @test @test_logs (:info, r"^Cached repository found in") download_source(gs; verbose = true, downloads_dir = dir) == sgs
                # Fetch the temp directory as a `DirectorySource`
                ds_follow = DirectorySource("./bundled_follow"; follow_symlinks=true)
                patchdir = abspath(joinpath(dir, ds_follow.path, "patches_follow"))
                mkpath(patchdir)
                write(abspath(joinpath(patchdir, "fix-windows-headers.patch")), "This is a patch file")
                # Create a symlink.  We'll want to check that `setup` follows symlinks
                link = joinpath(patchdir, "link.patch")
                symlink("fix-windows-headers.patch", link)
                @test islink(link)
                sds_follow = @test_logs (:info, r"^Directory .* found") download_source(ds_follow; verbose = true)
                # Try to fetch a non-existing directory
                @test_throws ErrorException download_source(DirectorySource(joinpath(dir, "does_not_exist")); verbose = true)
                # Another directory source, which doesn't follow symlinks
                ds_nofollow = DirectorySource("./bundled_nofollow")
                patchdir = abspath(joinpath(dir, ds_nofollow.path, "patches_nofollow"))
                mkpath(patchdir)
                write(abspath(joinpath(patchdir, "fix-windows-headers.patch")), "This is a patch file")
                # Create a symlink.  We'll want to check that `setup` follows symlinks
                link = joinpath(patchdir, "link.patch")
                symlink("fix-windows-headers.patch", link)
                @test islink(link)
                sds_nofollow = @test_logs (:info, r"^Directory .* found") download_source(ds_nofollow; verbose = true)
                # Yet another directory source, this time one that tries to unpack into a subdirectory
                ds_target = DirectorySource("./bundled_subdir_target", target="ds_unpack_target")
                filedir = mkpath(abspath(joinpath(dir, ds_target.path)))
                write(abspath(joinpath(filedir, "fix-windows-headers.patch")), "This is a patch file")
                sds_target = @test_logs (:info, r"^Directory .* found") download_source(ds_target; verbose = true)
                # Try to fetch a non-existing directory
                @test_throws ErrorException download_source(DirectorySource(joinpath(dir, "does_not_exist")); verbose = true)

                # Setup the sources with `setup`
                srcdir = joinpath(dir, "srcdir")
                target = joinpath(srcdir, as.unpack_target)
                @test_logs (:info, r"^Extracting tarball") setup(sas, target, true; tar_flags = "xof")
                @test isdir(target)
                target = joinpath(srcdir, fs.filename)
                @test_logs (:info, r"^Copying") setup(sfs, target, true)
                @test isfile(target)
                target = joinpath(srcdir, gs.unpack_target)
                @test_logs (:info, "Cloning ARCHDefs.git to ARCHDefs...") setup(sgs, target, true)
                @test isdir(target)
                # Setup directory source with links to follow
                target = abspath(joinpath(srcdir, "patches_follow"))
                @test_logs (:info, "Copying content of bundled_follow in srcdir...") setup(sds_follow, srcdir, true)
                @test isdir(target)
                # Make sure that the symlinks are followed
                @test isfile(joinpath(target, "link.patch"))
                @test !islink(joinpath(target, "link.patch"))
                # Setup directory source with links to not follow
                target = abspath(joinpath(srcdir, "patches_nofollow"))
                @test_logs (:info, "Copying content of bundled_nofollow in srcdir...") setup(sds_nofollow, srcdir, true)
                @test isdir(target)
                # Make sure that the symlinks are not followed
                @test isfile(joinpath(target, "link.patch"))
                @test islink(joinpath(target, "link.patch"))

                # Make sure in srcdir there are all files and directories we expect
                @test Set(readdir(srcdir)) == Set(["ARCHDefs", "ARCHDefs-2.0.3x", fs.filename, "patches_follow", "patches_nofollow"])

                # Setup the sources with `setup_workspace`
                workspace = joinpath(dir, "workspace")
                mkpath(workspace)
                prefix = @test_logs(
                    (:info, r"^Copying"), (:info, r"^Copying"), (:info, r"^Copying"), (:info, r"^Copying"), (:info, r"^Cloning"),
                    setup_workspace(workspace, [sfs, sds_follow, sds_nofollow, sds_target, sgs], HostPlatform(); verbose=true)
                )
                @test Set(readdir(joinpath(prefix.path, "srcdir"))) == Set(
                    ["ARCHDefs", "file-source.tar.gz", "patches_follow", "patches_nofollow", "ds_unpack_target"]
                )
                @test isfile(joinpath(prefix.path, "srcdir", "ds_unpack_target", "fix-windows-headers.patch"))
            end
        end
    end

    as = ArchiveSource("https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz", "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e")
    fs = FileSource("https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz.sig", "7b295c84ab6f90c328a203e234e4b2f5f45cb8d2e29eac43a977073933cd49a2")
    gs = GitSource("https://github.com/jedisct1/libsodium.git", "5b2ea7d73d3ffef2fb93b82b9f112f009d54c6e6")
    ds = DirectorySource("./bundled")

    @testset "JSON (de)serialization" begin
        jas = JSON.lower(as)
        @test jas == Dict("type" => "archive", "url" => as.url, "hash" => as.hash, "unpack_target" => as.unpack_target)
        @test sourcify(jas) == as
        jfs = JSON.lower(fs)
        @test jfs == Dict("type" => "file", "url" => fs.url, "hash" => fs.hash, "filename" => fs.filename)
        @test sourcify(jfs) == fs
        jgs = JSON.lower(gs)
        @test jgs == Dict("type" => "git", "url" => gs.url, "hash" => gs.hash, "unpack_target" => gs.unpack_target)
        @test sourcify(jgs) == gs
        jds = JSON.lower(ds)
        @test jds == Dict("type" => "directory", "path" => ds.path, "target" => "", "follow_symlinks" => false)
        @test sourcify(jds) == ds

        @test_throws ErrorException sourcify(Dict("type" => "error"))
    end

    @testset "Compat" begin
        @test coerce_source(as) == as
        @test @test_logs (:warn, "Using a pair as source is deprecated, use ArchiveSource instead") coerce_source(as.url => as.hash) == as
        @test coerce_source(fs) == fs
        @test @test_logs (:warn, "Using a pair as source is deprecated, use FileSource instead") coerce_source(fs.url => fs.hash) == fs
        @test coerce_source(gs) == gs
        @test @test_logs (:warn, "Using a pair as source is deprecated, use GitSource instead") coerce_source(gs.url => gs.hash) == gs
        @test coerce_source(ds) == ds
        @test @test_logs (:warn, "Using a string as source is deprecated, use DirectorySource instead") coerce_source(ds.path) == ds
    end
end

ext: jl


text:
function batchplot(As, Bs, save_root_folder, molecule_names, compound_select,
    display_reduction_factor, display_threshold_factor;
    canvas_size = (1000, 400),
    display_flag = false)

    A = As[compound_select]
    B = Bs[compound_select]
    name = molecule_names[compound_select]

    # create the functions for each resonance group.
    qs = collect( collect( ωω->B.qs[i][k](ωω-B.ss_params.d[i], B.ss_params.κs_λ[i]) for k = 1:length(B.qs[i]) ) for i = 1:length(B.qs) )
    q_singlets = ωω->NMRSpectraSimulator.evalsinglets(ωω, B.d_singlets, A.αs_singlets, A.Ωs_singlets, B.β_singlets, B.λ0, B.κs_λ_singlets)

    # create the function for the entire compound.
    q = uu->NMRSpectraSimulator.evalitpproxymixture(uu, As[compound_select:compound_select], Bs[compound_select:compound_select])

    # evaluate at the plotting positions.
    q_U = q.(U_rad)

    qs_U = collect( collect( qs[i][k].(U_rad) for k = 1:length(qs[i]) ) for i = 1:length(qs) )
    q_singlets_U = q_singlets.(U_rad)

    # sanity check.
    q_check_U = q_singlets_U
    if !isempty(qs) # some compounds only have singlets.
        q_check_U += sum( sum( qs[i][k].(U_rad) for k = 1:length(qs[i]) ) for i = 1:length(qs) )
    end
    discrepancy = norm(q_check_U- q_U)
    println("sanity check. This should be numerically zero: ", discrepancy)

    # reduce the plotting positions for low signal regions. Otherwise the plot store size will be too large, and the time to load the plot will be long.
    inds, _ = NMRSpectraSimulator.prunelowsignalentries(q_U, display_threshold_factor, display_reduction_factor)
    P_display = P[inds]
    U_display = U[inds]

    # plot.
    save_folder = joinpath(save_root_folder, molecule_names[compound_select])
    isdir(save_folder) || mkpath(save_folder)

    plots_save_path = joinpath(save_folder, "groups_real.html")
    title_string = "$(name) resonance groups, real part"
    plot_obj, q_U, qs_U, q_singlets_U = plotgroups(title_string, P_display, U_display, q, qs, q_singlets, real, P[1]; canvas_size = canvas_size)
    Plots.savefig(plot_obj, plots_save_path)

    if display_flag
        display(plot_obj)
    end

    return nothing
end

"""
save resonance groupings of a compound.
Choices for `f`` are: `real()`, `imag()`, or `abs()`. These corresponds to real part, imaginary part, and magnitude spectrum, respectively.
"""
function plotgroups(title_string::String,
    P,
    U,
    q,
    qs,
    q_singlets,
    f::Function,
    return_val_type::T;
    canvas_size::Tuple{Int,Int} = (1000, 400)) where T

    U_rad = U .* (2*π)

    q_U = q.(U_rad)
    plot_obj = Plots.plot( P,
        f.(q_U),
        title = title_string,
        label = "sum of sub-models",
        seriestype = :line,
        ticks = :native,
        xlims = (P[1],P[end]),
        hover = P,
        linewidth = 4,
        xflip = true,
        size = canvas_size)

    qs_U = Vector{Vector{Vector{Complex{T}}}}(undef, length(qs))
    for i = 1:length(qs)

        qs_U[i] = Vector{Vector{Complex{T}}}(undef, length(qs[i]))
        for k = 1:length(qs[i])

            qs_U[i][k] = qs[i][k].(U_rad)

            Plots.plot!(plot_obj, P, f.(qs_U[i][k]), label = "sub-group ($(i),$(k))",
                seriestype = :line,
                linestyle = :dot,
                xflip = true,
                linewidth = 4)
        end
    end

    q_singlets_U = q_singlets.(U_rad)
    Plots.plot!(plot_obj, P, f.(q_singlets_U), label = "group of all singlets",
        seriestype = :line,
        linestyle = :dot,
        xflip = true,
        linewidth = 4)
    #
    #Plots.plot!(plot_obj, P, f.(q_U),
    #markershape = :circle,
    #seriestype = :scatter,
    #xflip = true)

    return plot_obj, q_U, qs_U, q_singlets_U
end

println("Timing fitproxies()")
@time Bs = NMRSpectraSimulator.fitproxies(As, dummy_SSFID, λ0;
    #names = molecule_names[compound_select:compound_select],
    names = molecule_names,
    config_path = surrogate_config_path,
    Δcs_max_scalar_default = Δcs_max_scalar_default,
    κ_λ_lb_default = κ_λ_lb_default,
    κ_λ_ub_default = κ_λ_ub_default,
    u_min = u_min,
    u_max = u_max,
    Δr_default = Δr_default,
    Δκ_λ_default = Δκ_λ_default)

for compound_select = 1:length(As)

    display_reduction_factor = 100
    display_threshold_factor =  0.05/10

    batchplot(As, Bs, save_root_folder, molecule_names, compound_select,
        display_reduction_factor, display_threshold_factor;
        canvas_size = (1000, 400),
        display_flag = false)
end

# display number of resonance groups per spin system that aren't singlets.
for n = 1:length(Bs)
    B = Bs[n]
    qs_lens = collect( length(B.qs[i]) for i = 1:length(B.qs) )

    println("$(n). $(molecule_names[n]), qs_lens: ", qs_lens)
end


text:
@testset "lineartransform" begin
    rng = MersenneTwister(123546)

    @testset "Real inputs" begin
        P = randn(rng, 3, 1)
        t = LinearTransform(P)

        x = randn(rng, 4)
        x′ = map(t, x)

        @test all([t(x[n]) ≈ P * x[n] for n in eachindex(x)])
        @test all([t(x[n]) ≈ x′[n] for n in eachindex(x)])
    end

    @testset "Vector inputs" begin
        Din = 3
        Dout = 4
        P = randn(rng, Dout, Din)
        t = LinearTransform(P)

        x_vecs = [randn(rng, Din) for _ in 1:6]
        x_cols = ColVecs(randn(rng, Din, 8))
        x_rows = RowVecs(randn(rng, 9, Din))

        @testset "$(typeof(x))" for x in [x_vecs, x_cols, x_rows]
            x′ = map(t, x)
            @test all([t(x[n]) ≈ P * x[n] for n in eachindex(x)])
            @test all([t(x[n]) ≈ x′[n] for n in eachindex(x)])
        end
    end

    Din = 2
    Dout = 5
    P = randn(rng, Dout, Din)
    t = LinearTransform(P)

    P2 = randn(rng, Dout, Din)
    KernelFunctions.set!(t, P2)
    @test t.A == P2
    @test_throws ErrorException KernelFunctions.set!(t, rand(rng, Din + 1, Dout))

    @test_throws DimensionMismatch map(t, ColVecs(randn(rng, Din + 1, Dout)))

    @test repr(t) == "Linear transform (size(A) = ($Dout, $Din))"
    test_ADs(x->transform(SEKernel(), LinearTransform(x)), randn(rng, 3, 3))
end


text:
@doc raw"""
```
t = find_threshold(MinimumError(), histogram, edges)
```

Under the assumption that the histogram is a mixture of two Gaussian
distributions the binarization threshold is chosen such that the expected
misclassification error rate is minimised.

# Output

Returns the binarized image as an `Array{Gray{Bool},2}`.

# Details

Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the
``i``th bin of the histogram. Then the probability that an observation
belongs to the ``i``th bin is given by  ``p_i = \frac{f_i}{N}`` (``i = 1,
\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``.

The minimum error thresholding method assumes that one can find a threshold
``T`` which partitions the data into two categories,  ``C_0`` and ``C_1``, such that
the data can be modelled by a mixture of two Gaussian distribution. Let
```math
P_0(T) = \sum_{i = 1}^T p_i \quad \text{and} \quad P_1(T) = \sum_{i = T+1}^I p_i
```
denote the cumulative probabilities,
```math
\mu_0(T) = \sum_{i = 1}^T i \frac{p_i}{P_0(T)} \quad \text{and} \quad \mu_1(T) = \sum_{i = T+1}^I i \frac{p_i}{P_1(T)}
```
denote the means, and
```math
\sigma_0^2(T) = \sum_{i = 1}^T (i-\mu_0(T))^2 \frac{p_i}{P_0(T)} \quad \text{and} \quad \sigma_1^2(T) = \sum_{i = T+1}^I (i-\mu_1(T))^2 \frac{p_i}{P_1(T)}
```
denote the variances of categories ``C_0`` and ``C_1``, respectively.

Kittler and Illingworth proposed to use the minimum error criterion function
```math
J(T) = 1 + 2 \left[ P_0(T) \ln \sigma_0(T) + P_1(T) \ln \sigma_1(T) \right] - 2 \left[P_0(T) \ln P_0(T) + P_1(T) \ln P_1(T) \right]
```
to assess the discreprancy between the mixture of Gaussians implied by a particular threshold ``T``,
and the piecewise-constant probability density function represented by the histogram.
The discrete value ``T`` which minimizes the function ``J(T)`` produces
the sought-after threshold value (i.e. the bin which determines the threshold).

# Arguments

The function argument is described in more detail below.

##  `img`

An `AbstractArray` representing an image. The image is automatically converted
to `Gray` in order to construct the requisite graylevel histogram.

# Example

Binarize the "cameraman" image in the `TestImages` package.

```julia
using TestImages, ImageBinarization

img = testimage("cameraman")
img_binary = binarize(MinimumError(), img)
```

# References

1. J. Kittler and J. Illingworth, “Minimum error thresholding,” Pattern Recognition, vol. 19, no. 1, pp. 41–47, Jan. 1986. [doi:10.1016/0031-3203(86)90030-0](https://doi.org/10.1016/0031-3203%2886%2990030-0)
2. Q.-Z. Ye and P.-E. Danielsson, “On minimum error thresholding and its implementations,” Pattern Recognition Letters, vol. 7, no. 4, pp. 201–206, Apr. 1988. [doi:10.1016/0167-8655(88)90103-1](https://doi.org/10.1016/0167-8655%2888%2990103-1)
"""
function binarize(algorithm::MinimumError,  img::AbstractArray{T,2}) where T <: Colorant
  img₀₁ = zeros(Gray{Bool}, axes(img))
  edges, counts = build_histogram(img,  256)
  t = find_threshold(HistogramThresholding.MinimumError(), counts[1:end], edges)
  for i in CartesianIndices(img)
    img₀₁[i] = img[i] < t ? 0 : 1
  end
  img₀₁
end


text:
using ValueOrientedRiskManagementInsurance
using DataFrames
using Base.Test

include("ECModel.jl")

println("Start ECModel test")

@test_approx_eq(round(total.gross.profit_mean, 2), 293.08)
@test_approx_eq(round(total.net.profit_mean, 2), 249.39)
@test_approx_eq(round(total.gross.eco_cap, 2), 1096.71)
@test_approx_eq(round(total.net.eco_cap, 2), 823.70)
@test_approx_eq(round(total.gross.rorac, 3), 0.267)
@test_approx_eq(round(total.net.rorac, 3), 0.303)
## risk adjusted pricing:
@test_approx_eq(round(ins_input_rp[1, :loss_ratio], 4), 0.7684)
@test_approx_eq(round(ins_input_rp[2, :loss_ratio], 4), 0.7158)
@test_approx_eq(round(ins_input_rp[3, :loss_ratio], 4), 0.7500)
## capital optimization
@test_approx_eq(round(rorac_net_oc[i_oc_opt], 4), 0.2690)
## product mix optimization, chosen quote f
@test_approx_eq(round(avg_ceded_ofr, 4), 0.4928)
@test_approx_eq(round(rorac_net_or_opt, 4), 0.3376)

println("End ECModel test")


text:
#function multiline(type,s::Real,d::Real,n::Real)
#    for i in s:d:n
#        col(type,T,colmatch,i)
#    end
#end

"""
`col(type,lum::LSpec,whitepoint::Color3,colmatch,dl)`

This function creates the color loci of the theoretically maximum color gamut under a given illuminant.

type: color space

lum: illuminant

whitepoint: XYZ whitepoint

colmatch: color matching function

dl: delta lambda

output: a,b,c (locus vectors in given color space)

        x,y,Y for Yxy

        X,Z,Y for XYZ

        a,b,L Lab, DIN99, DIN99d, DIN99o

        u,v,L for Luv
"""
function col(type,lum::LSpec,whitepoint::Color3,colmatch,dl)
    u=Vector{Float64}()
    v=Vector{Float64}()
    w=Vector{Float64}()
    #collection of spectral runs
    locus1=Vector{type}()
    locus2=Vector{type}()
    wp=lum
    normfact=1/(wp*cmf(colmatch)).y

    @inbounds for i in 390:830-dl+1
        bs=block_spec(390,830,1.0,i,i+dl-1,1.0,0)*wp
        xyzcol=(bs*cmf(colmatch))/XYZ(1,1/normfact,1).y
        labcol=convert(type,Colors.convert(Colors.Lab,xyzcol,whitepoint))
        push!(locus1,labcol)
    end
    if type == XYZ
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].y)
            push!(v,locus1[i].x)
            push!(w,locus1[i].z)
        end
    elseif type in (Lab, DIN99, DIN99d, DIN99o)
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].l)
            push!(v,locus1[i].a)
            push!(w,locus1[i].b)
        end
    elseif type == Luv
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].l)
            push!(v,locus1[i].u)
            push!(w,locus1[i].v)
        end
    else nothing
    end
    # type 1 block spectra
    @inbounds for i in 390:830-dl+1
        bs=block_spec(390,830,1.0,i,i+dl-1,1.0,1)*wp
        xyzcol=(bs*cmf(colmatch))/XYZ(1,1/normfact,1).y
        labcol=convert(type,Colors.convert(Colors.Lab,xyzcol,whitepoint))
        push!(locus2,labcol)
    end
    if type == XYZ
        @inbounds for i in 1:length(locus2)
            push!(u,locus2[i].y)
            push!(v,locus2[i].x)
            push!(w,locus2[i].z)
        end
    elseif type in (Lab, DIN99, DIN99d, DIN99o)
        @inbounds for i in 1:length(locus2)
            push!(u,locus2[i].l)
            push!(v,locus2[i].a)
            push!(w,locus2[i].b)
        end
    elseif type == Luv
        @inbounds for i in 1:length(locus2)
            push!(u,locus2[i].l)
            push!(v,locus2[i].u)
            push!(w,locus2[i].v)
        end
    else nothing
    end
    #scatter!(v,w,u,markersize=0.2,labels=false)
    locus1,locus2
end

"""
`colv(type,T,colmatch,s)`

type: color space

T: whitepoint correlated color temperature

colmatch: color matching function

dl: delta lambda

output: a,b,c (locus vectors in given color space)

        x,y,Y for Yxy

        X,Z,Y for XYZ

        a,b,L for Luv, Lab, DIN99, DIN99d, DIN99o
"""
function colv(type,lum::LSpec,whitepoint::Color3,colmatch,s) # s = start
    u=Vector{Float64}()
    v=Vector{Float64}()
    w=Vector{Float64}()
    #collection of spectral runs
    locus1=Vector{type}()
    locus2=Vector{type}()
    wp=lum
    normfact=1/(wp*cmf(colmatch)).y
    # dark spectrals
    @inbounds for h in 0.0:0.05:0.95
        bs=block_spec(390,830,1.0,s,s,h,0)*wp
        xyzcol=(bs*cmf(colmatch))/XYZ(1,1/normfact,1).y
        labcol=convert(type,Colors.convert(Colors.Lab,xyzcol,whitepoint))
        push!(locus1,labcol)
    end
    # normal spectrals
    @inbounds for i in s:830
        bs=block_spec(390,830,1.0,s,i,1.0,0)*wp
        xyzcol=(bs*cmf(colmatch))/XYZ(1,1/normfact,1).y
        labcol=convert(type,Colors.convert(Colors.Lab,xyzcol,whitepoint))
        push!(locus1,labcol)
    end
    if type == XYZ
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].y)
            push!(v,locus1[i].x)
            push!(w,locus1[i].z)
        end
    elseif type == xyY
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].Y)
            push!(v,locus1[i].x)
            push!(w,locus1[i].y)
        end
    elseif type in (Lab, DIN99, DIN99d, DIN99o)
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].l)
            push!(v,locus1[i].a)
            push!(w,locus1[i].b)
        end
    elseif type == Luv
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].l)
            push!(v,locus1[i].u)
            push!(w,locus1[i].v)
        end
    else nothing
    end
    # type 1 block spectra
    @inbounds for i in s:830
        bs=block_spec(390,830,1.0,s,i,1.0,1)*wp
        xyzcol=(bs*cmf(colmatch))/XYZ(1,1/normfact,1).y
        labcol=convert(type,Colors.convert(Colors.Lab,xyzcol,whitepoint))
        push!(locus2,labcol)
    end
    if type == XYZ
        @inbounds for i in 1:length(locus2)
            push!(u,locus2[i].y)
            push!(v,locus2[i].x)
            push!(w,locus2[i].z)
        end
    elseif type == xyY
        @inbounds for i in 1:length(locus1)
            push!(u,locus1[i].Y)
            push!(v,locus1[i].x)
            push!(w,locus1[i].y)
        end
    elseif type in (Lab, DIN99, DIN99d, DIN99o)
        @inbounds for i in 1:length(locus2)
            push!(u,locus2[i].l)
            push!(v,locus2[i].a)
            push!(w,locus2[i].b)
        end
    elseif type == Luv
        @inbounds for i in 1:length(locus2)
            push!(u,locus2[i].l)
            push!(v,locus2[i].u)
            push!(w,locus2[i].v)
        end
    else nothing
    end
    #scatter!(v,w,u,markersize=0.2,labels=false)
    locus1,locus2
end

"""
`sl(type,lum::LSpec,whitepoint,colmatch,s,d,e)`
"""
function sl(type,lum::LSpec,whitepoint,colmatch,s,d,e)
    locuscollect=Vector()
    @inbounds for dl in s:d:e
        l1,l2=colv(type,lum,whitepoint,colmatch,dl)
        push!(locuscollect,l1)
        push!(locuscollect,l2)
    end
    n=length(locuscollect)
    a=Vector{}()
    c=Vector{}()
    b=Vector{}()
    @inbounds for i in 1:n
        r=locuscollect[i] # each run of dl
        l=length(r)
        e=Vector{Float64}()
        f=Vector{Float64}()
        g=Vector{Float64}()
        if type == XYZ
            @inbounds for j in 1:l
                push!(e,r[j].x)
                push!(f,r[j].z)
                push!(g,r[j].y)
            end
        elseif type == xyY
            @inbounds for j in 1:l
                push!(e,r[j].x)
                push!(f,r[j].y)
                push!(g,r[j].Y)
            end
        elseif type in (Lab, DIN99, DIN99d, DIN99o)
            @inbounds for j in 1:l
                push!(e,r[j].a)
                push!(f,r[j].b)
                push!(g,r[j].l)
            end
        elseif type == Luv
            @inbounds for j in 1:l
                push!(e,r[j].u)
                push!(f,r[j].v)
                push!(g,r[j].l)
            end
        else nothing
        end
        push!(a,e)
        push!(b,f)
        push!(c,g)
    end
    a,b,c
end

function getslice(x,z,y,sl,type)
    a=Vector{}()
    b=Vector{}()
    c=Vector{}()
    xz=0.0
    yz=0.0
    #go throuhg all runs
    for i in 1:length(x)
        l=length(x[i])
        @inbounds for j in 1:l-1
            yt=y[i][j]
            y2=y[i][j+1]
            if (yt-sl)*(y2-sl) <= 0
                #x=(r[j+1].x-r[j].x) / (r[j+1].y-r[j].y) * (sl - r[j].y) + r[j].x
                u=(x[i][j+1]-x[i][j]) / (y[i][j+1]-y[i][j]) * (sl-y[i][j]) + x[i][j]
                #z=(r[j+1].z-r[j].z) / (r[j+1].y-r[j].y) * (sl - r[j].y) + r[j].z
                v=(z[i][j+1]-z[i][j]) / (y[i][j+1]-y[i][j]) * (sl-y[i][j]) + z[i][j]
                w=sl
                push!(a,u)
                push!(b,v)
                push!(c,w)
            end
        end
    end
    if type == xyY
        xz=1/3
        yz=1/3
    elseif type == XYZ
        xz=sl
        yz=sl
    elseif type in (Lab, DIN99, DIN99d, DIN99o)
        xz=0.0
        yz=0.0
    end
    sortslice(a,b,c,xz,yz)
end

function polar(x::Real,y::Real,xz::Real,yz::Real)
    r=sqrt((x-xz)^2.0+(y-yz)^2.0)
    α=atan(y-yz,x-xz)
    r,α
end

function sortslice(a,b,c,xz,yz)
    r=Array{Float64,2}(undef,length(a),4)
    @inbounds for i in 1:length(a)
        rr,αα = polar(a[i],b[i],xz,yz)
        r[i,1]=αα
        r[i,2]=rr
        r[i,3]=a[i]
        r[i,4]=b[i]
    end
    s=sortslices(r, dims=1)
    a,b,c = s[:,3],s[:,4],c
end


text:
using HTTP
using JSON

function anki_request()
	response = HTTP.request("POST", "http://localhost:8765", [], "{\"action\": \"deckNames\", \"version\": 6}")
	j = JSON.parse(String(response.body))
	return j
end


text:
using Interact
using Blink
using Plots

# specify backend for Plots.jl
pyplot()

plotsui=@manipulate for a in -4:0.05:4
    x= -π:0.01:π
    y=sin.(x.-a)
    Plots.plot(x, y, xlim=(-π, π), ylim=(-1,1))
end

w=Window()
body!(w, plotsui)

max_stars_repo_name: terasakisatoshi/juliaExer

max_issues_repo_name: terasakisatoshi/juliaExer

max_forks_repo_name: terasakisatoshi/juliaExer


text:
"""
According to P. Sauer "Power System Dynamics and Stability" p. 67 the
relationship between v_f_d and i_in1 is nonlinear due to saturation
of the exciter iron.

The saturation is then often described through two characteristic points on the
saturation curve: S_E_max (saturation at maximal excitation voltage)
and S_E0.75(saturation at 0.75* maximal excitation voltage).

These can be used to fit the following function:
S_E(E_f_d) = A_x * exp(B_x * E_f_d)               P.Sauer, P.70, Eq(4.22)

In a steady-state it is also given that:
0 = - (K_e + S_E_max) * E_f_d_max + V_R_max       P.Sauer, P.69, Eq(4.21)
Inputs:
    - K_e: Exciter constant related to self-excited field [pu]
    - S_E_max: Saturation at maximal excitation voltage [pu]
    - S_E_tq: Saturation at 0.75 * maximal excitation voltage [pu]
    - V_R_max: Maximal reference voltage for the AVR [pu]
Outputs:
    - E_f_d_max: maximal excitation voltage
    - A_x: Fitting Parameter
    - B_x: Fitting Parameter
"""

function ExciterSaturationEq(K_e, S_E_max, S_E_tq, V_R_max)
    E_f_d_max = V_R_max / (K_e + S_E_max)
    A_x = S_E_tq^4 / S_E_max^3
    B_x = 4/3 * log(S_E_tq / A_x) / E_f_d_max
    return E_f_d_max, A_x, B_x
end

max_stars_repo_name: luap-pik/PowerDynIC

max_issues_repo_name: luap-pik/PowerDynIC

max_forks_repo_name: luap-pik/PowerDynIC


text:
using LinearAlgebra

export Affine

"""
    Affine()
    Affine(A, b)

Projection to an affine set such that `Ax = b`.
"""

struct Affine{T <: Real} <: ConvexSet
    VVt::Matrix{T}
    pinv_A_b::Vector{T}
    function Affine(
        A::Matrix{T},
        b::Vector{T};
        atol::Real = 0.0,
        rtol::Real = (eps(real(float(one(T)))) * min(size(A)...)) * iszero(atol)
        ) where T <: Real
        size(A, 1) == length(b) || throw(ArgumentError(
            "Number of rows of A does not match length of b")
            )
        A = LinearAlgebra.svd(A, full = false)
        tol = max(rtol * maximum(A.S), atol)
        Σ_pinv = zeros(length(A.S))
        index = A.S .> tol
        Σ_pinv[index] = 1. ./ A.S[index]
        Σ_pinv[findall(.!isfinite.(Σ_pinv))] .= 0.
        VVt = A.V * A.Vt
        pinv_A_b = A.V * Diagonal(Σ_pinv) * transpose(A.U) * b
        new{T}(VVt, pinv_A_b)
    end
end

function project!(s::Affine{T}, v::Vector{T}, y::Vector{T}) where T <: Real
    v .= y - s.VVt * y + s.pinv_A_b
end

function project(s::Affine{T}, y::Vector{T}) where T <: Real
    project!(s, similar(y), y)
end


text:
function addnumbers(numberone, numbertwo)
    return(numberone += numbertwo)
end

fifteen = addnumbers(5, 10)

fifteen = sum([5,10])

array = [5,10,15,20]

d = [:H => [5,10,15], :F => [10,15,20]]

typeof(d)

"string"

'a'

'b'

"ba"

if 5 == 5
    println("5 is 5")
end
if 6 == 6
    println("6 is 6")
end

5 == 5

function checknumber(num)
    if num > 5
        println("Greater than 5")
    else
        println("out of range")
    end
    if num > 10
        println("Greater than 10")
    end
    if num > 15
        println("Greater than 15")
    end
end

checknumber(18)

max_stars_repo_path: 2/Comprehensive Julia Tutorial (2).jl

max_stars_repo_name: Mechachleopteryx/JuliaLessons

max_issues_repo_path: 2/Comprehensive Julia Tutorial (2).jl

max_issues_repo_name: Mechachleopteryx/JuliaLessons

max_forks_repo_path: 2/Comprehensive Julia Tutorial (2).jl

max_forks_repo_name: Mechachleopteryx/JuliaLessons


text:
import Compat.Random.srand
srand(model::Model) = srand(model.rng)
srand(model::Model, seed...) = srand(model.rng, seed...)

@doc doc"""
Ising model with energy $E = -\sum_{ij} J_{ij} \sigma_i \sigma_j$,
where $\sigma_i$ takes value of 1 (up spin) or -1 (down spin).
"""
mutable struct Ising <: Model
    lat :: Lattice
    spins :: Vector{Int}
    rng :: Random.MersenneTwister

    function Ising(lat::Lattice)
        model = new()
        model.lat = lat
        model.rng = srand(Random.MersenneTwister(0))
        model.spins = rand(model.rng, [1,-1], numsites(lat))
        return model
    end
    function Ising(lat::Lattice, seed)
        model = new()
        model.lat = lat
        model.rng = srand(Random.MersenneTwister(0), seed...)
        model.spins = rand(model.rng, [1,-1], numsites(lat))
        return model
    end
end
@doc doc"""
    Ising(param)

Generates `Ising` using `param["Lattice"](param)` and `param["Seed"]` (if defined).
Each spin will be initialized randomly and independently.
"""
function Ising(param::Parameter)
    lat = param["Lattice"](param)
    if "Seed" in keys(param)
        return Ising(lat, param["Seed"])
    else
        return Ising(lat)
    end
end

@doc doc"""
`Q` state Potts model with energy $E = -\sum_{i,j} \delta_{\sigma_i, \sigma_j}$,
where $\sigma_i$ takes an integer value from $1$ to $Q$ and $\delta$ is a Kronecker's delta.
Order parameter (total magnetization) is defined as
\begin{equation}
    M = \frac{Q-1}{Q}N_1 - \frac{1}{Q}(N-N_1),
\end{equation}
where $N$ is the number of sites and $N_1$ is the number of $\sigma=1$ spins.
"""
mutable struct Potts <: Model
    lat :: Lattice
    Q :: Int
    spins :: Vector{Int}
    rng :: Random.MersenneTwister

    function Potts(lat::Lattice, Q::Integer)
        rng = srand(Random.MersenneTwister(0))
        spins = rand(rng, 1:Q, numsites(lat))
        return new(lat, Q, spins, rng)
    end
    function Potts(lat::Lattice, Q::Integer, seed)
        rng = srand(Random.MersenneTwister(0), seed)
        spins = rand(rng, 1:Q, numsites(lat))
        return new(lat, Q, spins, rng)
    end
end
@doc doc"""
    Potts(param)

Generates `Potts` using `param["Lattice"](param)`, `param["Q"]`, and `param["Seed"]` (if defined).
Each spin will be initialized randomly and independently.
"""
function Potts(param::Parameter)
    lat = param["Lattice"](param)
    Q = param["Q"]
    if "Seed" in keys(param)
        return Potts(lat, Q, param["Seed"])
    else
        return Potts(lat, Q)
    end
end

@doc doc"""
`Q` state clock model with energy $E = -\sum_{ij} J_{ij} \cos(\theta_i - \theta_j)$,
where $\theta_i = 2\pi \sigma_i/Q$ and $\sigma_i$ takes an integer value from $1$ to $Q$.
"""
mutable struct Clock <: Model
    lat :: Lattice
    Q :: Int
    spins :: Vector{Int}
    cosines :: Vector{Float64}
    sines :: Vector{Float64}
    sines_sw :: Vector{Float64}
    rng :: Random.MersenneTwister

    function Clock(lat::Lattice, Q::Integer)
        rng = srand(Random.MersenneTwister(0))
        spins = rand(rng, 1:Q, numsites(lat))
        cosines = [cospi(2s/Q) for s in 1:Q]
        sines = [sinpi(2s/Q) for s in 1:Q]
        sines_sw = [sinpi(2(s-0.5)/Q) for s in 1:Q]
        return new(lat, Q, spins, cosines, sines, sines_sw, rng)
    end
    function Clock(lat::Lattice, Q::Integer, seed)
        rng = srand(Random.MersenneTwister(0), seed)
        spins = rand(rng, 1:Q, numsites(lat))
        cosines = [cospi(2s/Q) for s in 1:Q]
        sines = [sinpi(2s/Q) for s in 1:Q]
        sines_sw = [sinpi(2(s-0.5)/Q) for s in 1:Q]
        return new(lat, Q, spins, cosines, sines, sines_sw, rng)
    end
end
@doc doc"""
    Clock(param)

Generates `Clock` using `param["Lattice"](param)`, `param["Q"]`,  and `param["Seed"]` (if defined).
Each spin $\sigma_i$ will be initialized randomly and independently.
"""
function Clock(param::Parameter)
    lat = param["Lattice"](param)
    Q = param["Q"]
    if "Seed" in keys(param)
        return Clock(lat, Q, param["Seed"])
    else
        return Clock(lat, Q)
    end
end

@doc doc"""
XY model with energy $E = -\sum_{ij} J_{ij} \cos(\theta_i - \theta_j)$,
where $\theta_i = 2\pi \sigma_i$ and $\sigma_i \in [0, 1)$.
"""
mutable struct XY <: Model
    lat :: Lattice
    spins :: Vector{Float64}
    rng :: Random.MersenneTwister

    function XY(lat::Lattice)
        model = new()
        model.rng = srand(Random.MersenneTwister(0))
        model.lat = lat
        model.spins = rand(model.rng, numsites(lat))
        return model
    end
    function XY(lat::Lattice, seed)
        model = new()
        model.rng = srand(Random.MersenneTwister(0), seed)
        model.lat = lat
        model.spins = rand(model.rng, numsites(lat))
        return model
    end
end
@doc doc"""
   XY(param)

Generates `XY` using `param["Lattice"](param)`,  and `param["Seed"]` (if defined).
Each spin $\sigma_i$ will be initialized randomly and independently.
"""
function XY(param::Parameter)
    lat = param["Lattice"](param)
    if "Seed" in keys(param)
        return XY(lat, param["Seed"])
    else
        return XY(lat)
    end
end


text:
using JuliaVariables
using MLStyle
import MacroTools

unwrap_scoped(ex) = @match ex begin
    Expr(:scoped, _, a) => unwrap_scoped(a)
    Expr(head, args...) => Expr(head, map(unwrap_scoped, args)...)
    a => a
end

globals(s::Symbol) = [s]
globals(x) = []

function globals(ex::Expr)
    branch(head, newargs) = union(newargs...)

    function leaf(v::JuliaVariables.Var)
        v.is_global ? [v.name] : Symbol[]
    end

    leaf(x) = []

    solved_ex = unwrap_scoped(solve_from_local!(simplify_ex(ex)))

    return foldall(leaf, branch)(solved_ex)
end


text:
import AlphaZero.GI
using StaticArrays

const BOARD_SIDE = 3
const NUM_POSITIONS = BOARD_SIDE ^ 2

const Player = Bool
const WHITE = true
const BLACK = false

const Cell = Union{Nothing, Player}
const Board = SVector{NUM_POSITIONS, Cell}
const INITIAL_BOARD = Board(repeat([nothing], NUM_POSITIONS))
const INITIAL_STATE = (board=INITIAL_BOARD, curplayer=WHITE)

mutable struct Game <: GI.AbstractGame
  board :: Board
  curplayer :: Player
  function Game(state=INITIAL_STATE)
    new(state.board, state.curplayer)
  end
end

GI.State(::Type{Game}) = typeof(INITIAL_STATE)

GI.Action(::Type{Game}) = Int

GI.two_players(::Type{Game}) = true

#####
##### Defining winning conditions
#####

pos_of_xy((x, y)) = (y - 1) * BOARD_SIDE + (x - 1) + 1

xy_of_pos(pos) = ((pos - 1) % BOARD_SIDE + 1, (pos - 1) ÷ BOARD_SIDE + 1)

const ALIGNMENTS =
  let N = BOARD_SIDE
  let XY = [
    [[(i, j) for j in 1:N] for i in 1:N];
    [[(i, j) for i in 1:N] for j in 1:N];
    [[(i, i) for i in 1:N]];
    [[(i, N - i + 1) for i in 1:N]]]
  [map(pos_of_xy, al) for al in XY]
end end

function has_won(g::Game, player)
  any(ALIGNMENTS) do al
    all(al) do pos
      g.board[pos] == player
    end
  end
end

#####
##### Game API
#####

const ACTIONS = collect(1:NUM_POSITIONS)

GI.actions(::Type{Game}) = ACTIONS

GI.actions_mask(g::Game) = map(isnothing, g.board)

GI.current_state(g::Game) = (board=g.board, curplayer=g.curplayer)

GI.white_playing(::Type{Game}, state) = state.curplayer

function terminal_white_reward(g::Game)
  has_won(g, WHITE) && return 1.
  has_won(g, BLACK) && return -1.
  isempty(GI.available_actions(g)) && return 0.
  return nothing
end

GI.game_terminated(g::Game) = !isnothing(terminal_white_reward(g))

function GI.white_reward(g::Game)
  z = terminal_white_reward(g)
  return isnothing(z) ? 0. : z
end

function GI.play!(g::Game, pos)
  g.board = setindex(g.board, g.curplayer, pos)
  g.curplayer = !g.curplayer
end

#####
##### Simple heuristic for minmax
#####

function alignment_value_for(g::Game, player, alignment)
  γ = 0.3
  N = 0
  for pos in alignment
    mark = g.board[pos]
    if mark == player
      N += 1
    elseif !isnothing(mark)
      return 0.
    end
  end
  return γ ^ (BOARD_SIDE - 1 - N)
end

function heuristic_value_for(g::Game, player)
  return sum(alignment_value_for(g, player, al) for al in ALIGNMENTS)
end

function GI.heuristic_value(g::Game)
  mine = heuristic_value_for(g, g.curplayer)
  yours = heuristic_value_for(g, !g.curplayer)
  return mine - yours
end

#####
##### Machine Learning API
#####

function flip_colors(board)
  flip(cell) = isnothing(cell) ? nothing : !cell
  # Inference fails when using `map`
  return @SVector Cell[flip(board[i]) for i in 1:NUM_POSITIONS]
end

# Vectorized representation: 3x3x3 array
# Channels: free, white, black
# The board is represented from the perspective of white
# (as if white were to play next)
function GI.vectorize_state(::Type{Game}, state)
  board = GI.white_playing(Game, state) ? state.board : flip_colors(state.board)
  return Float32[
    board[pos_of_xy((x, y))] == c
    for x in 1:BOARD_SIDE,
        y in 1:BOARD_SIDE,
        c in [nothing, WHITE, BLACK]]
end

#####
##### Symmetries
#####

function generate_dihedral_symmetries()
  N = BOARD_SIDE
  rot((x, y)) = (y, N - x + 1) # 90° rotation
  flip((x, y)) = (x, N - y + 1) # flip along vertical axis
  ap(f) = p -> pos_of_xy(f(xy_of_pos(p)))
  sym(f) = map(ap(f), collect(1:NUM_POSITIONS))
  rot2 = rot ∘ rot
  rot3 = rot2 ∘ rot
  return [
    sym(rot), sym(rot2), sym(rot3),
    sym(flip), sym(flip ∘ rot), sym(flip ∘ rot2), sym(flip ∘ rot3)]
end

const SYMMETRIES = generate_dihedral_symmetries()

function GI.symmetries(::Type{Game}, s)
  return [
    ((board=Board(s.board[sym]), curplayer=s.curplayer), sym)
    for sym in SYMMETRIES]
end

#####
##### Interaction API
#####

function GI.action_string(::Type{Game}, a)
  string(Char(Int('A') + a - 1))
end

function GI.parse_action(g::Game, str)
  length(str) == 1 || (return nothing)
  x = Int(uppercase(str[1])) - Int('A')
  (0 <= x < NUM_POSITIONS) ? x + 1 : nothing
end

function read_board(::Type{Game})
  n = BOARD_SIDE
  str = reduce(*, ((readline() * "   ")[1:n] for i in 1:n))
  white = ['w', 'r', 'o']
  black = ['b', 'b', 'x']
  function cell(i)
    if (str[i] ∈ white) WHITE
    elseif (str[i] ∈ black) BLACK
    else nothing end
  end
  @SVector [cell(i) for i in 1:NUM_POSITIONS]
end

function GI.read_state(::Type{Game})
  b = read_board(Game)
  nw = count(==(WHITE), b)
  nb = count(==(BLACK), b)
  if nw == nb
    return (board=b, curplayer=WHITE)
  elseif nw == nb + 1
    return (board=b, curplayer=BLACK)
  else
    return nothing
  end
end

using Crayons

player_color(p) = p == WHITE ? crayon"light_red" : crayon"light_blue"
player_name(p)  = p == WHITE ? "Red" : "Blue"
player_mark(p)  = p == WHITE ? "o" : "x"

function GI.render(g::Game; with_position_names=true, botmargin=true)
  pname = player_name(g.curplayer)
  pcol = player_color(g.curplayer)
  print(pcol, pname, " plays:", crayon"reset", "\n\n")
  for y in 1:BOARD_SIDE
    for x in 1:BOARD_SIDE
      pos = pos_of_xy((x, y))
      c = g.board[pos]
      if isnothing(c)
        print(" ")
      else
        print(player_color(c), player_mark(c), crayon"reset")
      end
      print(" ")
    end
    if with_position_names
      print(" | ")
      for x in 1:BOARD_SIDE
        print(GI.action_string(Game, pos_of_xy((x, y))), " ")
      end
    end
    print("\n")
  end
  botmargin && print("\n")
end

max_stars_repo_name: btheodorou/Bridge-NN-Research

max_issues_repo_name: btheodorou/Bridge-NN-Research

max_forks_repo_name: btheodorou/Bridge-NN-Research