Category: Math

  • Solving Differential Equations with Laplace Transforms

    Solving Differential Equations with Laplace Transforms

    In this blog post, you will learn what a Laplace Transform is, and how to use them to solve differential equations.

    What is a Laplace Transform?

    Laplace Transforms are important because they enable you to solve differential equations algebraically. Therefore, they are important for solving problems in science, engineering, and applied math.

    The definition of a Laplace Transform is the following:

    Given a function f(t) defined for all t greater than or equal to zero, the Laplace Transform of f is the function F defined as follows:

    For all values of s for which the improper integral converges.

    Computing the Laplace Transform by Definition

    Let’s look at a simple function f(t)=2, and compute its Laplace Transform.

    Note: once you compute some of these by hand, you notice you keep using the same ones over and over. Therefore, there are tables of Laplace transforms you can reference to compute them. You can also use them for computing the inverse of the transform.

    Inverse Transforms

    As you might guess, sometimes, we want to reverse F(s) back to a function f(t), which is the inverse Laplace Transform. Since, we just computed one, let’s reverse it.

    This is important when you want to transform the algebraically solved expression back into a particular solution of a differential equation (more on that later.)

    Solving a Differential Equation with Laplace Transforms

    Consider the following differential equation:

    with the initial conditions x(0)=x'(0)=0. Taking the Laplace transform of it converts it into the following:

    Solving for X(s), we get the following:

    Now, to compute the inverse Laplace transform, which will give us x(t). We must use partial fractions, which gives us the following:

    Solving this partial fraction, you will obtain A=C=0, B= 3/5, and D=-3/5.

    Therefore, we have the following:

    By computing the inverse Laplace transform, we get x(t), which is the following:

    This is the particular solution to the equation given the initial conditions.

    Final Thoughts

    In this blog post, you learned what Laplace transforms are, and you learned how to use them to solve differential equations.

  • Simulating Poker Odds with Python

    In this blog post, you will learn how to manually calculate and check poker odds.

    The Game of Poker

    A deck of cards has a total of 52 cards with four suits, which are hearts, diamonds, clubs, and spades. Each suit has 13 card values, which are the numbers 2-10, Jack, Queen, King, Ace. The ace card can act as a high or low value card.

    In the game of poker, 5 cards make up a hand. The following diagram shows what type of hands you can get.

    Image found at https://www.poker.org/poker-hands-ranking-chart/

    Getting a pair, two pair, three of a kind, and four of a kind are pretty straightforward.

    A straight is a sequence of cards that are in increasing order, and the cards can be of different suits. The hand must contain each consecutively increasing card value. For example, the cards, 2,3,4,5,6 is a straight, but the cards 2,5,7,9,10 is not a straight.

    A flush is a sequence of cards of the same suit. The cards do not need to be consecutively increasing like a straight. For example, the cards 5, 7, 8, 10, K would be a flush.

    A full house is a hand with both three of a kind and a pair.

    A straight flush is a hand that is both a flush and a straight at the same time.

    A royal flush is a hand that contains the values, 10, J, Q, K, A where the ace counts as a high card.

    The Odds of Getting a Pair

    Let’s first go over the theory of receiving a hand with a pair in poker. To count the number of ways, we use a combination of combinatorics and the multiplication rule. The first thing we want to do is choose 1 card value of 13 values. Then choose 2 cards from two different suits. Afterwards, we want to choose 3 different card values from the remaining 12 values, and choose each of the three cards from four different suits. The total number of ways to get a pair is then the following:

    This number is 1,098,240. To get the odds, we divide the previous number with the total number of ways to choose 5 cards from a deck of 52 cards. The odds turn out to be 42.2569% to get a pair.

    The following is the code to simulate the odds of getting a pair:

    import random
    
    class Card():
        def __init__(self, value, suit):
            self._value = value
            self._suit = suit
        
        def value(self):
            return self._value
    
        def suit(self):
            return self._suit
        
        def __str__(self):
            return "" + self._suit + str(self._value)
    
    def create_deck():
        deck = []
        suit = ["H", "D", "S", "C"]
    
        for i in range(13):
            for j in range(4):
                deck.append(Card(i, suit[j]))
        
        return deck
    
    def shuffle_deck(deck):
        random.shuffle(deck)
    
    def draw_hand(deck):
        hand = []
    
        for i in range(5):
            c = deck.pop()
            hand.append(c)
        
        return hand
    
    def print_hand(hand):
        print("Hand:", end="")
        for c in hand:
            print(c, "", end="")
        print("")
    
    def has_pair(hand):
        # This variable ensures there will be only one
        # pair
        meta_count = 0
    
        for i in range(5):
            # Count the number of cards with
            # the same value as the i-th card.
            count = 0
            for j in range(i+1, 5):
                if hand[i].value() == hand[j].value():
                    count += 1
                    continue
                
            
            if count == 1:
                meta_count += 1
            elif count == 2:
                return False
            elif count == 3:
                return False
        
        return meta_count == 1
    
    
    def main():
        run_count = 100000
        pair_count = 0
    
        for x in range(run_count):
            deck = create_deck()
            shuffle_deck(deck)
            hand = draw_hand(deck)
            print_hand(hand)
    
            if has_pair(hand):
                pair_count += 1
        
        print("Expected Probability: 42.2569%")
        print("Simulated Probability: " + str(pair_count/ 100000.0))
    
    if __name__ == "__main__":
        main()

    The Odds of Getting Three of a Kind

    The theory for getting three of a kind is as follows:

    From 13 different values, we want to choose one value, then of four suits we want to choose 3 from 4. Then, from 12 remaining values, we want to choose only two more cards and then choose one card from four suits twice.

    The count turns out to be the following:

    To get the odds, we divide that number by the total number of ways to choose 5 cards from a deck of 52 cards. This turns out to be 2.11285%

    The following is the code to simulate the odds of getting three of a kind:

    import random
    
    class Card():
        def __init__(self, value, suit):
            self._value = value
            self._suit = suit
        
        def value(self):
            return self._value
    
        def suit(self):
            return self._suit
        
        def __str__(self):
            return "" + self._suit + str(self._value)
    
    def create_deck():
        deck = []
        suit = ["H", "D", "S", "C"]
    
        for i in range(13):
            for j in range(4):
                deck.append(Card(i, suit[j]))
        
        return deck
    
    def shuffle_deck(deck):
        random.shuffle(deck)
    
    def draw_hand(deck):
        hand = []
    
        for i in range(5):
            c = deck.pop()
            hand.append(c)
        
        return hand
    
    def print_hand(hand):
        print("Hand:", end="")
        for c in hand:
            print(c, "", end="")
        print("")
    
    def has_pair(hand):
        meta_count = 0
        for i in range(5):
            count = 0
            for j in range(i+1, 5):
                if hand[i].value() == hand[j].value():
                    count += 1
                    continue
                
            
            if count == 1:
                meta_count += 1
            elif count == 2:
                return False
            elif count == 3:
                return False
        
        return meta_count == 1
    
    def three_of_a_kind(hand):
        count_map = {}
    
        for c in hand:
            if c.value() not in count_map:
                count_map[c.value()] = 1
            else:
                count_map[c.value()] += 1
        
        has_two_of_a_kind = False
        has_three_of_a_kind = False
    
        for k in count_map:
            if count_map[k] == 2:
                has_two_of_a_kind = True
            elif count_map[k] == 3:
                has_three_of_a_kind = True
        
        return has_three_of_a_kind and not has_two_of_a_kind
    
    def main():
        run_count = 100000
        three_of_a_kind_count = 0
    
        for x in range(run_count):
            deck = create_deck()
            shuffle_deck(deck)
            hand = draw_hand(deck)
            print_hand(hand)
    
            if three_of_a_kind(hand):
                three_of_a_kind_count += 1
        
        print("Simulation Probability: " + str(three_of_a_kind_count/ 100000.0))
    
    if __name__ == "__main__":
        main()

    The Odds of Getting Four of a Kind

    The theory for getting four of a kind is as follows:

    First, we want to choose 1 card value from 13 possible card values. Second, choose all 4 from 4 different suits. Next we want to multiply that with choosing one card value from 12 remaining values, and then multiply that by choosing 1 suit from 4 suits. This turns out to be the following:

    To get the odds, we divide 624 by the total number of ways to choose 5 cards from a 52 card deck. This turns out to be 0.024%

    The code to simulate the odds of getting four of a kind is the following:

    import random
    
    class Card():
        def __init__(self, value, suit):
            self._value = value
            self._suit = suit
        
        def value(self):
            return self._value
    
        def suit(self):
            return self._suit
        
        def __str__(self):
            return "" + self._suit + str(self._value)
    
    def create_deck():
        deck = []
        suit = ["H", "D", "S", "C"]
    
        for i in range(13):
            for j in range(4):
                deck.append(Card(i, suit[j]))
        
        return deck
    
    def shuffle_deck(deck):
        random.shuffle(deck)
    
    def draw_hand(deck):
        hand = []
    
        for i in range(5):
            c = deck.pop()
            hand.append(c)
        
        return hand
    
    
    def print_hand(hand):
        print("Hand:", end="")
        for c in hand:
            print(c, "", end="")
        print("")
    
    def has_pair(hand):
        meta_count = 0
        for i in range(5):
            count = 0
            for j in range(i+1, 5):
                if hand[i].value() == hand[j].value():
                    count += 1
                    continue
                
            
            if count == 1:
                meta_count += 1
            elif count == 2:
                return False
            elif count == 3:
                return False
        
        return meta_count == 1
    
    def three_of_a_kind(hand):
        count_map = {}
    
        for c in hand:
            if c.value() not in count_map:
                count_map[c.value()] = 1
            else:
                count_map[c.value()] += 1
        
        has_two_of_a_kind = False
        has_three_of_a_kind = False
    
        for k in count_map:
            if count_map[k] == 2:
                has_two_of_a_kind = True
            elif count_map[k] == 3:
                has_three_of_a_kind = True
        
        return has_three_of_a_kind and not has_two_of_a_kind
    
    def four_of_a_kind(hand):
        count_map = {}
    
        for c in hand:
            if c.value() not in count_map:
                count_map[c.value()] = 1
            else:
                count_map[c.value()] += 1
        
        has_four_of_a_kind = False
    
        for k in count_map:
            if count_map[k] == 4:
                return True
        
        return False
    
    
    
    def main():
        run_count = 100000
        four_of_a_kind_count = 0
    
        for x in range(run_count):
            deck = create_deck()
            shuffle_deck(deck)
            hand = draw_hand(deck)
            print_hand(hand)
    
            if four_of_a_kind(hand):
                four_of_a_kind_count += 1
        
        print("Simulation Probability: " + str(four_of_a_kind_count/ 100000.0))
    
    if __name__ == "__main__":
        main()

    The Odds of Getting Flushes, Straights, and Straight Flushes

    The theory of getting the flush is the following. There are 13 values to choose from, and we want to choose 5 different values. Then, since all of the cards must be the same suit, it must be that we multiply it by 4 choose 1 since there are a total of four suits and pick one suit. Therefore, the total number of ways to get a flush is the following:

    The odds of getting a straight is the following. There are 10 ways to get a sequence (let T stand for 10). A2345, 23456,34567,45678,56789,6789T, 789TJ, 89TJQ, 9TJQK,TJQKA. Next, there are 4 ways to choose the card 5 times each. Therefore, total straight count is the following:

    Next, we have to count the number of straight flushes so that we can subtract the count from both of the previous numbers that have been computed. The number of straight flushes will be to multiply 10 (the number of sequences) by four (the total number of suits.)

    Therefore, the total number of straights will be 10,240-40 =10,200. The total number of flushes will be 5148-40=5,108. To get the odds divide each number by the total ways to choose 5 cards from a 52 card deck.

    The code to simulate each are the following:

    import random
    
    class Card():
        def __init__(self, value, suit):
            self._value = value
            self._suit = suit
        
        def value(self):
            return self._value
    
        def suit(self):
            return self._suit
        
        def __str__(self):
            return "" + self._suit + str(self._value)
    
    def create_deck():
        deck = []
        suit = ["H", "D", "S", "C"]
    
        for i in range(13):
            for j in range(4):
                deck.append(Card(i, suit[j]))
        
        return deck
    
    def shuffle_deck(deck):
        random.shuffle(deck)
    
    def draw_hand(deck):
        hand = []
    
        for i in range(5):
            c = deck.pop()
            hand.append(c)
        
        return hand
    
    def print_hand(hand):
        print("Hand:", end="")
        for c in hand:
            print(c, "", end="")
        print("")
    
    def has_pair(hand):
        meta_count = 0
        for i in range(5):
            count = 0
            for j in range(i+1, 5):
                if hand[i].value() == hand[j].value():
                    count += 1
                    continue
                
            
            if count == 1:
                meta_count += 1
            elif count == 2:
                return False
            elif count == 3:
                return False
        
        return meta_count == 1
    
    def three_of_a_kind(hand):
        count_map = {}
    
        for c in hand:
            if c.value() not in count_map:
                count_map[c.value()] = 1
            else:
                count_map[c.value()] += 1
        
        has_two_of_a_kind = False
        has_three_of_a_kind = False
    
        for k in count_map:
            if count_map[k] == 2:
                has_two_of_a_kind = True
            elif count_map[k] == 3:
                has_three_of_a_kind = True
        
        return has_three_of_a_kind and not has_two_of_a_kind
    
    def four_of_a_kind(hand):
        count_map = {}
    
        for c in hand:
            if c.value() not in count_map:
                count_map[c.value()] = 1
            else:
                count_map[c.value()] += 1
        
        has_four_of_a_kind = False
    
        for k in count_map:
            if count_map[k] == 4:
                return True
        
        return False
    
    def has_straight(hand):
        # Get the values and sort them
        values = sorted([c.value() for c in hand])
        
        # Check for Ace-High straight explicitly: [0, 9, 10, 11, 12]
        # (Assuming 0 is Ace, 9 is 10, 10 is J, 11 is Q, 12 is K)
        if values == [0, 9, 10, 11, 12]:
            return True
    
        if len(set(values)) == 5 and (values[-1] - values[0] == 4):
            return True
    
        return False
    
    def flush(hand):
        s = hand[0].suit()
    
        for i in range(1, 5):
            if s != hand[i].suit():
                return False
        
        return True
    
    def main():
        run_count = 100000
        straight_count = 0
        flush_count = 0
        straight_flush_count = 0
    
        for x in range(run_count):
            deck = create_deck()
            shuffle_deck(deck)
            hand = draw_hand(deck)
            print_hand(hand)
    
            if flush(hand):
                flush_count += 1
            
            if has_straight(hand):
                straight_count += 1
            
            if flush(hand) and has_straight(hand):
                straight_flush_count += 1
        
        print("Simulation Probability Flush: " + str((flush_count - straight_flush_count)/ 100000.0))
        print("Simulation Probability Straight: " + str((straight_count - straight_flush_count)/ 100000.0))
        print("Simulation Probability Straight Flush: " + str((straight_flush_count)/ 100000.0))
    
    if __name__ == "__main__":
        main()

    Final Thoughts

    The remaining hands are left as exercises for you to compute the probability by hand, and then simulate in python. In this blog post, you learned how to compute the odds of getting certain hands and simulating the odds in Python.

    References

    Miller, S. J. (2017). The probability Lifesaver: All the tools you need to understand chance. Princeton University Press.

  • Logistic Population Models

    Logistic Population Models

    Logistic population models are one of the most important tools in mathematical biology because they describe how populations grow when resources are limited. Unlike simple exponential growth models, logistic models capture the realistic behavior of populations slowing down as they approach a maximum sustainable size, known as the carrying capacity. From ecology and epidemiology to economics and engineering, these models provide a powerful framework for understanding real-world systems.

    In this blog post, you will learn how to develop a mathematical model for logistic population growth step by step. We will begin by exploring the key ideas behind logistic growth and how they differ from exponential models. Then, we will build the governing differential equation and interpret the meaning of each parameter in the model.

    Modeling the Problem

    Suppose a population only changes based on birth and death rates.

    Let B(t) = The number of births per unit population per unit of time t.

    Let D(t) = the number of deaths per unit population per unit of time t.

    This means the following:

    This means the change in population is the following:

    which means that

    The error should approach zero as delta t approaches 0, which yields the following differential equation:

    Now, suppose that the birthrate is a linear decreasing function of the population size, so B = B_0 – B_1*P where B_0 and B_1 are constants and greater than zero. Also suppose the death rate is constant, then the following holds:

    Therefore, this results in the following equation where a = B_0 – D_0 and b = B_1

    It is useful to rewrite as the following where k=b and M = a/b. Here M is the limiting population.

    Solving for a Solution

    The equation is separable, so it is separated as follows:

    Now, we just integrate both sides. Also, note: for the left hand side, one must use partial fractions to integrate. You should get the following result:

    Solving for P, you should get the following the result:

    where B is equal to the following when t = 0,

    Plugging that value for B, we obtain the final result for the population:

    Solving a Logistic Population Problem

    Suppose in 1885 the population of a certain country a population of 50 million with a growing rate of 750k per year. Suppose the population in 1940 is 100 million with a growing rate of 1 million per year.

    Given the following equation:

    we can solve the following two equations simultaneously:

    Doing so will give the values M = 200 and k = 0.0001

    Now, let 1940 be t=0. Since M is the limiting population, we have that M = 200 million and P_0 = 100 million

    If we want to know what the population will be in the year 2000, we can let t=60, and plug the values into the equation we solved in the previous section. This becomes

    Therefore, the population is approximately 153.7 million people in the year 2000.

    Final Thoughts

    In this blog post, you learned how to model logistic populations, and you learned how to solve problems using the derived equation.

  • Solving Systems of Differential Equations with MATLAB

    Solving Systems of Differential Equations with MATLAB

    A system of differential equations contain two or more equations that are related by two dependent variables that share a single common independent variable like t for time. Applications of systems of differential equations range from engineering, science, and applied mathematics.

    Consider the following system of differential equations:

    First, we manually solve for a general solution of the system, then we use MATLAB to come up with a solution.

    Solving for a General Solution Manually

    The first step is to create a coefficient matrix of the system, then solve for the eigenvalues and associated eigenvectors.

    The coefficient matrix for the system is the following:

    Next, the eigenvalues are solved for, which means solve for the determinant, and set it equal to zero:

    By solving this, you will obtain the following eigenvalues:

    Now that we have the eigenvalues, we can solve for the associated eigenvectors. This can be done by solving the following for each eigenvalue:

    Doing so, we obtain the following associated eigenvectors:

    Therefore, the general solution will be the following:

    Solving For a General Solution with MATLAB

    First, let’s solve for the eigenvectors and eigenvalues using MATLAB. The following code will do that.

    % Coefficient matrix 
    A = [1 2; 2 1];
    
    % Solve for eigenvalues D and eigenvectors V
    [V,D] = eig(A);
    
    % Get the first eigenvector and scale
    eigV1 = V(:,1) ./ min(V(:,1))
    
    % Get the second eigenvector and scale
    eigV2 = V(:,2) ./ min(V(:,2))
    
    x1Vec = -1:0.05:1;
    x2Vec = -1:0.05:1;
    
    % x2 = -x1
    v1 = -1*x1Vec;
    % x2 = x1
    v2 = x1Vec;
    
    % Plot the eigenvectors
    figure;
    hold on;
    plot(x1Vec,v1,'b');
    plot(x1Vec,v2,'b');
    xlabel('x_{1}');
    ylabel('x_{2}');
    grid on;
    
    title('Eigenvectors');
    % put the axes on the same limits
    xlim([-1 1]);
    ylim([-1 1]);

    The following is the plot that was generated by MATLAB of the two eigenvectors:

    Next, let’s plot the direction field, which will show the direction of solution curves with respect to time.

    The following MATLAB code will do that:

    [x1, x2] = meshgrid(x1Vec,x2Vec);
    % Compute the direction field at the grid of points
    x1dot = x1 + 2*x2;
    x2dot = 2*x1 + x2;
    
    % The quiver command can be used to plot the direction field
    figure;
    quiver(x1,x2,x1dot, x2dot);
    xlabel('x_{1}');
    ylabel('x_{2}');
    title('Direction Field');
    xlim([-1 1]);
    ylim([-1 1]);
    

    The following direction field was generated by MATLAB:

    Next, a phase portrait will be generated with MATLAB, which will show the relationship between x1 and x2 with respect to time. The arrows shows the point on a particular solution curve with respect to time.

    figure;
    streamslice(x1Vec,x2Vec,x1dot,x2dot);
    title('Phase Portrait');

    The following is the plot that was generated:

    This image indicates that the point (0,0) is a saddle point. Also, you can tell that the graph will be a saddle because the eigenvalues are both real and one is positive and the other is negative.

    Final Words

    You saw an example of solving a system of differential equations, and you learned how to solve it using MATLAB.

  • Katherine Johnson and Numerically Approximating a Solution to a Differential Equation with Euler’s Method using MATLAB

    Katherine Johnson and Numerically Approximating a Solution to a Differential Equation with Euler’s Method using MATLAB

    Euler’s method is used for numerical approximation of a solution to a differential equation that can’t be solved for an explicit solution using elementary methods, such as substitution for homogeneous equations or Bernoulli equations (Henry Edwards et al., 2022). Katherine Johnson used Euler’s method to solve real-world problems at NASA (Meyers, 2017). Personally, I disagree with her claim that math is either absolutely right or absolutely wrong, though. 

    The algorithm for Euler’s method is to first pick an initial point 

    of the particular solution you want to compute (Henry Edwards et al., 2022). Then, one chooses a horizontal step-size h, which is the horizontal distance between the approximated points of the solution. The formula to calculate the (n+1)-th point is the following (Henry Edwards et al., 2022):

    where

    One must be careful when using Euler’s method because there is a local error when moving away from the initial point, which is a vertical distance from the approximated point to the actual point of the particular solution curve (Henry Edwards et al., 2022). As one moves further from the initial point, the error accumulates, and it is called cumulative error (Henry Edwards et al., 2022). The remedy for this is to choose a sufficiently small step size, which will reduce the error (Henry Edwards et al., 2022). However, there is typically a tradeoff when picking h because when h is too small, it may take thousands of years to compute even with a computer (Henry Edwards et al., 2022).

    Katherine Johnson was an African American applied mathematician who worked at NASA (Meyers, 2017). She suggested to use Euler’s method to solve differential equations related to the trajectory of astronaut John Glenn’s space capsule. Despite the method being “ancient”, as described by one of her male colleagues, she rebutted that “it works numerically” (Meyers, 2017).

    When Katherine Johnson claims “math: you’re either right or wrong — and that I liked about it” (National Geographic, 2021, 0:45) , I tend to disagree. I disagree because sometimes oversimplified models can enable one to capture important insights into the problem. For example, the Lotka-Volterra predator-prey model allows one to see that predator-prey populations oscillate; however, the models typically do not allow one to actually predict the populations in nature (Chasnov, 2022; Clark & Neuhauser, 2022). This is a case in mathematics where it is possible to be partially correct. 

    In conclusion, Euler’s method is an old yet effective way to numerically approximate solutions to differential equations. It has been used to solve real-world problems in NASA by applied mathematicians like Katherine Johnson to calculate trajectories of a space capsule. Also, despite Katherine Johnson’s claim that one is either right or wrong in math, I think that it is possible to be partially correct in math in the case of oversimplified models that allow one to gain further insights into problems like the Lotka-Volterra predator-prey model.

    Using MATLAB to Obtain a Numerical Approximation of a Solution

    Next, MATLAB will be used to solve the following differential equation:

    with initial condition y(0) = -3.

    The following is the MATLAB code to do so:

    % Euler's Method for solving:
    % y' = x + (1/5)y
    % y(0) = -3
    
    clc;
    clear;
    
    % Step size
    h = 1;
    
    % Interval
    x0 = 0;
    xn = 5;
    
    % Initial condition
    y0 = -3;
    
    % Number of steps
    n = (xn - x0)/h;
    
    % Arrays to store values
    x = zeros(1, n+1);
    y = zeros(1, n+1);
    
    % Initial values
    x(1) = x0;
    y(1) = y0;
    
    % Euler's Method loop
    for i = 1:n
        x(i+1) = x(i) + h;
        y(i+1) = y(i) + h * ( x(i) + (1/5)*y(i) );
    end
    
    % Display results
    disp('   x          y');
    disp([x' y']);
    
    % Plot the approximation
    plot(x, y, '-', 'LineWidth', 1.5);
    xlabel('x');
    ylabel('y');
    title('Euler Method Approximation');
    grid on;

    References

    Chasnov, J. R. (2022, January 5). 1.4: The Lotka-Volterra predator-prey model. Mathematics LibreTexts; Libretexts. https://math.libretexts.org/Bookshelves/Applied_Mathematics/Mathematical_Biology_%28Chasnov%29/01%3A_Population_Dynamics/1.04%3A_The_Lotka-Volterra_Predator-Prey_Model

    Clark, A. T., & Neuhauser, C. (2018). Harnessing uncertainty to approximate mechanistic models of interspecific interactions. Theoretical Population Biology123, 35–44. https://doi.org/10.1016/j.tpb.2018.05.002

    Henry Edwards, C., Penney, D. E., & Calvis., D. (2022). Differential Equations and Boundary Value Problems: Computing and Modeling (6th Edition).

    Meyers, C. (2017, February 24). Exploring the math in “hidden figures.” AIP. https://www.aip.org/inside-science/exploring-the-math-in-hidden-figures