using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("3");
WillReturn.Add("1 2");
WillReturn.Add("-1 1");
WillReturn.Add("2 -1");
//5
}
else if (InputPattern == "Input2") {
WillReturn.Add("10");
WillReturn.Add("3 2");
WillReturn.Add("3 2");
WillReturn.Add("-1 1");
WillReturn.Add("2 -1");
WillReturn.Add("-3 -9");
WillReturn.Add("-8 12");
WillReturn.Add("7 7");
WillReturn.Add("8 1");
WillReturn.Add("8 2");
WillReturn.Add("8 4");
//479
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
struct VectorInfoDef
{
internal long X;
internal long Y;
}
static List<VectorInfoDef> mVectorList = new List<VectorInfoDef>();
const long Hou = 1000000007;
static void Main()
{
List<string> InputList = GetInputList();
long[] wkArr = { };
Action<string> SplitAct = pStr =>
wkArr = pStr.Split(' ').Select(pX => long.Parse(pX)).ToArray();
foreach (string EachStr in InputList.Skip(1)) {
SplitAct(EachStr);
VectorInfoDef WillAdd;
WillAdd.X = wkArr[0];
WillAdd.Y = wkArr[1];
mVectorList.Add(WillAdd);
}
long Result = Solve();
Console.WriteLine(Result);
}
class CntInfoDef
{
internal long Syougen1Cnt;
internal long Syougen2Cnt;
}
// 傾きでベクトルをグループ化する
static Dictionary<string, CntInfoDef> mCntInfoDict = new Dictionary<string, CntInfoDef>();
static long Solve()
{
long ZeroVectorCnt = 0; // 0ベクトルの個数
foreach (VectorInfoDef EachVector in mVectorList) {
long CurrX = EachVector.X;
long CurrY = EachVector.Y;
if (CurrX == 0 && CurrY == 0) {
ZeroVectorCnt++;
continue;
}
if (CurrX == 0 && CurrY != 0) {
CurrY /= CurrY;
}
if (CurrX != 0 && CurrY == 0) {
CurrX /= CurrX;
}
// 原点と対称移動するか
bool WillRotate = false;
// Y座標がマイナスなら、原点と対称移動
if (CurrY < 0) {
WillRotate = true;
}
// Y座標が0でXが負なら、原点と対称移動
if (CurrY == 0 && CurrX < 0) {
WillRotate = true;
}
if (WillRotate) {
CurrX *= -1;
CurrY *= -1;
}
if (CurrX != 0 && CurrY != 0) {
long GCD = DeriveGCD(Math.Abs(CurrX), Math.Abs(CurrY));
CurrX /= GCD;
CurrY /= GCD;
}
// 傾きを求める
long XZouka = CurrX;
long YZouka = CurrY;
// X座標が0以下なら、-90度回転
int CurrSyougen = 1;
if (CurrX <= 0) {
CurrSyougen = 2;
long SavedXZouka = XZouka;
long SavedYZouka = YZouka;
XZouka = SavedYZouka;
YZouka = -SavedXZouka;
}
string KeyStr = string.Format("{0},{1}", XZouka, YZouka);
if (mCntInfoDict.ContainsKey(KeyStr) == false) {
mCntInfoDict[KeyStr] = new CntInfoDef();
}
if (CurrSyougen == 1) {
mCntInfoDict[KeyStr].Syougen1Cnt++;
}
if (CurrSyougen == 2) {
mCntInfoDict[KeyStr].Syougen2Cnt++;
}
}
long Answer = 1;
foreach (var EachPair in mCntInfoDict) {
long CurrPatternCnt = 0;
// 第1象限から選ぶ場合の数
CurrPatternCnt += DeriveBekijyou(2, EachPair.Value.Syougen1Cnt, Hou) - 1;
CurrPatternCnt %= Hou;
// 第2象限から選ぶ場合の数
CurrPatternCnt += DeriveBekijyou(2, EachPair.Value.Syougen2Cnt, Hou) - 1;
CurrPatternCnt %= Hou;
// どれも選ばない場合の数
CurrPatternCnt++;
CurrPatternCnt %= Hou;
Answer *= CurrPatternCnt;
Answer %= Hou;
}
// 0ベクトルから選ぶ場合
Answer += ZeroVectorCnt;
// 1つも選ばない場合を引く
Answer--;
if (Answer < 0) Answer += Hou;
return Answer;
}
// ユークリッドの互除法で2数の最大公約数を求める
static long DeriveGCD(long pVal1, long pVal2)
{
long WarareruKazu = pVal2;
long WaruKazu = pVal1;
while (true) {
long Amari = WarareruKazu % WaruKazu;
if (Amari == 0) return WaruKazu;
WarareruKazu = WaruKazu;
WaruKazu = Amari;
}
}
// 繰り返し2乗法で、(NのP乗) Mod Mを求める
static long DeriveBekijyou(long pN, long pP, long pM)
{
long CurrJyousuu = pN % pM;
long CurrShisuu = 1;
long WillReturn = 1;
while (true) {
// 対象ビットが立っている場合
if ((pP & CurrShisuu) > 0) {
WillReturn = (WillReturn * CurrJyousuu) % pM;
}
CurrShisuu *= 2;
if (CurrShisuu > pP) return WillReturn;
CurrJyousuu = (CurrJyousuu * CurrJyousuu) % pM;
}
}
}