... | @@ -437,10 +437,10 @@ This program uses three Python modules: |
... | @@ -437,10 +437,10 @@ This program uses three Python modules: |
|
|
|
|
|
The last line (i.e., `Point = Tuple[float, float]` is a type alias.
|
|
The last line (i.e., `Point = Tuple[float, float]` is a type alias.
|
|
|
|
|
|
> I am a stickler for type hints and function/method documentation. Anytime
|
|
*I am a stickler for type hints and function/method documentation. Anytime
|
|
> code is written... **it must be documented** at the API level. While Python
|
|
code is written... **it must be documented** at the API level. While Python
|
|
> type hints do no necessarily gain us a performance benifit, type hints
|
|
type hints do no necessarily gain us a performance benefit, type hints
|
|
> increase readbility. Type hints are an important part of documentation.
|
|
increase readability. Type hints are an important part of documentation.*
|
|
|
|
|
|
|
|
|
|
### Point Generation
|
|
### Point Generation
|
... | @@ -486,6 +486,13 @@ any time a variable is required syntactically, but the value will be ignored. |
... | @@ -486,6 +486,13 @@ any time a variable is required syntactically, but the value will be ignored. |
|
|
|
|
|
### The Main Function
|
|
### The Main Function
|
|
|
|
|
|
|
|
Always wrap your main/driver code in a main function. This will prevent
|
|
|
|
variables from ending up in the global/module namespace... which can **(will)**
|
|
|
|
lead to frustrating bugs later.
|
|
|
|
|
|
|
|
Let us start with a *naive* main function, one that has quite a bit of room for
|
|
|
|
improvement.
|
|
|
|
|
|
```python
|
|
```python
|
|
def naive_main():
|
|
def naive_main():
|
|
"""
|
|
"""
|
... | @@ -511,8 +518,53 @@ def naive_main(): |
... | @@ -511,8 +518,53 @@ def naive_main(): |
|
integral_result = (limit_b - limit_a) / float(num_points) * temp_sum
|
|
integral_result = (limit_b - limit_a) / float(num_points) * temp_sum
|
|
|
|
|
|
print(f"{integral_result:16.8f}")
|
|
print(f"{integral_result:16.8f}")
|
|
|
|
```
|
|
|
|
|
|
|
|
The first three (3) lines
|
|
|
|
|
|
|
|
```python
|
|
|
|
num_points = int(sys.argv[1])
|
|
|
|
limit_a = float(sys.argv[2])
|
|
|
|
limit_b = float(sys.argv[3])
|
|
|
|
```
|
|
|
|
|
|
|
|
grab command line arguments and parse them into `int` of `float values`.
|
|
|
|
|
|
|
|
Next... I defined a lambda function. This is the mathematical function `f(x)`
|
|
|
|
that will be integrated.
|
|
|
|
|
|
|
|
```python
|
|
|
|
math_f = lambda x: x**2
|
|
|
|
```
|
|
|
|
|
|
|
|
Note that any line that includes `file=sys.stderr` is debugging output. By
|
|
|
|
convention (in C, C++, Java, Python, and Rust) production output is written to
|
|
|
|
standard out and debugging output is written to standard error.
|
|
|
|
|
|
|
|
The rest of the function is not very Pythonic...
|
|
|
|
|
|
|
|
```python
|
|
|
|
temp_sum = 0
|
|
|
|
for i, point in enumerate(generate_random_points(math_f, limit_a, limit_b, num_points)):
|
|
|
|
print(f"{i:5d} - ({point[0]:>12.8f}, {point[1]:>12.8f})", file=sys.stderr)
|
|
|
|
|
|
|
|
temp_sum += point[1]
|
|
|
|
|
|
|
|
integral_result = (limit_b - limit_a) / float(num_points) * temp_sum
|
|
|
|
|
|
|
|
print(f"{integral_result:16.8f}")
|
|
|
|
```
|
|
|
|
|
|
|
|
There is:
|
|
|
|
|
|
|
|
- a temporary sum variable `temp_sum`
|
|
|
|
- a line over 80 characters in length
|
|
|
|
- an increment operation (`temp_sum += point[1]`)
|
|
|
|
|
|
|
|
The next version of main (i.e., `not_so_naive_main`) corrects a few style and
|
|
|
|
design issues.
|
|
|
|
|
|
|
|
```python
|
|
def not_so_naive_main():
|
|
def not_so_naive_main():
|
|
"""
|
|
"""
|
|
This main function demonstrates the more "Pythonic" approach
|
|
This main function demonstrates the more "Pythonic" approach
|
... | @@ -533,8 +585,36 @@ def not_so_naive_main(): |
... | @@ -533,8 +585,36 @@ def not_so_naive_main(): |
|
sum(f_of_x_values))
|
|
sum(f_of_x_values))
|
|
|
|
|
|
print(f"{integral_result:16.8f}")
|
|
print(f"{integral_result:16.8f}")
|
|
|
|
```
|
|
|
|
|
|
|
|
Instead of looping over all the points
|
|
|
|
|
|
|
|
```python
|
|
|
|
for i, point in enumerate(generate_random_points(math_f, limit_a, limit_b, num_points)):
|
|
|
|
```
|
|
|
|
|
|
|
|
the generator is assigned to a variable:
|
|
|
|
|
|
|
|
```python
|
|
|
|
f_of_x_values = (y for x, y in point_sequence)
|
|
|
|
```
|
|
|
|
|
|
|
|
This leads to a far more concise and readable computation.
|
|
|
|
|
|
|
|
```python
|
|
|
|
integral_result = ((limit_b - limit_a) /
|
|
|
|
float(num_points) *
|
|
|
|
sum(f_of_x_values))
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
point_sequence = generate_random_points(math_f, limit_a, limit_b, num_points)
|
|
|
|
```
|
|
|
|
|
|
|
|
Since we only need the `y` values from each point... an inline generator
|
|
|
|
expression can be used
|
|
|
|
|
|
|
|
```python
|
|
def main_without_a_table_flip():
|
|
def main_without_a_table_flip():
|
|
"""
|
|
"""
|
|
This main demonstrates the impact of the number of points on Monte Carlo
|
|
This main demonstrates the impact of the number of points on Monte Carlo
|
... | | ... | |