#![allow(unused_imports)]pub mod pcl{pub mod compat{pub mod std{pub mod ops{pub use std::ops::{Bound,RangeBounds};}}pub mod num{pub use num::{One,Zero};}}pub mod macros{#[macro_export]macro_rules!rtl{(@ lhs($($ lhs:tt)*)@ rest+=$($ rest:tt)*)=>{rtl!(@ op+=@ lhs($($ lhs)*)@ rhs()@ rest $($ rest)*)};(@ lhs($($ lhs:tt)*)@ rest-=$($ rest:tt)*)=>{rtl!(@ op-=@ lhs($($ lhs)*)@ rhs()@ rest $($ rest)*)};(@ lhs($($ lhs:tt)*)@ rest*=$($ rest:tt)*)=>{rtl!(@ op*=@ lhs($($ lhs)*)@ rhs()@ rest $($ rest)*)};(@ lhs($($ lhs:tt)*)@ rest/=$($ rest:tt)*)=>{rtl!(@ op/=@ lhs($($ lhs)*)@ rhs()@ rest $($ rest)*)};(@ lhs($($ lhs:tt)*)@ rest%=$($ rest:tt)*)=>{rtl!(@ op%=@ lhs($($ lhs)*)@ rhs()@ rest $($ rest)*)};(@ lhs($($ lhs:tt)*)@ rest $ head:tt $($ rest:tt)*)=>{rtl!(@ lhs($($ lhs)*$ head)@ rest $($ rest)*);};(@ op $ op:tt @ lhs($($ lhs:tt)*)@ rhs($($ rhs:tt)*)@ rest)=>{let __rhs=$($ rhs)*;$($ lhs)*$ op __rhs;};(@ op $ op:tt @ lhs($($ lhs:tt)*)@ rhs($($ rhs:tt)*)@ rest $ head:tt $($ rest:tt)*)=>{rtl!(@ op $ op @ lhs($($ lhs)*)@ rhs($($ rhs)*$ head)@ rest $($ rest)*);};($($ rest:tt)*)=>{rtl!(@ lhs()@ rest $($ rest)*)};}}pub mod math{pub mod modint{#[macro_use]pub mod consts{use super::ModintInnerType;pub trait ModintConst{const MOD:ModintInnerType;}#[macro_export]macro_rules!define_modint_const{($(#[doc=$ doc:expr])*pub const $ name:ident=$ value:literal;)=>{$(#[doc=$ doc])*pub enum $ name{}impl $ crate::pcl::math::modint::consts::ModintConst for $ name{const MOD:$ crate::pcl::math::modint::ModintInnerType=$ value;}};}}use num::Num;use self::consts::ModintConst;use super::super::compat::num::{One,Zero};use std::cmp::Ordering;use std::fmt;use std::hash::{Hash,Hasher};use std::iter::{Product,Sum};use std::marker::PhantomData;use std::mem;use std::ops::{Add,AddAssign,Div,DivAssign,Mul,MulAssign,Neg,Rem,RemAssign,Sub,SubAssign,};pub type ModintInnerType=i64;define_modint_const!{#[doc="1e9+7 を表す定数。"]pub const MOD17=1_000_000_007;}pub type Modint17=Modint<MOD17>;pub struct Modint<C>{value:ModintInnerType,marker:PhantomData<C>,}impl<C>Modint<C>{pub unsafe fn new_unchecked(value:ModintInnerType)->Modint<C>{#[allow(unknown_lints,renamed_and_removed_lints,redundant_field_names)]Modint{value:value,marker:PhantomData,}}pub fn inner(self)->ModintInnerType{self.value}}impl<C:ModintConst>Modint<C>{pub fn new(mut value:ModintInnerType)->Modint<C>{let modulus=C::MOD;assert_ne!(modulus,0,"MOD is 0");if value<0{let m=(-value)/modulus;value+=(m+1)*modulus;}unsafe{Modint::new_unchecked(value%modulus)}}pub fn inv(self)->Modint<C>{let mut modulus=C::MOD;let mut a=self.value;let mut u=1;let mut v=0;while modulus>0{let t=a/modulus;a-=t*modulus;u-=t*v;mem::swap(&mut a,&mut modulus);mem::swap(&mut u,&mut v);}Modint::new(u)}}impl<C:ModintConst>PartialEq for Modint<C>{fn eq(&self,other:&Self)->bool{self.inner()==other.inner()}}impl<C:ModintConst>PartialOrd for Modint<C>{fn partial_cmp(&self,other:&Self)->Option<Ordering>{self.inner().partial_cmp(&other.inner())}}impl<C:ModintConst>Eq for Modint<C>{}impl<C:ModintConst>Ord for Modint<C>{fn cmp(&self,other:&Self)->Ordering{self.inner().cmp(&other.inner())}}impl<C:ModintConst>Hash for Modint<C>{fn hash<H:Hasher>(&self,state:&mut H){self.inner().hash(state);}}impl<C>fmt::Debug for Modint<C>{fn fmt<'a>(&self,f:&mut fmt::Formatter<'a>)->fmt::Result{write!(f,"{}",self.inner())}}impl<C>Clone for Modint<C>{fn clone(&self)->Self{*self}}impl<C>Copy for Modint<C>{}impl<C:ModintConst>AddAssign for Modint<C>{fn add_assign(&mut self,rhs:Modint<C>){let modulus=C::MOD;self.value+=rhs.value;if self.value>=modulus{self.value-=modulus;}}}impl<C:ModintConst>SubAssign for Modint<C>{fn sub_assign(&mut self,rhs:Modint<C>){let modulus=C::MOD;self.value-=rhs.value;if self.value<0{self.value+=modulus;}}}impl<C:ModintConst>MulAssign for Modint<C>{fn mul_assign(&mut self,rhs:Modint<C>){let modulus=C::MOD;self.value*=rhs.value;self.value%=modulus;}}impl<C:ModintConst>DivAssign for Modint<C>{fn div_assign(&mut self,rhs:Modint<C>){if rhs.value==0{panic!("attempted to divide by zero");}*self*=rhs.inv();}}impl<C:ModintConst>RemAssign for Modint<C>{fn rem_assign(&mut self,rhs:Modint<C>){if rhs.value==0{panic!("attempted to divide by zero.")}self.value%=rhs.value;}}impl<C:ModintConst>Neg for Modint<C>{type Output=Self;fn neg(self)->Self{Self::zero()-self}}macro_rules!impl_arith_by_assign{(impl $ traitname:ident::$ fnname:ident{use $ op:tt;})=>{impl<C:ModintConst>$ traitname for Modint<C>{type Output=Modint<C>;fn $ fnname(mut self,rhs:Modint<C>)->Modint<C>{self $ op rhs;self}}};}impl_arith_by_assign!(impl Add::add{use+=;});impl_arith_by_assign!(impl Sub::sub{use-=;});impl_arith_by_assign!(impl Mul::mul{use*=;});impl_arith_by_assign!(impl Div::div{use/=;});impl_arith_by_assign!(impl Rem::rem{use%=;});impl<C:ModintConst>One for Modint<C>{fn one()->Modint<C>{let modulus=C::MOD;assert_ne!(modulus,1,"one()is called for Modint with MOD=1");unsafe{Modint::new_unchecked(1)}}}impl<C:ModintConst>Sum for Modint<C>{fn sum<I:Iterator<Item=Modint<C>>>(iter:I)->Modint<C>{iter.fold(Modint::zero(),Add::add)}}impl<C:ModintConst>Product for Modint<C>{fn product<I:Iterator<Item=Modint<C>>>(iter:I)->Self{iter.fold(Modint::one(),Mul::mul)}}impl<C:ModintConst>Zero for Modint<C>{fn zero()->Modint<C>{unsafe{Modint::new_unchecked(0)}}fn is_zero(&self)->bool{self.inner()==0}}impl<C>fmt::Display for Modint<C>{fn fmt(&self,b:&mut fmt::Formatter)->fmt::Result{write!(b,"{}",self.inner())}}impl<C:ModintConst>Num for Modint<C>{type FromStrRadixErr=<ModintInnerType as Num>::FromStrRadixErr;fn from_str_radix(src:&str,radix:u32)->Result<Self,Self::FromStrRadixErr>{ModintInnerType::from_str_radix(src,radix).map(Modint::new)}}}pub mod sum{use super::super::compat::std::ops::RangeBounds;use super::super::traits::Group;use super::super::utils::range;pub struct CumSum<T>{psum:Vec<T>,}#[allow(unknown_lints,renamed_and_removed_lints,len_without_is_empty)]impl<T:Group+Copy>CumSum<T>{pub fn from_array<A:AsRef<[T]>>(array:A)->CumSum<T>{let array=array.as_ref();let mut psum=vec![T::id();array.len()+1];for i in 0..array.len(){let i=i+1;psum[i]=T::op(psum[i-1],array[i-1]);}#[allow(unknown_lints,renamed_and_removed_lints,redundant_field_names)]CumSum{psum:psum}}pub fn sum<R:RangeBounds<usize>>(&self,range:R)->T{let orig_len=self.psum.len()-1;let start=range::range_start(&range,0);let end=range::range_end(&range,orig_len);if end<=start{return T::id();}T::op(self.psum[end],T::inv(self.psum[start]))}pub fn len(&self)->usize{self.psum.len()-1}}pub struct CumSum2D<T>{psum:Vec<Vec<T>>,}impl<T:Group+Copy>CumSum2D<T>{pub fn from_matrix<M,A>(matrix:M)->CumSum2D<T>where M:AsRef<[A]>,A:AsRef<[T]>,{let array=matrix.as_ref();let height=array.len();if height==0{return CumSum2D{psum:vec![vec![T::id()]],};}let width=array[0].as_ref().len();let mut psum=vec![vec![T::id();width+1];height+1];for i in 0..height{let i=i+1;for j in 0..width{let j=j+1;assert_eq!(array[i-1].as_ref().len(),width,"the array's length is differ line by line");psum[i][j]=T::op(T::op(T::op(psum[i-1][j],psum[i][j-1]),T::inv(psum[i-1][j-1]),),array[i-1].as_ref()[j-1],)}}#[allow(unknown_lints,renamed_and_removed_lints,redundant_field_names)]CumSum2D{psum:psum}}pub fn sum<RY,RX>(&self,yrange:RY,xrange:RX)->T where RY:RangeBounds<usize>,RX:RangeBounds<usize>,{let orig_height=self.psum.len()-1;let orig_width=unsafe{self.psum.get_unchecked(0).len()}-1;let ystart=range::range_start(&yrange,0);let yend=range::range_end(&yrange,orig_height);let xstart=range::range_start(&xrange,0);let xend=range::range_end(&xrange,orig_width);if yend<=ystart||xend<=xstart{return T::id();}T::op(T::op(T::op(self.psum[yend][xend],self.psum[ystart][xstart]),T::inv(self.psum[ystart][xend]),),T::inv(self.psum[yend][xstart]),)}pub fn size(&self)->(usize,usize){(self.psum.len()-1,self.psum[0].len()-1)}}}pub use self::modint::{Modint,Modint17};pub use self::sum::{CumSum,CumSum2D};}pub mod structure{pub mod disjoint_sets{use std::mem::swap;pub struct DisjointSets{par:Vec<i64>,size:usize,}impl DisjointSets{pub fn new(n:usize)->DisjointSets{DisjointSets{par:vec![-1;n],size:n,}}pub fn merge(&mut self,mut x:usize,mut y:usize)->bool{let len=self.par.len();assert!(x<len,"index out of range:x is{}but len is{}",x,len);assert!(y<len,"index out of range:y is{}but len is{}",y,len);x=self.root(x);y=self.root(y);if x==y{return false;}if self.par[x]<self.par[y]{swap(&mut x,&mut y);}debug_assert!(self.par[x]<0&&self.par[y]<0,"critical error:parent has invalid value for rank");self.par[x]+=self.par[y];self.par[y]=x as i64;self.size-=1;true}pub fn in_same(&mut self,x:usize,y:usize)->bool{self.root(x)==self.root(y)}pub fn root(&mut self,x:usize)->usize{let parx=self.par[x];if parx<0{x}else{let root=self.root(parx as usize);self.par[x]=root as i64;root}}pub fn size_of(&mut self,mut x:usize)->usize{x=self.root(x);debug_assert!(self.par[x]<0,"critical error:parent has invalid value for rank");-self.par[x]as usize}pub fn size(&self)->usize{self.size}}}pub mod segment_tree{use super::super::compat::std::ops::RangeBounds;use super::super::traits::math::Monoid;use super::super::utils::range;use std::fmt;pub struct SegmentTree<T>{data:Vec<T>,lenexp2:usize,len:usize,}impl<T:fmt::Debug>fmt::Debug for SegmentTree<T>{fn fmt<'a>(&self,f:&mut fmt::Formatter<'a>)->fmt::Result{f.debug_struct("SegmentTree").field("data",&self.data).finish()}}impl<T>SegmentTree<T>where T:Monoid+Copy,{pub fn from_array<A:AsRef<[T]>>(arr:A)->SegmentTree<T>{let arr=arr.as_ref();let len=arr.len();let lenexp2=calc_lenexp2(len);let data={let mut v=vec![T::id();lenexp2*2];v[lenexp2..(lenexp2+len)].copy_from_slice(arr);v};#[allow(unknown_lints,renamed_and_removed_lints,redundant_field_names)]SegmentTree{data:data,lenexp2:lenexp2,len:len,}}pub fn update(&mut self,mut idx:usize,value:T){assert!(idx<=self.len);idx+=self.lenexp2;self.data[idx]=value;#[allow(unknown_lints,renamed_and_removed_lints,while_immutable_condition)]while{idx>>=1;idx!=0}{self.data[idx]=T::op(self.data[idx*2],self.data[idx*2+1]);}}pub fn query<R:RangeBounds<usize>>(&self,range:R)->T{let mut start=range::range_start(&range,0);let mut end=range::range_end(&range,self.len);start+=self.lenexp2;end+=self.lenexp2;let mut res1=T::id();let mut res2=T::id();while start<end{if start&1!=0{res1=T::op(res1,self.data[start]);start+=1;}if end&1!=0{end-=1;res2=T::op(self.data[end],res2);}start>>=1;end>>=1;}T::op(res1,res2)}}fn calc_lenexp2(mut len:usize)->usize{len-=1;len|=len>>1;len|=len>>2;len|=len>>4;len|=len>>8;len|=len>>16;len|=len>>32;len+1}}pub use self::disjoint_sets::DisjointSets;pub use self::segment_tree::SegmentTree;}pub mod traits{pub mod math{pub mod group{use super::monoid::Monoid;pub trait Group:Monoid{fn inv(x:Self)->Self;}use super::super::super::compat::num::Zero;use std::ops::{Add,Neg};#[derive(Debug)]pub struct Additive<T>(pub T);impl<T:Clone>Clone for Additive<T>{fn clone(&self)->Self{Additive(self.0.clone())}}impl<T:Copy>Copy for Additive<T>{}impl<T>Monoid for Additive<T>where T:Zero+Add<Output=T>,{fn op(x:Self,y:Self)->Self{Additive(x.0+y.0)}fn id()->Self{Additive(T::zero())}}impl<T>Group for Additive<T>where T:Zero+Add<Output=T>+Neg<Output=T>,{fn inv(x:Self)->Self{Additive(-x.0)}}}pub mod monoid{pub trait Monoid{fn op(x:Self,y:Self)->Self;fn id()->Self;}use super::super::utils::num::{MaxValue,MinValue};use std::cmp::Ord;use std::cmp::{max,min};use std::fmt;pub struct Min<T>(pub T);impl<T:fmt::Debug>fmt::Debug for Min<T>{fn fmt<'a>(&self,f:&mut fmt::Formatter<'a>)->fmt::Result{f.debug_tuple("Min").field(&self.0).finish()}}impl<T:Clone>Clone for Min<T>{fn clone(&self)->Self{Min(self.0.clone())}}impl<T:Copy>Copy for Min<T>{}impl<T:Ord+MaxValue>Monoid for Min<T>{fn op(x:Self,y:Self)->Self{Min(min(x.0,y.0))}fn id()->Self{Min(T::max_value())}}pub struct Max<T>(pub T);impl<T:fmt::Debug>fmt::Debug for Max<T>{fn fmt<'a>(&self,f:&mut fmt::Formatter<'a>)->fmt::Result{f.debug_tuple("Max").field(&self.0).finish()}}impl<T:Clone>Clone for Max<T>{fn clone(&self)->Self{Max(self.0.clone())}}impl<T:Copy>Copy for Max<T>{}impl<T:Ord+MinValue>Monoid for Max<T>{fn op(x:Self,y:Self)->Self{Max(max(x.0,y.0))}fn id()->Self{Max(T::min_value())}}}pub use self::group::Group;pub use self::monoid::Monoid;}pub mod utils{pub mod num{pub trait MaxValue{fn max_value()->Self;}pub trait MinValue{fn min_value()->Self;}macro_rules!impl_minmax_value_for_primitives{($($ ty:tt)*)=>{$(impl MaxValue for $ ty{fn max_value()->$ ty{::std::$ ty::MAX}}impl MinValue for $ ty{fn min_value()->$ ty{::std::$ ty::MIN}})*};}impl_minmax_value_for_primitives!{u8 u16 u32 u64 usize i8 i16 i32 i64 isize}}}pub use self::math::*;}pub mod utils{pub mod range{use std::cmp;use super::super::compat::std::ops::{Bound,RangeBounds};pub fn range_start<R:RangeBounds<usize>>(range:&R,min:usize)->usize{let start=match range.start_bound(){Bound::Included(&x)=>x,Bound::Excluded(&x)=>x+1,Bound::Unbounded=>0,};cmp::max(start,min)}pub fn range_end<R:RangeBounds<usize>>(range:&R,max:usize)->usize{let end=match range.end_bound(){Bound::Included(&x)=>x+1,Bound::Excluded(&x)=>x,Bound::Unbounded=>max,};cmp::min(end,max)}}}}
use itertools::Itertools as _;
use num::integer::gcd;
use num::pow;
use num::One;
use pcl::math::modint::Modint17 as M;
use proconio::input;
use std::collections::HashMap;
use std::fmt;
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
struct Chara {
    x: i64,
    y: i64,
}
impl fmt::Debug for Chara {
    fn fmt(&self, b: &mut fmt::Formatter) -> fmt::Result {
        write!(b, "({} {})", self.x, self.y)
    }
}
impl Chara {
    fn new(mut x: i64, mut y: i64) -> Chara {
        assert!(x != 0 || y != 0);
        if y == 0 {
            x = -1;
        } else if x == 0 {
            y = 1;
        } else {
            let g = gcd(x, y);
            x /= g;
            y /= g;
        }
        if y < 0 {
            x *= -1;
            y *= -1;
        }
        assert!(y >= 0);
        assert!(x != 0 || y == 1);
        assert!(y != 0 || x == -1);
        Chara { x, y }
    }
    fn bad_pair(self) -> Chara {
        Chara::new(self.y, -self.x)
    }
}
fn main() {
    input! { mut fish : [ ( i64 , i64 ) ] }
    let zeros = {
        let orig = fish.len();
        fish.retain(|&ch| ch != (0, 0));
        orig - fish.len()
    };
    let chs: HashMap<Chara, usize> = fish
        .into_iter()
        .map(|(x, y)| Chara::new(x, y))
        .sorted()
        .group_by(|&ch| ch)
        .into_iter()
        .map(|(k, g)| (k, g.count()))
        .collect();
    let bad_pairs: HashMap<Chara, (usize, usize)> = {
        let mut m = HashMap::new();
        for (&ch, &num) in &chs {
            let pair = ch.bad_pair();
            let num_pair = chs.get(&pair).copied().unwrap_or(0);
            if m.contains_key(&pair) {
                assert_eq!(m[&pair], (num_pair, num));
                continue;
            }
            assert!(!m.contains_key(&ch));
            m.insert(ch, (num, num_pair));
        }
        m
    };
    let mut ans = M::one();
    for (_, (a, b)) in bad_pairs {
        ans *= pow(M::new(2), a) + pow(M::new(2), b) - M::one();
    }
    println!("{}", ans + M::new(zeros as i64) - M::one());
}



Measured memory_bytes: 32988