import random

def sample_experiment():
    ### BEGIN SOLUTION
    """
    Returns true if a random number is less than 0
    """
    return random.random() < 0


number_of_experiments = 1000
sum(
    sample_experiment() for repetition in range(number_of_experiments)
) / number_of_experiments
### END SOLUTION

q1_a_answer = _
q1_a_expected_answer = 0
feedback_text = """Your output is not the expected answer
"""
assert q1_a_answer == q1_a_expected_answer, feedback_text

feedback_text = """You did not include a docstring. This is important to help document your code.


It is done  using triple quotation marks. For example:

def get_remainder(m, n):
    \"\"\"
    This function returns the remainder of m when dividing by n
    \"\"\"
    …

Using that it's possible to access the docstring,
one way to do this is to type: `get_remainder?`
(which only works in Jupyter) or help(get_remainder).

We can also comment code using `#` but this is completely
ignored by Python so cannot be accessed in the same way.

"""
try:
    assert  sample_experiment.__doc__ is not None, feedback_text
except NameError:
    assert False, "You did not create a function called `sample_experiment`"

"""
Explanation: Computing for Mathematics - 2020/2021 individual coursework
Important Do not delete the cells containing: 
```
BEGIN SOLUTION
END SOLUTION
```
write your solution attempts in those cells.
To submit this notebook:

Change the name of the notebook from main to: &lt;student_number&gt;. For example, if your student number is c1234567 then change the name of the notebook to c1234567.
Write all your solution attempts in the correct locations;
Do not delete any code that is already in the cells;
Save the notebook (File&gt;Save As);
Follow the instructions given in class/email to submit.

Question 1
(Hint: This question is similar to the first exercise of the Probability chapter of Python for mathematics.)
For each of the following, write a function sample_experiment, and repeatedly use it to simulate the probability of an event occurring with the following chances.
For each chance output the simulated probability.
a. $0$ 
Available marks: 2
End of explanation
"""


def sample_experiment():
    ### BEGIN SOLUTION
    """
    Returns true if a random number is less than 1 / 2
    """
    return random.random() < 1 / 2


number_of_experiments = 1000
sum(
    sample_experiment() for repetition in range(number_of_experiments)
) / number_of_experiments
### END SOLUTION

import numpy as np

q1_b_answer = _
q1_b_expected_answer = 1 / 2
feedback_text = """Your output is not within an acceptable range of the expected answer
"""
assert q1_b_expected_answer * .75 <= q1_b_answer <= q1_b_expected_answer * 1.25, feedback_text

feedback_text = """You did not include a docstring. This is important to help document your code.


It is done  using triple quotation marks. For example:

def get_remainder(m, n):
    \"\"\"
    This function returns the remainder of m when dividing by n
    \"\"\"
    …

Using that it's possible to access the docstring,
one way to do this is to type: `get_remainder?`
(which only works in Jupyter) or help(get_remainder).

We can also comment code using `#` but this is completely
ignored by Python so cannot be accessed in the same way.

"""
try:
    assert  sample_experiment.__doc__ is not None, feedback_text
except NameError:
    assert False, "You did not create a function called `sample_experiment`"

"""
Explanation: b. $1/2$
Available marks: 2
End of explanation
"""


def sample_experiment():
    ### BEGIN SOLUTION
    """
    Returns true if a random number is less than 3 / 4
    """
    return random.random() < 3 / 4


number_of_experiments = 1000
sum(
    sample_experiment() for repetition in range(number_of_experiments)
) / number_of_experiments
### END SOLUTION

q1_c_answer = _
q1_c_expected_answer = 3 / 4
feedback_text = """Your output is not within an acceptable range of the expected answer
"""
assert q1_c_expected_answer * .75 <= q1_c_answer <= q1_c_expected_answer * 1.25, feedback_text

feedback_text = """You did not include a docstring. This is important to help document your code.


It is done  using triple quotation marks. For example:

def get_remainder(m, n):
    \"\"\"
    This function returns the remainder of m when dividing by n
    \"\"\"
    …

Using that it's possible to access the docstring,
one way to do this is to type: `get_remainder?`
(which only works in Jupyter) or help(get_remainder).

We can also comment code using `#` but this is completely
ignored by Python so cannot be accessed in the same way.

"""
try:
    assert  sample_experiment.__doc__ is not None, feedback_text
except NameError:
    assert False, "You did not create a function called `sample_experiment`"

"""
Explanation: c. $3/4$
Available marks: 2
End of explanation
"""


def sample_experiment():
    ### BEGIN SOLUTION
    """
    Returns true if a random number is less than 1
    """
    return random.random() < 1

number_of_experiments = 1000
sum(
    sample_experiment() for repetition in range(number_of_experiments)
) / number_of_experiments
### END SOLUTION

q1_d_answer = _
q1_d_expected_answer = 1
feedback_text = """Your output is not the expected answer
"""
assert q1_d_answer == q1_d_expected_answer, feedback_text

feedback_text = """You did not include a docstring. This is important to help document your code.


It is done  using triple quotation marks. For example:

def get_remainder(m, n):
    \"\"\"
    This function returns the remainder of m when dividing by n
    \"\"\"
    …

Using that it's possible to access the docstring,
one way to do this is to type: `get_remainder?`
(which only works in Jupyter) or help(get_remainder).

We can also comment code using `#` but this is completely
ignored by Python so cannot be accessed in the same way.

"""
try:
    assert  sample_experiment.__doc__ is not None, feedback_text
except NameError:
    assert False, "You did not create a function called `sample_experiment`"

"""
Explanation: d. $1$
Available marks: 2
End of explanation
"""


import itertools

pets = ("cat", "dog", "fish", "lizard", "hamster")

### BEGIN SOLUTION
permutations = tuple(itertools.permutations(pets, 4))
number_of_permutations = len(permutations)
### END SOLUTION

expected_number_of_permutations = 120
feedback = "The expected answer is 120"

try:
    assert expected_number_of_permutations == number_of_permutations, feedback
except NameError:
    assert False, "You did not create a variable called `number_of_permutations`"

"""
Explanation: Question 2
(Hint: This question is similar to the second exercise of the Combinatorics chapter of Python for mathematics.)
a. Create a variable number_of_permutations that gives the number of permutations of pets = ("cat", "dog", "fish", "lizard", "hamster) of size 4. Do this by generating and counting them.
Available marks: 2
End of explanation
"""


import scipy.special

### BEGIN SOLUTION
direct_number_of_permutations = scipy.special.perm(5, 4)
### END SOLUTION

expected_number_of_permutations = 120
feedback = "The expected answer is 120"

try:
    assert expected_number_of_permutations == direct_number_of_permutations, feedback
except NameError:
    assert False, "You did not create a variable called `direct_number_of_permutations`"

"""
Explanation: b. Create a variable direct_number_of_permutations that gives the number of permutations of pets of size 4 by direct computation.
Available marks: 1
End of explanation
"""


import sympy as sym


x = sym.Symbol("x")
c1 = sym.Symbol("c1")
### BEGIN SOLUTION
second_derivative = 4 * x + sym.cos(x)
derivative = sym.integrate(second_derivative, x) + c1
### END SOLUTION

feedback_text = """`derivative` is not a symbolic expression.

You are expected to use sympy for this question.
"""
try:
    assert derivative.expand(), feedback_text
except NameError:
    assert False, "You did not create a variable called `derivative`"

import sympy as sym
x = sym.Symbol("x")
c1 = sym.Symbol("c1")

expected_answer = c1 + 2 * x ** 2 + sym.sin(x)
feedback_text = f"""Your answer is not correct.

The expected answer is {expected_answer}."""
assert sym.expand(derivative - expected_answer) == 0, feedback_text

"""
Explanation: Question 3
(Hint: This question uses concepts from the Algebra and Calculus chapters of Python for mathematics.)
Consider the second derivative $f''(x)=4 x + \cos(x)$.
a. Create a variable derivative which has value $f'(x)$ (use the variables x and c1 if necessary):
Available marks: 3
End of explanation
"""


### BEGIN SOLUTION
equation = sym.Eq(derivative.subs({x:0}), 0)
### END SOLUTION

expected_lhs = c1
feedback = f"The expected left hand side is {expected_lhs}"
try:
    assert sym.expand(equation.lhs - expected_lhs) == 0, feedback
except AttributeError:
    assert False, "You did not create a symbolic equation"

expected_rhs = 0
feedback = f"The expected right hand side is {expected_rhs}. Note that the exact value of pi can be used from the sympy library."
try:
    assert sym.expand(equation.rhs - expected_rhs) == 0, feedback
except AttributeError:
    assert "You did not create a symbolic equation", False

"""
Explanation: b. Create a variable equation that has value the equation $f'(0)=0$.
Available marks: 4
End of explanation
"""


### BEGIN SOLUTION
particular_derivative = derivative.subs({c1: 0})
function = sym.integrate(particular_derivative) + c1
sym.integrate(function, (x, 0, 5 * sym.pi))
### END SOLUTION

q3_c_answer = _
feedback_text = """your output is not a symbolic expression.

You are expected to use sympy for this question.
"""
assert q3_c_answer.expand(), feedback_text

expected_answer = 625 * sym.pi ** 4 / 6
delta = sym.expand((q3_c_answer - expected_answer) / (5 * sym.pi))
feedback_text = f"""The expected answer is: {expected_answer} (up to a constant of integration). 
This is done by substituting the value of c1=pi+1 and then integrating.)

If your we let your answer be Y, so that Y={q3_c_answer}

Then

(Y - {expected_answer}) / (5 pi) should be a constant (with respect to x) but you have:

(Y - {expected_answer}) / (5 pi) = {delta}
"""
assert sym.diff(delta, x) == 0, feedback_text

"""
Explanation: c. Using the solution to that equation, output the value of $\int_{0}^{5\pi}f(x)dx$.
Available marks: 4
End of explanation
"""


c = sym.Symbol("c")
### BEGIN SOLUTION
def get_sequence_a(n):
    """
    Return the sequence a.
    """
    if n == 1:
        return c
    return 3 * get_sequence_a(n - 1) + c / n


sum(get_sequence_a(n) for n in range(1, 16))
### END SOLUTION

q4_a_answer = _
feedback_text = """your output is not a symbolic expression.

You are expected to use sympy for this question.
"""

assert q4_a_answer.expand(), feedback_text

expected_answer = 2748995546 * c / 315
feedback_text = f"The expected answer is: {expected_answer}."
assert sym.expand(q4_a_answer - expected_answer) == 0, feedback_text

"""
Explanation: Question 4
(Hint: This question uses concepts from the Calculus and Sequences chapters of Python for mathematics.)
Consider this recursive definition for the sequence $a_n$:
$$
a_n = \begin{cases}
        c & \text{ if n = 1}\
        3a_{n - 1} + \frac{c}{n}
      \end{cases}
$$
a. Output the sum of the 15 terms.
Available marks: 5
End of explanation
"""


### BEGIN SOLUTION
f = (get_sequence_a(n=1) + get_sequence_a(n=2) * x + get_sequence_a(n=3) * x ** 2 + + get_sequence_a(n=4) * x ** 3).subs({c: 2})
sym.diff(f, x)
### END SOLUTION

q4_b_answer = _
feedback_text = """your output is not a symbolic expression.

You are expected to use sympy for this question.
"""

assert q4_b_answer.expand(), feedback_text

import sympy as sym
expected_answer = sym.S(393) * x ** 2 / 2 + sym.S(130) * x / 3 + sym.S(7)
feedback_text = f"The expected answer is: {expected_answer}."
assert sym.expand(q4_b_answer - expected_answer) == 0, feedback_text

"""
Explanation: b. Given that $c=2$ output $\frac{df}{dx}$ where:
$$
f(x) = a_1 + a_2 x + a_3 x ^ 2 + a_4 x ^ 3
$$
Available marks: 4
End of explanation
"""


### BEGIN SOLUTION
sym.integrate(f, x)
### END SOLUTION

q4_c_answer = _
feedback_text = """your output is not a symbolic expression.

You are expected to use sympy for this question.
"""

assert q4_c_answer.expand(), feedback_text

import sympy as sym

def _get_sequence_a(n):
    """
    Return the sequence a.
    """
    if n == 1:
        return c
    return 3 * _get_sequence_a(n - 1) + c / n

expected_derivative = (_get_sequence_a(n=1) + _get_sequence_a(n=2) * x + _get_sequence_a(n=3) * x ** 2 + _get_sequence_a(n=4) * x ** 3).subs({c: 2})
feedback_text = f"The expected answer is: {sym.integrate(expected_derivative, x)}."
assert sym.expand(sym.diff(q4_c_answer, x) - expected_derivative) == 0, feedback_text

"""
Explanation: c. Given that $c=2$ output $\int f(x)dx$
Available marks: 4
End of explanation
"""


