diff --git a/VSharp.ML.AIAgent/agent/messages.py b/VSharp.ML.AIAgent/agent/messages.py index 77c70ddae..76cb10556 100644 --- a/VSharp.ML.AIAgent/agent/messages.py +++ b/VSharp.ML.AIAgent/agent/messages.py @@ -4,8 +4,11 @@ from enum import Enum from typing import Optional +from .unsafe_json import obj_from_dict + from common.game import GameMap, GameState, Reward from dataclasses_json import config, dataclass_json +from config import FeatureConfig class ClientMessageType(str, Enum): @@ -16,28 +19,28 @@ class ClientMessageType(str, Enum): @dataclass_json -@dataclass +@dataclass(slots=True) class ClientMessageBody: def type(self) -> ClientMessageType: pass @dataclass_json -@dataclass +@dataclass(slots=True) class GetTrainMapsMessageBody(ClientMessageBody): def type(self) -> ClientMessageType: return ClientMessageType.GETTRAINMAPS @dataclass_json -@dataclass +@dataclass(slots=True) class GetValidationMapsMessageBody(ClientMessageBody): def type(self) -> ClientMessageType: return ClientMessageType.GETVALIDATIONMAPS @dataclass_json -@dataclass +@dataclass(slots=True) class StartMessageBody(ClientMessageBody): MapId: int StepsToPlay: int @@ -47,7 +50,7 @@ def type(self) -> ClientMessageType: @dataclass_json -@dataclass +@dataclass(slots=True) class StepMessageBody(ClientMessageBody): StateId: int PredictedStateUsefulness: float @@ -57,7 +60,7 @@ def type(self) -> ClientMessageType: @dataclass_json -@dataclass +@dataclass(slots=True) class ClientMessage: MessageType: str = field(init=False) MessageBody: ClientMessageBody = field( @@ -73,7 +76,7 @@ def __post_init__(self): @dataclass_json -@dataclass +@dataclass(slots=True) class MapsMessageBody: Maps: list[GameMap] @@ -87,45 +90,48 @@ class ServerMessageType(str, Enum): @dataclass_json -@dataclass +@dataclass(slots=True) class ServerMessage: MessageType: ServerMessageType class DeserializationException(Exception): pass - def from_json_handle(*args, expected, **kwargs): + def from_json_handle(data, expected): + if FeatureConfig.DISABLE_MESSAGE_CHECKS: + return obj_from_dict(json.loads(data)) + try: - return expected.from_json(args[0], **kwargs) + return expected.from_json(data) except Exception as e: err_to_display = f"{type(e)} - {e}: tried to decode {expected}, got unmatched structure, registered to app.log under [ERROR] tag" - error = f"{type(e)} - {e}: tried to decode {expected}, got raw data: {json.dumps(json.loads(args[0]), indent=2)}" + error = f"{type(e)} - {e}: tried to decode {expected}, got raw data: {json.dumps(json.loads(data), indent=2)}" logging.error(error) raise ServerMessage.DeserializationException(err_to_display) -@dataclass +@dataclass(slots=True) class GameStateServerMessage(ServerMessage): MessageBody: GameState -@dataclass +@dataclass(slots=True) class RewardServerMessage(ServerMessage): MessageBody: Reward -@dataclass +@dataclass(slots=True) class MapsServerMessage(ServerMessage): MessageBody: MapsMessageBody -@dataclass +@dataclass(slots=True) class GameOverServerMessageBody: ActualCoverage: Optional[int] TestsCount: int ErrorsCount: int -@dataclass +@dataclass(slots=True) class GameOverServerMessage(ServerMessage): MessageBody: GameOverServerMessageBody diff --git a/VSharp.ML.AIAgent/agent/unsafe_json.py b/VSharp.ML.AIAgent/agent/unsafe_json.py new file mode 100644 index 000000000..708ee813b --- /dev/null +++ b/VSharp.ML.AIAgent/agent/unsafe_json.py @@ -0,0 +1,41 @@ +from dataclasses import asdict as dataclasses_asdict, is_dataclass +from typing import ClassVar, Protocol + + +class Dataclass(Protocol): + __dataclass_fields__: ClassVar[dict] + + +BareObject = type("BareObject", (), {}) + + +def obj_from_dict(data: dict | list | str | float) -> BareObject: + if isinstance(data, list): + return [obj_from_dict(item) for item in data] + if isinstance(data, dict): + inner_dict = { + field_name: obj_from_dict(field_raw) + for field_name, field_raw in data.items() + } + bare_obj = BareObject() + bare_obj.__dict__ = inner_dict + return bare_obj + return data + + +def asdict( + data: BareObject | Dataclass | list | dict | str | float, +) -> dict | list | str | float: + if isinstance(data, BareObject): + return {asdict(k): asdict(v) for k, v in data.__dict__.items()} + elif is_dataclass(data): + try: + return data.to_json() + except TypeError: + return asdict(dataclasses_asdict(data)) + elif isinstance(data, list): + return [asdict(item) for item in data] + elif isinstance(data, dict): + return {asdict(k): asdict(v) for k, v in data.items()} + else: + return data diff --git a/VSharp.ML.AIAgent/agent/utils.py b/VSharp.ML.AIAgent/agent/utils.py index 9cdd68ead..aa99f6181 100644 --- a/VSharp.ML.AIAgent/agent/utils.py +++ b/VSharp.ML.AIAgent/agent/utils.py @@ -30,7 +30,9 @@ def switch_maps_type( def send_all(message_body: ClientMessage): for ws_string in websocket_strings: - with closing(websocket.create_connection(ws_string)) as ws: + with closing( + websocket.create_connection(ws_string, skip_utf8_validation=True) + ) as ws: ws.send(message_body.to_json()) ws.recv() diff --git a/VSharp.ML.AIAgent/common/game.py b/VSharp.ML.AIAgent/common/game.py index 938fd0e90..c0698dbcf 100644 --- a/VSharp.ML.AIAgent/common/game.py +++ b/VSharp.ML.AIAgent/common/game.py @@ -4,14 +4,14 @@ @dataclass_json -@dataclass +@dataclass(slots=True) class StateHistoryElem: GraphVertexId: int NumOfVisits: int @dataclass_json -@dataclass +@dataclass(slots=True) class State: Id: int Position: int @@ -28,7 +28,7 @@ def __hash__(self) -> int: @dataclass_json -@dataclass +@dataclass(slots=True) class GameMapVertex: Uid: int Id: int @@ -41,13 +41,13 @@ class GameMapVertex: @dataclass_json -@dataclass +@dataclass(slots=True) class GameEdgeLabel: Token: int @dataclass_json -@dataclass +@dataclass(slots=True) class GameMapEdge: VertexFrom: int VertexTo: int @@ -55,7 +55,7 @@ class GameMapEdge: @dataclass_json -@dataclass +@dataclass(slots=True) class GameState: GraphVertices: list[GameMapVertex] States: list[State] @@ -63,7 +63,7 @@ class GameState: @dataclass_json -@dataclass +@dataclass(slots=True) class GameMap: Id: int MaxSteps: int @@ -78,7 +78,7 @@ def __hash__(self) -> int: @dataclass_json -@dataclass +@dataclass(slots=True) class MoveReward: ForCoverage: int ForVisitedInstructions: int @@ -103,7 +103,7 @@ def printable(self, verbose=False) -> str: @dataclass_json -@dataclass +@dataclass(slots=True) class Reward: ForMove: MoveReward MaxPossibleReward: int diff --git a/VSharp.ML.AIAgent/config.py b/VSharp.ML.AIAgent/config.py index 9099edd77..e93007a86 100644 --- a/VSharp.ML.AIAgent/config.py +++ b/VSharp.ML.AIAgent/config.py @@ -1,10 +1,13 @@ import logging +import ml.models + class GeneralConfig: SERVER_COUNT = 8 MAX_STEPS = 3000 LOGGER_LEVEL = logging.INFO + MODEL_INIT = lambda: ml.models.SAGEConvModel(16) class BrokerConfig: @@ -20,3 +23,4 @@ class FeatureConfig: SHOW_SUCCESSORS = True NAME_LEN = 7 N_BEST_SAVED_EACH_GEN = 2 + DISABLE_MESSAGE_CHECKS = True diff --git a/VSharp.ML.AIAgent/conn/classes.py b/VSharp.ML.AIAgent/conn/classes.py index c69f958f5..02701fa08 100644 --- a/VSharp.ML.AIAgent/conn/classes.py +++ b/VSharp.ML.AIAgent/conn/classes.py @@ -1,13 +1,22 @@ from dataclasses import dataclass, field +from typing import Callable from dataclasses_json import config, dataclass_json -from ml.model_wrappers.nnwrapper import NNWrapper, encode, decode +from agent.unsafe_json import asdict +from config import FeatureConfig +from ml.model_wrappers.nnwrapper import NNWrapper, decode, encode from selection.classes import Map2Result +def custom_encoder_if_disable_message_checks() -> Callable | None: + return asdict if FeatureConfig.DISABLE_MESSAGE_CHECKS else None + + @dataclass_json @dataclass class Agent2ResultsOnMaps: agent: NNWrapper = field(metadata=config(encoder=encode, decoder=decode)) - results: list[Map2Result] + results: list[Map2Result] = field( + metadata=config(encoder=custom_encoder_if_disable_message_checks()) + ) diff --git a/VSharp.ML.AIAgent/conn/socket_manager.py b/VSharp.ML.AIAgent/conn/socket_manager.py index 39f05e24c..538b9ce15 100644 --- a/VSharp.ML.AIAgent/conn/socket_manager.py +++ b/VSharp.ML.AIAgent/conn/socket_manager.py @@ -8,7 +8,7 @@ @contextmanager def game_server_socket_manager(): socket_url = aquire_ws() - socket = websocket.create_connection(socket_url) + socket = websocket.create_connection(socket_url, skip_utf8_validation=True) try: yield socket finally: diff --git a/VSharp.ML.AIAgent/epochs_statistics/common.py b/VSharp.ML.AIAgent/epochs_statistics/common.py index 2d0d737ca..6f92c9e32 100644 --- a/VSharp.ML.AIAgent/epochs_statistics/common.py +++ b/VSharp.ML.AIAgent/epochs_statistics/common.py @@ -1,13 +1,13 @@ from dataclasses import dataclass -@dataclass +@dataclass(slots=True) class Name2ResultViewModel: model_name: str pretty_result: str -@dataclass +@dataclass(slots=True) class Interval: left: float right: float @@ -16,14 +16,14 @@ def pretty(self): return f"{self.left:.2f}%-{self.right:.2f}%, diff={self.right - self.left:.2f}%" -@dataclass +@dataclass(slots=True) class Name2Stats: mutable_name: str av_coverage: float interval: Interval -@dataclass +@dataclass(slots=True) class CoverageStats: euc_dist2_full_cov: float average_cov: float diff --git a/VSharp.ML.AIAgent/learning/r_learn.py b/VSharp.ML.AIAgent/learning/r_learn.py index 9e3a52ef5..8c1449269 100644 --- a/VSharp.ML.AIAgent/learning/r_learn.py +++ b/VSharp.ML.AIAgent/learning/r_learn.py @@ -4,14 +4,16 @@ import random from collections import defaultdict from os import getpid +from statistics import StatisticsError import numpy.typing as npt import pygad.torchga import tqdm +import ml from agent.n_agent import NAgent from agent.utils import MapsType, get_maps -from common.constants import DEVICE, IMPORTED_DICT_MODEL_PATH, TQDM_FORMAT_DICT +from common.constants import DEVICE, TQDM_FORMAT_DICT from common.utils import get_states from config import FeatureConfig, GeneralConfig from conn.classes import Agent2ResultsOnMaps @@ -25,7 +27,6 @@ ) from ml.model_wrappers.nnwrapper import NNWrapper from ml.model_wrappers.protocols import Predictor -from ml.utils import load_model_with_last_layer from selection.classes import AgentResultsOnGameMaps, GameResult, Map2Result from selection.scorer import straight_scorer from timer.resources_manager import manage_map_inference_times_array @@ -94,11 +95,16 @@ def play_map(with_agent: NAgent, with_model: Predictor) -> GameResult: ) with manage_map_inference_times_array(): - map_inference_times = get_map_inference_times() - mean, std = compute_statistics(map_inference_times) - logging.info( - f"Inference stats for <{with_model.name()}> on {with_agent.map.MapName}: {mean=}ms, {std=}ms" - ) + try: + map_inference_times = get_map_inference_times() + mean, std = compute_statistics(map_inference_times) + logging.info( + f"Inference stats for <{with_model.name()}> on {with_agent.map.MapName}: {mean=}ms, {std=}ms" + ) + except StatisticsError: + logging.info( + f"<{with_model.name()}> on {with_agent.map.MapName}: too few samples for stats count" + ) return model_result @@ -191,7 +197,8 @@ def fitness_function(ga_inst, solution, solution_idx) -> float: maps_type = MapsType.TRAIN max_steps = GeneralConfig.MAX_STEPS - model = load_model_with_last_layer(IMPORTED_DICT_MODEL_PATH, [1 for _ in range(8)]) + model = GeneralConfig.MODEL_INIT() + model.forward(*ml.onnx.onnx_import.create_torch_dummy_input()) model_weights_dict = pygad.torchga.model_weights_as_dict( model=model, weights_vector=solution ) diff --git a/VSharp.ML.AIAgent/main.py b/VSharp.ML.AIAgent/main.py index fb7948f06..6705bb27a 100644 --- a/VSharp.ML.AIAgent/main.py +++ b/VSharp.ML.AIAgent/main.py @@ -2,96 +2,23 @@ import pygad.torchga import learning.genetic_alorithm as ga -from common.constants import BASE_NN_OUT_FEATURES_NUM, IMPORTED_DICT_MODEL_PATH +import ml.onnx.onnx_import from config import GeneralConfig -from ml.utils import ( - load_model_with_last_layer, - model_weights_with_random_last_layer, - random_model_weights, -) from selection.crossover_type import CrossoverType from selection.mutation_type import MutationType from selection.parent_selection_type import ParentSelectionType -def weights_vector(weights: list[float], model_path: str = IMPORTED_DICT_MODEL_PATH): - model = load_model_with_last_layer( - model_path, - weights, - ) - assert len(weights) == BASE_NN_OUT_FEATURES_NUM - return pygad.torchga.model_weights_as_vector(model) - - -def n_random_model_weights( - n: int, low: float, hi: float, model_path: str = IMPORTED_DICT_MODEL_PATH -): - rv = [ - random_model_weights(low=low, hi=hi, model_load_path=model_path) - for _ in range(n) - ] - return rv - - -def n_random_last_layer_model_weights( - n: int, low: float, hi: float, model_path: str = IMPORTED_DICT_MODEL_PATH -): - rv = [ - model_weights_with_random_last_layer(low=low, hi=hi, model_load_path=model_path) - for _ in range(n) - ] - return rv - - def main(): - num_models_with_random_last_layer = 28 - num_random_models = 30 num_generations = 20 num_parents_mating = 10 keep_elitism = 2 - random_init_weights_max_val = 5.0 - random_init_weights_min_val = -5.0 - initial_weights = [ - [ - -0.7853140655460631, - 0.7524892603731441, - 0.2844810949678288, - -0.6819831165289404, - -0.0830326280153653, - 0.1779108098019602, - 0.95478059636744, - 0.27937866719070503, - ], - [ - -0.7853139452883172, - 0.752490045931864, - 0.2844807733073216, - -0.6819766889604519, - -0.08303258833890134, - 0.17791068654815034, - 0.9555442824877577, - 0.2793786892860371, - ], - ] - initial_population = [] - for last_layer in initial_weights: - initial_population.append(weights_vector(last_layer)) - - with_random_last_layer_weights = n_random_last_layer_model_weights( - n=num_models_with_random_last_layer, - low=random_init_weights_min_val, - hi=random_init_weights_max_val, - ) - initial_population += with_random_last_layer_weights - - with_random_weights = n_random_model_weights( - n=num_random_models, - low=random_init_weights_min_val, - hi=random_init_weights_max_val, - ) + model = GeneralConfig.MODEL_INIT() - initial_population += with_random_weights + model.forward(*ml.onnx.onnx_import.create_torch_dummy_input()) + torch_ga = pygad.torchga.TorchGA(model=model, num_solutions=60) + initial_population = torch_ga.population_weights ga.run( server_count=GeneralConfig.SERVER_COUNT, diff --git a/VSharp.ML.AIAgent/ml/data_loader_compact.py b/VSharp.ML.AIAgent/ml/data_loader_compact.py index 8448d6644..05806adbb 100644 --- a/VSharp.ML.AIAgent/ml/data_loader_compact.py +++ b/VSharp.ML.AIAgent/ml/data_loader_compact.py @@ -45,6 +45,7 @@ def convert_input_to_tensor(input: GameState) -> Tuple[HeteroData, Dict[int, int edges_index_v_s_in = [] edges_index_s_v_history = [] edges_index_v_s_history = [] + edge_attr_history = [] edges_attr_v_v = [] state_map: Dict[int, int] = {} # Maps real state id to its index @@ -102,6 +103,7 @@ def convert_input_to_tensor(input: GameState) -> Tuple[HeteroData, Dict[int, int v_to = vertex_map[h.GraphVertexId] edges_index_s_v_history.append(np.array([state_index, v_to])) edges_index_v_s_history.append(np.array([v_to, state_index])) + edge_attr_history.append(h.NumOfVisits) state_index = state_index + 1 else: state_doubles += 1 @@ -161,6 +163,10 @@ def null_if_empty(tensor): .t() .contiguous() ) + data["state_vertex", "history", "game_vertex"].edge_attr = torch.tensor( + edge_attr_history, dtype=torch.int32 + ) + data["game_vertex", "history", "state_vertex"].edge_index = null_if_empty( gv_his_sv ) diff --git a/VSharp.ML.AIAgent/ml/models.py b/VSharp.ML.AIAgent/ml/models.py index 02c687d98..3bb8ce26f 100644 --- a/VSharp.ML.AIAgent/ml/models.py +++ b/VSharp.ML.AIAgent/ml/models.py @@ -6,6 +6,7 @@ ARMAConv, FeaStConv, GATConv, + GatedGraphConv, GCNConv, HeteroConv, Linear, @@ -16,6 +17,7 @@ global_mean_pool, to_hetero, ) +from torchvision.ops import MLP from timer.wrapper import timeit @@ -495,3 +497,79 @@ def forward(self, x_dict, edge_index_dict): # return self.decoder(z_dict, edge_index_dict) # TODO: process separately return z_dict + + +class SAGEConvModel(torch.nn.Module): + def __init__( + self, + hidden_channels, + num_gv_layers=2, + num_sv_layers=2, + ): + super().__init__() + self.gv_layers = nn.ModuleList() + self.gv_layers.append(SAGEConv(-1, hidden_channels)) + for i in range(num_gv_layers - 1): + sage_gv = SAGEConv(-1, hidden_channels) + self.gv_layers.append(sage_gv) + + self.sv_layers = nn.ModuleList() + self.sv_layers.append(SAGEConv(-1, hidden_channels)) + for i in range(num_sv_layers - 1): + sage_sv = SAGEConv(-1, hidden_channels) + self.sv_layers.append(sage_sv) + + self.history1 = GATConv((-1, -1), hidden_channels, add_self_loops=False) + self.in1 = SAGEConv((-1, -1), hidden_channels) + + self.sv_layers2 = nn.ModuleList() + self.sv_layers2.append(SAGEConv(-1, hidden_channels)) + for i in range(num_sv_layers - 1): + sage_sv = SAGEConv(-1, hidden_channels) + self.sv_layers2.append(sage_sv) + self.mlp = MLP(hidden_channels, [1]) + + @timeit + def forward(self, x_dict, edge_index_dict, edge_attr_dict): + game_x = self.gv_layers[0]( + x_dict["game_vertex"], + edge_index_dict[("game_vertex", "to", "game_vertex")], + ).relu() + for layer in self.gv_layers[1:]: + game_x = layer( + game_x, + edge_index_dict[("game_vertex", "to", "game_vertex")], + ).relu() + + state_x = self.sv_layers[0]( + x_dict["state_vertex"], + edge_index_dict[("state_vertex", "parent_of", "state_vertex")], + ).relu() + for layer in self.sv_layers[1:]: + state_x = layer( + state_x, + edge_index_dict[("state_vertex", "parent_of", "state_vertex")], + ).relu() + + history_x = self.history1( + (game_x, state_x), + edge_index_dict[("game_vertex", "history", "state_vertex")], + edge_attr_dict, + size=(game_x.size(0), state_x.size(0)), + ).relu() + + in_x = self.in1( + (game_x, history_x), edge_index_dict[("game_vertex", "in", "state_vertex")] + ).relu() + + state_x = self.sv_layers2[0]( + in_x, + edge_index_dict[("state_vertex", "parent_of", "state_vertex")], + ).relu() + for layer in self.sv_layers2[1:]: + state_x = layer( + state_x, + edge_index_dict[("state_vertex", "parent_of", "state_vertex")], + ).relu() + x = self.mlp(in_x) + return x diff --git a/VSharp.ML.AIAgent/ml/onnx/onnx_import.py b/VSharp.ML.AIAgent/ml/onnx/onnx_import.py index d7c785478..07af5adc2 100644 --- a/VSharp.ML.AIAgent/ml/onnx/onnx_import.py +++ b/VSharp.ML.AIAgent/ml/onnx/onnx_import.py @@ -24,6 +24,7 @@ def create_onnx_dummy_input(): return { "x_dict": hetero_data.x_dict, "edge_index_dict": hetero_data.edge_index_dict, + "edge_attr_dict": hetero_data.edge_attr_dict, } @@ -50,7 +51,7 @@ def create_onnxruntime_dummy_input(): def create_torch_dummy_input(): hetero_data = create_dummy_hetero_data() - return hetero_data.x_dict, hetero_data.edge_index_dict + return hetero_data.x_dict, hetero_data.edge_index_dict, hetero_data.edge_attr_dict def export_onnx_model(model: torch.nn.Module, save_path: str): diff --git a/VSharp.ML.AIAgent/ml/predict_state_vector_hetero.py b/VSharp.ML.AIAgent/ml/predict_state_vector_hetero.py index 8a3c71281..a00579f9d 100644 --- a/VSharp.ML.AIAgent/ml/predict_state_vector_hetero.py +++ b/VSharp.ML.AIAgent/ml/predict_state_vector_hetero.py @@ -119,11 +119,11 @@ def predict_state_single_out( reversed_state_map = {v: k for k, v in state_map.items()} with torch.no_grad(): - out = model.forward(data.x_dict, data.edge_index_dict) + out = model.forward(data.x_dict, data.edge_index_dict, data.edge_attr_dict) remapped = [] - for index, vector in enumerate(out["state_vertex"]): + for index, vector in enumerate(out): state_vector_mapping = StateVectorMapping( state=reversed_state_map[index], vector=(vector.detach().cpu().numpy()).tolist(), diff --git a/VSharp.ML.GameServer/Maps.fs b/VSharp.ML.GameServer/Maps.fs index 1df627370..303e621a6 100644 --- a/VSharp.ML.GameServer/Maps.fs +++ b/VSharp.ML.GameServer/Maps.fs @@ -223,12 +223,16 @@ let trainMaps, validationMaps = add 0u "Algorithms.dll" CoverageZone.Method "GreatestCommonDivisor.FindGCDStein" add 0u "Algorithms.dll" CoverageZone.Method "SieveOfEratosthenes.GeneratePrimesUpTo" - + //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscHasher.Calculate_PSX_BizIDHash" //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscHasher.Calculate_PSX_RedumpHash" - //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscMountJob.Run" - //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscHasher.OldHash" + add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscMountJob.Run" + add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscHasher.OldHash" + + //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscSectorReader.ReadLBA_SubQ" + //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscSectorReader.ReadLBA_2448" + //add 0u "BizHawk.Emulation.DiscSystem.dll" CoverageZone.Method "DiscStream.Read" add 0u "BizHawk.Emulation.Cores.dll" CoverageZone.Method "TI83LinkPort.Update" add 0u "BizHawk.Emulation.Cores.dll" CoverageZone.Method "AmstradGateArray.ClockCycle" @@ -246,7 +250,6 @@ let trainMaps, validationMaps = add 0u "Virtu.dll" CoverageZone.Method "DiskIIController.WriteIoRegionC0C0" //add 0u "Virtu.dll" CoverageZone.Method "Keyboard.SetKeys" - //add 0u "Algorithms.dll" CoverageZone.Method "TopologicalSorter.Sort" //add 0u "Algorithms.dll" CoverageZone.Method "DijkstraShortestPaths.ShortestPathTo" //add 0u "Algorithms.dll" CoverageZone.Method "DijkstraAllPairsShortestPaths.ShortestPath"