aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorericmarin <maarin.eric@gmail.com>2026-03-26 20:28:38 +0100
committerericmarin <maarin.eric@gmail.com>2026-03-26 21:27:10 +0100
commit3e338c3be65638ef1898c32c707c50422acafb18 (patch)
tree80a29ac6b7baee3bbfe4f161fc893fd5948d9409 /examples
parent689c34076d08e59b1382864f9efcd983c8665ae5 (diff)
downloadvein-3e338c3be65638ef1898c32c707c50422acafb18.tar.gz
vein-3e338c3be65638ef1898c32c707c50422acafb18.zip
added LICENSE
Diffstat (limited to 'examples')
-rw-r--r--examples/fashion_mnist/fashion_mnist.py53
-rw-r--r--examples/fashion_mnist/fashion_mnist_argmax.vnnlib133
-rw-r--r--examples/fashion_mnist/fashion_mnist_epsilon.vnnlib124
-rw-r--r--examples/fashion_mnist/fashion_mnist_strict.vnnlib123
-rw-r--r--examples/verify_fashion_mnist.py50
-rw-r--r--examples/verify_xor.py50
-rw-r--r--examples/xor/xor.py40
-rw-r--r--examples/xor/xor_argmax.vnnlib14
-rw-r--r--examples/xor/xor_epsilon.vnnlib15
-rw-r--r--examples/xor/xor_strict.vnnlib14
10 files changed, 616 insertions, 0 deletions
diff --git a/examples/fashion_mnist/fashion_mnist.py b/examples/fashion_mnist/fashion_mnist.py
new file mode 100644
index 0000000..1c9dcf7
--- /dev/null
+++ b/examples/fashion_mnist/fashion_mnist.py
@@ -0,0 +1,53 @@
+import torch, torch.nn as nn
+from torchvision.datasets import FashionMNIST
+from torch.utils.data import DataLoader
+from torchvision import transforms
+
+class FashionMNIST_MLP(nn.Module):
+ def __init__(self, hidden_dim):
+ super().__init__()
+ self.layers = nn.Sequential(
+ nn.Flatten(),
+ nn.Linear(6 * 6, hidden_dim),
+ nn.ReLU(),
+ nn.Linear(hidden_dim, 2)
+ )
+ def forward(self, x):
+ return self.layers(x)
+
+transform = transforms.Compose([
+ transforms.Resize((6, 6)),
+ transforms.ToTensor(),
+])
+
+train_dataset = FashionMNIST('./', download=True, transform=transform, train=True)
+tshirts_trousers = [id for id, data in enumerate(train_dataset.targets) if data.item() == 0 or data.item() == 1]
+train_dataset = torch.utils.data.Subset(train_dataset, tshirts_trousers)
+
+trainloader = DataLoader(train_dataset, batch_size=128, shuffle=True)
+
+def train_model(name: str, dim):
+ net = FashionMNIST_MLP(hidden_dim=dim)
+ loss_fn = nn.CrossEntropyLoss()
+ optimizer = torch.optim.Adam(net.parameters(), lr=1e-4)
+
+ print(f"Training {name}...")
+ for epoch in range(100):
+ global loss
+ for data in trainloader:
+ inputs, targets = data
+ optimizer.zero_grad()
+ outputs = net(inputs)
+ loss = loss_fn(outputs, targets)
+ loss.backward()
+ optimizer.step()
+ if (epoch + 1) % 10 == 0:
+ print(f" Epoch {epoch+1}, Loss: {loss.item():.4f}")
+ return net
+
+if __name__ == "__main__":
+ torch_net_a = train_model("Network A", 6).eval()
+ torch_net_b = train_model("Network B", 12).eval()
+
+ torch.onnx.export(torch_net_a, (torch.randn(1, 1, 6, 6),), "fashion_mnist_a.onnx")
+ torch.onnx.export(torch_net_b, (torch.randn(1, 1, 6, 6),), "fashion_mnist_b.onnx")
diff --git a/examples/fashion_mnist/fashion_mnist_argmax.vnnlib b/examples/fashion_mnist/fashion_mnist_argmax.vnnlib
new file mode 100644
index 0000000..0c4412b
--- /dev/null
+++ b/examples/fashion_mnist/fashion_mnist_argmax.vnnlib
@@ -0,0 +1,133 @@
+; Argmax Equivalence for reduced FashionMNIST
+
+; Constant declaration
+(declare-const X_0 Real)
+(declare-const X_1 Real)
+(declare-const X_2 Real)
+(declare-const X_3 Real)
+(declare-const X_4 Real)
+(declare-const X_5 Real)
+(declare-const X_6 Real)
+(declare-const X_7 Real)
+(declare-const X_8 Real)
+(declare-const X_9 Real)
+(declare-const X_10 Real)
+(declare-const X_11 Real)
+(declare-const X_12 Real)
+(declare-const X_13 Real)
+(declare-const X_14 Real)
+(declare-const X_15 Real)
+(declare-const X_16 Real)
+(declare-const X_17 Real)
+(declare-const X_18 Real)
+(declare-const X_19 Real)
+(declare-const X_20 Real)
+(declare-const X_21 Real)
+(declare-const X_22 Real)
+(declare-const X_23 Real)
+(declare-const X_24 Real)
+(declare-const X_25 Real)
+(declare-const X_26 Real)
+(declare-const X_27 Real)
+(declare-const X_28 Real)
+(declare-const X_29 Real)
+(declare-const X_30 Real)
+(declare-const X_31 Real)
+(declare-const X_32 Real)
+(declare-const X_33 Real)
+(declare-const X_34 Real)
+(declare-const X_35 Real)
+(declare-const Y_0 Real)
+(declare-const Y_1 Real)
+(declare-const Y_2 Real)
+(declare-const Y_3 Real)
+
+; Bounded inputs: X must be within [0, 1]
+(assert (>= X_0 0.0))
+(assert (<= X_0 1.0))
+(assert (>= X_1 0.0))
+(assert (<= X_1 1.0))
+(assert (>= X_2 0.0))
+(assert (<= X_2 1.0))
+(assert (>= X_3 0.0))
+(assert (<= X_3 1.0))
+(assert (>= X_4 0.0))
+(assert (<= X_4 1.0))
+(assert (>= X_5 0.0))
+(assert (<= X_5 1.0))
+(assert (>= X_6 0.0))
+(assert (<= X_6 1.0))
+(assert (>= X_7 0.0))
+(assert (<= X_7 1.0))
+(assert (>= X_8 0.0))
+(assert (<= X_8 1.0))
+(assert (>= X_9 0.0))
+(assert (<= X_9 1.0))
+(assert (>= X_10 0.0))
+(assert (<= X_10 1.0))
+(assert (>= X_11 0.0))
+(assert (<= X_11 1.0))
+(assert (>= X_12 0.0))
+(assert (<= X_12 1.0))
+(assert (>= X_13 0.0))
+(assert (<= X_13 1.0))
+(assert (>= X_14 0.0))
+(assert (<= X_14 1.0))
+(assert (>= X_15 0.0))
+(assert (<= X_15 1.0))
+(assert (>= X_16 0.0))
+(assert (<= X_16 1.0))
+(assert (>= X_17 0.0))
+(assert (<= X_17 1.0))
+(assert (>= X_18 0.0))
+(assert (<= X_18 1.0))
+(assert (>= X_19 0.0))
+(assert (<= X_19 1.0))
+(assert (>= X_20 0.0))
+(assert (<= X_20 1.0))
+(assert (>= X_21 0.0))
+(assert (<= X_21 1.0))
+(assert (>= X_22 0.0))
+(assert (<= X_22 1.0))
+(assert (>= X_23 0.0))
+(assert (<= X_23 1.0))
+(assert (>= X_24 0.0))
+(assert (<= X_24 1.0))
+(assert (>= X_25 0.0))
+(assert (<= X_25 1.0))
+(assert (>= X_26 0.0))
+(assert (<= X_26 1.0))
+(assert (>= X_27 0.0))
+(assert (<= X_27 1.0))
+(assert (>= X_28 0.0))
+(assert (<= X_28 1.0))
+(assert (>= X_29 0.0))
+(assert (<= X_29 1.0))
+(assert (>= X_30 0.0))
+(assert (<= X_30 1.0))
+(assert (>= X_31 0.0))
+(assert (<= X_31 1.0))
+(assert (>= X_32 0.0))
+(assert (<= X_32 1.0))
+(assert (>= X_33 0.0))
+(assert (<= X_33 1.0))
+(assert (>= X_34 0.0))
+(assert (<= X_34 1.0))
+(assert (>= X_35 0.0))
+(assert (<= X_35 1.0))
+
+; Violation of argmax equivalence
+(assert (or
+ (and
+ (> Y_0 Y_1)
+ (or
+ (> Y_3 Y_2)
+ )
+ )
+ (and
+ (> Y_1 Y_0)
+ (or
+ (> Y_2 Y_3)
+ )
+ )
+))
diff --git a/examples/fashion_mnist/fashion_mnist_epsilon.vnnlib b/examples/fashion_mnist/fashion_mnist_epsilon.vnnlib
new file mode 100644
index 0000000..948e501
--- /dev/null
+++ b/examples/fashion_mnist/fashion_mnist_epsilon.vnnlib
@@ -0,0 +1,124 @@
+; Strict Equivalence for reduced FashionMNIST
+
+; Constant declaration
+(declare-const X_0 Real)
+(declare-const X_1 Real)
+(declare-const X_2 Real)
+(declare-const X_3 Real)
+(declare-const X_4 Real)
+(declare-const X_5 Real)
+(declare-const X_6 Real)
+(declare-const X_7 Real)
+(declare-const X_8 Real)
+(declare-const X_9 Real)
+(declare-const X_10 Real)
+(declare-const X_11 Real)
+(declare-const X_12 Real)
+(declare-const X_13 Real)
+(declare-const X_14 Real)
+(declare-const X_15 Real)
+(declare-const X_16 Real)
+(declare-const X_17 Real)
+(declare-const X_18 Real)
+(declare-const X_19 Real)
+(declare-const X_20 Real)
+(declare-const X_21 Real)
+(declare-const X_22 Real)
+(declare-const X_23 Real)
+(declare-const X_24 Real)
+(declare-const X_25 Real)
+(declare-const X_26 Real)
+(declare-const X_27 Real)
+(declare-const X_28 Real)
+(declare-const X_29 Real)
+(declare-const X_30 Real)
+(declare-const X_31 Real)
+(declare-const X_32 Real)
+(declare-const X_33 Real)
+(declare-const X_34 Real)
+(declare-const X_35 Real)
+(declare-const Y_0 Real)
+(declare-const Y_1 Real)
+(declare-const Y_2 Real)
+(declare-const Y_3 Real)
+
+; Bounded inputs: X must be within [0, 1]
+(assert (>= X_0 0.0))
+(assert (<= X_0 1.0))
+(assert (>= X_1 0.0))
+(assert (<= X_1 1.0))
+(assert (>= X_2 0.0))
+(assert (<= X_2 1.0))
+(assert (>= X_3 0.0))
+(assert (<= X_3 1.0))
+(assert (>= X_4 0.0))
+(assert (<= X_4 1.0))
+(assert (>= X_5 0.0))
+(assert (<= X_5 1.0))
+(assert (>= X_6 0.0))
+(assert (<= X_6 1.0))
+(assert (>= X_7 0.0))
+(assert (<= X_7 1.0))
+(assert (>= X_8 0.0))
+(assert (<= X_8 1.0))
+(assert (>= X_9 0.0))
+(assert (<= X_9 1.0))
+(assert (>= X_10 0.0))
+(assert (<= X_10 1.0))
+(assert (>= X_11 0.0))
+(assert (<= X_11 1.0))
+(assert (>= X_12 0.0))
+(assert (<= X_12 1.0))
+(assert (>= X_13 0.0))
+(assert (<= X_13 1.0))
+(assert (>= X_14 0.0))
+(assert (<= X_14 1.0))
+(assert (>= X_15 0.0))
+(assert (<= X_15 1.0))
+(assert (>= X_16 0.0))
+(assert (<= X_16 1.0))
+(assert (>= X_17 0.0))
+(assert (<= X_17 1.0))
+(assert (>= X_18 0.0))
+(assert (<= X_18 1.0))
+(assert (>= X_19 0.0))
+(assert (<= X_19 1.0))
+(assert (>= X_20 0.0))
+(assert (<= X_20 1.0))
+(assert (>= X_21 0.0))
+(assert (<= X_21 1.0))
+(assert (>= X_22 0.0))
+(assert (<= X_22 1.0))
+(assert (>= X_23 0.0))
+(assert (<= X_23 1.0))
+(assert (>= X_24 0.0))
+(assert (<= X_24 1.0))
+(assert (>= X_25 0.0))
+(assert (<= X_25 1.0))
+(assert (>= X_26 0.0))
+(assert (<= X_26 1.0))
+(assert (>= X_27 0.0))
+(assert (<= X_27 1.0))
+(assert (>= X_28 0.0))
+(assert (<= X_28 1.0))
+(assert (>= X_29 0.0))
+(assert (<= X_29 1.0))
+(assert (>= X_30 0.0))
+(assert (<= X_30 1.0))
+(assert (>= X_31 0.0))
+(assert (<= X_31 1.0))
+(assert (>= X_32 0.0))
+(assert (<= X_32 1.0))
+(assert (>= X_33 0.0))
+(assert (<= X_33 1.0))
+(assert (>= X_34 0.0))
+(assert (<= X_34 1.0))
+(assert (>= X_35 0.0))
+(assert (<= X_35 1.0))
+
+; Violation of epsilon equivalence (epsilon = 0.1)
+(define-fun absolute ((x Real)) Real (if (>= x 0) x (- x)))
+(assert (or
+ (> (absolute (- Y_0 Y_2)) 0.1)
+ (> (absolute (- Y_1 Y_3)) 0.1)
+))
diff --git a/examples/fashion_mnist/fashion_mnist_strict.vnnlib b/examples/fashion_mnist/fashion_mnist_strict.vnnlib
new file mode 100644
index 0000000..8242c34
--- /dev/null
+++ b/examples/fashion_mnist/fashion_mnist_strict.vnnlib
@@ -0,0 +1,123 @@
+; Strict Equivalence for reduced FashionMNIST
+
+; Constant declaration
+(declare-const X_0 Real)
+(declare-const X_1 Real)
+(declare-const X_2 Real)
+(declare-const X_3 Real)
+(declare-const X_4 Real)
+(declare-const X_5 Real)
+(declare-const X_6 Real)
+(declare-const X_7 Real)
+(declare-const X_8 Real)
+(declare-const X_9 Real)
+(declare-const X_10 Real)
+(declare-const X_11 Real)
+(declare-const X_12 Real)
+(declare-const X_13 Real)
+(declare-const X_14 Real)
+(declare-const X_15 Real)
+(declare-const X_16 Real)
+(declare-const X_17 Real)
+(declare-const X_18 Real)
+(declare-const X_19 Real)
+(declare-const X_20 Real)
+(declare-const X_21 Real)
+(declare-const X_22 Real)
+(declare-const X_23 Real)
+(declare-const X_24 Real)
+(declare-const X_25 Real)
+(declare-const X_26 Real)
+(declare-const X_27 Real)
+(declare-const X_28 Real)
+(declare-const X_29 Real)
+(declare-const X_30 Real)
+(declare-const X_31 Real)
+(declare-const X_32 Real)
+(declare-const X_33 Real)
+(declare-const X_34 Real)
+(declare-const X_35 Real)
+(declare-const Y_0 Real)
+(declare-const Y_1 Real)
+(declare-const Y_2 Real)
+(declare-const Y_3 Real)
+
+; Bounded inputs: X must be within [0, 1]
+(assert (>= X_0 0.0))
+(assert (<= X_0 1.0))
+(assert (>= X_1 0.0))
+(assert (<= X_1 1.0))
+(assert (>= X_2 0.0))
+(assert (<= X_2 1.0))
+(assert (>= X_3 0.0))
+(assert (<= X_3 1.0))
+(assert (>= X_4 0.0))
+(assert (<= X_4 1.0))
+(assert (>= X_5 0.0))
+(assert (<= X_5 1.0))
+(assert (>= X_6 0.0))
+(assert (<= X_6 1.0))
+(assert (>= X_7 0.0))
+(assert (<= X_7 1.0))
+(assert (>= X_8 0.0))
+(assert (<= X_8 1.0))
+(assert (>= X_9 0.0))
+(assert (<= X_9 1.0))
+(assert (>= X_10 0.0))
+(assert (<= X_10 1.0))
+(assert (>= X_11 0.0))
+(assert (<= X_11 1.0))
+(assert (>= X_12 0.0))
+(assert (<= X_12 1.0))
+(assert (>= X_13 0.0))
+(assert (<= X_13 1.0))
+(assert (>= X_14 0.0))
+(assert (<= X_14 1.0))
+(assert (>= X_15 0.0))
+(assert (<= X_15 1.0))
+(assert (>= X_16 0.0))
+(assert (<= X_16 1.0))
+(assert (>= X_17 0.0))
+(assert (<= X_17 1.0))
+(assert (>= X_18 0.0))
+(assert (<= X_18 1.0))
+(assert (>= X_19 0.0))
+(assert (<= X_19 1.0))
+(assert (>= X_20 0.0))
+(assert (<= X_20 1.0))
+(assert (>= X_21 0.0))
+(assert (<= X_21 1.0))
+(assert (>= X_22 0.0))
+(assert (<= X_22 1.0))
+(assert (>= X_23 0.0))
+(assert (<= X_23 1.0))
+(assert (>= X_24 0.0))
+(assert (<= X_24 1.0))
+(assert (>= X_25 0.0))
+(assert (<= X_25 1.0))
+(assert (>= X_26 0.0))
+(assert (<= X_26 1.0))
+(assert (>= X_27 0.0))
+(assert (<= X_27 1.0))
+(assert (>= X_28 0.0))
+(assert (<= X_28 1.0))
+(assert (>= X_29 0.0))
+(assert (<= X_29 1.0))
+(assert (>= X_30 0.0))
+(assert (<= X_30 1.0))
+(assert (>= X_31 0.0))
+(assert (<= X_31 1.0))
+(assert (>= X_32 0.0))
+(assert (<= X_32 1.0))
+(assert (>= X_33 0.0))
+(assert (<= X_33 1.0))
+(assert (>= X_34 0.0))
+(assert (<= X_34 1.0))
+(assert (>= X_35 0.0))
+(assert (<= X_35 1.0))
+
+; Violation of strict equivalence
+(assert (or
+ (not (= Y_0 Y_2))
+ (not (= Y_1 Y_3))
+))
diff --git a/examples/verify_fashion_mnist.py b/examples/verify_fashion_mnist.py
new file mode 100644
index 0000000..4de0d1e
--- /dev/null
+++ b/examples/verify_fashion_mnist.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2026 Eric Marin
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import sys, os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import z3
+import nneq
+
+def check_property(onnx_a, onnx_b, vnnlib):
+ solver = nneq.Solver()
+
+ print(f"--- Checking {vnnlib} ---")
+
+ solver.load_onnx(onnx_a)
+ solver.load_onnx(onnx_b)
+ solver.load_vnnlib(vnnlib)
+
+ result = solver.check()
+
+ if result == z3.unsat:
+ print("VERIFIED (UNSAT): The networks are equivalent under this property.")
+ elif result == z3.sat:
+ print("FAILED (SAT): The networks are NOT equivalent.")
+ print("Counter-example input:")
+ print(solver.model())
+ # m = solver.model()
+ # sorted_symbols = sorted([s for s in m.decls() if s.name().startswith("X_")], key=lambda s: s.name())
+ # for s in sorted_symbols:
+ # print(f" {s.name()} = {m[s]}")
+ else:
+ print("UNKNOWN")
+ print("")
+
+if __name__ == "__main__":
+ check_property("./examples/fashion_mnist/fashion_mnist_a.onnx", "./examples/fashion_mnist/fashion_mnist_b.onnx", "./examples/fashion_mnist/fashion_mnist_strict.vnnlib")
+ check_property("./examples/fashion_mnist/fashion_mnist_a.onnx", "./examples/fashion_mnist/fashion_mnist_b.onnx", "./examples/fashion_mnist/fashion_mnist_epsilon.vnnlib")
+ check_property("./examples/fashion_mnist/fashion_mnist_a.onnx", "./examples/fashion_mnist/fashion_mnist_b.onnx", "./examples/fashion_mnist/fashion_mnist_argmax.vnnlib")
diff --git a/examples/verify_xor.py b/examples/verify_xor.py
new file mode 100644
index 0000000..f1de0cb
--- /dev/null
+++ b/examples/verify_xor.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2026 Eric Marin
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import sys, os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import z3
+import nneq
+
+def check_property(onnx_a, onnx_b, vnnlib):
+ solver = nneq.Solver()
+
+ print(f"--- Checking {vnnlib} ---")
+
+ solver.load_onnx(onnx_a)
+ solver.load_onnx(onnx_b)
+ solver.load_vnnlib(vnnlib)
+
+ result = solver.check()
+
+ if result == z3.unsat:
+ print("VERIFIED (UNSAT): The networks are equivalent under this property.")
+ elif result == z3.sat:
+ print("FAILED (SAT): The networks are NOT equivalent.")
+ print("Counter-example input:")
+ print(solver.model())
+ # m = solver.model()
+ # sorted_symbols = sorted([s for s in m.decls() if s.name().startswith("X_")], key=lambda s: s.name())
+ # for s in sorted_symbols:
+ # print(f" {s.name()} = {m[s]}")
+ else:
+ print("UNKNOWN")
+ print("")
+
+if __name__ == "__main__":
+ check_property("./examples/xor/xor_a.onnx", "./examples/xor/xor_b.onnx", "./examples/xor/xor_strict.vnnlib")
+ check_property("./examples/xor/xor_a.onnx", "./examples/xor/xor_b.onnx", "./examples/xor/xor_epsilon.vnnlib")
+ check_property("./examples/xor/xor_a.onnx", "./examples/xor/xor_b.onnx", "./examples/xor/xor_argmax.vnnlib")
diff --git a/examples/xor/xor.py b/examples/xor/xor.py
new file mode 100644
index 0000000..ebc5477
--- /dev/null
+++ b/examples/xor/xor.py
@@ -0,0 +1,40 @@
+import torch
+import torch.nn as nn
+import torch.onnx
+
+class xor_mlp(nn.Module):
+ def __init__(self, hidden_dim):
+ super().__init__()
+ self.layers = nn.Sequential(
+ nn.Linear(2, hidden_dim),
+ nn.ReLU(),
+ nn.Linear(hidden_dim, 1)
+ )
+ def forward(self, x):
+ return self.layers(x)
+
+def train_model(name: str, dim):
+ X = torch.tensor([[0,0], [0,1], [1,0], [1,1]], dtype=torch.float32)
+ Y = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)
+
+ net = xor_mlp(hidden_dim=dim)
+ loss_fn = nn.MSELoss()
+ optimizer = torch.optim.Adam(net.parameters(), lr=0.1)
+
+ print(f"Training {name}...")
+ for epoch in range(1000):
+ optimizer.zero_grad()
+ out = net(X)
+ loss = loss_fn(out, Y)
+ loss.backward()
+ optimizer.step()
+ if (epoch+1) % 100 == 0:
+ print(f" Epoch {epoch+1}, Loss: {loss.item():.4f}")
+ return net
+
+if __name__ == "__main__":
+ torch_net_a = train_model("Network A", 8).eval()
+ torch_net_b = train_model("Network B", 16).eval()
+
+ torch.onnx.export(torch_net_a, (torch.randn(1, 2),), "xor_a.onnx")
+ torch.onnx.export(torch_net_b, (torch.randn(1, 2),), "xor_b.onnx")
diff --git a/examples/xor/xor_argmax.vnnlib b/examples/xor/xor_argmax.vnnlib
new file mode 100644
index 0000000..d38bc31
--- /dev/null
+++ b/examples/xor/xor_argmax.vnnlib
@@ -0,0 +1,14 @@
+; Argmax Equivalence for XOR
+
+; Constant declaration
+(declare-const X_0 Real)
+(declare-const X_1 Real)
+(declare-const Y_0 Real)
+(declare-const Y_1 Real)
+
+; Bounded inputs: X must be 0 or 1
+(assert (or (= X_0 0) (= X_0 1)))
+(assert (or (= X_1 0) (= X_1 1)))
+
+; Violation of argmax equivalence
+(assert (not (= (> Y_0 0.5) (> Y_1 0.5))))
diff --git a/examples/xor/xor_epsilon.vnnlib b/examples/xor/xor_epsilon.vnnlib
new file mode 100644
index 0000000..427243e
--- /dev/null
+++ b/examples/xor/xor_epsilon.vnnlib
@@ -0,0 +1,15 @@
+; Epsilon Equivalence for XOR
+
+; Constant declaration
+(declare-const X_0 Real)
+(declare-const X_1 Real)
+(declare-const Y_0 Real)
+(declare-const Y_1 Real)
+
+; Bounded inputs: X must be 0 or 1
+(assert (or (= X_0 0) (= X_0 1)))
+(assert (or (= X_1 0) (= X_1 1)))
+
+; Violation of epsilon equivalence (epsilon = 0.1)
+(define-fun absolute ((x Real)) Real (if (>= x 0) x (- x)))
+(assert (> (absolute (- Y_0 Y_1)) 0.1))
diff --git a/examples/xor/xor_strict.vnnlib b/examples/xor/xor_strict.vnnlib
new file mode 100644
index 0000000..bead476
--- /dev/null
+++ b/examples/xor/xor_strict.vnnlib
@@ -0,0 +1,14 @@
+; Strict Equivalence for XOR
+
+; Constant declaration
+(declare-const X_0 Real)
+(declare-const X_1 Real)
+(declare-const Y_0 Real)
+(declare-const Y_1 Real)
+
+; Bounded inputs: X must be 0 or 1
+(assert (or (= X_0 0) (= X_0 1)))
+(assert (or (= X_1 0) (= X_1 1)))
+
+; Violation of strict equivalence: outputs are different
+(assert (not (= Y_0 Y_1)))