text:
#------------------------------------------------------------------------------
# LIBRARY INITIALIZATION
#------------------------------------------------------------------------------

"""
C function:
```
fdb_error_t fdb_select_api_version_impl(
    int runtime_version,
    int header_version
)
```

This is the actual entry point called by fdb_select_api_version(). It should
never be called directly. fdb_select_api_version(v) is equivalent to
fdb_select_api_version_impl(v, FDB_API_VERSION).

It is an error to call this function after it has returned successfully. It is
not thread safe, and if called from more than one thread simultaneously its
behavior is undefined.

Parameters:
- runtime_version: Version of run-time behavior the API is requested to provide.
    Must be less than or equal to header_version, and almost always be equal.
- header_version: Version of the ABI (application binary interface) that the
    calling code expects to find in the shared library. If you are using an FFI,
    this must correspond to the version of the API you are using as a reference
    (currently 510). E.g., the number of arguments that a function takes may be
    affected by this value, and an incorrect value is unlikely to yield success.
"""
function fdb_select_api_version_impl(runtime_version, header_version)
    ccall((:fdb_select_api_version_impl, fdb_c), fdb_error_t, (Cint, Cint), Cint(runtime_version), Cint(header_version))
end

"""
C function:
`fdb_error_t fdb_select_api_version(int version)`

Must be called before any other API functions.
Parameter version must be <= FDB_API_VERSION (and almost always equal).

Parameters:
- runtime_version: Version of run-time behavior the API is requested to provide.
    Must be <= header_version, and should almost always be equal.
"""
function fdb_select_api_version(runtime_version)
    fdb_select_api_version_impl(Cint(runtime_version), fdb_get_max_api_version())
end

"""
C function:
`int fdb_get_max_api_version()`

Returns FDB_API_VERSION, the current version of the FoundationDB C API.
This is the maximum version that may be passed to fdb_select_api_version().
"""
function fdb_get_max_api_version()
    ccall((:fdb_get_max_api_version, fdb_c), Cint, ())
end

function fdb_get_client_version()
    ccall((:fdb_get_client_version, fdb_c), Cstring, ())
end

#------------------------------------------------------------------------------
# STARTING / STOPPING THE CLIENT EVENT LOOP
#------------------------------------------------------------------------------
"""
C function:
```
fdb_error_t fdb_network_set_option(
    FDBNetworkOption option,
    uint8_t const* value,
    int value_length
)
```

Called to set network options.

If the given option is documented as taking a parameter, you must also pass a
pointer to the parameter value and the parameter value’s length. If the option
is documented as taking an Int parameter, value must point to a signed 64-bit
integer (little-endian), and value_length must be 8. This memory only needs to
be valid until fdb_network_set_option() returns.
"""
function fdb_network_set_option(option::fdb_network_option_t, value, value_length::Cint)
    ccall((:fdb_network_set_option, fdb_c), fdb_error_t, (fdb_network_option_t, Ptr{UInt8}, Cint), option, value, value_length)
end

"""
C function:
`fdb_error_t fdb_setup_network()`

Can be called only once, and after fdb_select_api_version() (and zero or more
calls to fdb_network_set_option()) and before any other function in this API.
"""
function fdb_setup_network(local_address)
    ccall((:fdb_setup_network, fdb_c), fdb_error_t, (Cstring,), local_address)
end

"""
C function:
`fdb_error_t fdb_run_network()`

Must be called after fdb_setup_network() before any asynchronous functions in
this API can be expected to complete. Unless your program is entirely event
driven based on results of asynchronous functions in this API and has no event
loop of its own, you will want to invoke this function on an auxiliary thread
(which it is your responsibility to create).

This function will not return until fdb_stop_network() is called by you or a
serious error occurs. You must not invoke fdb_run_network() concurrently or
reentrantly while it is already running.

In Julia, use `@threadcall` to invoke this.
"""
function fdb_run_network()
    ccall((:fdb_run_network, fdb_c), fdb_error_t, ())
end
function fdb_run_network_in_thread()
    if Threads.nthreads() < 2
        error("You must enable threading in Julia and configure JULIA_NUM_THREADS to be at least 2. It is now $(Threads.nthreads())")
    end
    @threadcall((:fdb_run_network, fdb_c), fdb_error_t, ())
end

"""
C function:
`fdb_error_t fdb_stop_network()`

Signals the event loop invoked by fdb_run_network() to terminate. You must call
this function and wait for fdb_run_network() to return before allowing your
program to exit, or else the behavior is undefined.

This function may be called from any thread. Once the network is stopped it
cannot be restarted during the lifetime of the running program.
"""
function fdb_stop_network()
    ccall((:fdb_stop_network, fdb_c), fdb_error_t, ())
end

"""
C function:
```
fdb_error_t fdb_add_network_thread_completion_hook(
    void (*hook)(void*),
    void *hook_parameter)
```

Must be called after fdb_setup_network() and prior to fdb_run_network() if
called at all. This will register the given callback to run at the completion of
the network thread. If there are multiple network threads running (which might
occur if one is running multiple versions of the client, for example), then the
callback is invoked once on each thread. When the supplied function is called,
the supplied parameter is passed to it.

In Julia, if the network is being run with `@threadcall`, this method must not
be used, because calling back to Julia is not allowed in `@threadcall`.
"""
function fdb_add_network_thread_completion_hook(hook, hook_parameter)
    ccall((:fdb_add_network_thread_completion_hook, fdb_c), fdb_error_t, (Ptr{Nothing}, Ptr{Nothing}), hook, hook_parameter)
end

#------------------------------------------------------------------------------
# FUTURES
#------------------------------------------------------------------------------
"""
C function:
`void fdb_future_cancel(FDBFuture* future)`

Cancels an FDBFuture object and its associated asynchronous operation. If called
before the future is ready, attempts to access its value will return an
operation_cancelled error. Cancelling a future which is already ready has no
effect. Note that even if a future is not ready, its associated asynchronous
operation may have succesfully completed and be unable to be cancelled.
"""
function fdb_future_cancel(f)
    ccall((:fdb_future_cancel, fdb_c), Nothing, (fdb_future_ptr_t,), f)
end

"""
C function:
`void fdb_future_destroy(FDBFuture* future)`

Destroys an FDBFuture object. It must be called exactly once for each FDBFuture*
returned by an API function. It may be called before or after the future is
ready. It will also cancel the future (and its associated operation if the
latter is still outstanding).
"""
function fdb_future_destroy(f)
    ccall((:fdb_future_destroy, fdb_c), Nothing, (fdb_future_ptr_t,), f)
end

"""
C function:
`fdb_error_t fdb_future_block_until_ready(FDBFuture* future)`

Blocks the calling thread until the given Future is ready. It will return
success even if the Future is set to an error – you must call
fdb_future_get_error() to determine that. fdb_future_block_until_ready() will
return an error only in exceptional conditions (e.g. out of memory or other
operating system resources).
"""
function fdb_future_block_until_ready(f)
    ccall((:fdb_future_block_until_ready, fdb_c), fdb_error_t, (fdb_future_ptr_t,), f)
end
function fdb_future_block_until_ready_in_thread(f)
    if Threads.nthreads() < 2
        error("You must enable threading in Julia and configure JULIA_NUM_THREADS to be at least 2. It is now $(Threads.nthreads())")
    end
    @threadcall((:fdb_future_block_until_ready, fdb_c), fdb_error_t, (fdb_future_ptr_t,), f)
end

"""
C function:
`fdb_bool_t fdb_future_is_ready(FDBFuture* future)`

Returns non-zero if the Future is ready. A Future is ready if it has been set to
a value or an error.
"""
function fdb_future_is_ready(f)
    ccall((:fdb_future_is_ready, fdb_c), fdb_bool_t, (fdb_future_ptr_t,), f)
end

"""
C function:
```
fdb_error_t fdb_future_set_callback(
    FDBFuture* future,
    FDBCallback callback,
    void* callback_parameter)
```

Causes the FDBCallback function to be invoked as
callback(future, callback_parameter) when the given Future is ready. If the
Future is already ready, the call may occur in the current thread before this
function returns (but this behavior is not guaranteed). Alternatively, the call
may be delayed indefinitely and take place on the thread on which
fdb_run_network() was invoked, and the callback is responsible for any necessary
thread synchronization (and/or for posting work back to your application event
loop, thread pool, etc. if your application’s architecture calls for that).
"""
function fdb_future_set_callback(f, callback::FDBCallback, callback_parameter)
    ccall((:fdb_future_set_callback, fdb_c), fdb_error_t, (fdb_future_ptr_t, FDBCallback, Ptr{Nothing}), f, callback, callback_parameter)
end

"""
C function:
`void fdb_future_release_memory(FDBFuture* future)`

This function may only be called after a successful (zero return value) call to
fdb_future_get_key(), fdb_future_get_value(), or
fdb_future_get_keyvalue_array(). It indicates that the memory returned by the
prior get call is no longer needed by the application. After this function has
been called the same number of times as fdb_future_get_*(), further calls to
fdb_future_get_*() will return a future_released error. It is still necessary to
later destroy the future with fdb_future_destroy().

Calling this function is optional, since fdb_future_destroy() will also release
the memory returned by get functions. However, fdb_future_release_memory()
leaves the future object itself intact and provides a specific error code which
can be used for coordination by multiple threads racing to do something with the
results of a specific future. This has proven helpful in writing binding code.

Note: This function provides no benefit to most application code. It is designed
for use in writing generic, thread-safe language bindings. Applications should
normally call fdb_future_destroy() only.
"""
function fdb_future_release_memory(f)
    ccall((:fdb_future_release_memory, fdb_c), Nothing, (fdb_future_ptr_t,), f)
end

"""
C function:
`fdb_error_t fdb_future_get_error(FDBFuture* future)`

Returns zero if future is ready and not in an error state, and a non-zero error code otherwise.
"""
function fdb_future_get_error(f, out_description)
    ccall((:fdb_future_get_error, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{Cstring}), f, out_description)
end

#=
# API has been removed (throws: REMOVED FDB API FUNCTION)
function fdb_future_is_error(f)
    ccall((:fdb_future_is_error, fdb_c), fdb_bool_t, (fdb_future_ptr_t,), f)
end
=#

"""
C function:
```
fdb_error_t fdb_future_get_keyvalue_array(
    FDBFuture* future,
    FDBKeyValue const** out_kv,
    int* out_count,
    fdb_bool_t* out_more
)
```

Extracts an array of FDBKeyValue objects from an FDBFuture into caller-provided
variables. future must represent a result of the appropriate type (i.e. must
have been returned by a function documented as returning this type), or the
results are undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).

Parameters:
- out_kv: Set to point to the first FDBKeyValue object in the array.
- out_count: Set to the number of FDBKeyValue objects in the array.
- out_more: Set to true if (but not necessarily only if) values remain in the
    key range requested (possibly beyond the limits requested).

The memory referenced by the result is owned by the FDBFuture object and will be
valid until either fdb_future_destroy(future) or
fdb_future_release_memory(future) is called.
"""
function fdb_future_get_keyvalue_array(f, out_kv, out_count, out_more)
    ccall((:fdb_future_get_keyvalue_array, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{Ptr{Nothing}}, Ptr{Cint}, Ptr{fdb_bool_t}), f, out_kv, out_count, out_more)
end

"""
C function:
`fdb_error_t fdb_future_get_version(FDBFuture* future, int64_t* out_version)`

Extracts a value of type version from an FDBFuture into a caller-provided
variable of type int64_t. future must represent a result of the appropriate type
(i.e. must have been returned by a function documented as returning this type),
or the results are undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).
"""
function fdb_future_get_version(f, out_version)
    ccall((:fdb_future_get_version, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{Int64}), f, out_version)
end

"""
C function:
```
fdb_error_t fdb_future_get_key(
    FDBFuture* future,
    uint8_t const** out_key,
    int* out_key_length
)
```

Extracts a value of type key from an FDBFuture into caller-provided variables of
type uint8_t* (a pointer to the beginning of the key) and int (the length of the
key). future must represent a result of the appropriate type (i.e. must have
been returned by a function documented as returning this type), or the results
are undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).

The memory referenced by the result is owned by the FDBFuture object and will be
valid until either fdb_future_destroy(future) or
fdb_future_release_memory(future) is called.
"""
function fdb_future_get_key(f, out_key, out_key_length)
    ccall((:fdb_future_get_key, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{Ptr{UInt8}}, Ptr{Cint}), f, out_key, out_key_length)
end

"""
C function:
```
fdb_error_t fdb_future_get_cluster(
    FDBFuture* future,
    FDBCluster** out_cluster
)
```

Extracts a value of type FDBCluster* from an FDBFuture into a caller-provided
variable. future must represent a result of the appropriate type (i.e. must have
been returned by a function documented as returning this type), or the results
are undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).

This function may only be called once on a given FDBFuture object, as it
transfers ownership of the FDBCluster to the caller. The caller is responsible
for calling fdb_cluster_destroy() when finished with the result.
"""
function fdb_future_get_cluster(f, out_cluster)
    ccall((:fdb_future_get_cluster, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{fdb_cluster_ptr_t}), f, out_cluster)
end

"""
C function:
```
fdb_error_t fdb_future_get_database(
    FDBFuture* future,
    FDBDatabase** out_database
)
```

Extracts a value of type FDBDatabase* from an FDBFuture into a caller-provided
variable. future must represent a result of the appropriate type (i.e. must have
been returned by a function documented as returning this type), or the results
are undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).

This function may only be called once on a given FDBFuture object, as it
transfers ownership of the FDBDatabase to the caller. The caller is responsible
for calling fdb_database_destroy(*out_database) when finished with the result.
"""
function fdb_future_get_database(f, out_database)
    ccall((:fdb_future_get_database, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{fdb_database_ptr_t}), f, out_database)
end

"""
C function:
```
fdb_error_t fdb_future_get_value(
    FDBFuture* future,
    fdb_bool_t* out_present,
    uint8_t const** out_value,
    int* out_value_length
)
```

Extracts a database value from an FDBFuture into caller-provided variables.
future must represent a result of the appropriate type (i.e. must have been
returned by a function documented as returning this type), or the results are
undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).

Parameters:
- out_present: Set to non-zero if (and only if) the requested value was present
    in the database. (If zero, the other outputs are meaningless.)
- out_value: Set to point to the first byte of the value.
- out_value_length: Set to the length of the value (in bytes).

The memory referenced by the result is owned by the FDBFuture object and will be
valid until either fdb_future_destroy(future) or
fdb_future_release_memory(future) is called.
"""
function fdb_future_get_value(f, out_present, out_value, out_value_length)
    ccall((:fdb_future_get_value, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{fdb_bool_t}, Ptr{Ptr{UInt8}}, Ptr{Cint}), f, out_present, out_value, out_value_length)
end

"""
C function:
```
fdb_error_t fdb_future_get_string_array(
    FDBFuture* future,
    const char*** out_strings,
    int* out_count
)
```

Extracts an array of null-terminated C strings from an FDBFuture into
caller-provided variables. future must represent a result of the appropriate
type (i.e. must have been returned by a function documented as returning this
type), or the results are undefined.

Returns zero if future is ready and not in an error state, and a non-zero error
code otherwise (in which case the value of any out parameter is undefined).

Parameters:
- out_strings: Set to point to the first string in the array.
- out_count: Set to the number of strings in the array.

The memory referenced by the result is owned by the FDBFuture object and will be
valid until either fdb_future_destroy(future) or
fdb_future_release_memory(future) is called.
"""
function fdb_future_get_string_array(f, out_strings, out_count)
    ccall((:fdb_future_get_string_array, fdb_c), fdb_error_t, (fdb_future_ptr_t, Ptr{Ptr{Cstring}}, Ptr{Cint}), f, out_strings, out_count)
end

#------------------------------------------------------------------------------
# CLUSTER
#------------------------------------------------------------------------------

"""
C function:
`FDBFuture* fdb_create_cluster(const char* cluster_file_path)`

Returns an FDBFuture which will be set to an FDBCluster object. You must first
wait for the FDBFuture to be ready, check for errors, call
fdb_future_get_cluster() to extract the FDBCluster object, and then destroy the
FDBFuture with fdb_future_destroy().

Parameters:
- cluster_file_path: A NULL-terminated string giving a local path of a cluster
    file (often called ‘fdb.cluster’) which contains connection information for
    the FoundationDB cluster. If cluster_file_path is NULL or an empty string,
    then a default cluster file will be used.
"""
function fdb_create_cluster(cluster_file_path)
    ccall((:fdb_create_cluster, fdb_c), fdb_future_ptr_t, (Cstring,), cluster_file_path)
end

"""
C function:
`void fdb_cluster_destroy(FDBCluster* cluster)`

Destroys an FDBCluster object. It must be called exactly once for each
successful call to fdb_future_get_cluster(). This function only destroys a
handle to the cluster - your cluster will be fine!
"""
function fdb_cluster_destroy(c)
    ccall((:fdb_cluster_destroy, fdb_c), Nothing, (fdb_cluster_ptr_t,), c)
end

"""
C function:
```
fdb_error_t fdb_cluster_set_option(
    FDBCluster* cluster,
    FDBClusterOption option,
    uint8_t const* value,
    int value_length
)
```

Called to set an option on an FDBCluster. If the given option is documented as
taking a parameter, you must also pass a pointer to the parameter value and the
parameter value’s length. If the option is documented as taking an Int
parameter, value must point to a signed 64-bit integer (little-endian), and
value_length must be 8. This memory only needs to be valid until
fdb_cluster_set_option() returns.
"""
function fdb_cluster_set_option(c, option::fdb_cluster_option_t, value, value_length::Cint)
    ccall((:fdb_cluster_set_option, fdb_c), fdb_error_t, (fdb_cluster_ptr_t, fdb_cluster_option_t, Ptr{UInt8}, Cint), c, option, value, value_length)
end

"""
C function:
```
FDBFuture* fdb_cluster_create_database(
    FDBCluster *cluster,
    uint8_t const* db_name,
    int db_name_length
)
```

Returns an FDBFuture which will be set to an FDBDatabase object. You must first
wait for the FDBFuture to be ready, check for errors, call
fdb_future_get_database() to extract the FDBDatabase object, and then destroy
the FDBFuture with fdb_future_destroy().

Parameters:
- db_name: A pointer to the name of the database to be opened. The value does
    not need to be NULL-terminated. In the current FoundationDB API, the
    database name must be “DB”.
- db_name_length: The length of the parameter specified by db_name.
"""
function fdb_cluster_create_database(c, db_name, db_name_length::Cint)
    ccall((:fdb_cluster_create_database, fdb_c), fdb_future_ptr_t, (fdb_cluster_ptr_t, Ptr{UInt8}, Cint), c, db_name, db_name_length)
end

#------------------------------------------------------------------------------
# DATABASE
#------------------------------------------------------------------------------

"""
C function:
`void fdb_database_destroy(FDBDatabase* database)`

Destroys an FDBDatabase object. It must be called exactly once for each
successful call to fdb_future_get_database(). This function only destroys a
handle to the database – your database will be fine!
"""
function fdb_database_destroy(d)
    ccall((:fdb_database_destroy, fdb_c), Nothing, (fdb_database_ptr_t,), d)
end

"""
C function:
```
fdb_error_t fdb_database_set_option(
    FDBDatabase* database,
    FDBDatabaseOption option,
    uint8_t const* value,
    int value_length)
```

Called to set an option an on FDBDatabase. If the given option is documented as
taking a parameter, you must also pass a pointer to the parameter value and the
parameter value’s length. If the option is documented as taking an Int
parameter, value must point to a signed 64-bit integer (little-endian), and
value_length must be 8. This memory only needs to be valid until
fdb_database_set_option() returns.
"""
function fdb_database_set_option(d, option::fdb_database_option_t, value, value_length::Cint)
    ccall((:fdb_database_set_option, fdb_c), fdb_error_t, (fdb_database_ptr_t, fdb_database_option_t, Ptr{UInt8}, Cint), d, option, value, value_length)
end

"""
C function:
`fdb_error_t fdb_database_create_transaction(FDBDatabase* database, FDBTransaction** out_transaction)`

Creates a new transaction on the given database. The caller assumes ownership of
the FDBTransaction object and must destroy it with fdb_transaction_destroy().

Parameters:
- out_transaction: Set to point to the newly created FDBTransaction.
"""
function fdb_database_create_transaction(d, out_transaction)
    ccall((:fdb_database_create_transaction, fdb_c), fdb_error_t, (fdb_database_ptr_t, Ptr{fdb_transaction_ptr_t}), d, out_transaction)
end

#------------------------------------------------------------------------------
# TRANSACTION
#------------------------------------------------------------------------------

"""
C function:
`void fdb_transaction_destroy(FDBTransaction* transaction)`

Destroys an FDBTransaction object. It must be called exactly once for each
successful call to fdb_database_create_transaction(). Destroying a transaction
which has not had fdb_transaction_commit() called implicitly “rolls back” the
transaction (sets and clears do not take effect on the database).
"""
function fdb_transaction_destroy(tr)
    ccall((:fdb_transaction_destroy, fdb_c), Nothing, (fdb_transaction_ptr_t,), tr)
end

"""
C function:
```
fdb_error_t fdb_transaction_set_option(
    FDBTransaction* transaction,
    FDBTransactionOption option,
    uint8_t const* value,
    int value_length)
```

Called to set an option on an FDBTransaction. If the given option is documented
as taking a parameter, you must also pass a pointer to the parameter value and
the parameter value’s length. If the option is documented as taking an Int
parameter, value must point to a signed 64-bit integer (little-endian), and
value_length must be 8. This memory only needs to be valid until
fdb_transaction_set_option() returns.
"""
function fdb_transaction_set_option(tr, option::fdb_transaction_option_t)
    ccall((:fdb_transaction_set_option, fdb_c), Nothing, (fdb_transaction_ptr_t, fdb_transaction_option_t), tr, option)
end

"""
C function:
`void fdb_transaction_cancel(FDBTransaction* transaction)`

Cancels the transaction. All pending or future uses of the transaction will
return a transaction_cancelled error. The transaction can be used again after it
is reset.

Warning: Be careful if you are using fdb_transaction_reset() and
    fdb_transaction_cancel() concurrently with the same transaction. Since they
    negate each other’s effects, a race condition between these calls will leave
    the transaction in an unknown state.

Warning: If your program attempts to cancel a transaction after
    fdb_transaction_commit() has been called but before it returns,
    unpredictable behavior will result. While it is guaranteed that the
    transaction will eventually end up in a cancelled state, the commit may or
    may not occur. Moreover, even if the call to fdb_transaction_commit()
    appears to return a transaction_cancelled error, the commit may have
    occurred or may occur in the future. This can make it more difficult to
    reason about the order in which transactions occur.
"""
function fdb_transaction_cancel(tr)
    ccall((:fdb_transaction_cancel, fdb_c), Nothing, (fdb_transaction_ptr_t,), tr)
end

"""
C function:
```
void fdb_transaction_set_read_version(
    FDBTransaction* transaction,
    int64_t version
)
```

Sets the snapshot read version used by a transaction. This is not needed in
simple cases. If the given version is too old, subsequent reads will fail with
error_code_past_version; if it is too new, subsequent reads may be delayed
indefinitely and/or fail with error_code_future_version. If any of
fdb_transaction_get_*() have been called on this transaction already, the result
is undefined.
"""
function fdb_transaction_set_read_version(tr, version::Int64)
    ccall((:fdb_transaction_set_read_version, fdb_c), Nothing, (fdb_transaction_ptr_t, Int64), tr, version)
end

"""
C function:
`FDBFuture* fdb_transaction_get_read_version(FDBTransaction* transaction)`

Returns an FDBFuture which will be set to the transaction snapshot read version.
You must first wait for the FDBFuture to be ready, check for errors, call
fdb_future_get_version() to extract the version into an int64_t that you
provide, and then destroy the FDBFuture with fdb_future_destroy().

The transaction obtains a snapshot read version automatically at the time of the
first call to fdb_transaction_get_*() (including this one) and (unless causal
consistency has been deliberately compromised by transaction options) is
guaranteed to represent all transactions which were reported committed before
that call.
"""
function fdb_transaction_get_read_version(tr)
    ccall((:fdb_transaction_get_read_version, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t,), tr)
end

"""
C function:
```
FDBFuture* fdb_transaction_get_addresses_for_key(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length
)
```

Returns a list of public network addresses as strings, one for each of the
storage servers responsible for storing key_name and its associated value.

Returns an FDBFuture which will be set to an array of strings. You must
first wait for the FDBFuture to be ready, check for errors, call
fdb_future_get_string_array() to extract the string array, and then destroy the
FDBFuture with fdb_future_destroy().

Parameters:
- key_name: A pointer to the name of the key whose location is to be queried.
- key_name_length: The length of the parameter specified by key_name.
"""
function fdb_transaction_get_addresses_for_key(tr, key_name, key_name_length::Cint)
    ccall((:fdb_transaction_get_addresses_for_key, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint), tr, key_name, key_name_length)
end

"""
C function:
```
void fdb_transaction_set(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length,
    uint8_t const* value,
    int value_length
)
```

Modify the database snapshot represented by transaction to change the given key
to have the given value. If the given key was not previously present in the
database it is inserted.

The modification affects the actual database only if transaction is later
committed with fdb_transaction_commit().

Parameters:
- key_name: A pointer to the name of the key to be inserted into the database.
    The value does not need to be NULL-terminated.
- key_name_length: The length of the parameter specified by key_name.
- value: A pointer to the value to be inserted into the database. The value does
    not need to be NULL-terminated.
- value_length: The length of the parameter specified by value.
"""
function fdb_transaction_set(tr, key_name, key_name_length::Cint, value, value_length::Cint)
    ccall((:fdb_transaction_set, fdb_c), Nothing, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, Ptr{UInt8}, Cint), tr, key_name, key_name_length, value, value_length)
end

"""
C function:
```
void fdb_transaction_atomic_op(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length,
    uint8_t const* param,
    int param_length,
    FDBMutationType operationType
)
```

Modify the database snapshot represented by transaction to perform the operation
indicated by operationType with operand param to the value stored by the given
key.

An atomic operation is a single database command that carries out several
logical steps: reading the value of a key, performing a transformation on that
value, and writing the result. Different atomic operations perform different
transformations. Like other database operations, an atomic operation is used
within a transaction; however, its use within a transaction will not cause the
transaction to conflict.

Atomic operations do not expose the current value of the key to the client but
simply send the database the transformation to apply. In regard to conflict
checking, an atomic operation is equivalent to a write without a read. It can
only cause other transactions performing reads of the key to conflict.

By combining these logical steps into a single, read-free operation,
FoundationDB can guarantee that the transaction will not conflict due to the
operation. This makes atomic operations ideal for operating on keys that are
frequently modified. A common example is the use of a key-value pair as a
counter.

Warning: If a transaction uses both an atomic operation and a serializable read
on the same key, the benefits of using the atomic operation (for both conflict
checking and performance) are lost.

The modification affects the actual database only if transaction is later
committed with fdb_transaction_commit().

- key_name: A pointer to the name of the key whose value is to be mutated.
- key_name_length: The length of the parameter specified by key_name.
- param: A pointer to the parameter with which the atomic operation will mutate
    the value associated with key_name.
- param_length: The length of the parameter specified by param.
- operation_type: One of the FDBMutationType values indicating which operation
    should be performed.
"""
function fdb_transaction_atomic_op(tr, key_name, key_name_length::Cint, param, param_length::Cint, operation_type::fdb_mutation_type_t)
    ccall((:fdb_transaction_atomic_op, fdb_c), Nothing, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, Ptr{UInt8}, Cint, fdb_mutation_type_t), tr, key_name, key_name_length, param, param_length, operation_type)
end

"""
C function:
```
void fdb_transaction_clear(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length
)
```

Modify the database snapshot represented by transaction to remove the given key
from the database. If the key was not previously present in the database, there
is no effect.

The modification affects the actual database only if transaction is later
committed with fdb_transaction_commit().

Parameters:
- key_name: A pointer to the name of the key to be removed from the database.
    The value does not need to be NULL-terminated.
- key_name_length: The length of the parameter specified by key_name.
"""
function fdb_transaction_clear(tr, key_name, key_name_length::Cint)
    ccall((:fdb_transaction_clear, fdb_c), Nothing, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint), tr, key_name, key_name_length)
end

"""
C function:
```
void fdb_transaction_clear_range(
    FDBTransaction* transaction,
    uint8_t const* begin_key_name,
    int begin_key_name_length,
    uint8_t const* end_key_name,
    int end_key_name_length
)
```

Modify the database snapshot represented by transaction to remove all keys (if
any) which are lexicographically greater than or equal to the given begin key
and lexicographically less than the given end_key.

The modification affects the actual database only if transaction is later
committed with fdb_transaction_commit().

Parameters:
- begin_key_name: A pointer to the name of the key specifying the beginning of
    the range to clear. The value does not need to be NULL-terminated.
- begin_key_name_length: The length of the parameter specified by begin_key_name
- end_key_name: A pointer to the name of the key specifying the end of the range
    to clear. The value does not need to be NULL-terminated.
- end_key_name_length: The length of the parameter specified by
    end_key_name_length.
"""
function fdb_transaction_clear_range(tr, begin_key_name, begin_key_name_length::Cint, end_key_name, end_key_name_length::Cint)
    ccall((:fdb_transaction_clear_range, fdb_c), Nothing, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, Ptr{UInt8}, Cint), tr, begin_key_name, begin_key_name_length, end_key_name, end_key_name_length)
end

"""
C function:
```
FDBFuture* fdb_transaction_watch(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length
)
```

A watch’s behavior is relative to the transaction that created it. A watch
will report a change in relation to the key’s value as readable by that
transaction. The initial value used for comparison is either that of the
transaction’s read version or the value as modified by the transaction
itself prior to the creation of the watch. If the value changes and then
changes back to its initial value, the watch might not report the change.

Until the transaction that created it has been committed, a watch will not
report changes made by other transactions. In contrast, a watch will
immediately report changes made by the transaction itself. Watches cannot be
created if the transaction has set the READ_YOUR_WRITES_DISABLE transaction
option, and an attempt to do so will return an watches_disabled error.

If the transaction used to create a watch encounters an error during commit,
then the watch will be set with that error. A transaction whose commit result is
unknown will set all of its watches with the commit_unknown_result error. If an
uncommitted transaction is reset or destroyed, then any watches it created will
be set with the transaction_cancelled error.

Returns an FDBFuture representing an empty value that will be set once the watch
has detected a change to the value at the specified key. You must first wait for
the FDBFuture to be ready, check for errors, and then destroy the FDBFuture with
fdb_future_destroy().

By default, each database connection can have no more than 10,000 watches that
have not yet reported a change. When this number is exceeded, an attempt to
create a watch will return a too_many_watches error. This limit can be changed
using the MAX_WATCHES database option. Because a watch outlives the transaction
that creates it, any watch that is no longer needed should be cancelled by
calling fdb_future_cancel() on its returned future.

Parameters:
- key_name: A pointer to the name of the key to watch. The value does not need
    to be NULL-terminated.
- key_name_length: The length of the parameter specified by key_name.
"""
function fdb_transaction_watch(tr, key_name, key_name_length::Cint)
    ccall((:fdb_transaction_watch, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint), tr, key_name, key_name_length)
end

"""
C function:
`FDBFuture* fdb_transaction_commit(FDBTransaction* transaction)`

Attempts to commit the sets and clears previously applied to the database
snapshot represented by transaction to the actual database. The commit may or
may not succeed – in particular, if a conflicting transaction previously
committed, then the commit must fail in order to preserve transactional
isolation. If the commit does succeed, the transaction is durably committed to
the database and all subsequently started transactions will observe its effects.

It is not necessary to commit a read-only transaction – you can simply call
fdb_transaction_destroy().

Returns an FDBFuture representing an empty value. You must first wait for the
FDBFuture to be ready, check for errors, and then destroy the FDBFuture with
fdb_future_destroy().

Callers will usually want to retry a transaction if the commit or a prior
fdb_transaction_get_*() returns a retryable error (see
fdb_transaction_on_error()).

As with other client/server databases, in some failure scenarios a client may be
unable to determine whether a transaction succeeded. In these cases,
fdb_transaction_commit() will return a commit_unknown_result error. The
fdb_transaction_on_error() function treats this error as retryable, so retry
loops that don’t check for commit_unknown_result could execute the transaction
twice. In these cases, you must consider the idempotence of the transaction. For
more information, see Transactions with unknown results.

Normally, commit will wait for outstanding reads to return. However, if those
reads were snapshot reads or the transaction option for disabling
“read-your-writes” has been invoked, any outstanding reads will immediately
return errors.
"""
function fdb_transaction_commit(tr)
    ccall((:fdb_transaction_commit, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t,), tr)
end

"""
C function:
```
fdb_error_t fdb_transaction_get_committed_version(
    FDBTransaction* transaction,
    int64_t* out_version
)
```

Retrieves the database version number at which a given transaction was
committed. fdb_transaction_commit() must have been called on transaction and the
resulting future must be ready and not an error before this function is called,
or the behavior is undefined. Read-only transactions do not modify the database
when committed and will have a committed version of -1. Keep in mind that a
transaction which reads keys and then sets them to their current values may be
optimized to a read-only transaction.

Note that database versions are not necessarily unique to a given transaction
and so cannot be used to determine in what order two transactions completed. The
only use for this function is to manually enforce causal consistency when
calling fdb_transaction_set_read_version() on another subsequent transaction.

Most applications will not call this function.
"""
function fdb_transaction_get_committed_version(tr, out_version)
    ccall((:fdb_transaction_get_committed_version, fdb_c), fdb_error_t, (fdb_transaction_ptr_t, Ptr{Int64}), tr, out_version)
end

"""
C function:
`FDBFuture* fdb_transaction_get_versionstamp(FDBTransaction* transaction)`

Returns an FDBFuture which will be set to the versionstamp which was used by any
versionstamp operations in this transaction. You must first wait for the
FDBFuture to be ready, check for errors, call fdb_future_get_key() to extract
the key, and then destroy the FDBFuture with fdb_future_destroy().

The future will be ready only after the successful completion of a call to
fdb_transaction_commit() on this Transaction. Read-only transactions do not
modify the database when committed and will result in the future completing with
an error. Keep in mind that a transaction which reads keys and then sets them to
their current values may be optimized to a read-only transaction.

Most applications will not call this function.
"""
function fdb_transaction_get_versionstamp(tr)
    ccall((:fdb_transaction_get_versionstamp, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t,), tr)
end

"""
C function:
```
FDBFuture* fdb_transaction_on_error(
    FDBTransaction* transaction,
    fdb_error_t error
)
```

Implements the recommended retry and backoff behavior for a transaction. This
function knows which of the error codes generated by other fdb_transaction_*()
functions represent temporary error conditions and which represent application
errors that should be handled by the application. It also implements an
exponential backoff strategy to avoid swamping the database cluster with
excessive retries when there is a high level of conflict between transactions.

On receiving any type of error from an fdb_transaction_*() function, the
application should:
- Call fdb_transaction_on_error() with the returned fdb_error_t code.
- Wait for the resulting future to be ready.
- If the resulting future is itself an error, destroy the future and
    FDBTransaction and report the error in an appropriate way.
- If the resulting future is not an error, destroy the future and restart the
    application code that performs the transaction. The transaction itself will
    have already been reset to its initial state, but should not be destroyed
    and re-created because state used by fdb_transaction_on_error() to implement
    its backoff strategy and state related to timeouts and retry limits is
    stored there.

Returns an FDBFuture representing an empty value. You must first wait for the
FDBFuture to be ready, check for errors, and then destroy the FDBFuture with
fdb_future_destroy().
"""
function fdb_transaction_on_error(tr, error::fdb_error_t)
    ccall((:fdb_transaction_on_error, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, fdb_error_t), tr, error)
end

"""
C function:
`void fdb_transaction_reset(FDBTransaction* transaction)`

Reset transaction to its initial state. This is similar to calling
fdb_transaction_destroy() followed by fdb_database_create_transaction(). It is
not necessary to call fdb_transaction_reset() when handling an error with
fdb_transaction_on_error() since the transaction has already been reset.
"""
function fdb_transaction_reset(tr)
    ccall((:fdb_transaction_reset, fdb_c), Nothing, (fdb_transaction_ptr_t,), tr)
end

"""
C function:
```
fdb_error_t fdb_transaction_add_conflict_range(
    FDBTransaction* transaction,
    uint8_t const* begin_key_name,
    int begin_key_name_length,
    uint8_t const* end_key_name,
    int end_key_name_length,
    FDBConflictRangeType type
)
```

Adds a conflict range to a transaction without performing the associated read or
write.

Note: Most applications will use the serializable isolation that transactions
provide by default and will not need to manipulate conflict ranges.

Parameters:
- begin_key_name: A pointer to the name of the key specifying the beginning of
    the conflict range. The value does not need to be NULL-terminated.
- begin_key_name_length: The length of the parameter specified by begin_key_name
- end_key_name: A pointer to the name of the key specifying the end of the
    conflict range. The value does not need to be NULL-terminated.
- end_key_name_length: The length of the parameter specified by
    end_key_name_length.
- type: One of the FDBConflictRangeType values indicating what type of conflict
    range is being set.
"""
function fdb_transaction_add_conflict_range(tr, begin_key_name, begin_key_name_length::Cint, end_key_name, end_key_name_length::Cint, _type::fdb_conflict_range_type_t)
    ccall((:fdb_transaction_add_conflict_range, fdb_c), fdb_error_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, Ptr{UInt8}, Cint, fdb_conflict_range_type_t), tr, begin_key_name, begin_key_name_length, end_key_name, end_key_name_length, _type)
end

"""
C function:
```
FDBFuture* fdb_transaction_get(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length,
    fdb_bool_t snapshot
)
```

Reads a value from the database snapshot represented by transaction.

Returns an FDBFuture which will be set to the value of key_name in the database.
You must first wait for the FDBFuture to be ready, check for errors, call
fdb_future_get_value() to extract the value, and then destroy the FDBFuture with
fdb_future_destroy().

See fdb_future_get_value() to see exactly how results are unpacked. If key_name
is not present in the database, the result is not an error, but a zero for
*out_present returned from that function.

Parameters:
- key_name: A pointer to the name of the key to be looked up in the database.
    The value does not need to be NULL-terminated.
- key_name_length: The length of the parameter specified by key_name.
- snapshot: Non-zero if this is a snapshot read.
"""
function fdb_transaction_get(tr, key_name, key_name_length::Cint)
    ccall((:fdb_transaction_get, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint), tr, key_name, key_name_length)
end

"""
C function:
```
FDBFuture* fdb_transaction_get_key(
    FDBTransaction* transaction,
    uint8_t const* key_name,
    int key_name_length,
    fdb_bool_t or_equal,
    int offset,
    fdb_bool_t snapshot
)
```

Resolves a key selector against the keys in the database snapshot represented
by transaction.

Returns an FDBFuture which will be set to the key in the database matching the
key selector. You must first wait for the FDBFuture to be ready, check for
errors, call fdb_future_get_key() to extract the key, and then destroy the
FDBFuture with fdb_future_destroy().

Parameters:
- key_name, key_name_length, or_equal, offset: The four components of a key selector.
- snapshot: Non-zero if this is a snapshot read.
"""
function fdb_transaction_get_key(tr, key_name, key_name_length::Cint, or_equal::fdb_bool_t, offset::Cint)
    ccall((:fdb_transaction_get_key, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, fdb_bool_t, Cint), tr, key_name, key_name_length, or_equal, offset)
end

"""
C function:
```
FDBFuture* fdb_transaction_get_range(
    FDBTransaction* transaction,
    uint8_t const* begin_key_name,
    int begin_key_name_length,
    fdb_bool_t begin_or_equal,
    int begin_offset,
    uint8_t const* end_key_name,
    int end_key_name_length,
    fdb_bool_t end_or_equal,
    int end_offset,
    int limit,
    int target_bytes,
    FDBStreamingMode mode,
    int iteration,
    fdb_bool_t snapshot,
    fdb_bool_t reverse
)
```

Reads all key-value pairs in the database snapshot represented by transaction
(potentially limited by limit, target_bytes, or mode) which have a key
lexicographically greater than or equal to the key resolved by the begin key
selector and lexicographically less than the key resolved by the end key
selector.

Returns an FDBFuture which will be set to an FDBKeyValue array. You must first
wait for the FDBFuture to be ready, check for errors, call
fdb_future_get_keyvalue_array() to extract the key-value array, and then destroy
the FDBFuture with fdb_future_destroy().

Parameters:
- begin_key_name, begin_key_name_length, begin_or_equal, begin_offset: The four
    components of a key selector describing the beginning of the range.
- end_key_name, end_key_name_length, end_or_equal, end_offset: The four
    components of a key selector describing the end of the range.
- limit: If non-zero, indicates the maximum number of key-value pairs to return.
    If this limit was reached before the end of the specified range, then the
    *more return of fdb_future_get_keyvalue_array() will be set to a non-zero
    value.
- target_bytes: If non-zero, indicates a (soft) cap on the combined number of
    bytes of keys and values to return. If this limit was reached before the end
    of the specified range, then the *more return of
    fdb_future_get_keyvalue_array() will be set to a non-zero value.
- mode: One of the FDBStreamingMode values indicating how the caller would like
    the data in the range returned.
- iteration: If mode is FDB_STREAMING_MODE_ITERATOR, this parameter should start
    at 1 and be incremented by 1 for each successive call while reading this
    range. In all other cases it is ignored.
- snapshot: Non-zero if this is a snapshot read.
- reverse: If non-zero, key-value pairs will be returned in reverse
    lexicographical order beginning at the end of the range.
"""
function fdb_transaction_get_range(tr,
        begin_key_name, begin_key_name_length::Cint, begin_or_equal::fdb_bool_t, begin_offset::Cint,
        end_key_name, end_key_name_length::Cint, end_or_equal::fdb_bool_t, end_offset::Cint,
        limit::Cint=Cint(0), target_bytes::Cint=Cint(0), streaming_mode::Cint=FDBStreamingMode.WANT_ALL, iteration::Cint=Cint(0), snapshot::Cint=Cint(0), reverse::fdb_bool_t=fdb_bool_t(0))
    ccall((:fdb_transaction_get_range, fdb_c), fdb_future_ptr_t,
        (fdb_transaction_ptr_t,
        Ptr{UInt8}, Cint, fdb_bool_t, Cint,
        Ptr{UInt8}, Cint, fdb_bool_t, Cint,
        Cint, Cint, Cint, Cint, Cint, fdb_bool_t),
        tr,
        begin_key_name, begin_key_name_length, begin_or_equal, begin_offset,
        end_key_name, end_key_name_length, end_or_equal, end_offset,
        limit, target_bytes, streaming_mode, iteration, snapshot, reverse)
end
#=
API signature has changed
function fdb_transaction_get_range(tr, begin_key_name, begin_key_name_length::Cint, end_key_name, end_key_name_length::Cint, limit::Cint)
    ccall((:fdb_transaction_get_range, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, Ptr{UInt8}, Cint, Cint), tr, begin_key_name, begin_key_name_length, end_key_name, end_key_name_length, limit)
end
=#

#=
API has been removed
function fdb_transaction_get_range_selector(tr, begin_key_name, begin_key_name_length::Cint, begin_or_equal::fdb_bool_t, begin_offset::Cint, end_key_name, end_key_name_length::Cint, end_or_equal::fdb_bool_t, end_offset::Cint, limit::Cint)
    ccall((:fdb_transaction_get_range_selector, fdb_c), fdb_future_ptr_t, (fdb_transaction_ptr_t, Ptr{UInt8}, Cint, fdb_bool_t, Cint, Ptr{UInt8}, Cint, fdb_bool_t, Cint, Cint), tr, begin_key_name, begin_key_name_length, begin_or_equal, begin_offset, end_key_name, end_key_name_length, end_or_equal, end_offset, limit)
end
=#

#------------------------------------------------------------------------------
# MISCELLANEOUS
#------------------------------------------------------------------------------

"""
C function:
`const char* fdb_get_error(fdb_error_t code)`

Returns a (somewhat) human-readable English message from an error code. The
return value is a statically allocated null-terminated string that must not be
freed by the caller.
"""
function fdb_get_error(code::fdb_error_t)
    ccall((:fdb_get_error, fdb_c), Cstring, (fdb_error_t,), code)
end

"""
C function:
`fdb_bool_t fdb_error_predicate(int predicate_test, fdb_error_t code)`

Evaluates a predicate against an error code. The predicate to run should be one
of the codes listed by the FDBErrorPredicate enum defined within
fdb_c_options.g.h. Sample predicates include FDB_ERROR_PREDICATE_RETRYABLE,
which can be used to determine whether the error with the given code is a
retryable error or not.
"""
function fdb_error_predicate(predicate_test::fdb_error_predicate_t, code::fdb_error_t)
    ccall((:fdb_error_predicate, fdb_c), fdb_bool_t, (fdb_error_predicate_t, fdb_error_t), predicate_test, code)
end


text:
using Clustering
using Distances
using JuMP
using MLKernels
using NearestNeighbors
using SVDD

using LinearAlgebra
using Statistics
using Random

abstract type Sampler end

DataSet = Array{Float64, 2}

function sample(sampler::Sampler, data::DataSet, labels::Vector{Symbol})::BitArray{1} end

struct SamplingException <: Exception
    msg
end

Base.showerror(io::IO, e::SamplingException) = print(io, "Exception during sampling: $(e.msg)")

include("density_util.jl")
include("pwc.jl")

include("sampler_util.jl")
include("sampler/RandomSampler.jl")
include("sampler/HDS.jl")

include("sampler/RAPID.jl")

include("sampler/BPS.jl")
include("sampler/CSSVDD.jl")
include("sampler/DAEDS.jl")
include("sampler/DBSRSVDD.jl")
include("sampler/FBPE.jl")
include("sampler/HSC.jl")
include("sampler/HSR.jl")
include("sampler/IESRSVDD.jl")
include("sampler/KFNCBD.jl")
include("sampler/KMSVDD.jl")
include("sampler/NDPSR.jl")
include("sampler/RSSVDD.jl")

include("sampler/PreFilteringWrapper.jl")

ext: jl


text:
export rrule
"""
    ChainRulesCore.rrule(itp::AbstractInterpolation, x...)

ChainRulesCore.jl `rrule` for integration with automatic differentiation libraries.
Note that it gives the gradient only with respect to the evaluation point `x`,
and not the data inside `itp`.
"""
function ChainRulesCore.rrule(itp::AbstractInterpolation, x...)
    y = itp(x...)
    function interpolate_pullback(Δy)
        nope = ChainRulesCore.@not_implemented "`Interpolations.gradient` does not calculate a gradient with respect to the original data, only the evaluation point"
        (nope, (Δy * Interpolations.gradient(itp, x...))...)
    end
    y, interpolate_pullback
end


text:
using ADI
using ProgressLogging
using PSFModels: Gaussian
using PyCall
using SiriusB

fits = pyimport("astropy.io.fits")
pro = pyimport("proplot")

pro.use_style("ggplot")
pro.rc["image.cmap"] = "inferno"
pro.rc["image.origin"] = "lower"
pro.rc["grid"] = false

function plot_results(res_cube, angles, curve; fwhm, epoch, label)
    # produce images
    flat_res = collapse(res_cube, angles)
    snr_map = detectionmap(flat_res, fwhm)
    sig_map = detectionmap(significance, flat_res, fwhm)
    stim_map = stimmap(res_cube, angles)

    arcsec = curve.distance .* SiriusB.pxscale

    ticks = range(20, 179, length=5)
    tick_labs = @. string(round(SiriusB.pxscale * (ticks - 99.5), digits=1))
    # start plotting
    # NB: proplot does tricky things with the Axes class that doesn't play nicely
    # with PyCall.jl because it's treated as a Vector in Julia, so use Python
    # syntax instead
    py"""
    import proplot as pro

    layout = [[1, 2, 5, 5],
              [3, 4, 5, 5]]
    fig, axs = pro.subplots(
        layout,
        width="7.5in",
        wspace=("3.25em", "2.5em", "0"),
        hspace="1.25em",
        share=0
    )
    axs[0].imshow($flat_res, colorbar="r", colorbar_kw=dict(space=0))
    axs[0].text(6, 6, "flat residual", color="w")
    axs[2].imshow($stim_map, vmin=0, vmax=1, colorbar="r", colorbar_kw=dict(space=0))
    axs[2].text(6, 6, "STIM prob.", color="w")
    axs[1].imshow($snr_map, vmin=0, vmax=5, colorbar="r", colorbar_kw=dict(space=0))
    axs[1].text(6, 6, "S/N", color="w")
    axs[3].imshow($sig_map, vmin=0, vmax=5, colorbar="r", colorbar_kw=dict(space=0))
    axs[3].text(6, 6, "significance", color="w")

    axs[0].format(xticks="null")
    axs[1].format(xticks="null", yticks="null")
    axs[3].format(yticks="null")
    axs[0].format(
        yticks=$ticks,
        yticklabels=$tick_labs,
        ylabel="y [arcsec]"
    )
    axs[2].format(
        yticks=$ticks,
        yticklabels=$tick_labs,
        ylabel="y [arcsec]"
    )
    axs[2:4].format(
        xticks=$ticks,
        xticklabels=$tick_labs,
        xlabel="x [arcsec]",
    )

    axs[4].plot($arcsec, $(curve.contrast), label="Gaussian", lw=1)
    axs[4].plot($arcsec, $(curve.contrast_corr), color="C0", ls="--", label="Student-t", lw=1)
    axs[4].fill_between($arcsec, $(curve.contrast), $(curve.contrast_corr), color="C0", alpha=0.2)
    axs[4].legend(ncol=1)
    axs[4].format(
        ytickloc="right",
        yformatter="log",
        xlabel="separation [arcsec]",
        ylabel="5σ contrast",
        yscale="log",
        grid=True,
        ylim=(10**(-3.5), 1)
    )
    fig.savefig($(figuredir("reports", make_filename_friendly("$(epoch)_$label") * ".pdf")))
    """
end

function make_filename_friendly(filename)
    tmp = lowercase(filename)
    tmp = replace(tmp, " " => "_")
    tmp = replace(tmp, "(" => "-")
    tmp = replace(tmp,  ")" => "")
end

_epochs = Dict("2020feb04" => "2020-02-04", "2020nov21" => "2020-11-21", "2020nov28" => "2020-11-28")

function plot_curves(contrast_curves; epoch)
    py"""
    import proplot as pro
    fig, axs = pro.subplots(width="7.5in", height="4in")
    """
    i = 0
    for (label, curve) in contrast_curves
        dist = curve.distance .* SiriusB.auscale
        color = "C$i"
        py"""
        axs.plot($dist, $(curve.contrast), color=$color, label=$label)
        axs.plot($dist, $(curve.contrast_corr), color=$color, ls="--")
        """
        i += 1
    end
    py"""
    axs.text(0.25, 5e-4, $(_epochs[epoch]), fontsize=14)
    axs.legend(ncol=2)
    axs.dualx(lambda x: x * $(SiriusB.parallax), label="separation [arcsec]")
    ax2 = axs.alty(
        yticks=[0.09, 0.31, 0.54, 0.77, 1],
        yticklabels=["8", "6", "4", "2", "0"],
        label="Δ mag"
    )

    axs.format(
        ylim=(10**(-3.5), 1),
        yscale="log",
        yformatter="log",
        xlabel="projected separation [AU]",
        ylabel="5σ contrast",
        grid=True
    )
    fig.savefig($(figuredir(make_filename_friendly("$(epoch)_contrast_curves") * ".pdf")))
    """
end

_ncomps = Dict(
    "2020feb04" => 1,
    "2020nov21" => 3,
    "2020nov28" => 2
)

@progress name="epoch" for epoch in ["2020feb04", "2020nov21", "2020nov28"]
    cube = fits.getdata(datadir("epoch_$epoch", "processed", "$(epoch)_sirius-b_cube_calib_registered_crop.fits"))
    angles = fits.getdata(datadir("epoch_$epoch", "processed", "$(epoch)_sirius-b_pa.fits"))

    raw_median = collapse(cube)
    res = gaussian_fit(raw_median, (100.5, 100.5))
    fwhm = res[3]

    psf_model = Gaussian{eltype(cube)}(res[1:2], fwhm)
    @info "FWHM for epoch $epoch is $fwhm px"
    starphot = Metrics.estimate_starphot(cube, fwhm)
    psfphot = Metrics.estimate_starphot(psf_model, fwhm) * exp(res[4])
    @info "star photometry is ≈ $starphot (PSF photometry was $psfphot)"

    # naturally mask out inner FWHM
    av_cube = AnnulusView(cube .- minimum(cube); inner=fwhm)
    # create annular view for potential reductions
    mav_cube = MultiAnnulusView(cube .- minimum(cube), fwhm; inner=fwhm)

    # orient algorithms with appropriate data structures
    ncomp = _ncomps[epoch]
    targets = [
        (Classic(), av_cube, "median"),
        (PCA(ncomp), av_cube, "PCA($ncomp)"),
        (NMF(ncomp), av_cube, "NMF($ncomp)"),
        (GreeDS(ncomp), av_cube, "GreeDS($ncomp)"),
        (Framewise(Classic(), delta_rot=0.5), mav_cube, "annular median"),
        (Framewise(PCA(ncomp), delta_rot=0.5), mav_cube, "annular PCA($ncomp)"),
        (Framewise(NMF(ncomp), delta_rot=0.5), mav_cube, "annular NMF($ncomp)"),
    ]

    contrast_curves = Dict{String, Any}()

    @progress "algorithm" for (alg, target, label) in targets
        @info label
        filename = make_filename_friendly(datadir("epoch_$epoch", "residuals", "$(epoch)_sirius-b_residual_$label.fits"))
        res_cube = load_or_produce(filename) do
            subtract(alg, target; angles, fwhm)
        end
        filename = make_filename_friendly(datadir("epoch_$epoch", "residuals", "$(epoch)_sirius-b_contrast-curve_$label.csv"))
        cc = load_or_produce(filename) do
            contrast_curve(alg, target, angles, psf_model; fwhm, angles, starphot, nbranch=6)
        end
        push!(contrast_curves, label => cc)
        plot_results(res_cube, angles, cc; fwhm, label, epoch)
    end

    plot_curves(contrast_curves; epoch)
end

max_stars_repo_name: mileslucas/sirius-b

max_issues_repo_name: mileslucas/sirius-b

max_forks_repo_name: mileslucas/sirius-b


text:
# # Intro to `InverseProblem`
#
# This example illustrates the construction of an "ensemble simulation" that can
# evaluate an ensemble of column models given an ensemble of free parameter sets.
# The example then builds an `InverseProblem` from observations, an ensemble simulation,
# and a set of free parameters, and illustrates its basic usage.
#
# ## Install dependencies
#
# First let's make sure we have all required packages installed.

# ```julia
# using Pkg
# pkg"add ParameterEstimocean, Oceananigans, Distributions, CairoMakie"
# ```

# First we load few things

using ParameterEstimocean

using Oceananigans
using Oceananigans.Architectures: arch_array
using Oceananigans.Units
using Oceananigans.Models.HydrostaticFreeSurfaceModels: ColumnEnsembleSize
using Oceananigans.TurbulenceClosures: ConvectiveAdjustmentVerticalDiffusivity

using CairoMakie
using Distributions
using JLD2

# We reuse some utilities from a previous example to build observations:

examples_path = joinpath(pathof(ParameterEstimocean), "..", "..", "examples")
include(joinpath(examples_path, "intro_to_observations.jl"))
data_path = generate_synthetic_observations()
observations = SyntheticObservations(data_path, field_names=:b, transformation=ZScore())

# # Building an "ensemble simulation"
#
# Our next task is to construct a parameterized `Oceananigans.Simulation`
# that generates the "foward map" for an ensemble of free parameter sets.
# To generate an ensemble of column model outputs efficiently, we construct one
# 3D `Oceananigans.Simulation` consisting of `Nx` by `Ny` independent column models.
#
# The calibration problem then uses the ensemble
# simulation to find optimal parameters by minimizing the discrepancy between
# the observations and the forward map.

"""
    extract_perfect_parameters(observations, Nensemble)

Extract parameters from a batch of "perfect" observations.
"""
function extract_perfect_parameters(observations, Nensemble)
    Nbatch = length(observations)
    Qᵘ, Qᵇ, N², f = [zeros(Nensemble, Nbatch) for i = 1:4]

    Nz = first(observations).grid.Nz
    Hz = first(observations).grid.Hz
    Lz = first(observations).grid.Lz
    Δt = first(observations).metadata.parameters.Δt

    for (j, obs) in enumerate(observations)
        Qᵘ[:, j] .= obs.metadata.parameters.Qᵘ
        Qᵇ[:, j] .= obs.metadata.parameters.Qᵇ
        N²[:, j] .= obs.metadata.parameters.N²
        f[:, j] .= obs.metadata.coriolis.f
    end

    file = jldopen(first(observations).path)
    closure = file["serialized/closure"]
    close(file)

    return Qᵘ, Qᵇ, N², f, Δt, Lz, Nz, Hz, closure
end

"""
    build_ensemble_simulation(observations, arch=CPU(); Nensemble=1)

Returns an `Oceananigans.Simulation` representing an `Nensemble × 1`
ensemble of column models designed to reproduce `observations`.
"""
function build_ensemble_simulation(observations, arch=CPU(); Nensemble=1)

    observations isa Vector || (observations = [observations]) # Singleton batch
    Nbatch = length(observations)

    Qᵘ, Qᵇ, N², f, Δt, Lz, Nz, Hz, closure = extract_perfect_parameters(observations, Nensemble)

    column_ensemble_size = ColumnEnsembleSize(Nz=Nz, ensemble=(Nensemble, Nbatch), Hz=Hz)
    ensemble_grid = RectilinearGrid(arch, size = column_ensemble_size, topology = (Flat, Flat, Bounded), z = (-Lz, 0))

    coriolis_ensemble = arch_array(arch, [FPlane(f=f[i, j]) for i = 1:Nensemble, j=1:Nbatch])
    closure_ensemble = arch_array(arch, [deepcopy(closure) for i = 1:Nensemble, j=1:Nbatch])

    Qᵘ, Qᵇ, N² = Tuple(arch_array(arch, p) for p in (Qᵘ, Qᵇ, N²))

    u_bcs = FieldBoundaryConditions(top = FluxBoundaryCondition(Qᵘ))
    b_bcs = FieldBoundaryConditions(top = FluxBoundaryCondition(Qᵇ), bottom = GradientBoundaryCondition(N²))

    tracers = first(observations).metadata.parameters.tracers

    ensemble_model = HydrostaticFreeSurfaceModel(grid = ensemble_grid,
                                                 tracers = tracers,
                                                 buoyancy = BuoyancyTracer(),
                                                 boundary_conditions = (; u=u_bcs, b=b_bcs),
                                                 coriolis = coriolis_ensemble,
                                                 closure = closure_ensemble)

    ensemble_simulation = Simulation(ensemble_model; Δt=Δt, stop_time=first(observations).times[end])

    return ensemble_simulation, closure
end

# The following illustrations uses a simple ensemble simulation with two ensemble members:

ensemble_simulation, closure★ = build_ensemble_simulation(observations; Nensemble=3)

# # Free parameters
#
# We construct some prior distributions for our free parameters. We found that it often helps to
# constrain the prior distributions so that neither very high nor very low values for diffusivities
# can be drawn out of the distribution.

priors = (convective_κz = lognormal(mean=0.3, std=0.05),
          background_κz = lognormal(mean=2.5e-4, std=0.25e-4))

free_parameters = FreeParameters(priors)

# We also take the opportunity to collect a named tuple of the optimal parameters

θ★ = (convective_κz = closure★.convective_κz,
      background_κz = closure★.background_κz)

# ## Visualizing the priors
#
# We visualize our prior distributions by plotting a huge number
# of samples:

using ParameterEstimocean.Parameters: unconstrained_prior, transform_to_constrained

Nsamples = 50000000

sample(prior) = [transform_to_constrained(prior, X) for X in rand(unconstrained_prior(prior), Nsamples)]

convective_κz_samples = sample(priors.convective_κz)
background_κz_samples = sample(priors.background_κz)

fig = Figure()
ax_top = Axis(fig[1, 1], xlabel = "convective κᶻ [m² s⁻¹]", ylabel = "Density")
density!(ax_top, convective_κz_samples)
xlims!(ax_top, 0, 10)

ax_bottom = Axis(fig[2, 1], xlabel = "background κᶻ [m² s⁻¹]", ylabel = "Density")
density!(ax_bottom, background_κz_samples)

save("prior_visualization.svg", fig)
nothing # hide

# ![](prior_visualization.svg)

# # The `InverseProblem`

# We can construct the inverse problem ``y = G(θ) + η``. Here, ``y`` are the `observations` and ``G`` is the
# `ensemble_model`.

calibration = InverseProblem(observations, ensemble_simulation, free_parameters)

# ## Using `InverseProblem` to compute `forward_map`
#
# As a sanity check we apply the `forward_map` on the calibration after we initialize all ensemble
# members with the true parameter values. We then confirm that the output of the `forward_map` matches
# the observations to machine precision.

θ¹ = (convective_κz = 0.8 * θ★.convective_κz,
      background_κz = 9.0 * θ★.background_κz)

θ² = (convective_κz = 2.0 * θ★.convective_κz,
      background_κz = 0.1 * θ★.background_κz)

θ_ensemble = [θ★, θ¹, θ²]

G = forward_map(calibration, θ_ensemble)
y = observation_map(calibration)

# The `forward_map` output `G` is a two-dimensional matrix whose first dimension
# is the size of the state space and whose second dimension is the `ensemble_size`.
# Here, we ensure that first ensemble member of the mapped output, which was run with
# the "true" parameters, is identical to the mapped observations:

G[:, 1] ≈ y

# ## Visualizing forward model output
#
# Next we visualize the discrepancy between solutions generated by true
# and non-optimal parameter sets `θ¹` and `θ²`. Time-series data from
# the ensemble run is collected by `calibration.time_series_collector`:

time_series_collector = calibration.time_series_collector
times = time_series_collector.times

## Extract last save point and plot each solution component
Nt = length(times)

b = time_series_collector.field_time_serieses.b[Nt]
t = times[Nt]
z = znodes(b)

## The ensemble varies along the first, or `x`-dimension:
b★ = interior(b)[1, 1, :]
b¹ = interior(b)[2, 1, :]
b² = interior(b)[3, 1, :]

fig = Figure()
ax = Axis(fig[1, 1],
          xlabel = "Buoyancy [m s⁻²]",
          ylabel = "Depth [m]")

b★_label = "true b at t = " * prettytime(t)
b¹_label = "b with $θ¹"
b²_label = "b with $θ²"

lines!(ax, b★, z; label=b★_label, linewidth=2)
lines!(ax, b¹, z; label=b¹_label, linewidth=2)
lines!(ax, b², z; label=b²_label, linewidth=2)

axislegend(ax, position=:lt)

save("ensemble_simulation_demonstration.svg", fig); nothing # hide

# ![](ensemble_simulation_demonstration.svg)


text:
const project_root_dir = realpath(joinpath(@__DIR__, ".."))
unique!(push!(LOAD_PATH, realpath(joinpath(project_root_dir, "experiments/utils/MonteCarloStudy"))))
unique!(push!(LOAD_PATH, realpath(joinpath(project_root_dir, "test/utils"))))

import Distributed
Distributed.@everywhere begin
    import Pkg
    Pkg.activate($project_root_dir)
    union!(LOAD_PATH, $LOAD_PATH)

    import MonteCarloStudy
    import CollisionAvoidanceGame
    import TestDynamics
    using PartiallyObservedInverseGames.ForwardGame: IBRGameSolver, KKTGameSolver
    using PartiallyObservedInverseGames.InverseGames:
        InverseKKTConstraintSolver, InverseKKTResidualSolver
end

import PartiallyObservedInverseGames.TrajectoryVisualization
import VegaLite
import LazyGroupBy: grouped

# Utils
include("utils/misc.jl")
include("utils/simple_caching.jl")
load_cache_if_not_defined!("unicycle")

#==================================== Forward Game Formulation =====================================#

T = 25

control_system =
    TestDynamics.ProductSystem([TestDynamics.Unicycle(0.25), TestDynamics.Unicycle(0.25)])

player_angles = let
    n_players = length(control_system.subsystems)
    map(eachindex(control_system.subsystems)) do ii
        angle_fraction = n_players == 2 ? pi / 2 : 2pi / n_players
        (ii - 1) * angle_fraction
    end
end

x0 = mapreduce(vcat, player_angles) do player_angle
    [unitvector(player_angle + pi); 0.1; player_angle + deg2rad(10)]
end

position_indices = mapreduce(vcat, eachindex(control_system.subsystems)) do subsystem_idx
    TestDynamics.state_indices(control_system, subsystem_idx)[1:2]
end

partial_state_indices = mapreduce(vcat, eachindex(control_system.subsystems)) do subsystem_idx
    TestDynamics.state_indices(control_system, subsystem_idx)[[1, 2, 4]]
end

player_cost_models_gt = map(enumerate(player_angles)) do (ii, player_angle)
    cost_model_p1 = CollisionAvoidanceGame.generate_player_cost_model(;
        player_idx = ii,
        control_system,
        T,
        goal_position = unitvector(player_angle),
    )
end

#======================================== Monte Carlo Study ========================================#

## Dataset Generation
n_observation_sequences_per_noise_level = 40

@run_cached forward_solution_gt, dataset = MonteCarloStudy.generate_dataset(;
    solve_args = (; solver = IBRGameSolver(), control_system, player_cost_models_gt, x0, T),
    noise_levels = unique([0:0.001:0.01; 0.01:0.005:0.03; 0.03:0.01:0.1]),
    n_observation_sequences_per_noise_level,
)

## Estimation
estimator_setup = (;
    dataset,
    control_system,
    player_cost_models = player_cost_models_gt,
    solver_attributes = (; print_level = 1),
)
estimator_setup_partial =
    merge(estimator_setup, (; expected_observation = x -> x[partial_state_indices, :]))
@run_cached estimates_conKKT =
    MonteCarloStudy.estimate(InverseKKTConstraintSolver(); estimator_setup...)
@run_cached estimates_conKKT_partial =
    MonteCarloStudy.estimate(InverseKKTConstraintSolver(); estimator_setup_partial...)
@run_cached estimates_resKKT =
    MonteCarloStudy.estimate(InverseKKTResidualSolver(); estimator_setup...)
@run_cached estimates_resKKT_partial =
    MonteCarloStudy.estimate(InverseKKTResidualSolver(); estimator_setup_partial...)

## Forward Solution Augmentation
augmentor_kwargs = (;
    solver = KKTGameSolver(),
    control_system,
    player_cost_models_gt,
    x0,
    T,
    match_equilibrium = (; forward_solution_gt.x),
    init = (; forward_solution_gt.x, forward_solution_gt.u),
    solver_attributes = (; print_level = 1),
)
@run_cached augmented_estimates_resKKT =
    MonteCarloStudy.augment_with_forward_solution(estimates_resKKT; augmentor_kwargs...)
@run_cached augmented_estimates_resKKT_partial =
    MonteCarloStudy.augment_with_forward_solution(estimates_resKKT_partial; augmentor_kwargs...)
estimates = [
    estimates_conKKT
    estimates_conKKT_partial
    augmented_estimates_resKKT
    augmented_estimates_resKKT_partial
]

## Error Ststistics Computation
demo_gt = merge((; player_cost_models_gt), forward_solution_gt)
@save_json errstats = map(estimates) do estimate
    MonteCarloStudy.estimator_statistics(estimate; dataset, demo_gt, position_indices)
end

## Visualization
demo_noise_level = 0.1
trajectory_viz_domain = (; x_position_domain = (-1.2, 1.2), y_position_domain = (-1.2, 1.2))

@save_json trajectory_data_gt =
    TrajectoryVisualization.trajectory_data(control_system, forward_solution_gt.x)

@save_json trajectory_data_obs = [
    TrajectoryVisualization.trajectory_data(control_system, d.x) for
    d in dataset if d.σ == demo_noise_level
]

@save_json trajectory_data_estimates =
    map.(
        e -> TrajectoryVisualization.trajectory_data(control_system, e.x),
        grouped(
            e -> e.estimator_name,
            Iterators.filter(
                e -> e.converged && dataset[e.observation_idx].σ == demo_noise_level,
                estimates,
            ),
        ),
    )

ground_truth_viz =
    TrajectoryVisualization.visualize_trajectory(
        trajectory_data_gt;
        canvas = VegaLite.@vlplot(width = 200, height = 200),
        legend = VegaLite.@vlfrag(orient = "top", offset = 5),
        trajectory_viz_domain...,
    ) + VegaLite.@vlplot(
        data = filter(s -> s.t == 1, trajectory_data_gt),
        mark = {"text", dx = 8, dy = 8},
        text = "player",
        x = "px",
        y = "py",
    )

observations_bundle_viz =
    VegaLite.@vlplot() + MonteCarloStudy.visualize_trajectory_batch(
        trajectory_data_obs;
        trajectory_viz_domain...,
        draw_line = false,
    )

viz_trajectory_estiamtes = Dict(
    k => TrajectoryVisualization.visualize_trajectory_batch(v; trajectory_viz_domain...) for
    (k, v) in trajectory_data_estimates
)

@saveviz demo_trajs_viz = hcat(
    VegaLite.@vlplot(title = "Ground Truth") + ground_truth_viz,
    VegaLite.@vlplot(title = "Observations") + observations_bundle_viz,
)

@saveviz ours_trajs_viz = hcat(
    VegaLite.@vlplot(title = "Full Observation") + viz_trajectory_estiamtes["Ours Full"],
    VegaLite.@vlplot(title = "Partial Observation") + viz_trajectory_estiamtes["Ours Partial"],
)

@saveviz baseline_trajs_viz = hcat(
    VegaLite.@vlplot(title = "Full Observation") + viz_trajectory_estiamtes["Baseline Full"],
    VegaLite.@vlplot(title = "Partial Observation") + viz_trajectory_estiamtes["Baseline Partial"],
)

frame = [-1.5n_observation_sequences_per_noise_level, 0]
@saveviz parameter_error_viz =
    errstats |> MonteCarloStudy.visualize_paramerr(; frame, round_x_axis = false)
@saveviz position_error_viz =
    errstats |> MonteCarloStudy.visualize_poserr(; frame, round_x_axis = false)


text:
function _fit(glr::GLR{RobustLoss{ρ},<:L2R}, solver::IWLSCG, X, y, scratch
              ) where {ρ}
    n,p,_ = npc(scratch)
    _Mv! = Mv!(glr, X, y, scratch; threshold=solver.threshold)
    κ    = solver.damping # between 0 and 1, 1 = fully take the new iteration
    # cache
    θ  = zeros(p)
    θ_ = zeros(p)
    b  = zeros(p)  # will contain X'Wy
    ω  = zeros(n)  # will contain the diagonal of W
    # params for the  loop
    max_cg_steps = min(solver.max_inner, p)
    k, tol = 0, Inf
    while k < solver.max_iter && tol > solver.tol
        # update the weights and retrieve the application function
        # Mθv! corresponds to the current application of (X'WX + λI) on v
        Mθv! = _Mv!(ω, θ)
        Mm  = LinearMap(Mθv!, p;
                        ismutating=true, isposdef=true, issymmetric=true)
        Wy  = ω .* y
        b   = X'Wy
        if glr.fit_intercept
            b = vcat(b, sum(Wy))
        end
        # update
        θ  .= (1-κ) .* θ .+ κ .* cg(Mm, b; maxiter=max_cg_steps)
        # check tolerance
        tol = norm(θ .- θ_) / (norm(θ) + eps())
        # update cache
        copyto!(θ_, θ)
        k  += 1
    end
    tol ≤ solver.tol ||
        @warn "IWLS did not converge in $(solver.max_iter) iterations."
    return θ
end


text:
module FinElt

using Printf: @sprintf
using SparseArrays: sparse

export Mesh, read_msh_file, write_pos_file
export GeomType, LINE, TRIANGLE, TETRAHEDRON, get_mesh_h, count_elms
export save_nodal_scalar_field, save_nodal_vector_field
export save_warp_nodal_scalar_field, write_format_version
export save_elm_scalar_field
export get_node_coords, get_nodal_vals, successive_refine

export VariationalProblem, assign_bdry_vals!, EigenProblem
export DoF, degrees_of_freedom
export assembled_linear_system, assembled_eigenproblem_matrices
export assembled_matrix, assembled_vector!, assembled_vector
export complete_soln, complete_soln!

include("FinElt/Gmsh.jl")
include("FinElt/FEM.jl")
include("FinElt/PlanarPoisson.jl")

end # module


text:
# Naive implementation

struct DQExperience{N <: Real,T <: Real, A<:AbstractArray{T}}
    s::A
    a::N
    r::T
    sp::A
    done::Bool
end

function Base.convert(::Type{DQExperience{Int32, Float32, C}}, x::DQExperience{A, B, C}) where {A, B, C}
    return DQExperience{Int32, Float32, C}(convert(C, x.s),
                                            convert(Int32, x.a),
                                            convert(Float32, x.r),
                                            convert(C, x.sp),
                                            x.done)
end

mutable struct PrioritizedReplayBuffer{N<:Integer, T<:AbstractFloat,CI,Q,A<:AbstractArray{T}}
    max_size::Int64
    batch_size::Int64
    rng::AbstractRNG
    α::Float32
    β::Float32
    ϵ::Float32
    _curr_size::Int64
    _idx::Int64
    _priorities::Vector{T}
    _experience::Vector{DQExperience{N,T,Q}}

    _s_batch::A
    _a_batch::Vector{CI}
    _r_batch::Vector{T}
    _sp_batch::A
    _done_batch::Vector{T}
    _weights_batch::Vector{T}
end

function PrioritizedReplayBuffer(env::AbstractEnvironment{OV},
                                max_size::Int64,
                                batch_size::Int64;
                                rng::AbstractRNG = MersenneTwister(0),
                                α::Float32 = 6f-1,
                                β::Float32 = 4f-1,
                                ϵ::Float32 = 1f-3) where {OV}
    s_dim = obs_dimensions(env)
    experience = Vector{DQExperience{Int32, Float32, OV}}(undef, max_size)
    priorities = Vector{Float32}(undef, max_size)
    _s_batch = zeros(Float32, s_dim..., batch_size)
    _a_batch = [CartesianIndex(0,0) for i=1:batch_size]
    _r_batch = zeros(Float32, batch_size)
    _sp_batch = zeros(Float32, s_dim..., batch_size)
    _done_batch = zeros(Float32, batch_size)
    _weights_batch = zeros(Float32, batch_size)
    return PrioritizedReplayBuffer(max_size, batch_size, rng, α, β, ϵ, 0, 1, priorities, experience,
                _s_batch, _a_batch, _r_batch, _sp_batch, _done_batch, _weights_batch)
end

is_full(r::PrioritizedReplayBuffer) = r._curr_size == r.max_size

max_size(r::PrioritizedReplayBuffer) = r.max_size

function add_exp!(r::PrioritizedReplayBuffer, expe::DQExperience, td_err::T=abs(expe.r)) where T
    @assert td_err + r.ϵ > 0.
    priority = (td_err + r.ϵ)^r.α
    r._experience[r._idx] = expe
    r._priorities[r._idx] = priority
    r._idx = mod1((r._idx + 1),r.max_size)
    if r._curr_size < r.max_size
        r._curr_size += 1
    end
end

function update_priorities!(r::PrioritizedReplayBuffer, indices::Vector{Int64}, td_errors::V) where V <: AbstractArray
    new_priorities = (abs.(td_errors) .+ r.ϵ).^r.α
    @assert all(new_priorities .> 0f0)
    r._priorities[indices] = new_priorities
end

function StatsBase.sample(r::PrioritizedReplayBuffer)
    @assert r._curr_size >= r.batch_size
    @assert r.max_size >= r.batch_size # could be checked during construction
    sample_indices = sample(r.rng, 1:r._curr_size, Weights(r._priorities[1:r._curr_size]), r.batch_size, replace=false)
    return get_batch(r, sample_indices)
end

function get_batch(r::PrioritizedReplayBuffer, sample_indices::Vector{Int64})
    @assert length(sample_indices) == size(r._s_batch)[end]
    for (i, idx) in enumerate(sample_indices)
        @inbounds begin
            r._s_batch[.., i] = vec(r._experience[idx].s)
            r._a_batch[i] = CartesianIndex(r._experience[idx].a, i)
            r._r_batch[i] = r._experience[idx].r
            r._sp_batch[.., i] = vec(r._experience[idx].sp)
            r._done_batch[i] = r._experience[idx].done
            r._weights_batch[i] = r._priorities[idx]
        end
    end
    p = r._weights_batch ./ sum(r._priorities[1:r._curr_size])
    weights = (r._curr_size * p).^(-r.β)
    return r._s_batch, r._a_batch, r._r_batch, r._sp_batch, r._done_batch, sample_indices, weights
end

function populate_replay_buffer!(replay::PrioritizedReplayBuffer,
                                 env::AbstractEnvironment,
                                 action_indices;
                                 max_pop::Int64=replay.max_size, max_steps::Int64=100,
                                 policy::Policy = RandomPolicy(env.problem))
    o = reset!(env)
    done = false
    step = 0
    for t=1:(max_pop - replay._curr_size)
        a = action(policy, o)
        ai = action_indices[a]
        op, rew, done, info = step!(env, a)
        exp = DQExperience(o, ai, Float32(rew), op, done)
        add_exp!(replay, exp, abs(Float32(rew))) # assume initial td error is r
        o = op
        # println(o, " ", action, " ", rew, " ", done, " ", info) #TODO verbose?
        step += 1
        if done || step >= max_steps
            o = reset!(env)
            done = false
            step = 0
        end
    end
    @assert replay._curr_size >= replay.batch_size
end


text:
using DataFrames
using CSV

# TODO REFACTOR
function load_tables()

    cols = ["source", "target", "flags"]

    df_icd9 = CSV.File("2018_I9gem.txt", delim = ' ',
        header = false, type=String, ignorerepeated=true) |> DataFrame
    rename!(df_icd9, cols)
    df_icd10 = CSV.File("2018_I10gem.txt", delim = ' ', header = false, type=String, ignorerepeated=true) |> DataFrame
    rename!(df_icd10, cols)

    df_icd9_desc = CSV.File("CMS32_DESC_LONG_DX.txt", delim = '\t', header = false, type=String) |> DataFrame
    df_icd10_desc = CSV.File("icd10cm_codes_2018.txt", delim = '\t', header = false, type=String) |> DataFrame

    df_icd9_pcs = CSV.File("gem_i9pcs.txt", delim = ' ', header = false, type=String, ignorerepeated=true) |> DataFrame
    rename!(df_icd9_pcs, cols)
    df_icd10_pcs = CSV.File("gem_pcsi9.txt", delim = ' ', header = false, type=String, ignorerepeated=true) |> DataFrame
    rename!(df_icd10_pcs, cols)

    df_icd10_pcs_desc = CSV.File("icd10pcs_order_2014.txt", delim = '\t', header = false, type=String) |> DataFrame

    return df_icd9, df_icd10, df_icd9_desc, df_icd10_desc, df_icd9_pcs, df_icd10_pcs, df_icd10_pcs_desc
end

function make_flag_cols(df_in)

    df = copy(df_in)

    df[:flags] = map(x -> lpad(split(string(x), " ")[1], 5, "0"), df[:flags])

    flag_types = [ "approximate", "no map", "combination", "scenario", "choice list"]

    for (index, flag) in enumerate(flag_types)
        df[Symbol(flag_types[index])] = map(x -> string(x[index]), df[:flags])
    end

    select!(df, Not(:flags))

    return df
end

function make_desc_cols(df_in, code::String)

    df = copy(df_in)

    df[:code] = map(x -> split(string(x), " ")[1], df[:Column1])
    df[:descriptions] = map(x -> lstrip(join(split(string(x), " ")[2:end], " ")), df[:Column1])

    select!(df, Not(:Column1))

    return df
end

function join_icd_descriptions(df_gems, df_desc)

    df = join(df_gems, df_desc,
        on = :target => :code,
        kind = :inner)
    return df
end

function join_icd_descriptions(df_gems, df_desc_target, df_source_description)

    df = join(df_gems, df_desc_target,
        on = :target => :code,
        kind = :inner)
    rename!(df, Dict(:descriptions => :target_descriptions))

    df = join(df, df_source_description,
        on = :source => :code,
        kind = :left)
    rename!(df, Dict(:descriptions => :source_descriptions))

    return df
end

cd("../data/")

gems9, gems10, desc9, desc10, icd9_pcs, icd10_pcs, icd10_pcs_desc = load_tables();

gems9 = make_flag_cols(gems9);
gems10 = make_flag_cols(gems10);
desc9 =  make_desc_cols(desc9, "icd9");
desc10 =  make_desc_cols(desc10, "icd10");
gems9_10 = join_icd_descriptions(gems9,desc10, desc9);
gems10_9 = join_icd_descriptions(gems10,desc9, desc10);

icd9_pcs = make_flag_cols(icd9_pcs);
icd10_pcs = make_flag_cols(icd10_pcs);
desc10pcs = make_desc_cols(icd10_pcs_desc, "icd10_pcs");

CSV.write("processed/gems9_10.csv", gems9_10)
CSV.write("processed/gems10_9.csv", gems10_9)
CSV.write("processed/gems_pcs_desc.csv", desc10pcs)
CSV.write("processed/gems_icd9_pcs.csv", icd9_pcs)
CSV.write("processed/gems_icd10_pcs.csv", icd10_pcs)

max_stars_repo_name: pkmklong/Gems

max_issues_repo_name: pkmklong/Gems

max_forks_repo_name: pkmklong/Gems


text:
@testset "ntdist.jl" begin
    rng = MersenneTwister(42)
    q = Normal()
    d = NTDist(q)
    n = 10
    ds = [NTDist(q) for _ in 1:n]
    @test d isa SpecialDistributions.AbstractNTDist
    @test rand(rng, Float64, d) isa NamedTuple
    @test rand(rng, d) isa NamedTuple
    @test rand(rng, d, n) isa TupleVector
    @test length(rand(rng, d, n)) == 10
    @test dist(d) == q
    @test mean(d) isa NamedTuple
    @test mean(ds) isa TupleVector

    x = ntrand(d)
    @test keys(x) == (:ω,)
    @test logdensity_def(d, x) isa Real

    d2 = NTDist{:γ}(q)
    x = ntrand(d2)
    @test keys(x) == (:γ,)

    @test ntrand(q) isa NamedTuple
    @test ntmean(q) isa NamedTuple

    @test tvrand(rng, ds) isa TupleVector
    @test tvmean(ds) isa TupleVector

    @test kldivergence(d, d) == 0
end


text:
###############################################################################
#
#  Number_field with Brauer
#
###############################################################################
@doc Markdown.doc"""
    NumberField_using_Brauer(CF::ClassField) -> NfRelNS{Nemo.nf_elem}
Given a (formal) abelian extension, compute the class field by
finding defining polynomials
for all prime power cyclic subfields using Brauer relations.
Note, by type this is always a non-simple extension.
"""
function NumberField_using_Brauer(CF::ClassField{S, T}; redo::Bool = false) where {S, T}
  if isdefined(CF, :A) && !redo
    return CF.A
  end

  res = ClassField_pp{S, T}[]
  G = codomain(CF.quotientmap)
  @assert issnf(G)
  q = GrpAbFinGenElem[G[i] for i=1:ngens(G)]
  for i=1:ngens(G)
    o = G.snf[i]
    lo = factor(o)
    for (p, e) = lo.fac
      q[i] = p^e*G[i]
      S1, mQ = quo(G, q, false)
      push!(res, ray_class_field_cyclic_pp_Brauer(CF, mQ))
    end
    q[i] = G[i]
  end
  CF.cyc = res
  CF.A = number_field(Generic.Poly{nf_elem}[x.A.pol for x = CF.cyc], check = false, cached = false)[1]
  @vprint :ClassField 1 "NumberField_using_Brauer finished\n"
  return CF.A
end

function ray_class_field_cyclic_pp_Brauer(CF::ClassField{S, T}, mQ::GrpAbFinGenMap) where {S, T}
  @vprint :ClassField 1 "cyclic prime power class field of degree $(degree(CF))\n"
  CFpp = ClassField_pp{S, T}()
  CFpp.quotientmap = compose(CF.quotientmap, mQ)
  CFpp.rayclassgroupmap = CF.rayclassgroupmap
  @assert domain(CFpp.rayclassgroupmap) == domain(CFpp.quotientmap)

  e = degree(CFpp)
  v, q = ispower(e)
  k = base_field(CFpp)
  CE = cyclotomic_extension(k, e)

  @vprint :ClassField 1 "computing the S-units...\n"
  @vtime :ClassField 1 _rcf_S_units_using_Brauer(CFpp)
  mr = CF.rayclassgroupmap
  mq = CFpp.quotientmap
  KK = kummer_extension(e, FacElem{nf_elem, AnticNumberField}[CFpp.a])
  ng, mng = norm_group(KK, CE.mp[2], mr)
  attempt = 1
  while !iszero(mng*mq)
    attempt += 1
    @show "attempt number $(attempt)"
    _rcf_S_units_enlarge(CE, CFpp)
    KK = kummer_extension(e, FacElem{nf_elem, AnticNumberField}[CFpp.a])
    ng, mng = norm_group(KK, CE.mp[2], mr)
  end

  @vprint :ClassField 1 "reducing the generator...\n"
  @vtime :ClassField 1 _rcf_reduce(CFpp)
  @vprint :ClassField 1 "descending ...\n"
  @vtime :ClassField 1 _rcf_descent(CFpp)
  return CFpp
end

################################################################################
#
#  S-units computation
#
################################################################################

function _rcf_S_units_enlarge(CE, CF::ClassField_pp)
  lP = CF.sup
  OK = order(lP[1])
  f = maximum(minimum(p) for p in lP)
  for i = 1:5
    f = next_prime(f)
    push!(lP, prime_decomposition(OK, f)[1][1])
  end
  e = degree(CF)
  @vtime :Fields 3 S, mS = NormRel.sunit_group_fac_elem_quo_via_brauer(nf(OK), lP, e)
  KK = kummer_extension(e, FacElem{nf_elem, AnticNumberField}[mS(S[i]) for i=1:ngens(S)])

  #gens mod n-th power - to speed up the frobenius computation
  gens = KK.gen
  gens_mod_nth = Vector{FacElem{nf_elem, AnticNumberField}}(undef, length(gens))
  for i = 1:length(gens)
    el = Dict{nf_elem, fmpz}()
    for (s, v) in gens[i].fac
      ex = mod(v, e)
      if ex != 0
        el[s] = ex
      end
    end
    gens_mod_nth[i] = FacElem(el)
  end
  KK.gen_mod_nth_power = gens_mod_nth
  CF.bigK = KK
  lf = factor(minimum(defining_modulus(CF)[1]))
  lfs = Set(collect(keys(lf.fac)))
  CE.kummer_exts[lfs] = (lP, KK)
  _rcf_find_kummer(CF)
  return nothing
end

function _rcf_S_units_using_Brauer(CF::ClassField_pp)

  f = defining_modulus(CF)[1]
  @vprint :ClassField 2 "Kummer extension with modulus $f\n"
  k1 = base_field(CF)

  #@assert Hecke.isprime_power(e)
  @vprint :ClassField 2 "Adjoining the root of unity\n"
  C = cyclotomic_extension(k1, degree(CF))
  automorphisms(C, copy = false)
  G, mG = automorphism_group(C.Ka)
  @vtime :ClassField 3 fl = NormRel.has_useful_brauer_relation(G)
  if fl
    @vtime :ClassField 3 lP, KK = _s_unit_for_kummer_using_Brauer(C, minimum(f))
  else
    lP, KK = _s_unit_for_kummer(C, minimum(f))
  end

  CF.bigK = KK
  CF.sup = lP
  _rcf_find_kummer(CF)
  return nothing
end

#This function finds a set S of primes such that we can find a Kummer generator in it.
function _s_unit_for_kummer_using_Brauer(C::CyclotomicExt, f::fmpz)

  e = C.n
  lf = factor(f)
  lfs = Set(collect(keys(lf.fac)))
  for (k, v) in C.kummer_exts
    if issubset(lfs, k)
      return v
    end
  end
  K = absolute_field(C)
  @vprint :ClassField 2 "Maximal order of cyclotomic extension\n"
  ZK = maximal_order(K)

  lP = Hecke.NfOrdIdl[]

  for p = keys(lf.fac)
    #I remove the primes that can't be in the conductor
    lp = prime_decomposition(ZK, p)
    for (P, s) in lp
      if gcd(norm(P), e) != 1 || gcd(norm(P)-1, e) != 1
        push!(lP, P)
      end
    end
  end

  if length(lP) < 10
    #add some primes
    pp = f+1
    while length(lP) < 10
      pp = next_prime(pp)
      lpp = prime_decomposition(ZK, pp)
      if length(lpp) == degree(K)
        push!(lP, lpp[1][1])
      end
    end
  end

  @vprint :Fields 3 "Computing S-units with $(length(lP)) primes\n"
  @vtime :Fields 3 S, mS = NormRel.sunit_group_fac_elem_quo_via_brauer(C.Ka, lP, e)
  KK = kummer_extension(e, FacElem{nf_elem, AnticNumberField}[mS(S[i]) for i=1:ngens(S)])

  #gens mod n-th power - to speed up the frobenius computation
  gens = KK.gen
  gens_mod_nth = Vector{FacElem{nf_elem, AnticNumberField}}(undef, length(gens))
  for i = 1:length(gens)
    el = Dict{nf_elem, fmpz}()
    for (s, v) in gens[i].fac
      ex = mod(v, e)
      if ex != 0
        el[s] = ex
      end
    end
    gens_mod_nth[i] = FacElem(el)
  end
  KK.gen_mod_nth_power = gens_mod_nth
  C.kummer_exts[lfs] = (lP, KK)
  return lP, KK
end


text:
using MPI

MPI.Init()
rank = MPI.Comm_rank(MPI.COMM_WORLD)
size = MPI.Comm_size(MPI.COMM_WORLD)

include("01-hello-impl.jl")
include("02-broadcast-impl.jl")
include("03-reduce-impl.jl")
include("04-sendrecv-impl.jl")

if length(ARGS) == 0
    println("Please specify a transport option to use [MPI|TCP]")
    MPI.Finalize()
    exit(1)
elseif ARGS[1] == "TCP"
    manager = MPI.start_main_loop(TCP_TRANSPORT_ALL) # does not return on worker
elseif ARGS[1] == "MPI"
    manager = MPI.start_main_loop(MPI_TRANSPORT_ALL) # does not return on worker
else
    println("Valid transport options are [MPI|TCP]")
    MPI.Finalize()
    exit(1)
end

# Check whether a worker accidentally returned
@assert rank == 0

nloops = 10^2
function foo(n)
    a=ones(n)
    remotecall_fetch(x->x, mod1(2, size), a);
    @elapsed for i in 1:nloops
        remotecall_fetch(x->x, mod1(2, size), a)
    end
end

n=10^3
foo(1)
t=foo(n)
println("$t seconds for $nloops loops of send-recv of array size $n")

n=10^6
foo(1)
t=foo(n)
println("$t seconds for $nloops loops of send-recv of array size $n")

# We cannot run these examples since they use MPI.Barrier and other blocking
# communication, disabling our event loop
# print("EXAMPLE: HELLO\n")
# @mpi_do manager do_hello()
# print("EXAMPLE: BROADCAST\n")
# @mpi_do manager do_broadcast()
# print("EXAMPLE: REDUCE\n")
# @mpi_do manager do_reduce()
# print("EXAMPLE: SENDRECV\n")
# @mpi_do manager do_sendrecv()

MPI.stop_main_loop(manager)


text:
function get_halfcheetah(;
    timestep=0.01,
    gravity=[0.0; 0.0; -9.81],
    friction_coefficient=0.4,
    contact_feet=true,
    contact_body=true,
    limits=true,
    spring=[240.0, 180.0, 120.0, 180.0, 120.0, 60.0],
    damper=0.0,
    parse_damper=true,
    joint_limits=[[-0.52, -0.785, -0.400, -1.0, -1.20, -0.5],
                  [ 1.05,  0.785,  0.785,  0.7,  0.87,  0.5]],
    T=Float64)

    path = joinpath(@__DIR__, "../deps/halfcheetah.urdf")
    mech = Mechanism(path; floating=false, T,
        gravity,
        timestep,
        parse_damper)

    # Adding springs and dampers
    set_springs!(mech.joints, spring)
    set_dampers!(mech.joints, damper)

    # joint limits
    joints = deepcopy(mech.joints)

    if limits
        bthigh = get_joint(mech, :bthigh)
        joints[bthigh.id] = add_limits(mech, bthigh,
            rot_limits=[SVector{1}(joint_limits[1][1]), SVector{1}(joint_limits[2][1])])

        bshin = get_joint(mech, :bshin)
        joints[bshin.id] = add_limits(mech, bshin,
            rot_limits=[SVector{1}(joint_limits[1][2]), SVector{1}(joint_limits[2][2])])

        bfoot = get_joint(mech, :bfoot)
        joints[bfoot.id] = add_limits(mech, bfoot,
            rot_limits=[SVector{1}(joint_limits[1][3]), SVector{1}(joint_limits[2][3])])

        fthigh = get_joint(mech, :fthigh)
        joints[fthigh.id] = add_limits(mech, fthigh,
            rot_limits=[SVector{1}(joint_limits[1][4]), SVector{1}(joint_limits[2][4])])

        fshin = get_joint(mech, :fshin)
        joints[fshin.id] = add_limits(mech, fshin,
            rot_limits=[SVector{1}(joint_limits[1][5]), SVector{1}(joint_limits[2][5])])

        ffoot = get_joint(mech, :ffoot)
        joints[ffoot.id] = add_limits(mech, ffoot,
            rot_limits=[SVector{1}(joint_limits[1][6]), SVector{1}(joint_limits[2][6])])

        mech = Mechanism(Origin{T}(), [mech.bodies...], [joints...];
            gravity,
            timestep)
    end

    if contact_feet
        origin = Origin{T}()
        bodies = mech.bodies
        joints = mech.joints

        normal = [0.0; 0.0; 1.0]
        names = contact_body ? getfield.(mech.bodies, :name) : [:ffoot, :bfoot]
        models = []
        for name in names
            body = get_body(mech, name)
            if name == :torso # need special case for torso
                # torso
                pf = [+0.5 * body.shape.shape[1].rh[2]; 0.0; 0.0]
                pb = [-0.5 * body.shape.shape[1].rh[2]; 0.0; 0.0]
                o = body.shape.shape[1].rh[1]
                push!(models, contact_constraint(body, normal;
                    friction_coefficient,
                    contact_origin=pf,
                    contact_radius=o))
                push!(models, contact_constraint(body, normal;
                    friction_coefficient,
                    contact_origin=pb,
                    contact_radius=o))

                # head
                pf = [+0.5 * body.shape.shape[1].rh[2] + 0.214; 0.0; 0.1935]
                o = body.shape.shape[2].rh[1]
                push!(models, contact_constraint(body, normal;
                    friction_coefficient,
                    contact_origin=pf,
                    contact_radius=o))
            else
                p = [0;0; -0.5 * body.shape.rh[2]]
                o = body.shape.rh[1]
                push!(models, contact_constraint(body, normal;
                    friction_coefficient,
                    contact_origin=p,
                    contact_radius=o))
            end
        end

        set_minimal_coordinates!(mech, get_joint(mech, :floating_joint), [0.576509, 0.0, 0.02792])

        mech = Mechanism(origin, bodies, joints, [models...];
            gravity,
            timestep)
    end
    return mech
end

function initialize_halfcheetah!(mechanism::Mechanism{T};
    body_position=[0.0, 0.0],
    body_orientation=0.0) where T

    set_minimal_coordinates!(mechanism,
                 get_joint(mechanism, :floating_joint),
                 [body_position[2] + 0.576509, -body_position[1], -body_orientation + 0.02792])
    for joint in mechanism.joints
        (joint.name != :floating_joint) && set_minimal_coordinates!(mechanism, joint, zeros(input_dimension(joint)))
    end
    zero_velocity!(mechanism)
end