Skip to content

Commit

Permalink
Got working example for Julia server and Python client
Browse files Browse the repository at this point in the history
  • Loading branch information
sverhoeven committed Jul 8, 2024
1 parent 6e59790 commit 2b962d7
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 229 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ coverage.xml
__pycache__

/venv
python/.venv/
python/heat.toml
RemoteBMI.jl/example/Project.toml
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ Given you have a model class called `MyModel` and a BMI called `BMI` inside the
using MyPackage
using RemoteBMI

port = parse(Int, get(ENV, "BMI_PORT", 50051))
RemoteBMI.run(MyPackage.Model, "0.0.0.0", 50555)
port = parse(Int, get(ENV, "BMI_PORT", "50051"))
RemoteBMI.run(MyPackage.Model, "0.0.0.0", port)
```

### R provider
Expand Down
15 changes: 15 additions & 0 deletions RemoteBMI.jl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,18 @@ If you use RemoteBMI.jl in your work, please cite using the reference given in [

If you want to make contributions of any kind, please first that a look into our [contributing guide directly on GitHub](docs/src/90-contributing.md) or the [contributing page on the website](https://eWaterCycle.github.io/RemoteBMI.jl/dev/contributing/).

## Code generation

The skeleton of the package was generated with

```jula
using BestieTemplate
BestieTemplate.generate("RemoteBMI.jl")
```

The openapi server stubs where generated using the following command:

```shell
npx @openapitools/openapi-generator-cli generate -i ./openapi.yaml -g julia-server -o julia-server --additional-properties=packageName=BmiServer --additional-properties=exportModels=true
# Copy the generated files to RemoteBMI.jl/src/
```
3 changes: 0 additions & 3 deletions RemoteBMI.jl/example/Project.toml

This file was deleted.

11 changes: 8 additions & 3 deletions RemoteBMI.jl/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dev ..
CTRL-D
# Run server
export BMI_SERVER_PORT=50555
julia heat_bmi_server.jl
julia --project=$PWD heat_bmi_server.jl
```

Prepare a config file
Expand All @@ -24,13 +24,18 @@ Interact with it using the Python client.

```python
from remotebmi.client.client import RemoteBmiClient
from remotebmi.client.reserve import reserve_values

client = RemoteBmiClient('http://localhost:50555')
client.initialize('<absolute path>/heat.toml')
# TODO use placeholder for path
# client.initialize('<absolute path>/heat.toml')
client.initialize('/home/stefanv/git/eWaterCycle/remotebmi/python/heat.toml')
# TODO Julia server throws error here
client.get_component_name()
client.update()
client.get_current_time()
client.get_value('plate_surface__temperature')
dest = reserve_values(client, 'plate_surface__temperature')
r = client.get_value('plate_surface__temperature', dest)
r
client.finalize()
```
2 changes: 1 addition & 1 deletion RemoteBMI.jl/example/heat_bmi_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ using Heat

import RemoteBMI

port = parse(Int, get(ENV, "BMI_PORT", 50051))
port = parse(Int, get(ENV, "BMI_PORT", "50051"))
RemoteBMI.run(Heat.Model, "0.0.0.0", port)
13 changes: 10 additions & 3 deletions RemoteBMI.jl/src/APIServer.jl → RemoteBMI.jl/src/BmiServer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


@doc raw"""
Encapsulates generated server code for APIServer
Encapsulates generated server code for BmiServer
The following server methods must be implemented:
Expand Down Expand Up @@ -128,7 +128,7 @@ The following server methods must be implemented:
- *invocation:* GET /get_var_units/{name}
- *signature:* get_var_units(req::HTTP.Request, name::String;) -> String
"""
module APIServer
module BmiServer

using HTTP
using URIs
Expand Down Expand Up @@ -186,4 +186,11 @@ function register(router::HTTP.Router, impl; path_prefix::String="", optional_mi
return router
end

end # module APIServer
# export models
export BmiGetGridTypeResponse
export BmiInitializeRequest
export BmiSetValueAtIndicesRequest
export GetVarLocationResponseLocation
export ProblemDetails

end # module BmiServer
275 changes: 270 additions & 5 deletions RemoteBMI.jl/src/RemoteBMI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,283 @@ module RemoteBMI

using HTTP

include("./APIServer.jl")
using .APIServer
include("./BmiServer.jl")
using .BmiServer

include("./serverimpl.jl")
using .ServerImpl
import BasicModelInterface as BMI

# TODO move route implementations to own module

function initialize(req::HTTP.Request, bmi_initialize_request::BmiInitializeRequest)::Nothing
global m
m = BMI.initialize(MyModel, bmi_initialize_request.config_file)
return nothing
end

function get_component_name(req::HTTP.Request;)::String
return BMI.get_component_name(m)
end

function get_input_item_count(req::HTTP.Request;)::Int64
return BMI.get_input_item_count(m)
end

function get_input_var_names(req::HTTP.Request;)::Vector{String}
return BMI.get_input_var_names(m)
end

function get_output_item_count(req::HTTP.Request;)::Int64
return BMI.get_output_item_count(m)
end

function get_output_var_names(req::HTTP.Request;)::Vector{String}
return BMI.get_output_var_names(m)
end

function reserve_get_value(m, name::String;)::Vector{Float64}
raw_dtype = BMI.get_var_type(m, name)
item_size = BMI.get_var_itemsize(m, name)
total_size = BMI.get_var_nbytes(m, name)
size = total_size ÷ item_size
dtype_table = Dict(
"Float64" => Float64,
"Float32" => Float32,
"Int64" => Int64,
"Int32" => Int32,
"Bool" => Bool,
)
dtype = get(dtype_table, raw_dtype, Any)
if dtype == Any
error("Invalid data type: $raw_dtype")
end
return zeros(dtype, size)
end

function get_value(req::HTTP.Request, name::String;)::Vector{Float64}
dest = reserve_get_value(m, name)
return BMI.get_value(m, name, dest)
end

function reserve_get_value_at_indices(m, name::String, indices::Vector{Int64};)::Vector{Float64}
total_size = length(indices)
raw_dtype = BMI.get_var_type(m, name)
dtype_table = Dict(
"Float64" => Float64,
"Float32" => Float32,
"Int64" => Int64,
"Int32" => Int32,
"Bool" => Bool,
)
dtype = get(dtype_table, raw_dtype, Any)
if dtype == Any
error("Invalid data type: $raw_dtype")
end
return zeros(dtype, total_size)
end

function get_value_at_indices(req::HTTP.Request, name::String, indices::Vector{Int64};)::Vector{Float64}
dest = reserve_get_value_at_indices(m, name, indices)
return BMI.get_value_at_indices(m, name, dest, indices)
end

function get_grid_rank(req::HTTP.Request, grid::Int64;)::Int64
return BMI.get_grid_rank(m, grid)
end

function get_grid_size(req::HTTP.Request, grid::Int64;)::Int64
return BMI.get_grid_size(m, grid)
end

function get_grid_type(req::HTTP.Request, grid::Int64;)::BmiGetGridTypeResponse
return BMI.get_grid_type(m, grid)
end

function finalize(req::HTTP.Request;)::Nothing
BMI.finalize(m)
return nothing
end

function update(req::HTTP.Request;)::Nothing
BMI.update(m)
return nothing
end

function update_until(req::HTTP.Request, body::Float64;)::Nothing
BMI.update_until(m, body)
return nothing
end

function reserve_grid_coords(m, grid::Int64, dim_index::Int8)::Vector{Float64}
mtype = BMI.get_grid_type(m, grid)
if mtype in ["uniform_rectilinear", "rectilinear"]
shape = BMI.get_grid_shape(m, grid)
size = reverse(shape)[dim_index]
elseif mtype in ["structured_quadrilateral"]
size = BMI.get_grid_size(m, grid)
elseif mtype in ["unstructured"]
size = BMI.get_grid_node_count(m, grid)
else
error("Unsupported grid type: $mtype")
return zeros(Float64, size)
end

function get_grid_x(req::HTTP.Request, grid::Int64;)::Vector{Float64}
x = reserve_grid_coords(m, grid, 1)
return BMI.get_grid_x(m, grid, x)
end

function get_grid_y(req::HTTP.Request, grid::Int64;)::Vector{Float64}
y = reserve_grid_coords(m, grid, 2)
return BMI.get_grid_y(m, grid, y)
end

function get_grid_z(req::HTTP.Request, grid::Int64;)::Vector{Float64}
z = reserve_grid_coords(m, grid, 3)
return BMI.get_grid_z(m, grid, z)
end

function set_value(req::HTTP.Request, name::String, request_body::Vector{Float64};)::Nothing
BMI.set_value(m, name, request_body)
end

function set_value_at_indices(req::HTTP.Request, name::String, bmi_set_value_at_indices_request::BmiSetValueAtIndicesRequest;)::Nothing
BMI.set_value_at_indices(m, name, bmi_set_value_at_indices_request)
end

function get_current_time(req::HTTP.Request;)::Float64
return BMI.get_current_time(m)
end

function get_end_time(req::HTTP.Request;)::Float64
return BMI.get_end_time(m)
end

function get_start_time(req::HTTP.Request;)::Float64
return BMI.get_start_time(m)
end

function get_time_step(req::HTTP.Request;)::Float64
return BMI.get_time_step(m)
end

function get_time_units(req::HTTP.Request;)::String
return BMI.get_time_units(m)
end

function get_grid_origin(req::HTTP.Request, grid::Int64;)::Vector{Float64}
origin = reserve_grid_spacing(m, grid)
return BMI.get_grid_origin(m, grid, origin)
end

function reserve_grid_shape(m, grid::Int64;)::Vector{Int64}
rank = BMI.get_grid_rank(m, grid)
return zeros(Int64, rank)
end

function get_grid_shape(req::HTTP.Request, grid::Int64;)::Vector{Int64}
shape = reserve_grid_shape(m, grid)
return BMI.get_grid_shape(m, grid, shape)
end

function reserve_grid_spacing(m, grid::Int64;)::Vector{Float64}
rank = BMI.get_grid_rank(m, grid)
return zeros(Float64, rank)
end

function get_grid_spacing(req::HTTP.Request, grid::Int64;)::Vector{Float64}
spacing = reserve_grid_spacing(m, grid)
return BMI.get_grid_spacing(m, grid, spacing)
end

function get_grid_edge_count(req::HTTP.Request, grid::Int64;)::Int64
return BMI.get_grid_edge_count(m, grid)
end

function reserve_grid_edge_nodes(m, grid::Int64;)::Vector{Int64}
edge_count = BMI.get_grid_edge_count(m, grid)
return zeros(Int64, 2 * edge_count)
end

function get_grid_edge_nodes(req::HTTP.Request, grid::Int64;)::Vector{Int64}
edge_nodes = reserve_grid_edge_nodes(m, grid)
return BMI.get_grid_edge_nodes(m, grid, edge_nodes)
end

function get_grid_face_count(req::HTTP.Request, grid::Int64;)::Int64
return BMI.get_grid_face_count(m, grid)
end

function reserve_grid_nodes_per_face(m, grid::Int64;)::Vector{Int64}
face_count = BMI.get_grid_face_count(m, grid)
return zeros(Int64, face_count)
end

function reserve_grid_face(edges, grid::Int64;)::Vector{Int64}
nodes_per_face = reserve_grid_nodes_per_face(m, grid)
BMI.get_grid_nodes_per_face(m, grid, nodes_per_face)
return zeros(Int64, sum(nodes_per_face))
end

function get_grid_face_edges(req::HTTP.Request, grid::Int64;)::Vector{Int64}
face_edges = reserve_grid_face(m, grid)
return BMI.get_grid_face_edges(m, grid, face_edges)
end

function get_grid_face_nodes(req::HTTP.Request, grid::Int64;)::Vector{Int64}
face_nodes = reserve_grid_face(m, grid)
return BMI.get_grid_face_nodes(m, grid, face_nodes)
end

function get_grid_node_count(req::HTTP.Request, grid::Int64;)::Int64
return BMI.get_grid_node_count(m, grid)
end

function get_grid_nodes_per_face(req::HTTP.Request, grid::Int64;)::Vector{Int64}
nodes_per_face = reserve_grid_nodes_per_face(m, grid)
return BMI.get_grid_nodes_per_face(m, grid, nodes_per_face)
end

function get_var_grid(req::HTTP.Request, name::String;)::Int64
return BMI.get_var_grid(m, name)
end

function get_var_itemsize(req::HTTP.Request, name::String;)::Int64
return BMI.get_var_itemsize(m, name)
end

function get_var_location(req::HTTP.Request, name::String;)::GetVarLocationResponseLocation
return BMI.get_var_location(m, name)
end

function get_var_nbytes(req::HTTP.Request, name::String;)::Int64
return BMI.get_var_nbytes(m, name)
end

function get_var_type(req::HTTP.Request, name::String;)::String
raw_type = BMI.get_var_type(m, name)
map = Dict(
"Float64" => "float64",
"Float32" => "float32",
"Int64" => "int64",
"Int32" => "int32",
"Bool" => "bool",
)
type = get(map, raw_type, Any)
if type == Any
error("Invalid data type returned by model: $raw_type, allowed types are: Float64, Float32, Int64, Int32")
end
return type
end

function get_var_units(req::HTTP.Request, name::String;)::String
return BMI.get_var_units(m, name)
end

function run(model, host, port)
global MyModel = model
try
router = HTTP.Router()
router = APIServer.register(router, ServerImpl;)
router = BmiServer.register(router, @__MODULE__)
server = HTTP.serve!(router, host, port; verbose=true)
wait(server)
catch ex
Expand Down
Loading

0 comments on commit 2b962d7

Please sign in to comment.