
from __future__ import annotations

import math


def 						UpperCAmelCase      (     a_		,       a_						)		->     Tuple:







				"""simple docstring"""



				if len(a_						) != 2 or len(a[0]						) != 2 or len(a_						) != 2 or len(b[0]						) != 2:
								raise Exception("Matrices are not 2x2"						)
				__A             =			[
				    [a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]],
				    [a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]],
				]
				return new_matrix


def 						UpperCAmelCase      (     a_		,       a_						)		->     Optional[int]:







				"""simple docstring"""



				return [
				    [matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]						)						)]
				    for row in range(len(a_						)						)
				]


def 						UpperCAmelCase      (     a_		,       a_						)		->     Optional[int]:







				"""simple docstring"""



				return [
				    [matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]						)						)]
				    for row in range(len(a_						)						)
				]


def 						UpperCAmelCase      (     a_						)		->     Tuple:







				"""simple docstring"""



				if len(a_						) % 2 != 0 or len(a[0]						) % 2 != 0:
								raise Exception("Odd matrices are not supported!"						)

				__A             =			len(a_						)
				__A             =			matrix_length // 2

				__A             =			[[a[i][j] for j in range(a_		,       a_						)] for i in range(a_						)]
				__A             =			[
				    [a[i][j] for j in range(a_		,       a_						)] for i in range(a_		,       a_						)
				]

				__A             =			[[a[i][j] for j in range(a_						)] for i in range(a_						)]
				__A             =			[[a[i][j] for j in range(a_						)] for i in range(a_		,       a_						)]

				return top_left, top_right, bot_left, bot_right


def 						UpperCAmelCase      (     a_						)		->     Union[str, Any]:







				"""simple docstring"""



				return len(a_						), len(matrix[0]						)


def 						UpperCAmelCase      (     a_						)		->     Union[str, Any]:







				"""simple docstring"""



				print("\n".join(str(a_						) for line in matrix						)						)


def 						UpperCAmelCase      (     a_		,       a_						)		->     List[Any]:







				"""simple docstring"""



				if matrix_dimensions(a_						) == (2, 2):
								return default_matrix_multiplication(a_		,       a_						)

				__A             =			split_matrix(a_						)
				__A             =			split_matrix(a_						)

				__A             =			actual_strassen(a_		,       matrix_subtraction(a_		,       a_						)						)
				__A             =			actual_strassen(matrix_addition(a_		,       a_						)		,       a_						)
				__A             =			actual_strassen(matrix_addition(a_		,       a_						)		,       a_						)
				__A             =			actual_strassen(a_		,       matrix_subtraction(a_		,       a_						)						)
				__A             =			actual_strassen(matrix_addition(a_		,       a_						)		,       matrix_addition(a_		,       a_						)						)
				__A             =			actual_strassen(matrix_subtraction(a_		,       a_						)		,       matrix_addition(a_		,       a_						)						)
				__A             =			actual_strassen(matrix_subtraction(a_		,       a_						)		,       matrix_addition(a_		,       a_						)						)

				__A             =			matrix_addition(matrix_subtraction(matrix_addition(a_		,       a_						)		,       a_						)		,       a_						)
				__A             =			matrix_addition(a_		,       a_						)
				__A             =			matrix_addition(a_		,       a_						)
				__A             =			matrix_subtraction(matrix_subtraction(matrix_addition(a_		,       a_						)		,       a_						)		,       a_						)

				# construct the new matrix from our 4 quadrants
				__A             =			[]
				for i in range(len(a_						)						):
								new_matrix.append(top_left[i] + top_right[i]						)
				for i in range(len(a_						)						):
								new_matrix.append(bot_left[i] + bot_right[i]						)
				return new_matrix


def 						UpperCAmelCase      (     a_		,       a_						)		->     Optional[Any]:







				"""simple docstring"""



				if matrix_dimensions(a_						)[1] != matrix_dimensions(a_						)[0]:
								__A             =			(
								    'Unable to multiply these matrices, please check the dimensions.\n'
								    F'''Matrix A: {matrixa}\n'''
								    F'''Matrix B: {matrixa}'''
								)
								raise Exception(a_						)
				__A             =			matrix_dimensions(a_						)
				__A             =			matrix_dimensions(a_						)

				if dimensiona[0] == dimensiona[1] and dimensiona[0] == dimensiona[1]:
								return [matrixa, matrixa]

				__A             =			max(*a_		,       *a_						)
				__A             =			int(math.pow(2		,       math.ceil(math.loga(a_						)						)						)						)
				__A             =			matrixa
				__A             =			matrixa

				# Adding zeros to the matrices so that the arrays dimensions are the same and also
				# power of 2
				for i in range(0		,       a_						):
								if i < dimensiona[0]:
												for _ in range(dimensiona[1]		,       a_						):
																new_matrixa[i].append(0						)
								else:
												new_matrixa.append([0] * maxim						)
								if i < dimensiona[0]:
												for _ in range(dimensiona[1]		,       a_						):
																new_matrixa[i].append(0						)
								else:
												new_matrixa.append([0] * maxim						)

				__A             =			actual_strassen(a_		,       a_						)

				# Removing the additional zeros
				for i in range(0		,       a_						):
								if i < dimensiona[0]:
												for _ in range(dimensiona[1]		,       a_						):
																final_matrix[i].pop()
								else:
												final_matrix.pop()
				return final_matrix


if __name__ == "__main__":
					SCREAMING_SNAKE_CASE							:int										=    [
					    [2, 3, 4, 5],
					    [6, 4, 3, 1],
					    [2, 3, 6, 7],
					    [3, 1, 2, 4],
					    [2, 3, 4, 5],
					    [6, 4, 3, 1],
					    [2, 3, 6, 7],
					    [3, 1, 2, 4],
					    [2, 3, 4, 5],
					    [6, 2, 3, 1],
					]
					SCREAMING_SNAKE_CASE							:Any										=    [[0, 2, 1, 1], [16, 2, 3, 3], [2, 2, 7, 7], [13, 11, 22, 4]]
					print(strassen(matrixa, matrixa))


import numpy





class 							UpperCAmelCase				:




	'''simple docstring'''





	def __init__(	self :			List[str]							,A :			numpy.ndarray							,A :			numpy.ndarray			):
					__A             =			input_array

					# Random initial weights are assigned where first argument is the
					# number of nodes in previous layer and second argument is the
					# number of nodes in the next layer.

					# Random initial weights are assigned.
					# self.input_array.shape[1] is used to represent number of nodes in input layer.
					# First hidden layer consists of 4 nodes.
					__A             =			numpy.random.rand(
					    self.input_array.shape[1]							,4			)

					# Random initial values for the first hidden layer.
					# First hidden layer has 4 nodes.
					# Second hidden layer has 3 nodes.
					__A             =			numpy.random.rand(
					    4							,3			)

					# Random initial values for the second hidden layer.
					# Second hidden layer has 3 nodes.
					# Output layer has 1 node.
					__A             =			numpy.random.rand(3							,1			)

					# Real output values provided.
					__A             =			output_array

					# Predicted output values by the neural network.
					# Predicted_output array initially consists of zeroes.
					__A             =			numpy.zeros(output_array.shape			)






	def     UpperCamelCase_  (	self :			Optional[Any]			):
					__A             =			sigmoid(
					    numpy.dot(self.input_array							,self.input_layer_and_first_hidden_layer_weights			)			)

					# layer_between_first_hidden_layer_and_second_hidden_layer is the layer
					# connecting the first hidden set of nodes with the second hidden set of nodes.
					__A             =			sigmoid(
					    numpy.dot(
					        self.layer_between_input_and_first_hidden_layer							,self.first_hidden_layer_and_second_hidden_layer_weights							,)			)

					# layer_between_second_hidden_layer_and_output is the layer connecting
					# second hidden layer with the output node.
					__A             =			sigmoid(
					    numpy.dot(
					        self.layer_between_first_hidden_layer_and_second_hidden_layer							,self.second_hidden_layer_and_output_layer_weights							,)			)

					return self.layer_between_second_hidden_layer_and_output






	def     UpperCamelCase_  (	self :			str			):
					__A             =			numpy.dot(
					    self.layer_between_first_hidden_layer_and_second_hidden_layer.T							,2
					    * (self.output_array - self.predicted_output)
					    * sigmoid_derivative(self.predicted_output			)							,)
					__A             =			numpy.dot(
					    self.layer_between_input_and_first_hidden_layer.T							,numpy.dot(
					        2
					        * (self.output_array - self.predicted_output)
					        * sigmoid_derivative(self.predicted_output			)							,self.second_hidden_layer_and_output_layer_weights.T							,)
					    * sigmoid_derivative(
					        self.layer_between_first_hidden_layer_and_second_hidden_layer			)							,)
					__A             =			numpy.dot(
					    self.input_array.T							,numpy.dot(
					        numpy.dot(
					            2
					            * (self.output_array - self.predicted_output)
					            * sigmoid_derivative(self.predicted_output			)							,self.second_hidden_layer_and_output_layer_weights.T							,)
					        * sigmoid_derivative(
					            self.layer_between_first_hidden_layer_and_second_hidden_layer			)							,self.first_hidden_layer_and_second_hidden_layer_weights.T							,)
					    * sigmoid_derivative(self.layer_between_input_and_first_hidden_layer			)							,)

					self.input_layer_and_first_hidden_layer_weights += (
					    updated_input_layer_and_first_hidden_layer_weights
					)
					self.first_hidden_layer_and_second_hidden_layer_weights += (
					    updated_first_hidden_layer_and_second_hidden_layer_weights
					)
					self.second_hidden_layer_and_output_layer_weights += (
					    updated_second_hidden_layer_and_output_layer_weights
					)






	def     UpperCamelCase_  (	self :			Tuple							,A :			numpy.ndarray							,A :			int							,A :			bool			):
					for iteration in range(1							,iterations + 1			):
									__A             =			self.feedforward()
									self.back_propagation()
									if give_loss:
													__A             =			numpy.mean(numpy.square(output - self.feedforward()			)			)
													print(f'''Iteration {iteration} Loss: {loss}'''			)







	def     UpperCamelCase_  (	self :			List[Any]							,A :			numpy.ndarray			):
					__A             =			input_arr

					__A             =			sigmoid(
					    numpy.dot(self.array							,self.input_layer_and_first_hidden_layer_weights			)			)

					__A             =			sigmoid(
					    numpy.dot(
					        self.layer_between_input_and_first_hidden_layer							,self.first_hidden_layer_and_second_hidden_layer_weights							,)			)

					__A             =			sigmoid(
					    numpy.dot(
					        self.layer_between_first_hidden_layer_and_second_hidden_layer							,self.second_hidden_layer_and_output_layer_weights							,)			)

					return int(self.layer_between_second_hidden_layer_and_output > 0.6			)





def 						UpperCAmelCase      (     a_						)		->     numpy.ndarray:







				"""simple docstring"""



				return 1 / (1 + numpy.exp(-value						))


def 						UpperCAmelCase      (     a_						)		->     numpy.ndarray:







				"""simple docstring"""



				return (value) * (1 - (value))


def 						UpperCAmelCase      (     )		->     int:







				"""simple docstring"""



				__A             =			numpy.array(
				    (
				        [0, 0, 0],
				        [0, 0, 1],
				        [0, 1, 0],
				        [0, 1, 1],
				        [1, 0, 0],
				        [1, 0, 1],
				        [1, 1, 0],
				        [1, 1, 1],
				    )		,       dtype=numpy.floataa		,       )

				# True output values for the given input values.
				__A             =			numpy.array(([0], [1], [1], [0], [1], [0], [0], [1])		,       dtype=numpy.floataa						)

				# Calling neural network class.
				__A             =			TwoHiddenLayerNeuralNetwork(
				    input_array=a_		,       output_array=a_						)

				# Calling training function.
				# Set give_loss to True if you want to see loss in every iteration.
				neural_network.train(output=a_		,       iterations=1_0		,       give_loss=a_						)

				return neural_network.predict(numpy.array(([1, 1, 1])		,       dtype=numpy.floataa						)						)


if __name__ == "__main__":
					example()
