Skip to content

Commit

Permalink
add scheduler
Browse files Browse the repository at this point in the history
  • Loading branch information
appad committed Mar 22, 2024
1 parent a267c41 commit 2da8e7c
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 3 deletions.
4 changes: 3 additions & 1 deletion docker-compose.grpc.only.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ services:
context: .
dockerfile: api/Dockerfile
env_file: .env
restart: always

envoy:
image: ghcr.io/apoorva64/eternity2/envoy:latest
build:
context: envoy
ports:
- '50052:50052'
- '50052:50052'
restart: always
2 changes: 1 addition & 1 deletion gui/eternitylib/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def generate_inner_symbols(size, number_of_symbols):

class Board:
def __init__(self):
self.pieces = []
self.pieces: list[Piece] = []
self._size = 0
self._pattern_count = 0
self.hints = []
Expand Down
11 changes: 11 additions & 0 deletions gui/eternitylib/piece.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from PIL import ImageDraw

from eternitylib.pattern import Pattern
from scheduler.solver.v1 import solver_pb2


class Piece:
Expand Down Expand Up @@ -80,3 +81,13 @@ def __repr__(self):
@property
def hash(self):
return hashlib.md5(".".join(pattern.pattern_code for pattern in self.patterns).encode()).hexdigest()


def to_grpc(self):
return solver_pb2.Piece(
top=int(self.patterns[0].pattern_code, 2),
right=int(self.patterns[1].pattern_code, 2),
bottom=int(self.patterns[2].pattern_code, 2),
left=int(self.patterns[3].pattern_code, 2),

)
3 changes: 2 additions & 1 deletion gui/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ colour
Pillow
gradio
plotly
kaleido
kaleido
lxml
5 changes: 5 additions & 0 deletions gui/scheduler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Generate ProtoBuf files

```bash
python3 -m grpc_tools.protoc --python_out=. --pyi_out=. --grpc_python_out=. ../../api/proto/solver/v1/solver.proto --proto_path ../api/proto/
```
Empty file added gui/scheduler/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions gui/scheduler/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from eternitylib.board import Board
from solve import solve
from solver.v1 import solver_pb2

servers = [
'localhost:50051',
'node-apoorva2.k3s.hs.ozeliurs.com:50051',
'node-apoorva3-abklev50.k3s.hs.ozeliurs.com:50051',
# 'vmpx15.polytech.hs.ozeliurs.com:50051'
]



if __name__ == '__main__':
board = Board()
board.generate(16, 22)

pieces = [piece.to_grpc() for piece in board.pieces]

# Create a request
request = solver_pb2.SolverSolveRequest(
pieces=pieces,
threads=50,
hash_threshold=7,
wait_time=1000,
use_cache=True,
cache_pull_interval=10
)

# Call the server
response = solve(servers, request)

print(response)
67 changes: 67 additions & 0 deletions gui/scheduler/solve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Dict

import grpc
import solver.v1.solver_pb2_grpc as solver_pb2_grpc
import time
# launch in threads
import threading

from solver.v1 import solver_pb2
import logging

logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)


def solve(_servers: list[str], _request: solver_pb2.SolverSolveRequest):
stubs = map(lambda _server: (_server, solver_pb2_grpc.SolverStub(grpc.insecure_channel(_server))), _servers)

# Make the
responses: Dict[str, list[
solver_pb2.SolverSolveResponse
]] = {}
for server in _servers:
responses[server] = []

found = [False]

def internal_solve(server_stub: tuple[str, solver_pb2_grpc.SolverStub], _request: solver_pb2.SolverSolveRequest,
_found: list[bool | solver_pb2.SolverSolveResponse]):
_stub = server_stub[1]
_server = server_stub[0]
for _response in _stub.Solve(_request):
responses[_server].append(_response)
if _found[0]:
return
_found[0] = responses[_server][-1]

threads = []
for stub in stubs:
thread = threading.Thread(target=internal_solve, args=(stub, _request, found))
threads.append(thread)
thread.start()

# if a thread has finished before the others, stop the others
while not found[0]:
time.sleep(1)
# print
for server in _servers:
server_responses = responses[server]
if len(server_responses) > 0:
# count the number of pieces
piece_count = len(server_responses[-1].rotated_pieces)
# count the number of null pieces
null_piece_count = sum([1 for piece in server_responses[-1].rotated_pieces if piece.piece.top == 0 and piece.piece.right == 0 and piece.piece.bottom == 0 and piece.piece.left == 0])
logger.info(f"Server {server}:")
logger.info(f" Time: {server_responses[-1].time}")
logger.info(f" Hashes per second: {server_responses[-1].hashes_per_second}")
logger.info(f" Hash table size: {server_responses[-1].hash_table_size}")
logger.info(f" Boards per second: {server_responses[-1].boards_per_second}")
logger.info(f" Boards analyzed: {server_responses[-1].boards_analyzed}")
logger.info(f" Hash table hits: {server_responses[-1].hash_table_hits}")
logger.info(f" Placed pieces: {piece_count - null_piece_count}/{piece_count}")

# stop the other threads
for thread in threads:
thread.join()
return found
Empty file.
Empty file.
38 changes: 38 additions & 0 deletions gui/scheduler/solver/v1/solver_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions gui/scheduler/solver/v1/solver_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from google.protobuf.internal import containers as _containers
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

class SolverSolveRequest(_message.Message):
__slots__ = ("pieces", "threads", "hash_threshold", "wait_time", "use_cache", "cache_pull_interval")
PIECES_FIELD_NUMBER: _ClassVar[int]
THREADS_FIELD_NUMBER: _ClassVar[int]
HASH_THRESHOLD_FIELD_NUMBER: _ClassVar[int]
WAIT_TIME_FIELD_NUMBER: _ClassVar[int]
USE_CACHE_FIELD_NUMBER: _ClassVar[int]
CACHE_PULL_INTERVAL_FIELD_NUMBER: _ClassVar[int]
pieces: _containers.RepeatedCompositeFieldContainer[Piece]
threads: int
hash_threshold: int
wait_time: int
use_cache: bool
cache_pull_interval: int
def __init__(self, pieces: _Optional[_Iterable[_Union[Piece, _Mapping]]] = ..., threads: _Optional[int] = ..., hash_threshold: _Optional[int] = ..., wait_time: _Optional[int] = ..., use_cache: bool = ..., cache_pull_interval: _Optional[int] = ...) -> None: ...

class Piece(_message.Message):
__slots__ = ("top", "right", "bottom", "left")
TOP_FIELD_NUMBER: _ClassVar[int]
RIGHT_FIELD_NUMBER: _ClassVar[int]
BOTTOM_FIELD_NUMBER: _ClassVar[int]
LEFT_FIELD_NUMBER: _ClassVar[int]
top: int
right: int
bottom: int
left: int
def __init__(self, top: _Optional[int] = ..., right: _Optional[int] = ..., bottom: _Optional[int] = ..., left: _Optional[int] = ...) -> None: ...

class PieceWithOptionalHint(_message.Message):
__slots__ = ("piece", "x", "y")
PIECE_FIELD_NUMBER: _ClassVar[int]
X_FIELD_NUMBER: _ClassVar[int]
Y_FIELD_NUMBER: _ClassVar[int]
piece: Piece
x: int
y: int
def __init__(self, piece: _Optional[_Union[Piece, _Mapping]] = ..., x: _Optional[int] = ..., y: _Optional[int] = ...) -> None: ...

class RotatedPiece(_message.Message):
__slots__ = ("piece", "rotation", "index")
PIECE_FIELD_NUMBER: _ClassVar[int]
ROTATION_FIELD_NUMBER: _ClassVar[int]
INDEX_FIELD_NUMBER: _ClassVar[int]
piece: Piece
rotation: int
index: int
def __init__(self, piece: _Optional[_Union[Piece, _Mapping]] = ..., rotation: _Optional[int] = ..., index: _Optional[int] = ...) -> None: ...

class SolverStepByStepResponse(_message.Message):
__slots__ = ("rotated_pieces",)
ROTATED_PIECES_FIELD_NUMBER: _ClassVar[int]
rotated_pieces: _containers.RepeatedCompositeFieldContainer[RotatedPiece]
def __init__(self, rotated_pieces: _Optional[_Iterable[_Union[RotatedPiece, _Mapping]]] = ...) -> None: ...

class SolverSolveResponse(_message.Message):
__slots__ = ("time", "hashes_per_second", "hash_table_size", "boards_per_second", "boards_analyzed", "hash_table_hits", "rotated_pieces")
TIME_FIELD_NUMBER: _ClassVar[int]
HASHES_PER_SECOND_FIELD_NUMBER: _ClassVar[int]
HASH_TABLE_SIZE_FIELD_NUMBER: _ClassVar[int]
BOARDS_PER_SECOND_FIELD_NUMBER: _ClassVar[int]
BOARDS_ANALYZED_FIELD_NUMBER: _ClassVar[int]
HASH_TABLE_HITS_FIELD_NUMBER: _ClassVar[int]
ROTATED_PIECES_FIELD_NUMBER: _ClassVar[int]
time: float
hashes_per_second: float
hash_table_size: int
boards_per_second: float
boards_analyzed: int
hash_table_hits: int
rotated_pieces: _containers.RepeatedCompositeFieldContainer[RotatedPiece]
def __init__(self, time: _Optional[float] = ..., hashes_per_second: _Optional[float] = ..., hash_table_size: _Optional[int] = ..., boards_per_second: _Optional[float] = ..., boards_analyzed: _Optional[int] = ..., hash_table_hits: _Optional[int] = ..., rotated_pieces: _Optional[_Iterable[_Union[RotatedPiece, _Mapping]]] = ...) -> None: ...
100 changes: 100 additions & 0 deletions gui/scheduler/solver/v1/solver_pb2_grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

from solver.v1 import solver_pb2 as solver_dot_v1_dot_solver__pb2


class SolverStub(object):
"""Missing associated documentation comment in .proto file."""

def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.Solve = channel.unary_stream(
'/solver.v1.Solver/Solve',
request_serializer=solver_dot_v1_dot_solver__pb2.SolverSolveRequest.SerializeToString,
response_deserializer=solver_dot_v1_dot_solver__pb2.SolverSolveResponse.FromString,
)
self.SolveStepByStep = channel.unary_stream(
'/solver.v1.Solver/SolveStepByStep',
request_serializer=solver_dot_v1_dot_solver__pb2.SolverSolveRequest.SerializeToString,
response_deserializer=solver_dot_v1_dot_solver__pb2.SolverStepByStepResponse.FromString,
)


class SolverServicer(object):
"""Missing associated documentation comment in .proto file."""

def Solve(self, request, context):
"""streams the data in response
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def SolveStepByStep(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_SolverServicer_to_server(servicer, server):
rpc_method_handlers = {
'Solve': grpc.unary_stream_rpc_method_handler(
servicer.Solve,
request_deserializer=solver_dot_v1_dot_solver__pb2.SolverSolveRequest.FromString,
response_serializer=solver_dot_v1_dot_solver__pb2.SolverSolveResponse.SerializeToString,
),
'SolveStepByStep': grpc.unary_stream_rpc_method_handler(
servicer.SolveStepByStep,
request_deserializer=solver_dot_v1_dot_solver__pb2.SolverSolveRequest.FromString,
response_serializer=solver_dot_v1_dot_solver__pb2.SolverStepByStepResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'solver.v1.Solver', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))


# This class is part of an EXPERIMENTAL API.
class Solver(object):
"""Missing associated documentation comment in .proto file."""

@staticmethod
def Solve(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(request, target, '/solver.v1.Solver/Solve',
solver_dot_v1_dot_solver__pb2.SolverSolveRequest.SerializeToString,
solver_dot_v1_dot_solver__pb2.SolverSolveResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

@staticmethod
def SolveStepByStep(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(request, target, '/solver.v1.Solver/SolveStepByStep',
solver_dot_v1_dot_solver__pb2.SolverSolveRequest.SerializeToString,
solver_dot_v1_dot_solver__pb2.SolverStepByStepResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

0 comments on commit 2da8e7c

Please sign in to comment.