import numpy as np

"""
Explanation: Numpy, sebagai salah satu library yang saling penting di pemrograman yang menggunakan matematika dan angka, memberikan kemudahan dalam melakukan operasi aljabar matriks. Bila deklarasi array a = [[1,0],[0,1]] memberikan array 2D biasa, maka dengan Numpy, a = np.array([[1,0],[0,1]]) memberikan objek a yang dapat dilakukan operasi aljabar matriks seperti penjumlahan, pengurangan, perkalian, transpose, dll. 
Pada bab ini, tidak akan dibahas Numpy secara keseluruhan, namun hanya dibahas tentang matriks dan sedikit Aljabar Linear dengan Numpy. 
Instalasi
Untuk menggunakan Numpy, kita harus melakukan import numpy karena ia merupakan suatu library yang bukan merupakan standar Python. Apabila Anda menginstall Python dengan Anaconda, kemungkinan besar Numpy sudah terinstal di mesin Anda dan terhubung dengan Python dan Anda bisa lanjut ke section berikutnya. Apabila Anda menginstall Python melalui distribusi resmi, kemungkinan Anda belum bisa menggunakan Numpy. Untuk menginstallnya, Anda bisa menggunakan package manager yang Anda gunakan, bisa pip atau conda atau yang lainnya. 
Untuk menginstall dengan pip, cukup buka terminal (Windows: commandprompt) dan mengetikkan perintah berikut.
Apabila Anda menggunakan miniconda atau anaconda, Anda bisa menginstall dengan perintah berikut. 
Apabila Anda kebetulan menggunakan Ubuntu 20.04 LTS, Anda bisa menginstall lewat aptitude. 
Dasar-dasar
Untuk menggunakan Numpy kita perlu mengimportnya. Biasanya import dilakukan dengan memberi singkatan np seperti di bawah ini.
End of explanation
"""


from scipy import some_module
some_module.some_function()

"""
Explanation: Kalau yang dibutuhkan hanya salah satu modul dari numpy dan tidak semuanya, import bisa dilakukan dengan perintah from ... import ... sebagai berikut.
End of explanation
"""


import numpy as np

a = np.array([[1,2,1],[1,0,1]])
b = np.arange(7)
c = np.arange(3,10)
print("a = ")
print(a)
print("b = ")
print(b)
print(c)
print()
# Special matrix

I = np.ones(3)
O = np.zeros(4)
I2 = np.ones((2,4))
O2 = np.zeros((3,3))

print("spesial matrix")
print("satu =",I)
print("nol = ",O)
print("matriks isi 1, dua dimensi")
print(I2)
print("nol dua dimensi =")
print(O2)

"""
Explanation: Array
Array pada Numpy berbeda dengan array pada module bawaan Python. Pada module bawaan Python, array.array  hanya memiliki fungsi terbatas. Pada Numpy, array disebut ndarray (dulu) atau array (alias).
Kita bisa membuat array dengan np.array(). Beberapa matriks khusus juga bisa langsung dibuat.
End of explanation
"""


# Salah
x = np.array(1,2,3)  

# Benar
x = np.array([1,2,3])

y = np.array([[1,0,0],[0,1,0]])
z = np.array([(1,0,0),(0,1,0)])

y-z

"""
Explanation: Beberapa kesalahan dalam membuat array seringkali terjadi karena kesalahan pada kurung. Ingat bahwa array ini adalah suatu fungsi, sehingga membutuhkan kurung biasa atau parenthesis sedangkan array membutuhkan kurung siku atau brackets. Pada prakteknya, ketika dimensi array lebih dari satu, setelah mengambil brackets di paling luar, menggunakan parenthesis lagi di dalam.
End of explanation
"""


# deklarasi biasa dengan menuliskan inputnya satu demi satu
vektor = np.array([1,5])
print(vektor)


# deklarasi dengan arange: start, stop, step
vektor1 = np.arange(start=1, stop=10, step=1)
vektor2 = np.arange(0,9,1)

print(vektor1, vektor2)

# deklarasi dengan linspace: start, stop, banyak titik
vektor3 = np.linspace(1,10,4)

print(vektor3)

"""
Explanation: Vektor
Secara matematis, vektor merupakan bentuk khusus dari matriks. Namun, di Numpy vektor bisa dideklarasikan dengan cara yang berbeda. Dua cara yang cukup sering digunakan adalah dengan arange dan dengan linspace. Perbedaan keduanya ada pendeklarasiannya. Bila arange mengambil input start, stop, dan step, maka linspace ini mengambil input start, stop, dan banyaknya entri. Ide dari linspace ini adalah membuat satu vektor yang jarak antar entrinya sama.
End of explanation
"""


np.shape(a)

np.ndim(a)

np.size(a)

"""
Explanation: Manipulasi Bentuk
Sebelum memanipulasi bentuk, kita bisa mengecek 'ukuran' dari matriks dengan np.shape; panjang dengan np.dim dan banyaknya entri dengan np.size.
End of explanation
"""


a.shape

a.ndim

a.size

"""
Explanation: Karena shape, dim, size dan lain-lain merupakan fungsi di numpy, bisa juga dipanggil dengan menambahkan fungsinya di belakang objek seperti contoh berikut.
End of explanation
"""


b = np.reshape(y,(1,6))   # reshape menjadi ukuran (1,6)
c = z.reshape(6,1)        # reshape menjadi ukuran (6,1)
d = c.T                   # return berupa transpose dari matriks 
e = np.transpose(d)

print(b)
print(c)

b.shape, c.shape, d.shape, e.shape

"""
Explanation: Berikutnya, untuk mengubah bentuk matriks (reshape) kita bisa menyusun ulang matriks dengan perintah np.reshape. Seperti halnya shape, ndim, dan size, kita bisa memanggil reshape di depan sebagai fungsi dan di belakang sebagai atribut. Matriks juga punya
End of explanation
"""


print(a)
a.reshape((1,6))
print("setelah reshape")
print(a)

print(a)
a.resize((1,6))
print("setelah resize")
print(a)

"""
Explanation: Bila reshape dan transpose ini bersifat non-destruktif (tidak mengubah objek aslinya), maka untuk mengubah bentuk matriks dengan mengubah objeknya bisa dilakukan dengan resize.
End of explanation
"""


a = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
a

for row in a:
    print("baris:",row)

for elemen in a.flat:
    print(elemen)

"""
Explanation: Melakukan Iterasi dengan Matrix
Pada matriks, kita bisa melakukan iterasi berdasarkan elemen-elemen matriks. Misalkan axis pertama (row) atau bahkan seluruh elemennya.
End of explanation
"""


# Indexing 
A_1D = np.array([0,1,1,2,3,5,8,13,21])
B_2D = np.array([A_1D,A_1D+2])
C_3D = np.array([B_2D,B_2D*2,B_2D*3,B_2D*4])

C_3D

print(A_1D[4],B_2D[0,0],C_3D[0,0,0])

# Contoh slicing
B_2D

# slicing bisa diambil dengan satu koordinat
B_2D[1]

B_2D[1,:]

B_2D[:,1]

# slicing pada matriks 3D
C_3D

C_3D[1]

C_3D[1,:,:]

C_3D[:,1,:]

C_3D[:,:,1]

# slicing lebih dari satu kolom/baris

A = np.linspace(0,9,10)
B, C = A + 10, A + 20

X = np.array([A,B,C])

X

X[0:2,3], X[0:3,3], X[1:3,3]

X[1:3,1:5]

"""
Explanation: Indexing dan Slicing
Mengambil satu elemen dari matrix mirip dengan mengambil dari sequence: kita cuma perlu tahu koordinatnya. Apabila matriks satu dimensi, maka koordinat cuma perlu satu dimensi. Apabila matriks 2D, maka koordinatnya perlu dua input, dst. Sementara itu, mengambil beberapa elemen dari satu array sering disebut dengan slicing (memotong).
End of explanation
"""


A = np.array([1,2,3,4,5,6])
B = np.array([[1,1,1],[1,0,1],[0,1,1]])
C = np.array([[1,2,3],[4,5,6],[7,8,9]])
D = np.array([[1,1],[1,0]])

2*A

A+3*A + 3*A**2+A**3

A**2

C**2

A < 5

np.sin(A)

"""
Explanation: Operasi Matriks
Operasi Dasar
Pada operasi matriks dengan menggunakan numpy, satu hal yang perlu diperhatikan adalah ada dua jenis operasi, yaitu operasi element-wise dan operasi matriks. Operasi element-wise menggunakan tanda operasi seperti halnya pada data berbentuk integer atau float. Operasi perkalian matriks menggunakan tanda @ untuk versi Python yang lebih tinggi dari 3.5; untuk versi sebelumnya harus menggunakan fungsi np.matmul dengan dua input.
End of explanation
"""


B @ C

d

B @ D

print(B.shape,D.shape)

"""
Explanation: Perkalian Matriks
End of explanation
"""


x1 = np.ones(3)
x2 = np.ones((3,1))
x3 = np.ones((1,3))

# Perkalian Matriks 3x3 dengan vektor 3x1
B @ x1

B @ x2

B @ x3

"""
Explanation: Perkalian Matriks dengan 'vektor'
End of explanation
"""


