Rust, 1210 1200 bytes
use std::collections::HashMap as H;type I=i64;type E=Vec<(I,I)>;fn d(g:&E)->bool{let mut s:Vec<(E,Vec<I>)>=vec![];for e in g{let(v,w)=e;let f=(*v,*w);let z=|x|s.iter().position(|p|p.1.contains(x));match(z(v),z(w)){(Some(i),Some(j))=>{if i!=j{let mut p=s.remove(i);let q=s.remove(j-(i<j)as usize);p.0.extend(q.0);p.1.extend(q.1);s.push(p)}else{s[i].0.push(f)}}(Some(i),_)=>{s[i].0.push(f);s[i].1.push(*w)}(_,Some(j))=>{s[j].0.push(f);s[j].1.push(*v)}_=>{s.push((vec![f], vec![*v, *w]))}}}s.iter().map(|h|{let mut p=H::new();let mut r=H::new();let mut i=0;for e in&h.0{let(v,w)=e;i+=2;p.insert(i-1,i);p.insert(i,i-1);r.entry(v).or_insert(vec![]).push(i-1);r.entry(w).or_insert(vec![]).push(i)}let mut r:Vec<Vec<I>>=r.values().cloned().collect();r.sort();let mut x=0;let m=r.iter().flat_map(|v|1..v.len()).fold(1,|p,n|p*n);for mut w in 0..m{let mut t=H::new();for u in&r{let mut v=u.clone();let s=v.pop().unwrap();let mut f=s;while v.len()>0{let o=v.remove(w%v.len());w/=v.len()+1;t.insert(f,o);f=o}t.insert(f,s);}let mut f=vec![];let mut n=0;for s in p.keys(){if!f.contains(s){n+=1;let mut c=s;loop{f.push(*c);c=&t[&p[c]];if c==s{break}}}}x=x.max(n)}1-(r.len()as I-g.len()as I+x as I)/2}).sum::<I>()<2}
A toroidal example:
Try it online!
That's this toroidal graph: 
A non-toroidal example: Try it online!
That's this non-toroidal graph: 
The original code I wrote, before golfing, was the following. It has printouts so you can see what's happening:
use std::collections::HashMap;
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
struct Vertex(u64);
type Edge = (Vertex, Vertex);
type Graph = Vec<Edge>;
fn full_genus(graph: &Graph) -> usize {
componets(graph).iter().map(|g| genus(g)).sum()
}
fn genus(graph: &Graph) -> usize {
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, PartialOrd, Ord)]
struct HalfEdge(usize);
let mut edge_pairing: HashMap<HalfEdge, HalfEdge> = HashMap::new();
let mut vertex_groups: HashMap<&Vertex, Vec<HalfEdge>> = HashMap::new();
let mut i = 0;
for edge in graph {
let (vert1, vert2) = edge;
let half1 = HalfEdge(i);
i += 1;
let half2 = HalfEdge(i);
i += 1;
edge_pairing.insert(half1, half2);
edge_pairing.insert(half2, half1);
vertex_groups.entry(vert1).or_insert(vec![]).push(half1);
vertex_groups.entry(vert2).or_insert(vec![]).push(half2);
}
let mut vertex_groups: Vec<Vec<HalfEdge>> = vertex_groups.values().cloned().collect();
vertex_groups.sort();
println!("{:?}", edge_pairing);
println!("{:?}", vertex_groups);
let mut max_faces = 0;
let num_rotations = vertex_groups
.iter()
.map(|v| v.len() - 1)
.map(|n| {
let mut prod = 1;
for i in 0..n {
prod *= i + 1;
}
prod
})
.fold(1, |p, n| p * n);
println!("\nNum rotations: {}\n", num_rotations);
for rotation_index in 0..num_rotations {
let mut working_index = rotation_index;
let mut rotation: HashMap<HalfEdge, HalfEdge> = HashMap::new();
let mut pretty_rotation: Vec<Vec<HalfEdge>> = vec![];
for group in &vertex_groups {
let mut removal_group = group.clone();
let start = removal_group.pop().unwrap();
let mut from = start;
let mut pretty_group = vec![from];
while !removal_group.is_empty() {
let index = working_index % removal_group.len();
working_index /= removal_group.len();
let to = removal_group.swap_remove(index);
rotation.insert(from, to);
pretty_group.push(to);
from = to;
}
rotation.insert(from, start);
pretty_rotation.push(pretty_group);
}
let mut seen_on_face: Vec<HalfEdge> = Vec::new();
let mut num_faces = 0;
for start_halfedge in edge_pairing.keys() {
if !seen_on_face.contains(start_halfedge) {
num_faces += 1;
let mut current_halfedge = start_halfedge;
loop {
seen_on_face.push(*current_halfedge);
let pair_halfedge = &edge_pairing[current_halfedge];
current_halfedge = &rotation[pair_halfedge];
if current_halfedge == start_halfedge {
break;
}
}
}
}
if num_faces > max_faces {
max_faces = num_faces;
let euler_characteristic: isize =
vertex_groups.len() as isize - graph.len() as isize + max_faces as isize;
let genus_num: isize = 1 - euler_characteristic / 2;
println!(
"Faces: {}, Genus <= {} on rotation {}",
max_faces, genus_num, rotation_index
);
println!("{:?}\n", pretty_rotation);
}
if rotation_index % 1e7 as usize == 0 {
println!("Faces: {} <= {} on rotation {}", num_faces, max_faces, rotation_index);
}
}
let euler_characteristic: isize =
vertex_groups.len() as isize - graph.len() as isize + max_faces as isize;
let genus_num: isize = 1 - euler_characteristic / 2;
assert!(genus_num >= 0);
genus_num as usize
}
fn componets(graph: &Graph) -> Vec<Graph> {
let mut graphs: Vec<(Graph, Vec<Vertex>)> = vec![];
for edge in graph {
let (vert1, vert2) = edge;
let g_index1 = graphs.iter().position(|(_g, h)| h.contains(&vert1));
let g_index2 = graphs.iter().position(|(_g, h)| h.contains(&vert2));
match (g_index1, g_index2) {
(Some(i1), Some(i2)) => {
if i1 != i2 {
let (mut graph1, mut vs1) = graphs.remove(i1);
let new_i2 = if i1 < i2 { i2 - 1 } else { i2 };
let (graph2, vs2) = graphs.remove(new_i2);
graph1.extend(graph2);
vs1.extend(vs2);
graphs.push((graph1, vs1));
} else {
graphs[i1].0.push(edge.clone());
}
}
(Some(i1), None) => {
graphs[i1].0.push(edge.clone());
graphs[i1].1.push(vert2.clone());
}
(None, Some(i2)) => {
graphs[i2].0.push(edge.clone());
graphs[i2].1.push(vert1.clone());
}
(None, None) => {
let edges = vec![edge.clone()];
let vs = vec!(vert1.clone(), vert2.clone());
graphs.push((edges, vs));
}
}
}
graphs.into_iter().map(|(g, _h)| g).collect()
}
fn main() {
let graph = vec![
(Vertex(0), Vertex(1)),
(Vertex(0), Vertex(2)),
(Vertex(0), Vertex(3)),
(Vertex(0), Vertex(4)),
(Vertex(0), Vertex(5)),
(Vertex(1), Vertex(2)),
(Vertex(1), Vertex(3)),
(Vertex(1), Vertex(4)),
(Vertex(1), Vertex(5)),
(Vertex(2), Vertex(3)),
(Vertex(2), Vertex(4)),
(Vertex(2), Vertex(5)),
(Vertex(3), Vertex(4)),
(Vertex(3), Vertex(5)),
];
let result = full_genus(&graph);
println!("Result: {}", result);
}
This program finds the minimum genus surface that the given graph can be embedded in. A graph is toroidal if that genus is at most 1, the genus of the torus.
The code works by separating the graph into its connected components, finding their genii separately, and adding up the results.
To find the genus of a connected graph, I brute force search over all possible rotation systems of the graph, and figure out which one has the most faces. A rotation system is simply an ordering on the edges emerging from each vertex, saying what order those edges are in going around the vertex. Each rotation system has a straightforward minimum genus it can be embedded in, and having more faces directly corresponds to having lower genus. Since every possible embedding corresponds to some rotation system, if there is a toroidal embedding, my program will find the corresponding rotation system and declare that the graph is toroidal, and vice versa.
This program is very slow, because it searches through a number of rotation systems equal to the product over all degrees d of the vertices of (d-1)!. However, it can search through a little over 10 million such rotation systems per minute, so it can verify the toroidality or non-toroidality of simple graphs like the ones shown above.
12Actual test cases would be helpful, for instance a few adjacency matrices. People are probably able to convert it to another appropriate format if they have to. – Stewie Griffin – 2017-08-25T18:44:12.910
1@StewieGriffin I'm not going to crowd the question with hundreds of lines of test-cases that are going to have to be converted by every user that wants to use them. – Post Rock Garf Hunter – 2017-08-25T18:46:20.497
2http://web.math.ucsb.edu/~padraic/ucsb_2013_14/math137b_s2014/math137b_s2014_lecture4.pdf the theorem at the end states: "If G is toroidal, then the Euler characteristic of G is 0" – Giuseppe – 2017-08-25T19:03:00.987
6What are K₃, K₇, K₃,₃ and K₈? – Erik the Outgolfer – 2017-08-25T19:16:06.430
@EriktheOutgolfer K_n is the complete graph with n nodes. K_n,m is the complete bipartite graph with partitions n and m. – Post Rock Garf Hunter – 2017-08-25T19:18:42.913
1
Wikipedia links: complete graph, complete bipartite graph.
– Jonathan Allan – 2017-08-25T19:20:21.8101@Giuseppe, but can we go the other way? What is a face of a graph with no embedding yet defined? – Jonathan Allan – 2017-08-25T19:27:22.780
3
Looks like the general purpose algorithm for this is quite extensive and probably beyond the scope of PPCG. See the Yu (2011) paper mentioned at: https://mathoverflow.net/questions/119493/toroidality-testing. If "maybe" is a suitable answer I've got a short 1-liner :)
– Kelly Lowder – 2017-08-25T19:48:07.2038@KellyLowder This is not meant to be an easy question. I don't really think there is a scope of difficulty on PPCG, after all we have the implement tetris in GoL question. – Post Rock Garf Hunter – 2017-08-25T19:50:26.293
@Wheat Wizard. Fair enough. Kudos to anyone that comes up with a good solution. – Kelly Lowder – 2017-08-25T20:53:05.057
This may work (just try all possible adjacency matrix and check how many faces it have, if the genus is 0 or 1 then done -- there must be no vertices without any edges attached) – user202729 – 2018-03-10T06:14:49.123
So why can't we just calculate the Euler characteristic? I don't see why that is not a clear test of toroidality. This challenge already does that.
– dylnan – 2018-04-13T23:43:12.960@dylnan I don't know that Euler characteristic can be used as a test of toroidality. I do know that every toroidal graph must have Euler characteristic zero, but I don't know if the converse is true. If you can prove this is the case or find a source proving it you are welcome to use that algorithm for an answer. – Post Rock Garf Hunter – 2018-04-13T23:53:39.877
I realized I was confused before you responded so I deleted the comment. Is a graph embedded on a klein bottle considered a torus when embedded in 3 space? – dylnan – 2018-04-14T00:14:01.440
@dylnan I don't believe so. – Post Rock Garf Hunter – 2018-04-14T00:17:19.307
You say that edges musnt intersect? but then they appear to for some test case examples, unless the 2d they do but don't on Taurus? – marshal craft – 2018-10-22T11:56:39.090
Cause I'd like to make sure I understand toroidal correctly, cause $k_7$ is toroidal while $k_8$ isn't and it's not easy to picture either on the torus? So my question is, $k_2$ on torus would have 3 edges instead of just 1? – marshal craft – 2018-10-23T19:05:02.053
@marshalcraft $K_7$ is toroidal and $K_8$ is not. $K_2$ has the same number of edges no matter on what surface you write it. An easier way to picture it: "A graph is toroidal if you can draw it on a piece of paper without crossings allowing you to jump from one side to the same place on the other side." – Post Rock Garf Hunter – 2018-10-23T22:52:56.843
Yeah that clears up nothing for me, sorry. I can read clearly that from the question about 7 or 8 so no new information there. My point are the graphs have like how many edges? and so like i said, it not most feasible thing to think about as an example. Again as I said it would seem to me as though some of the toroidal examples, certainly must have interescting edges on the 2 dimensional flat plane. So I assume that the torus some how this isn't necessary, jsut as it seems to me a simple undirected graph can have up to 4 non trivial edges for 2 nodes, different then in R^2. – marshal craft – 2018-10-24T10:07:29.727
Also I look at wikipedia toroidal graph page and it seems that indeed, 2 nodes with 4 edges is a simple graph on a torus. Wish I could say your comments were useful @Post Left Ghost Hunter but they weren't. No wonder there isn't an answer after a year. None of you even know what your talking about. – marshal craft – 2018-10-24T10:10:24.393
So basically valid edges are gonna be geodesics. which i would think if the torus was parameterized by angle about major/minor axis (I'm not a torus expert so forgive me if my terminology is wrong), then you have lines similar to 2d, like y=mx+b but where y and x are the angles. – marshal craft – 2018-10-24T10:14:47.827
So you have in general, all the combination of nodes, the equations of lines connecting them, there are 4 each. So 4*number of combinations if there exists coeficients such that there are no simultaneous solutions to the large list of equations they do not intersect and thus are toroidal. – marshal craft – 2018-10-24T10:16:51.117
Also I read from most searches on google, the literature on torus is concerned with embedding's in eucludian space which is really more related to differential geometry. For purposes here, which so far seem completely independent of diff. geometry what is concerened is intrinsic to the genus 1 (the torus). Thus it's a matter of system of equations not having solutions and cases for all the combinations, thus not having intersections. Seems an application for this is say some kind of network of nodes needing to be connected but constraint that connections can't take up the same space. – marshal craft – 2018-10-24T10:32:59.403
Some how using genus 1 and higher is a method of solving some kind of problem. – marshal craft – 2018-10-24T10:33:42.697
Mathematically the most general form of this question would be given a set of nodes and minimum edges, what is the minimum genus so that they do not interesect. – marshal craft – 2018-10-24T10:35:24.797
@marshalcraft Amusingly, that's the problem I solved in my answer. – isaacg – 2019-11-21T00:14:35.150