diff --git a/Forward_Pass/.gitignore b/Forward_Pass/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/Forward_Pass/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/Forward_Pass/Aggregator.h b/Forward_Pass/Aggregator.h new file mode 100644 index 0000000..10b7771 --- /dev/null +++ b/Forward_Pass/Aggregator.h @@ -0,0 +1,89 @@ +// Aggregator.h +#pragma once + +#include "Graph.h" +#include +#include +#include +#include +#include + +namespace OutputConverter { + + //────────────────────────────────────────────────────────────────────────── + // Type aliases matching OutputConverter signatures + //────────────────────────────────────────────────────────────────────────── + + // A vector of per‐node scalar scores + using NodeScores = std::vector; + + // A vector of per‐edge scalar scores + using EdgeScores = std::vector; + + // A vector of boolean flags + using BinaryVector = std::vector; + + // Combines two node‐scores into one edge‐score + using EdgeCombiner = std::function; + + // Aggregates all node‐scores into one graph‐score + using GraphAggregator = std::function; + + + //────────────────────────────────────────────────────────────────────────── + // Default implementations (used when the user omits their own) + //────────────────────────────────────────────────────────────────────────── + namespace DefaultAgg { + + // sum of endpoint scores + inline float sumCombiner(float a, float b) { + return a + b; + } + + // product of endpoint scores + inline float prodCombiner(float a, float b) { + return a * b; + } + + // maximum of endpoint scores + inline float maxCombiner(float a, float b) { + return std::max(a, b); + } + + // minimum of endpoint scores + inline float minCombiner(float a, float b) { + return std::min(a, b); + } + + // absolute difference of endpoint scores + inline float absDiffCombiner(float a, float b) { + return std::fabs(a - b); + } + + // sum of all node scores + inline float sumGraph(const NodeScores& scores) { + return std::accumulate(scores.begin(), scores.end(), 0.0f); + } + + // mean of all node scores + inline float meanGraph(const NodeScores& scores) { + if (scores.empty()) return 0.0f; + return std::accumulate(scores.begin(), scores.end(), 0.0f) + / static_cast(scores.size()); + } + + // maximum of all node scores + inline float maxGraph(const NodeScores& scores) { + if (scores.empty()) return 0.0f; + return *std::max_element(scores.begin(), scores.end()); + } + + // minimum of all node scores + inline float minGraph(const NodeScores& scores) { + if (scores.empty()) return 0.0f; + return *std::min_element(scores.begin(), scores.end()); + } + + } // namespace DefaultAgg + +} // namespace OutputConverter diff --git a/Forward_Pass/BaseLayer.h b/Forward_Pass/BaseLayer.h new file mode 100644 index 0000000..3ea1b02 --- /dev/null +++ b/Forward_Pass/BaseLayer.h @@ -0,0 +1,16 @@ +// BaseLayer.h +#pragma once +#include +using namespace std; + +// BaseLayer provides a standard interface for all GNN layers (GAT, GCN, GraphSAGE, etc.) +class BaseLayer { +public: + virtual ~BaseLayer() {} + + // Forward pass interface to be overridden by all derived GNN layers + virtual vector> forward( + const vector>& node_features, + const vector>& adjacency_list + ) = 0; +}; diff --git a/Forward_Pass/CMakeLists.txt b/Forward_Pass/CMakeLists.txt new file mode 100644 index 0000000..19eb369 --- /dev/null +++ b/Forward_Pass/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.10) +project(GraphGNN LANGUAGES CXX) + +# Choose C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# All source files +set(SOURCE_FILES + GATL.cpp + GCNL.cpp + GCNTest.cpp + Graph.cpp + GraphReader.cpp + GraphSage.cpp + output.cpp + output_main.cpp +) + +# Build the executable +add_executable(graph_app ${SOURCE_FILES}) + +# Make headers in this folder visible +target_include_directories(graph_app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +# After building graph_app, copy graph_data.txt into the build folder +add_custom_command(TARGET graph_app + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_SOURCE_DIR}/graph_data.txt" + "${CMAKE_BINARY_DIR}/graph_data.txt" + COMMENT "Copying graph_data.txt to build directory" +) diff --git a/Forward_Pass/GATL.cpp b/Forward_Pass/GATL.cpp new file mode 100644 index 0000000..c3a2c03 --- /dev/null +++ b/Forward_Pass/GATL.cpp @@ -0,0 +1,109 @@ +// GATLayer.cpp + +#include "GATL.h" +#include +#include +#include + +// Constructor with Xavier initialization +GATLayer::GATLayer(int input_dim, int output_dim) : input_dim(input_dim), output_dim(output_dim) { + W.resize(input_dim, vector(output_dim)); + a.resize(2 * output_dim); + + float limit = sqrt(6.0f / (input_dim + output_dim)); + random_device rd; + mt19937 gen(rd()); + uniform_real_distribution<> dis(0, limit); + + for (int i = 0; i < input_dim; i++) + for (int j = 0; j < output_dim; j++) + W[i][j] = dis(gen); + + for (int i = 0; i < 2 * output_dim; i++) + a[i] = dis(gen); +} + +// ReLU activation +float GATLayer::relu(float x) { + return max(0.0f, x); +} + +// Leaky ReLU activation +float GATLayer::leaky_relu(float x, float alpha) { + return (x > 0) ? x : alpha * x; +} + +// Linear transformation for a single node +vector GATLayer::linear_transform(const vector& features) { + vector z(output_dim, 0.0f); + for (int o = 0; o < output_dim; o++) + for (int d = 0; d < input_dim; d++) + z[o] += features[d] * W[d][o]; + return z; +} + +// Compute attention score e_ij using attention vector 'a' +float GATLayer::compute_attention_score(const vector& z_i, const vector& z_j) { + float score = 0.0f; + for (int o = 0; o < output_dim; o++) { + score += a[o] * z_i[o] + a[o + output_dim] * z_j[o]; + } + return leaky_relu(score); +} + +// Stable softmax computation +vector GATLayer::softmax(const vector& scores) { + float max_val = *max_element(scores.begin(), scores.end()); + float sum_exp = 0.0f; + vector exp_scores(scores.size()); + for (size_t i = 0; i < scores.size(); i++) { + exp_scores[i] = exp(scores[i] - max_val); + sum_exp += exp_scores[i]; + } + if(sum_exp!=0.0f) { + for (float &val : exp_scores) { + val /= sum_exp; + } + } + return exp_scores; +} + +// Forward pass +vector> GATLayer::forward( + const vector>& node_features, + const vector>& adjacency_list +) { + int n_nodes = node_features.size(); + vector> updated_features(n_nodes, vector(output_dim, 0.0f)); + vector> z(n_nodes); + + // Step 1: Linear transform each node's features + for (int i = 0; i < n_nodes; i++) { + z[i] = linear_transform(node_features[i]); + } + + // Step 2: Compute attention and aggregate + for (int i = 0; i < n_nodes; i++) { + vector neighbors = adjacency_list[i]; + neighbors.push_back(i); // self-loop + + vector e_ij(neighbors.size()); + for (size_t idx = 0; idx < neighbors.size(); idx++) { + int j = neighbors[idx]; + e_ij[idx] = compute_attention_score(z[i], z[j]); + } + + vector alpha_ij = softmax(e_ij); + + for (int o = 0; o < output_dim; o++) { + float agg = 0.0f; + for (size_t idx = 0; idx < neighbors.size(); idx++) { + int j = neighbors[idx]; + agg += alpha_ij[idx] * z[j][o]; + } + updated_features[i][o] = relu(agg); // ReLU activation + } + } + + return updated_features; +} diff --git a/Forward_Pass/GATL.h b/Forward_Pass/GATL.h new file mode 100644 index 0000000..d233b7b --- /dev/null +++ b/Forward_Pass/GATL.h @@ -0,0 +1,52 @@ +// GATL.h +#pragma once +#include "BaseLayer.h" +#include +using namespace std; + +// Implements a layer of Graph Attention Network(GAT) +// It takes into account the importance of each neighbour also in aggregation. +// It uses self-attention mechanism on graphs to compute this importance +class GATLayer : public BaseLayer { +public: + int input_dim, output_dim; // Input and output dimension + vector> W; // Weight matrix for linear transformation + vector a; // Attention vector used for computing attention coefficients + + // Constructor initializes the GAT layer with input and output dimensions + // and performs Xavier initialization for weights and attention parameters. + GATLayer(int input_dim, int output_dim); + + // Forward pass computes the updated node features based on attention mechanism. + // It projects input features, computes attention scores with neighbours, applies softmax, + // aggregates neighbour features weighted by attention, and applies ReLU. + vector> forward( + const vector>& node_features, // node-feature matrix:[number of nodes][input_dim] + const vector>& adjacency_list // represents the graph + ) override; + +private: + // Applies ReLU activation to a single float value + float relu(float x); + + // Applies LeakyReLU activation with a configurable alpha slope for negative inputs. + float leaky_relu(float x, float alpha = 0.2f); + + // Applies weight matrix to a single node's feature vector to transform feature vector of size output_dim. + vector linear_transform( + const vector& features // Input feature vector of a node + ); + + // Computes attention score (unnormalised) for node i and node j + // using attention vector applied to concatenation of projected features of node i and node j + float compute_attention_score( + const vector& z_i, // feature vector of node i + const vector& z_j // feature vector of node j (neighbour) + ); + + // Applies softmax function to a vector of unnormalised attention scores + // returns a vector of normalised attention coefficients + vector softmax( + const vector& scores // Unnormalised attention scores for a node and its neighbours + ); +}; diff --git a/Forward_Pass/GCNL.cpp b/Forward_Pass/GCNL.cpp new file mode 100644 index 0000000..0be1d5e --- /dev/null +++ b/Forward_Pass/GCNL.cpp @@ -0,0 +1,79 @@ +// GCNL.cpp + +#include "GCNL.h" +#include +#include +#include + +// Xavier Initialization +GCNLayer::GCNLayer(int input_dim, int output_dim) : input_dim(input_dim), output_dim(output_dim) { + weight_matrix.resize(input_dim, vector(output_dim)); + float limit = sqrt(6.0f / (input_dim + output_dim)); + random_device rd; + mt19937 gen(rd()); + uniform_real_distribution<> dis(0, limit); + for (int i = 0; i < input_dim; i++) + for (int j = 0; j < output_dim; j++) + weight_matrix[i][j] = dis(gen); +} + +// ReLU activation +float GCNLayer::relu(float x) { + return max(0.0f, x); +} + +// Aggregates normalized neighbor features for a node +vector GCNLayer::aggregate_neighbors( + int node, + const vector>& node_features, + const vector>& adjacency_list, + const vector& degrees +) { + vector aggregated(input_dim, 0.0f); + for (int neighbor : adjacency_list[node]) { + float normalization = sqrt(degrees[node] * degrees[neighbor]); + if (normalization != 0.0f) { + for (int d = 0; d < input_dim; d++) { + aggregated[d] += node_features[neighbor][d] / normalization; + } + } + } + return aggregated; +} + +// Applies weight matrix for a given output dimension +float GCNLayer::linear_transform( + const vector& aggregated_features, + int output_index +) { + float val = 0.0f; + for (int d = 0; d < input_dim; d++) { + val += aggregated_features[d] * weight_matrix[d][output_index]; + } + return val; +} + +// Forward pass for GCN Layer +vector> GCNLayer::forward( + const vector>& node_features, + const vector>& adjacency_list +) { + int n_nodes = node_features.size(); + vector> updated_features(n_nodes, vector(output_dim, 0.0f)); + + // Precompute degrees + vector degrees(n_nodes); + for (int i = 0; i < n_nodes; i++) { + degrees[i] = adjacency_list[i].size(); + } + + for (int i = 0; i < n_nodes; i++) { + vector aggregated = aggregate_neighbors(i, node_features, adjacency_list, degrees); + for (int o = 0; o < output_dim; o++) { + float val = linear_transform(aggregated, o); + updated_features[i][o] = relu(val); + } + } + + return updated_features; +} diff --git a/Forward_Pass/GCNL.h b/Forward_Pass/GCNL.h new file mode 100644 index 0000000..b13b446 --- /dev/null +++ b/Forward_Pass/GCNL.h @@ -0,0 +1,46 @@ +// GCNL.h + +#pragma once +#include "BaseLayer.h" +#include +using namespace std; + +// GCNLayer implements Graph Convolution Neural Network +// performs feature aggregation ONLY from neighbours and then does linear transformation. +class GCNLayer : public BaseLayer { +public: + // This constructor initialises the Layer with input and output dimensions + // performs Xavier initialisation of weight matrix. + GCNLayer(int input_dim, int output_dim); + + // forward pass computes the updated node features + // using the input features and the adjacency list + vector> forward( + const vector>& node_features, // feature matrix-[number of nodes][input_dim] + const vector>& adjacency_list // represents graph structure + ) override; + +private: + int input_dim; // dimension of input features + int output_dim; // dimension of output features + vector> weight_matrix; // weight matrix of shape [input_dim][output_dim] + + float relu(float x); // Applies ReLU function to a single value (Activation function) + + // Aggregates normalised neighbour features for a given node. + // Each neighbour's features are first scaled down by the inverse of + // the product of degrees of node and the neighbour, ensuring normalisation. + vector aggregate_neighbors( + int node, // the centre node + const vector>& node_features, // Feature matrix of nodes + const vector>& adjacency_list, // graph adjacency list + const vector& degrees // pre-computed degrees of each node + ); + + // Applies weight matrix to the aggregated neighbour features to compute + // the output for a single output dimension + float linear_transform( + const vector& aggregated_features, // Aggregated and normalised neighbour features + int output_index // index of output dimension being computed + ); +}; diff --git a/Forward_Pass/GCNTest.cpp b/Forward_Pass/GCNTest.cpp new file mode 100644 index 0000000..0a5bb81 --- /dev/null +++ b/Forward_Pass/GCNTest.cpp @@ -0,0 +1,72 @@ +// GCNL.cpp + +#include "GCNTest.h" +#include +#include +#include + +// Xavier Initialization +GCNTestLayer::GCNTestLayer(int input_dim, int output_dim) : input_dim(input_dim), output_dim(output_dim) { + weight_matrix.resize(input_dim, vector(output_dim,1.0f)); +} + +// ReLU activation +float GCNTestLayer::relu(float x) { + return max(0.0f, x); +} + +// Aggregates normalized neighbor features for a node +vector GCNTestLayer::aggregate_neighbors( + int node, + const vector>& node_features, + const vector>& adjacency_list, + const vector& degrees +) { + vector aggregated(input_dim, 0.0f); + for (int neighbor : adjacency_list[node]) { + float normalization = sqrt(degrees[node] * degrees[neighbor]); + if (normalization != 0.0f) { + for (int d = 0; d < input_dim; d++) { + aggregated[d] += node_features[neighbor][d] / normalization; + } + } + } + return aggregated; +} + +// Applies weight matrix for a given output dimension +float GCNTestLayer::linear_transform( + const vector& aggregated_features, + int output_index +) { + float val = 0.0f; + for (int d = 0; d < input_dim; d++) { + val += aggregated_features[d] * weight_matrix[d][output_index]; + } + return val; +} + +// Forward pass for GCN Layer +vector> GCNTestLayer::forward( + const vector>& node_features, + const vector>& adjacency_list +) { + int n_nodes = node_features.size(); + vector> updated_features(n_nodes, vector(output_dim, 0.0f)); + + // Precompute degrees + vector degrees(n_nodes); + for (int i = 0; i < n_nodes; i++) { + degrees[i] = adjacency_list[i].size(); + } + + for (int i = 0; i < n_nodes; i++) { + vector aggregated = aggregate_neighbors(i, node_features, adjacency_list, degrees); + for (int o = 0; o < output_dim; o++) { + float val = linear_transform(aggregated, o); + updated_features[i][o] = relu(val); + } + } + + return updated_features; +} diff --git a/Forward_Pass/GCNTest.h b/Forward_Pass/GCNTest.h new file mode 100644 index 0000000..20017f2 --- /dev/null +++ b/Forward_Pass/GCNTest.h @@ -0,0 +1,46 @@ +// GCNL.h + +#pragma once +#include "BaseLayer.h" +#include +using namespace std; + +// GCNTestLayer implements Graph Convolution Neural Network +// performs feature aggregation ONLY from neighbours and then does linear transformation. +class GCNTestLayer : public BaseLayer { +public: + // This constructor initialises the Layer with input and output dimensions + // performs Xavier initialisation of weight matrix. + GCNTestLayer(int input_dim, int output_dim); + + // forward pass computes the updated node features + // using the input features and the adjacency list + vector> forward( + const vector>& node_features, // feature matrix-[number of nodes][input_dim] + const vector>& adjacency_list // represents graph structure + ) override; + +private: + int input_dim; // dimension of input features + int output_dim; // dimension of output features + vector> weight_matrix; // weight matrix of shape [input_dim][output_dim] + + float relu(float x); // Applies ReLU function to a single value (Activation function) + + // Aggregates normalised neighbour features for a given node. + // Each neighbour's features are first scaled down by the inverse of + // the product of degrees of node and the neighbour, ensuring normalisation. + vector aggregate_neighbors( + int node, // the centre node + const vector>& node_features, // Feature matrix of nodes + const vector>& adjacency_list, // graph adjacency list + const vector& degrees // pre-computed degrees of each node + ); + + // Applies weight matrix to the aggregated neighbour features to compute + // the output for a single output dimension + float linear_transform( + const vector& aggregated_features, // Aggregated and normalised neighbour features + int output_index // index of output dimension being computed + ); +}; diff --git a/Forward_Pass/Graph.cpp b/Forward_Pass/Graph.cpp new file mode 100644 index 0000000..5e17477 --- /dev/null +++ b/Forward_Pass/Graph.cpp @@ -0,0 +1,50 @@ +#include "Graph.h" + +Graph::Graph(int num_nodes, int num_node_features) + : num_nodes(num_nodes), num_node_features(num_node_features) +{ + node_features.resize(num_nodes, vector(num_node_features, 0.0f)); + adjacency_list.resize(num_nodes); +} + +Graph::Graph(Graph&& other) noexcept + : num_nodes(other.num_nodes), + num_node_features(other.num_node_features), + node_features(std::move(other.node_features)), + adjacency_list(std::move(other.adjacency_list)), + edge_features(std::move(other.edge_features)), + global_features(std::move(other.global_features)), + edge_list(std::move(other.edge_list)) // Added +{} + +Graph& Graph::operator=(Graph&& other) noexcept { + if (this != &other) { + num_nodes = other.num_nodes; + num_node_features = other.num_node_features; + node_features = std::move(other.node_features); + adjacency_list = std::move(other.adjacency_list); + edge_features = std::move(other.edge_features); + global_features = std::move(other.global_features); + edge_list = std::move(other.edge_list); // Added + } + return *this; +} + +void Graph::add_edge(int src, int dst) { + adjacency_list[src].push_back(dst); + // If undirected: + adjacency_list[dst].push_back(src); + edge_list.emplace_back(src, dst); // Added +} + +void Graph::set_node_feature(int node_id, const vector& features) { + if (features.size() == num_node_features) { + node_features[node_id] = features; + } + // Otherwise, throw or handle mismatch +} + +// Added: Returns (src, dst) for given edge index +pair Graph::edge(size_t edge_id) const { + return edge_list[edge_id]; +} diff --git a/Forward_Pass/Graph.h b/Forward_Pass/Graph.h new file mode 100644 index 0000000..c93e98a --- /dev/null +++ b/Forward_Pass/Graph.h @@ -0,0 +1,45 @@ +#ifndef GRAPH_H +#define GRAPH_H + +#include +using namespace std; + +// Produces a clear abstraction for representing a graph structure +// with node features, adjacency list, optionally edge and global features +// to facilitate GNN architecture +class Graph { +public: + int num_nodes; // Number of nodes in the graph + int num_node_features; // Number of features per node + + vector> node_features; // Feature matrix : [n_nodes][n_node_features] + vector> adjacency_list; // graph_representation : [n_nodes][variable number of neighbors] + + // Optional features : + vector> edge_features; // For edge-conditioned GNNs + vector global_features; // For graph-level attributes + + // New addition: stores (src, dst) for every edge added + vector> edge_list; + + // Constructs a graph with the specified number of nodes and node feature dimensions. + // Initializes empty adjacency list and zero-initialized feature matrices. + Graph(int num_nodes, int num_node_features); + + // Move constructor for efficient transfers without deep copying + Graph(Graph&& other) noexcept; + + // Move assignment operator for efficient transfers without deep copying + Graph& operator=(Graph&& other) noexcept; + + // Adds an undirected edge between source node and destination node + void add_edge(int src, int dst); + + // Sets the feature vector of a specific node + void set_node_feature(int node_id, const vector& features); + + // New accessor: returns the src and dst for a given edge ID + pair edge(size_t edge_id) const; +}; + +#endif diff --git a/Forward_Pass/GraphReader.cpp b/Forward_Pass/GraphReader.cpp new file mode 100644 index 0000000..6b0d20c --- /dev/null +++ b/Forward_Pass/GraphReader.cpp @@ -0,0 +1,47 @@ +#include "GraphReader.h" +#include +#include +#include + +Graph read_graph_from_file(const string& filename) { + ifstream infile(filename); + if (!infile.is_open()) { + cerr << "Error opening file " << filename << "\n"; + exit(1); + } + + string line; + int num_nodes, num_features; + + // Read header + getline(infile, line); + istringstream header_stream(line); + header_stream >> num_nodes >> num_features; + + Graph g(num_nodes, num_features); + + // Read node features + for (int i = 0; i < num_nodes; i++) { + getline(infile, line); + istringstream iss(line); + int node_id; + iss >> node_id; + vector features(num_features); + for (int j = 0; j < num_features; j++) { + iss >> features[j]; + } + g.set_node_feature(node_id, features); + } + + // Read edges + while (getline(infile, line)) { + if (line.empty() || line[0] == '#') continue; + istringstream iss(line); + int src, dst; + iss >> src >> dst; + g.add_edge(src, dst); + } + + infile.close(); + return g; +} diff --git a/Forward_Pass/GraphReader.h b/Forward_Pass/GraphReader.h new file mode 100644 index 0000000..3ccd78f --- /dev/null +++ b/Forward_Pass/GraphReader.h @@ -0,0 +1,10 @@ +#ifndef GRAPH_DATA_READER_H +#define GRAPH_DATA_READER_H + +#include "Graph.h" +#include +using namespace std; + +Graph read_graph_from_file(const string& filename); + +#endif diff --git a/Forward_Pass/GraphReadermain.cpp b/Forward_Pass/GraphReadermain.cpp new file mode 100644 index 0000000..a069859 --- /dev/null +++ b/Forward_Pass/GraphReadermain.cpp @@ -0,0 +1,18 @@ +#include +#include "GraphReader.h" +#include "GCNL.h" // or GraphSAGELayer, GATLayer + +int main() { + Graph g = read_graph_from_file("graph_data.txt"); + int out; + std::cin >> out; + GCNLayer gcn(g.num_node_features, out); + auto updated_features = gcn.forward(g.node_features, g.adjacency_list); + + for (const auto& node : updated_features) { + for (float val : node) { + std::cout << val << " "; + } + std::cout << "\n"; + } +} diff --git a/Forward_Pass/GraphSage.cpp b/Forward_Pass/GraphSage.cpp new file mode 100644 index 0000000..f9150c6 --- /dev/null +++ b/Forward_Pass/GraphSage.cpp @@ -0,0 +1,92 @@ +// GraphSage.cpp + +#include "GraphSage.h" +#include +#include +#include + +// Xavier Initialization +GraphSAGELayer::GraphSAGELayer(int input_dim, int output_dim) : input_dim(input_dim), output_dim(output_dim) { + weight_matrix.resize(2 * input_dim, vector(output_dim)); + float limit = sqrt(6.0f / (2 * input_dim + output_dim)); + random_device rd; + mt19937 gen(rd()); + uniform_real_distribution<> dis(0, limit); + + for (int i = 0; i < 2 * input_dim; i++) + for (int j = 0; j < output_dim; j++) + weight_matrix[i][j] = dis(gen); +} + +// ReLU activation +float GraphSAGELayer::relu(float x) { + return max(0.0f, x); +} + +// Mean aggregation of neighbor features +vector GraphSAGELayer::aggregate_neighbors_mean( + int node, + const vector>& node_features, + const vector>& adjacency_list +) { + vector neighbor_agg(input_dim, 0.0f); + int neighbor_count = adjacency_list[node].size(); + + if (neighbor_count > 0) { + for (int neighbor : adjacency_list[node]) { + for (int d = 0; d < input_dim; d++) { + neighbor_agg[d] += node_features[neighbor][d]; + } + } + for (int d = 0; d < input_dim; d++) { + neighbor_agg[d] /= neighbor_count; // mean aggregation + } + } + return neighbor_agg; +} + +// Concatenate own features with neighbor aggregation +vector GraphSAGELayer::concatenate_self_and_neighbors( + const vector& self_features, + const vector& neighbor_features +) { + vector concat_features(2 * input_dim); + for (int d = 0; d < input_dim; d++) { + concat_features[d] = self_features[d]; + concat_features[d + input_dim] = neighbor_features[d]; + } + return concat_features; +} + +// Linear transformation for a given output index +float GraphSAGELayer::linear_transform( + const vector& concat_features, + int output_index +) { + float val = 0.0f; + for (int d = 0; d < 2 * input_dim; d++) { + val += concat_features[d] * weight_matrix[d][output_index]; + } + return val; +} + +// Forward pass for GraphSAGE layer +vector> GraphSAGELayer::forward( + const vector>& node_features, + const vector>& adjacency_list +) { + int n_nodes = node_features.size(); + vector> updated_features(n_nodes, vector(output_dim, 0.0f)); + + for (int i = 0; i < n_nodes; i++) { + vector neighbor_agg = aggregate_neighbors_mean(i, node_features, adjacency_list); + vector concat_features = concatenate_self_and_neighbors(node_features[i], neighbor_agg); + + for (int o = 0; o < output_dim; o++) { + float val = linear_transform(concat_features, o); + updated_features[i][o] = relu(val); + } + } + + return updated_features; +} diff --git a/Forward_Pass/GraphSage.h b/Forward_Pass/GraphSage.h new file mode 100644 index 0000000..c0ed9f9 --- /dev/null +++ b/Forward_Pass/GraphSage.h @@ -0,0 +1,52 @@ +// GraphSage.h + +#pragma once +#include "BaseLayer.h" +#include +using namespace std; + +// Implements GraphSage Layer which aggregates the neighbouring features +// using mean aggregation and CONCATENATES them with node's own features +// and then performs linear transformation. +class GraphSAGELayer : public BaseLayer { +public: + // This constructor initialises the Layer with input and output dimensions + // performs Xavier initialisation of weight matrix. + GraphSAGELayer(int input_dim, int output_dim); + + // forward pass computes updated node features by aggregating neighbour features, + // concatenating with self-features and multiplying with weight matrix followed by ReLU. + vector> forward( + const vector>& node_features, // node-feature matrix:[number of nodes][input_dim] + const vector>& adjacency_list // represents the graph + ) override; + +private: + int input_dim; // dimension of input features + int output_dim; // dimension of output features + vector> weight_matrix; // weight matrix of shape [input_dim][output_dim] + + // Applies ReLU activation to single float value + float relu(float x); + + // Aggregates features of the neighbours of this node using mean aggregation. + vector aggregate_neighbors_mean( + int node, // index of the central node + const vector>& node_features, // input node feature matrix + const vector>& adjacency_list // graph representation + ); + + // CONCATENATES node's own features to the aggregated neighbour features + // resulting feature vector is of size 2*input_dim. + vector concatenate_self_and_neighbors( + const vector& self_features, // node's own feature vector + const vector& neighbor_features // aggregated neighbour-feature vector + ); + + // Applies weight matrix to the aggregated feature vector to compute + // output for a single output dimension + float linear_transform( + const vector& concat_features, // the concatenated feature vector + int output_index // index of output dimension being computed + ); +}; diff --git a/Forward_Pass/graph_data.txt b/Forward_Pass/graph_data.txt new file mode 100644 index 0000000..4a39b0b --- /dev/null +++ b/Forward_Pass/graph_data.txt @@ -0,0 +1,11 @@ +5 3 +0 1 0 0.5 +1 0 1 0.3 +2 1 1 0.7 +3 0 0 0.2 +4 1 0 0.6 +0 1 +1 2 +2 3 +3 4 +4 0 diff --git a/Forward_Pass/output.cpp b/Forward_Pass/output.cpp new file mode 100644 index 0000000..9f7e7df --- /dev/null +++ b/Forward_Pass/output.cpp @@ -0,0 +1,64 @@ +#include "output.h" +#include +#include + +namespace OutputConverter { + + EdgeScores toEdgeScores( + const NodeScores& nodeScores, + const Graph& graph, + EdgeCombiner combiner, + bool undirected + ) { + EdgeScores out; + out.reserve(graph.num_nodes); + + unordered_set seen; + auto key = [&](int u, int v) { + return (static_cast(u) << 32) + | static_cast(v); + }; + + for (int u = 0; u < graph.num_nodes; ++u) { + for (int v : graph.adjacency_list[u]) { + if (undirected) { + if (u >= v) continue; + if (!seen.insert(key(u, v)).second) continue; + } + out.push_back(combiner(nodeScores[u], nodeScores[v])); + } + } + return out; + } + + BinaryVector toEdgeBinary( + const NodeScores& nodeScores, + const Graph& graph, + float threshold, + EdgeCombiner combiner, + bool undirected + ) { + auto scores = toEdgeScores(nodeScores, graph, combiner, undirected); + BinaryVector b; + b.reserve(scores.size()); + for (auto s : scores) + b.push_back(s > threshold); + return b; + } + + float toGraphScore( + const NodeScores& nodeScores, + GraphAggregator aggregator + ) { + return aggregator(nodeScores); + } + + bool toGraphBinary( + const NodeScores& nodeScores, + float threshold, + GraphAggregator aggregator + ) { + return aggregator(nodeScores) > threshold; + } + +} // namespace OutputConverter diff --git a/Forward_Pass/output.h b/Forward_Pass/output.h new file mode 100644 index 0000000..bf3569e --- /dev/null +++ b/Forward_Pass/output.h @@ -0,0 +1,53 @@ +#ifndef OUTPUT_CONVERTER_H +#define OUTPUT_CONVERTER_H + +#include +#include +#include "Graph.h" // your Graph class +#include "Aggregator.h" // brings in NodeScores, EdgeCombiner, GraphAggregator, DefaultAgg + +using namespace std; + +namespace OutputConverter { + + using NodeScores = vector; // one score per node + using EdgeScores = vector; // one score per edge + using BinaryVector = vector; // binary labels + + // pull in our aliases + using EdgeCombiner = ::OutputConverter::EdgeCombiner; + using GraphAggregator = ::OutputConverter::GraphAggregator; + + // If the user omits a combiner/aggregator, we default to these: + // DefaultAgg::prodCombiner → a * b (matches your old default) + // DefaultAgg::meanGraph → mean(v) (matches your old default) + + EdgeScores toEdgeScores( + const NodeScores& nodeScores, + const Graph& graph, + EdgeCombiner combiner = DefaultAgg::prodCombiner, + bool undirected = true + ); + + BinaryVector toEdgeBinary( + const NodeScores& nodeScores, + const Graph& graph, + float threshold, + EdgeCombiner combiner = DefaultAgg::prodCombiner, + bool undirected = true + ); + + float toGraphScore( + const NodeScores& nodeScores, + GraphAggregator aggregator = DefaultAgg::meanGraph + ); + + bool toGraphBinary( + const NodeScores& nodeScores, + float threshold, + GraphAggregator aggregator = DefaultAgg::meanGraph + ); + +} // namespace OutputConverter + +#endif // OUTPUT_CONVERTER_H diff --git a/Forward_Pass/output_main.cpp b/Forward_Pass/output_main.cpp new file mode 100644 index 0000000..c47e50c --- /dev/null +++ b/Forward_Pass/output_main.cpp @@ -0,0 +1,104 @@ +// main.cpp + +#include "Graph.h" // your Graph class +#include "GraphReader.h" // read_graph_from_file(...) +#include "GCNL.h" // your existing GCNLayer +#include "output.h" // OutputConverter API +#include +#include +#include +#include // for function + +int main(int argc, char** argv) { + // 1) Grab the input filename + if (argc < 2) { + cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + string filename = argv[1]; + + // 2) Read the graph + Graph g = read_graph_from_file(filename); + + // 3) Run one GCN layer + cout << "Enter output feature dimension: "; + int out_dim; + cin >> out_dim; + + GCNLayer gcn(g.num_node_features, out_dim); + auto features = gcn.forward(g.node_features, g.adjacency_list); + + cout << "=== Node Features (post-GCN) ===\n"; + for (size_t i = 0; i < features.size(); ++i) { + cout << "Node " << i << ": "; + for (float val : features[i]) { + cout << val << " "; + } + cout << "\n"; + } + cout << "\n"; + + // 4) Compute node‐level scores (sum of features) + vector nodeScores; + nodeScores.reserve(features.size()); + for (auto &feat : features) { + float sum = accumulate(feat.begin(), feat.end(), 0.0f); + nodeScores.push_back(sum); + } + + // 5) Define CUSTOM edge‐combiner and graph‐aggregator + // Example edge combiner: squared difference of endpoint scores + OutputConverter::EdgeCombiner customEdgeCombiner = + [](float a, float b) { + float diff = a - b; + return diff * diff; + }; + + // Example graph aggregator: maximum node score + OutputConverter::GraphAggregator customGraphAgg = + [](const OutputConverter::NodeScores& v) { + if (v.empty()) return 0.0f; + return *max_element(v.begin(), v.end()); + }; + + // 6) Use OutputConverter with CUSTOM functions + auto edgeScores = OutputConverter::toEdgeScores( + nodeScores, + g, + OutputConverter::DefaultAgg::prodCombiner,// use squared‐difference combiner + true // undirected graph + ); + + auto edgeTruth = OutputConverter::toEdgeBinary( + nodeScores, + g, + 0.5f, // threshold + OutputConverter::DefaultAgg::prodCombiner,// same combiner + true + ); + + float graphScore = OutputConverter::toGraphScore( + nodeScores, + OutputConverter::DefaultAgg::meanGraph // use max aggregator + ); + + bool graphTruth = OutputConverter::toGraphBinary( + nodeScores, + 0.5f, // threshold + OutputConverter::DefaultAgg::meanGraph // same aggregator + ); + + // 7) Print results + cout << "=== Graph‐Level ===\n"; + cout << "Score = " << graphScore + << "\n\n"; + + cout << "=== Edge‐Level ===\n"; + for (size_t i = 0; i < edgeScores.size(); ++i) { + cout << "Edge " << i + << " | score = " << edgeScores[i] + << "\n"; + } + + return 0; +}