# notes

path: Sound_recording_as_wave_phenomena.md
sortable_path: Sound_recording_as_wave_phenomena.md
title: 738d0481_sound_recording_as_wave_phenomena
checksum: a9dc1aead9226467ca2ae21b8f7233ae0d3b326c9e2b6048e2d122ff0141b496
created: 2025-10-01 13:31:01.535057558+00:00
modified: 2025-08-13 15:57:13.755201654+00:00
metadata: {"created":"Thursday, January 02, 2025","tags":["sound","analogue","physics"]}
lead:
  When the microphone captures alterations in sound pressure and converts it into
  an electric signal, we have a scenario where the same formal or mathematical
  relationships in the phenomena (amplitude, frequency, wavelength) are being
  expressed in two different modes of matter.

body:
  When the microphone captures alterations in sound pressure and converts it into
  an electric signal, we have a scenario where the same formal or mathematical
  relationships in the phenomena (amplitude, frequency, wavelength) are being
  expressed in two different modes of matter.

  This is quite profound especially as the resulting electrical waveform is
  _identical_ to the original sound wave. This is effectively what enables
  accurate playback. We can convert between the two mediums seamlessly providing
  we captured their initial expression accurately.

  It's like two different people doing the same dance:

  In air:

  - The dancers are air molecules
  - They move back and forth in waves of compression and rarefaction
  - Their "dance pattern" is the sound wave

  In wire:

  - The dancers are electrons
  - They move back and forth creating waves of voltage
  - They perform the exact same "dance pattern"

raw_content:
  ---
  tags: [sound, analogue, physics]
  created: Thursday, January 02, 2025
  ---

  # 738d0481_sound_recording_as_wave_phenomena

  When the microphone captures alterations in sound pressure and converts it into
  an electric signal, we have a scenario where the same formal or mathematical
  relationships in the phenomena (amplitude, frequency, wavelength) are being
  expressed in two different modes of matter.

  This is quite profound especially as the resulting electrical waveform is
  _identical_ to the original sound wave. This is effectively what enables
  accurate playback. We can convert between the two mediums seamlessly providing
  we captured their initial expression accurately.

  It's like two different people doing the same dance:

  In air:

  - The dancers are air molecules
  - They move back and forth in waves of compression and rarefaction
  - Their "dance pattern" is the sound wave

  In wire:

  - The dancers are electrons
  - They move back and forth creating waves of voltage
  - They perform the exact same "dance pattern"


path: Soundness.md
sortable_path: Soundness.md
title: Soundness
checksum: d7502906245e61759a05792bb1ac1529a125a95843565c46f58eb1f289042564
created: 2025-10-01 13:31:01.539979713+00:00
modified: 2025-08-13 15:57:14.024193103+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  Recall that in the definition of
  [deductive validity](Validity_and_entailment.md#validity)
  we do not say: an argument is valid iff if the premises _are true_ and the
  conclusion _is true_. We say _if it is possible for the premises to be true_.
  This is important: we are not interested in the actual truth of the premises or
  the conclusion.

body:
  Recall that in the definition of
  [deductive validity](Validity_and_entailment.md#validity)
  we do not say: an argument is valid iff if the premises _are true_ and the
  conclusion _is true_. We say _if it is possible for the premises to be true_.
  This is important: we are not interested in the actual truth of the premises or
  the conclusion.

  #### Demonstration

  Therefore this argument is valid:

  ```
  (P1) Oranges are the same colour as bananas.
  (P2) Bananas are yellow.
  ____________________________________________
  (C) Oranges are yellow.
  ```

  Of course oranges are not yellow but _were_ (P1) true, then given (P2), the
  conclusion must also be true.

  This argument is also valid:

  ```
  (P1) Oranges are the same colour as carrots.
  (P2) Carrots are orange.
  ____________________________________________
  (C) Oranges are orange in colour.
  ```

  The difference here is that the premises happen to be true and, given that the
  argument is valid, the conclusion must also be true. What we have defined here
  is **soundness**: the argument is said to be sound as well as valid. This is an
  additional and stronger criterion of evaluation.

  > An argument is sound if and only if it is deductively valid and all its
  > premises are true.

  We must not forget that truth alone is not the sole condition for soundness. We
  can have arguments whose conclusion and premises are all true without the
  argument being sound:

  ```
  (P1) London is the capital of the United Kingdom
  (P2) The capital of the United Kingdom is in the southern part of the United Kingdom.
  (P3) Cambridge is not the capital of the United Kingdom
  ____________________________________________
  (C) London is south of Cambridge
  ```

  All propositions are true here but the argument is not deductively valid: the
  premises are all true but the conclusion is false.

  We can also have arguments which are valid but which are not sound:

  ```
  (P1) Vitamin C prevents colds.
  (P2) Vitamin C does not prevent colds.
  ____________________________________________
  (C) Vitamin C is harmless
  ```

  This argument is valid because we cannot consistently assert the premises and
  deny the conclusion. In either case, the conclusion can be said to follow from
  the premises. The problem is that we cannot consistently assert both premises:
  it is not possible for both propositions to be true at the same time.

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Soundness

  Recall that in the definition of
  [deductive validity](Validity_and_entailment.md#validity)
  we do not say: an argument is valid iff if the premises _are true_ and the
  conclusion _is true_. We say _if it is possible for the premises to be true_.
  This is important: we are not interested in the actual truth of the premises or
  the conclusion.

  #### Demonstration

  Therefore this argument is valid:

  ```
  (P1) Oranges are the same colour as bananas.
  (P2) Bananas are yellow.
  ____________________________________________
  (C) Oranges are yellow.
  ```

  Of course oranges are not yellow but _were_ (P1) true, then given (P2), the
  conclusion must also be true.

  This argument is also valid:

  ```
  (P1) Oranges are the same colour as carrots.
  (P2) Carrots are orange.
  ____________________________________________
  (C) Oranges are orange in colour.
  ```

  The difference here is that the premises happen to be true and, given that the
  argument is valid, the conclusion must also be true. What we have defined here
  is **soundness**: the argument is said to be sound as well as valid. This is an
  additional and stronger criterion of evaluation.

  > An argument is sound if and only if it is deductively valid and all its
  > premises are true.

  We must not forget that truth alone is not the sole condition for soundness. We
  can have arguments whose conclusion and premises are all true without the
  argument being sound:

  ```
  (P1) London is the capital of the United Kingdom
  (P2) The capital of the United Kingdom is in the southern part of the United Kingdom.
  (P3) Cambridge is not the capital of the United Kingdom
  ____________________________________________
  (C) London is south of Cambridge
  ```

  All propositions are true here but the argument is not deductively valid: the
  premises are all true but the conclusion is false.

  We can also have arguments which are valid but which are not sound:

  ```
  (P1) Vitamin C prevents colds.
  (P2) Vitamin C does not prevent colds.
  ____________________________________________
  (C) Vitamin C is harmless
  ```

  This argument is valid because we cannot consistently assert the premises and
  deny the conclusion. In either case, the conclusion can be said to follow from
  the premises. The problem is that we cannot consistently assert both premises:
  it is not possible for both propositions to be true at the same time.


path: Split_into_array_in_Bash.md
sortable_path: Split_into_array_in_Bash.md
title: Splitting input into an array
lead: ## readarray
checksum: b613cfe6a16f00e820880c671aeeccbf3f0ecdae9e3770fc68016d7b50c6b413
created: 2025-10-01 13:31:01.55917516+00:00
modified: 2025-08-13 15:57:14.054439003+00:00
metadata: {"tags":["shell"]}
body:
  ## readarray

  `readarray` makes it really easy to split input into an array based on new
  lines. Say we have this file as input:

  ```
  123
  456
  789
  ```

  Then we can split like so:

  ```bash
  readarray -t name_for_array < ./input.text

  # Print all elements
  echo "${name_for_array[@]}"

  # Print element by index

  echo "${name_for_array[1]}"
  456
  ```

  If we want to read a string directly:

  ```bash
  readarray -t new_name_for_array <<< "here
  is
  some
  text"

  echo "${new_name_for_array[1]}"
  is
  ```

  > The _-t_ flag removes the trailing newline

  See more: https://linuxhint.com/split-string-array-bash/

  ## read

  For delimiters other than a space we have to use `read`, combined with `IFS` the
  **Internal Field Separator**.

  For example, to split by comma:

  ```plaintext
  # comma-input.txt
  something,something else,something more
  ```

  ```bash
  IFS=',' read -a arr < ./comma_inputs.txt
  ```

  > We use the `-a` flag to signal that we want each item read returned as an
  > element in an array

  ## mapfile

  We can use `mapfile` to read the lines of a input file in set segments.

  For the following file:

  ```
  line one
  line two
  line three
  line four
  ```

  We could generate the following output:

  ```
  -----SNIP-----
  line one
  line two
  -----SNIP-----
  line three
  line four
  ```

  With:

  ```bash
  while mapfile -t -n 2 ary && ((${#ary[@]})); do
    printf '%s\n' "${ary[@]}"
    printf -- '--- SNIP ---\n'
   done < $testInput
  ```

  So, while there are input lines, map each line into an array and return it in
  groups of 2.

raw_content:
  ---
  tags:
    - shell
  ---

  # Splitting input into an array

  ## readarray

  `readarray` makes it really easy to split input into an array based on new
  lines. Say we have this file as input:

  ```
  123
  456
  789
  ```

  Then we can split like so:

  ```bash
  readarray -t name_for_array < ./input.text

  # Print all elements
  echo "${name_for_array[@]}"

  # Print element by index

  echo "${name_for_array[1]}"
  456
  ```

  If we want to read a string directly:

  ```bash
  readarray -t new_name_for_array <<< "here
  is
  some
  text"

  echo "${new_name_for_array[1]}"
  is
  ```

  > The _-t_ flag removes the trailing newline

  See more: https://linuxhint.com/split-string-array-bash/

  ## read

  For delimiters other than a space we have to use `read`, combined with `IFS` the
  **Internal Field Separator**.

  For example, to split by comma:

  ```plaintext
  # comma-input.txt
  something,something else,something more
  ```

  ```bash
  IFS=',' read -a arr < ./comma_inputs.txt
  ```

  > We use the `-a` flag to signal that we want each item read returned as an
  > element in an array

  ## mapfile

  We can use `mapfile` to read the lines of a input file in set segments.

  For the following file:

  ```
  line one
  line two
  line three
  line four
  ```

  We could generate the following output:

  ```
  -----SNIP-----
  line one
  line two
  -----SNIP-----
  line three
  line four
  ```

  With:

  ```bash
  while mapfile -t -n 2 ary && ((${#ary[@]})); do
    printf '%s\n' "${ary[@]}"
    printf -- '--- SNIP ---\n'
   done < $testInput
  ```

  So, while there are input lines, map each line into an array and return it in
  groups of 2.


path: Stack_memory.md
sortable_path: Stack_memory.md
title: Stack memory
lead: ## Summary
checksum: a6795eec5cd09a44ca3c911da9b767b0ae15be93d4ca9c1eb6f336d9f6107b2c
created: 2025-10-01 13:31:01.564860249+00:00
modified: 2025-08-13 15:57:14.029919485+00:00
metadata: {"created":"Tuesday, April 16, 2024","id":"1fig","tags":["memory"]}
body:
  ## Summary

  - Along with [[Heap_memory|heap memory]] heap memory, programs make use of
    _stack memory_ during runtime.

  - Best suited for small-sized singular values that are temporary/ephemeral
    rather than large or complex data types that need to persist for a longer
    time.

  - Specifically used to keep track of memory used during function executions
    within a given scope: control flow, local variables, returned value.

  - Uses a LIFO data-structure

  Similarly to the [[Stacks|stack data structure]], it works on the basis of LIFO:
  the last item put on the stack is the first item to be withdrawn.

  Note, this doesn't mean that the data at the memory addresses that comprise the
  stack can _only_ be accessed in a LIFO manner. Any item currently on the stack
  can be accessed (be read or modified) at any time. LIFO applies when it is is
  time to clear memory from the stack: the most recent addition is cleared first
  from the top down.

  ### Stack pointer

  A **stack pointer** is used to manage the items in the stack. This value is
  stored in a [[CPU_architecture#registers|register]] and its value is the memory
  address of the item currently on the top of the stack. Note: _not_ the value
  itself, the _address_ of the value.

  When a new value is added to the top of the stack, the pointer's value is
  adjusted to increase the size of the stack and make room for the new value. So
  this will be a new memory address for the latest item. When an item is removed,
  the reverse happens. The previously top address is cleared and the pointer
  points to the new top item.

  ## Threads

  For every thread that a program spawns, it gets its own stack or "stack frame".
  Stack memory within the thread can only be accessed by processes within that
  thread which is what makes it well suited for managing scoped function memory.

  ### Role in program execution

  1. Function call management: a stack records the key information pertaining to
     the execution of a function: parameters, local variables, the location of the
     address for the returned value.

  2. Local variable storage: storing the variables local to a function's scope and
     removing them once the function returns, avoiding
     [[Memory_leaks|memory leaks]]

  3. Control flow: record from where a function is called and where its return
     value should be stored, meaning the right value is always returned no matter
     how nested it is.

  4. Recursion: individuating each call of a function that calls itself and its
     given step to avoid infinite loops

raw_content:
  ---
  id: 1fig
  tags: [memory]
  created: Tuesday, April 16, 2024
  ---

  # Stack memory

  ## Summary

  - Along with [[Heap_memory|heap memory]] heap memory, programs make use of
    _stack memory_ during runtime.

  - Best suited for small-sized singular values that are temporary/ephemeral
    rather than large or complex data types that need to persist for a longer
    time.

  - Specifically used to keep track of memory used during function executions
    within a given scope: control flow, local variables, returned value.

  - Uses a LIFO data-structure

  Similarly to the [[Stacks|stack data structure]], it works on the basis of LIFO:
  the last item put on the stack is the first item to be withdrawn.

  Note, this doesn't mean that the data at the memory addresses that comprise the
  stack can _only_ be accessed in a LIFO manner. Any item currently on the stack
  can be accessed (be read or modified) at any time. LIFO applies when it is is
  time to clear memory from the stack: the most recent addition is cleared first
  from the top down.

  ### Stack pointer

  A **stack pointer** is used to manage the items in the stack. This value is
  stored in a [[CPU_architecture#registers|register]] and its value is the memory
  address of the item currently on the top of the stack. Note: _not_ the value
  itself, the _address_ of the value.

  When a new value is added to the top of the stack, the pointer's value is
  adjusted to increase the size of the stack and make room for the new value. So
  this will be a new memory address for the latest item. When an item is removed,
  the reverse happens. The previously top address is cleared and the pointer
  points to the new top item.

  ## Threads

  For every thread that a program spawns, it gets its own stack or "stack frame".
  Stack memory within the thread can only be accessed by processes within that
  thread which is what makes it well suited for managing scoped function memory.

  ### Role in program execution

  1. Function call management: a stack records the key information pertaining to
     the execution of a function: parameters, local variables, the location of the
     address for the returned value.

  2. Local variable storage: storing the variables local to a function's scope and
     removing them once the function returns, avoiding
     [[Memory_leaks|memory leaks]]

  3. Control flow: record from where a function is called and where its return
     value should be stored, meaning the right value is always returned no matter
     how nested it is.

  4. Recursion: individuating each call of a function that calls itself and its
     given step to avoid infinite loops


path: Stacks.md
sortable_path: Stacks.md
title: A stack is a linear data structure that observes LIFO
checksum: a141ae765f7298ff9bd03a8d68bdae6ac6ac56e84d1369198460bc254462f9a8
created: 2025-10-01 13:31:01.595692567+00:00
modified: 2025-08-13 15:57:13.816906381+00:00
metadata: {"tags":["data-structures"]}
lead:
  Think of a stack like a pile of books: the last book that you add is the nearest
  to the top and therefore the first one that you can remove.

body:
  Think of a stack like a pile of books: the last book that you add is the nearest
  to the top and therefore the first one that you can remove.

  If you want to get a book that is not at the top, you have to first remove the
  books that are above it to get to it.

  This type of data structure is linear and only allows sequential, not random,
  access. It is an example of 'last one in first one out'.

  ## We can build a stack from an array

  A stack is an example of a data structure that can be built by adapting an
  array. If you think about it, all that is needed is an array to store the data,
  an `array push` method to add elements to the 'end' or the 'bottom' of the stack
  and an `array pop` method to remove the element at the top.

  ## Demonstration

  Below we create a stack constructor, using a class. An object created from this
  template will have the following properties and methods:

  - `items[]` → an array to store the data
  - `push()` → a method to add an element to the end of the stack
  - `pop()` → a method to remove an element from the front

  In addition we have the following helpers, which allow us to check the status of
  the stack and retrieve information about it:

  - `isEmpty()` → check if the stack is populated or not
  - `clear()` → empty the stack of its content (therefore making `isEmpty()`
    return `true`)
  - `size` → a property corresponding to the stack's length

  ```js
  class Stack {
    items = []; // the array that will store the elements that comprise the stack
    push = (element) => this.items.push(element); // add an element to the end of the stack
    pop = () => this.items.pop(); // remove and return the last element from the stack

    // We can add some useful helper methods, that return info about the state of the stack:
    isEmpty = () => this.items.length === 0; // return true if the stack is empty
    clear = () => (this.items.length = 0); // empty the stack
    size = () => this.items.length; // count elements in stack
  }
  ```

  ## Run through

  ```js
  let stack = new Stack();
  test.push(1); // Add some elements to the stack
  test.push(2);
  test.push(3);

  // Stack now looks like:
  console.log(stack.items); // [1, 2, 3]

  // Let's try removing the last element
  stack.pop(); // 3 -> this was the last element we added, so it's the first one that comes out

  // Now the stack looks like this:
  // [1,2]

  // Let's add a new element
  test.push(true);

  // Now the stack looks like:
  // [1,2, true]
  ```

  ## Practical applications

  - Any application that wants to got 'back in time' must utilise a stack. For
    example, the 'undo' function in most software is a function of a stack. The
    most recent action is at the top, and under that is the second most recent and
    so on all the way back to the first action.
  - Recursive functions: a function that calls itself repeatedly until a boundary
    condition is met is using a stack structure. As you drill down through the
    function calls you start from the most recent down to the last.
  - Balancing parentheses. Say you want to check if the following string is
    balanced `[()]` . Every time you find an opening parentheses. You push that to
    the front of a stack. You then compare the closing parentheses with the order
    of the stack. The same could be done when seeking to find palindromes. This
    sort of thing could be a code challenge so build an example.

raw_content:
  ---
  tags:
    - data-structures
  ---

  _A stack visualised vertically_ ![stack2.svg](stack2.svg)

  _A stack visualised horizontally_ ![stack1.svg](stack1.svg)

  ## A stack is a linear data structure that observes LIFO

  Think of a stack like a pile of books: the last book that you add is the nearest
  to the top and therefore the first one that you can remove.

  If you want to get a book that is not at the top, you have to first remove the
  books that are above it to get to it.

  This type of data structure is linear and only allows sequential, not random,
  access. It is an example of 'last one in first one out'.

  ## We can build a stack from an array

  A stack is an example of a data structure that can be built by adapting an
  array. If you think about it, all that is needed is an array to store the data,
  an `array push` method to add elements to the 'end' or the 'bottom' of the stack
  and an `array pop` method to remove the element at the top.

  ## Demonstration

  Below we create a stack constructor, using a class. An object created from this
  template will have the following properties and methods:

  - `items[]` → an array to store the data
  - `push()` → a method to add an element to the end of the stack
  - `pop()` → a method to remove an element from the front

  In addition we have the following helpers, which allow us to check the status of
  the stack and retrieve information about it:

  - `isEmpty()` → check if the stack is populated or not
  - `clear()` → empty the stack of its content (therefore making `isEmpty()`
    return `true`)
  - `size` → a property corresponding to the stack's length

  ```js
  class Stack {
    items = []; // the array that will store the elements that comprise the stack
    push = (element) => this.items.push(element); // add an element to the end of the stack
    pop = () => this.items.pop(); // remove and return the last element from the stack

    // We can add some useful helper methods, that return info about the state of the stack:
    isEmpty = () => this.items.length === 0; // return true if the stack is empty
    clear = () => (this.items.length = 0); // empty the stack
    size = () => this.items.length; // count elements in stack
  }
  ```

  ## Run through

  ```js
  let stack = new Stack();
  test.push(1); // Add some elements to the stack
  test.push(2);
  test.push(3);

  // Stack now looks like:
  console.log(stack.items); // [1, 2, 3]

  // Let's try removing the last element
  stack.pop(); // 3 -> this was the last element we added, so it's the first one that comes out

  // Now the stack looks like this:
  // [1,2]

  // Let's add a new element
  test.push(true);

  // Now the stack looks like:
  // [1,2, true]
  ```

  ## Practical applications

  - Any application that wants to got 'back in time' must utilise a stack. For
    example, the 'undo' function in most software is a function of a stack. The
    most recent action is at the top, and under that is the second most recent and
    so on all the way back to the first action.
  - Recursive functions: a function that calls itself repeatedly until a boundary
    condition is met is using a stack structure. As you drill down through the
    function calls you start from the most recent down to the last.
  - Balancing parentheses. Say you want to check if the following string is
    balanced `[()]` . Every time you find an opening parentheses. You push that to
    the front of a stack. You then compare the closing parentheses with the order
    of the stack. The same could be done when seeking to find palindromes. This
    sort of thing could be a code challenge so build an example.


path: Stale_branches_and_pruning.md
sortable_path: Stale_branches_and_pruning.md
title: Stale branches and pruning
checksum: d34c069d9cd0bcb5b5771381334d486c897fc97126ae662b213ca3fd8c8dfd30
created: 2025-10-01 13:31:01.604300153+00:00
modified: 2025-08-13 15:57:13.816303847+00:00
metadata: {"tags":["git"]}
lead:
  A **stale branch** is a
  [remote tracking branch](Remote_tracking_branches.md) that **no
  longer tracks anything** because the actual branch on the remote has been
  deleted.

body:
  A **stale branch** is a
  [remote tracking branch](Remote_tracking_branches.md) that **no
  longer tracks anything** because the actual branch on the remote has been
  deleted.

  If _you_ delete the remote branch, the remote tracking branch will also be
  deleted. However if a _collaborator_ deletes the remote branch, your remote
  tracking branch will remain, becoming a stale branch.

  In order to get rid of it, we need to manually remove it. We can do this with
  the general `prune` command:

  ```sh
  git remote prune origin

  # or run a preview of the action

  git remote prune origin --dry-run
  ```

raw_content:
  ---
  tags: [git]
  ---

  # Stale branches and pruning

  A **stale branch** is a
  [remote tracking branch](Remote_tracking_branches.md) that **no
  longer tracks anything** because the actual branch on the remote has been
  deleted.

  If _you_ delete the remote branch, the remote tracking branch will also be
  deleted. However if a _collaborator_ deletes the remote branch, your remote
  tracking branch will remain, becoming a stale branch.

  In order to get rid of it, we need to manually remove it. We can do this with
  the general `prune` command:

  ```sh
  git remote prune origin

  # or run a preview of the action

  git remote prune origin --dry-run
  ```


path: Step_functions_and_state_machines.md
sortable_path: Step_functions_and_state_machines.md
title: AWS Step functions and state machines
checksum: a290427d05b45787caf9f55ac711de8b9361af425c2875ebe09a2fa9ffe38ba2
created: 2025-10-01 13:31:01.616408271+00:00
modified: 2025-08-13 15:57:13.814277127+00:00
metadata: {"tags":["AWS"]}
lead:
  _AWS Step Functions_ is an AWS service that allows you to coordinate distributed
  applications and microservices by creating workflows.

body:
  _AWS Step Functions_ is an AWS service that allows you to coordinate distributed
  applications and microservices by creating workflows.

  Workflows are made up of a series of steps and are known as **state machines**.
  A state machine defines:

  - the states of the workflow
  - the transitions between states
  - the inputs and outputs of each state

  State machines are defined via a JSON object which specifies the states and the
  transitions between them. It also includes error catchers and retry logic.

  At the beginning you define a `StartAt` state which is the entrypoint of the
  state machine. This can be manually triggered, or more likely, triggered by
  another AWS service such as a
  [Lambda](Lambda_programming_model.md), an
  [API Gateway](AWS_API_Gateway.md) request or a messaging/queue
  event.

  The state machine ultimately ends at an end state. In between are various
  intermediary states which can include:

  - Task states (do something)
  - Choice states (do something based on conditions)
  - Wait states
  - Success states (if preceding state succeeds do X)
  - Fail states (if preceding state fails do Y)
  - Parallel states

  The diagram below shows the definition of a given state machine. On the left is
  the JSON specification. On the right is a diagramatic representation that shows
  the control flow and all possible steps that comprise a given pathway / state:

  ![](/img/state-machine-definition.png)

  For each execution of the state machine (each time it is triggered) you can
  review the runtime. The flow diagram will highlight green to show the given
  pathway, and you can also see the inputs and outputs for each step and any
  errors:

  ![](/img/state-machine-execution.png)

raw_content:
  ---
  tags: [AWS]
  ---

  # AWS Step functions and state machines

  _AWS Step Functions_ is an AWS service that allows you to coordinate distributed
  applications and microservices by creating workflows.

  Workflows are made up of a series of steps and are known as **state machines**.
  A state machine defines:

  - the states of the workflow
  - the transitions between states
  - the inputs and outputs of each state

  State machines are defined via a JSON object which specifies the states and the
  transitions between them. It also includes error catchers and retry logic.

  At the beginning you define a `StartAt` state which is the entrypoint of the
  state machine. This can be manually triggered, or more likely, triggered by
  another AWS service such as a
  [Lambda](Lambda_programming_model.md), an
  [API Gateway](AWS_API_Gateway.md) request or a messaging/queue
  event.

  The state machine ultimately ends at an end state. In between are various
  intermediary states which can include:

  - Task states (do something)
  - Choice states (do something based on conditions)
  - Wait states
  - Success states (if preceding state succeeds do X)
  - Fail states (if preceding state fails do Y)
  - Parallel states

  The diagram below shows the definition of a given state machine. On the left is
  the JSON specification. On the right is a diagramatic representation that shows
  the control flow and all possible steps that comprise a given pathway / state:

  ![](/img/state-machine-definition.png)

  For each execution of the state machine (each time it is triggered) you can
  review the runtime. The flow diagram will highlight green to show the given
  pathway, and you can also see the inputs and outputs for each step and any
  errors:

  ![](/img/state-machine-execution.png)


path: Strategies_for_constructing_proofs.md
sortable_path: Strategies_for_constructing_proofs.md
title: Strategies for constructing proofs in propositional logic
lead: ## General strategy
checksum: d836e2a38a2df56b856a2642dd68e59a49bec4680cf6d9dae8c194f2536358c0
created: 2025-10-01 13:31:01.63625462+00:00
modified: 2025-08-13 15:57:13.926645986+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  ## General strategy

  - Break complex propositions into simpler sentences by using the elimination
    rules
  - Recombine simple propositions into complex propositions using the introduction
    rules.

  ## Goal analysis

  The approach above describes the general form of a proof but of course it will
  not always work and there will be cases where the route to the desired
  derivation is more circuitous. In these instances it is to best to combine this
  general top level strategy with goal analysis.

  Goal analysis is a [recursive](Recursion.md) strategy which
  proceeds by using a 'goal' proposition to guide the construction of intermediary
  derivations.

  Assume that we want to show that an argument is
  [valid](Validity_and_entailment.md#validity). Then our
  ultimate goal is to derive the conclusion from the premises we are given. We
  first ask ourselves: _which propositions if we could derive them, would allow us
  to easily derive the conclusion_? (For example, these propositions might be two
  simple propositions that when combined with
  [Conjunction Introduction](Conjunction_Introduction.md) give us
  the conclusion.) Deriving these propositions then becomes the new intermediate
  goal.

  If arriving at these propositions is not trivial, we then ask ourselves the
  question again: _which propositions would permit us to derive the intermediary
  propositions we need_? You keep working back in this manner until you reach a
  base level. Then it is just a matter or working upwards from each set of derived
  intermediary propositions until you reach the ultimate goal.

  ### Demonstration

  Let's say we want to prove $(L \lor A) \land D$ from the propositions $\lnot N$
  and $((\lnot N \rightarrow L) \land (D \leftrightarrow \lnot N))$.

  First, we consider what is the easiest possible way of achieving the proposition
  $(L \lor A) \land D$. Clearly it is to separately derive each disjunct
  ($L \lor A$ and $D$) and then combine them with
  [Conjunction Introduction](Conditional_Introduction.md). This
  provides us with our first goal: to derive each of the separate conjuncts.

  Let's start with $D$: where does it occur in the assumptions? It occurs in the
  compound $(\lnot N \rightarrow L) \land (D \leftrightarrow \lnot N)$, but only
  in the first conjunct. We can get this simply but applying
  [Conjunction Elimination](Conjunction%20Elimination.md).

  So far we have:

  ![](/img/step1.png)

  Now we just need to get $D$ from the proposition at line 3. This is easy since
  we already have access to the consequent of the biconditional at line 1.
  Therefore we can apply
  [Biconditional Elimination](Biconditional_Elimination.md)) at line
  3 to get $D$. We are now halfway there:

  ![](/img/step2.png)

  Next we need to turn our attention to deriving $L \lor A$. How can we obtain $L$
  ? Well it is contained within the first conjunct of the assumption on line 2.
  Again, we can get this through the application of
  [Conjunction Elimination](Conjunction_Elimination.md). Now, how do
  we get $L$ from $(\lnot N \rightarrow L)$? Well, we already have the antecedent
  $\lnot N$ as an assumption on the first line, so we can use
  [Conditional Elimination](Conditional_Elimination.md) to derive
  $L$. These two steps give us:

  ![](/img/step3.png)

  Now we need to get from $L$ to $L \lor A$. This is really straightforward
  because by using
  [Disjunction Introduction](Disjunction_Introduction.md)) we can
  get from any sentence to a disjunction. Finally, having assembled all the
  constituent parts of the conjunction that is the conclusion, we can combine them
  with [Conjunction Introduction](Conjunction_Introduction.md) as we
  had planned at the outset.

  ![](/img/step4.png)

  ### A further example

  We will seek to prove the following:

  $$
  \{ \lnot L \leftrightarrow [X \land (\lnot S \lor B) ], (E \land C) \rightarrow \lnot L, (E \land R) \land C \} \vdash X \land (\lnot S \lor B)
  $$

  The requirements here could easily mislead us. We see that the target
  proposition is a conjunction so we might think that the best strategy is to seek
  to derive each conjunct and then combine them via
  [Conjunction Introduction](Conjunction_Introduction.md)).

  Actually, if we look more closely, there is a better approach. The target
  proposition is contained in the first premise as the consequent to the
  biconditional ($\lnot L \leftrightarrow [X \land (\lnot S \lor B)]$). A better
  approach is therefore to seek to derive the antecedent ($\lnot L$) and then use
  [Biconditional Elimination](Biconditional_Elimination.md) to
  extract the target sentence which is the consequent.

  ![proof.png](proof.png)

  ## Proving theorems

  When we are proving
  [theorems](Theorems_and_empty_sets.md#theorems-and-empty-sets)
  we do not have a set of assumptions to work from when constructing the proof. We
  must derive the target sentence from the 'empty set' which is the target
  sentence itself. It is therefore like a process of reverse engineering.

  ### Demonstration

  _Prove:_ $\vdash (U \land Y) \rightarrow [L \rightarrow (U \land L)]$

  Our strategy here is to identify the main connective in the proposition we want
  to derive (the material conditional). We then assume the antecedent and attempt
  to derive the consequent from it.

  ![](/img/theoremproof.png)

  ## A complex theorem proof

  _Prove_ $\vdash (\lnot A \lor \lnot B) \leftrightarrow \lnot(A \land B)$

  ![](/img/dsfdsfsdfwe.png)

  ### Walkthrough

  **Lines 1-12**

  - Our auxiliary goal is to prove
    $\lnot (A \lor B) \rightarrow \lnot (A \land B)$.

  - Our starting assumption is to a disjunction. Thus we can apply
    [Disjunction Elimination](Disjunction_Elimination.md) to show
    that our goal sentence $\lnot(A \land B)$ follows from each of the disjuncts
    ($\lnot A$ and $\lnot B$) in dedicated sub-proofs. If we can do this, we have
    the right to derive $\lnot (A \land B)$.

  - In both cases($\lnot A \vdash \lnot (A \land B)$) and
    ($\lnot B \vdash \lnot (A \land B)$ we require another sub-proof to reach the
    target as there is no easy path available. So we derive a negation from
    $A \land  B$ so that we can negate it as $\lnot (A \land B)$.

  - Having done this, we can discharge the
    [Disjunction Elimination](Disjunction_Elimination.md) sub-proofs
    and derive $\lnot (A \land B)$ from $\lnot A \lor \lnot B$

  **Lines 13-26**

  - Our auxiliary goal is to prove
    $\lnot (A \land B) \rightarrow \lnot A \lor  \lnot B$. This will require a
    different approach to the above because we are not working from a disjunction
    anymore, we have a negated conjunction.
  - We will do this by assuming the negation of what we want to prove
    ($\lnot (\lnot A \lor \lnot B)$) and then apply
    [Negation Elimination](Negation_Elimination.md) to get
    $\lnot A \lor \lnot B$.
  - This requires us to derive a contradiction. We get this on lines 23 and 24.
    This requires as previous steps that we have two sub-proofs that use
    [Negation Elimination](Negation_Elimination.md) to release $A$
    and $B$

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Strategies for constructing proofs in propositional logic

  ## General strategy

  - Break complex propositions into simpler sentences by using the elimination
    rules
  - Recombine simple propositions into complex propositions using the introduction
    rules.

  ## Goal analysis

  The approach above describes the general form of a proof but of course it will
  not always work and there will be cases where the route to the desired
  derivation is more circuitous. In these instances it is to best to combine this
  general top level strategy with goal analysis.

  Goal analysis is a [recursive](Recursion.md) strategy which
  proceeds by using a 'goal' proposition to guide the construction of intermediary
  derivations.

  Assume that we want to show that an argument is
  [valid](Validity_and_entailment.md#validity). Then our
  ultimate goal is to derive the conclusion from the premises we are given. We
  first ask ourselves: _which propositions if we could derive them, would allow us
  to easily derive the conclusion_? (For example, these propositions might be two
  simple propositions that when combined with
  [Conjunction Introduction](Conjunction_Introduction.md) give us
  the conclusion.) Deriving these propositions then becomes the new intermediate
  goal.

  If arriving at these propositions is not trivial, we then ask ourselves the
  question again: _which propositions would permit us to derive the intermediary
  propositions we need_? You keep working back in this manner until you reach a
  base level. Then it is just a matter or working upwards from each set of derived
  intermediary propositions until you reach the ultimate goal.

  ### Demonstration

  Let's say we want to prove $(L \lor A) \land D$ from the propositions $\lnot N$
  and $((\lnot N \rightarrow L) \land (D \leftrightarrow \lnot N))$.

  First, we consider what is the easiest possible way of achieving the proposition
  $(L \lor A) \land D$. Clearly it is to separately derive each disjunct
  ($L \lor A$ and $D$) and then combine them with
  [Conjunction Introduction](Conditional_Introduction.md). This
  provides us with our first goal: to derive each of the separate conjuncts.

  Let's start with $D$: where does it occur in the assumptions? It occurs in the
  compound $(\lnot N \rightarrow L) \land (D \leftrightarrow \lnot N)$, but only
  in the first conjunct. We can get this simply but applying
  [Conjunction Elimination](Conjunction%20Elimination.md).

  So far we have:

  ![](/img/step1.png)

  Now we just need to get $D$ from the proposition at line 3. This is easy since
  we already have access to the consequent of the biconditional at line 1.
  Therefore we can apply
  [Biconditional Elimination](Biconditional_Elimination.md)) at line
  3 to get $D$. We are now halfway there:

  ![](/img/step2.png)

  Next we need to turn our attention to deriving $L \lor A$. How can we obtain $L$
  ? Well it is contained within the first conjunct of the assumption on line 2.
  Again, we can get this through the application of
  [Conjunction Elimination](Conjunction_Elimination.md). Now, how do
  we get $L$ from $(\lnot N \rightarrow L)$? Well, we already have the antecedent
  $\lnot N$ as an assumption on the first line, so we can use
  [Conditional Elimination](Conditional_Elimination.md) to derive
  $L$. These two steps give us:

  ![](/img/step3.png)

  Now we need to get from $L$ to $L \lor A$. This is really straightforward
  because by using
  [Disjunction Introduction](Disjunction_Introduction.md)) we can
  get from any sentence to a disjunction. Finally, having assembled all the
  constituent parts of the conjunction that is the conclusion, we can combine them
  with [Conjunction Introduction](Conjunction_Introduction.md) as we
  had planned at the outset.

  ![](/img/step4.png)

  ### A further example

  We will seek to prove the following:

  $$
  \{ \lnot L \leftrightarrow [X \land (\lnot S \lor B) ], (E \land C) \rightarrow \lnot L, (E \land R) \land C \} \vdash X \land (\lnot S \lor B)
  $$

  The requirements here could easily mislead us. We see that the target
  proposition is a conjunction so we might think that the best strategy is to seek
  to derive each conjunct and then combine them via
  [Conjunction Introduction](Conjunction_Introduction.md)).

  Actually, if we look more closely, there is a better approach. The target
  proposition is contained in the first premise as the consequent to the
  biconditional ($\lnot L \leftrightarrow [X \land (\lnot S \lor B)]$). A better
  approach is therefore to seek to derive the antecedent ($\lnot L$) and then use
  [Biconditional Elimination](Biconditional_Elimination.md) to
  extract the target sentence which is the consequent.

  ![proof.png](proof.png)

  ## Proving theorems

  When we are proving
  [theorems](Theorems_and_empty_sets.md#theorems-and-empty-sets)
  we do not have a set of assumptions to work from when constructing the proof. We
  must derive the target sentence from the 'empty set' which is the target
  sentence itself. It is therefore like a process of reverse engineering.

  ### Demonstration

  _Prove:_ $\vdash (U \land Y) \rightarrow [L \rightarrow (U \land L)]$

  Our strategy here is to identify the main connective in the proposition we want
  to derive (the material conditional). We then assume the antecedent and attempt
  to derive the consequent from it.

  ![](/img/theoremproof.png)

  ## A complex theorem proof

  _Prove_ $\vdash (\lnot A \lor \lnot B) \leftrightarrow \lnot(A \land B)$

  ![](/img/dsfdsfsdfwe.png)

  ### Walkthrough

  **Lines 1-12**

  - Our auxiliary goal is to prove
    $\lnot (A \lor B) \rightarrow \lnot (A \land B)$.

  - Our starting assumption is to a disjunction. Thus we can apply
    [Disjunction Elimination](Disjunction_Elimination.md) to show
    that our goal sentence $\lnot(A \land B)$ follows from each of the disjuncts
    ($\lnot A$ and $\lnot B$) in dedicated sub-proofs. If we can do this, we have
    the right to derive $\lnot (A \land B)$.

  - In both cases($\lnot A \vdash \lnot (A \land B)$) and
    ($\lnot B \vdash \lnot (A \land B)$ we require another sub-proof to reach the
    target as there is no easy path available. So we derive a negation from
    $A \land  B$ so that we can negate it as $\lnot (A \land B)$.

  - Having done this, we can discharge the
    [Disjunction Elimination](Disjunction_Elimination.md) sub-proofs
    and derive $\lnot (A \land B)$ from $\lnot A \lor \lnot B$

  **Lines 13-26**

  - Our auxiliary goal is to prove
    $\lnot (A \land B) \rightarrow \lnot A \lor  \lnot B$. This will require a
    different approach to the above because we are not working from a disjunction
    anymore, we have a negated conjunction.
  - We will do this by assuming the negation of what we want to prove
    ($\lnot (\lnot A \lor \lnot B)$) and then apply
    [Negation Elimination](Negation_Elimination.md) to get
    $\lnot A \lor \lnot B$.
  - This requires us to derive a contradiction. We get this on lines 23 and 24.
    This requires as previous steps that we have two sub-proofs that use
    [Negation Elimination](Negation_Elimination.md) to release $A$
    and $B$


path: Streams_in_NodeJS.md
sortable_path: Streams_in_NodeJS.md
title: Handling streams with fs
checksum: f8299b5e6a1242c1e024952c4e37aaa85dbc54a9b22f1a262b3658605e2ec655
created: 2025-10-01 13:31:01.757725239+00:00
modified: 2025-08-13 15:57:13.877153812+00:00
metadata: {"tags":["node-js"]}
lead:
  When reading from a file, the
  [`fs.readFile()`](fs.md) method waits
  until the entire file has been read before executing the callback. It's obvious
  why this might not be ideal in certain cases. If the file is very large you are
  utilising a lot of [memory](Memory.md) for a
  single process. Additionally, the data you need might appear early in the file,
  in which case, once you find the data you want, there is no need to read to the
  end of the file.

body:
  When reading from a file, the
  [`fs.readFile()`](fs.md) method waits
  until the entire file has been read before executing the callback. It's obvious
  why this might not be ideal in certain cases. If the file is very large you are
  utilising a lot of [memory](Memory.md) for a
  single process. Additionally, the data you need might appear early in the file,
  in which case, once you find the data you want, there is no need to read to the
  end of the file.

  > An example of this in practice is watching a Netflix film: we don't have to
  > wait for the whole film to download, we can start watching it immediately
  > because it is passed to us in chunks.

  ## Reading streams

  This is why the ability to read files and data as streams exists in Node.js.
  Streams promote memory and time efficiency. As you are not necessarily reading
  the whole file, you can extract the data you need quickly and you are not
  putting the whole file in memory.

  When read as a stream, the file is broken up into smaller chunks. Readable
  streams raise data events and pass the chunks to our callbacks. Hence we don't
  have to wait for the whole file before registering data chunks.

  ## Implementation

  Instead of, e.g :

  ```js
  fs.readFile("./lorem.md", "UTF-8", function (err, fileContents) {
    console.log(fileContents);
  });
  ```

  We would:

  ```js
  let stream = fs.createReadStream("./lorem.md", "UTF-8");

  let data;

  stream.once("data", (chunk) => {
    console.log("read stream started");
  });

  stream.on("data", (chunk) => {
    data += chunk;
    console.log(data);
  });

  stream.on("end", () => console.log("stream ends"));
  ```

  We use `once` to do something at the moment the stream starts to be read.

  We use `on` to target the `"data"` and `"end"` events. We use this to log the
  chunks via the `data` variable.

  ## Writing streams

  When we write as a stream, we break data up and pass this to a reader as a
  stream. It is what creates a stream in the first place.

  For example we could create a stream that handles `stdin` and writes this to a
  file in chunks:

  ```js
  var stream = fs.createWriteStream("lorem.txt")

  process.stdin.once("data", (data) => {
      stream.write(data, function() => {
          // data is now written.
      })
  })

  process.on("exit", function () {
      stream.close()
  })
  ```

  Note that we always have to close the stream on exit

raw_content:
  ---
  tags:
    - node-js
  ---

  # Handling streams with fs

  When reading from a file, the
  [`fs.readFile()`](fs.md) method waits
  until the entire file has been read before executing the callback. It's obvious
  why this might not be ideal in certain cases. If the file is very large you are
  utilising a lot of [memory](Memory.md) for a
  single process. Additionally, the data you need might appear early in the file,
  in which case, once you find the data you want, there is no need to read to the
  end of the file.

  > An example of this in practice is watching a Netflix film: we don't have to
  > wait for the whole film to download, we can start watching it immediately
  > because it is passed to us in chunks.

  ## Reading streams

  This is why the ability to read files and data as streams exists in Node.js.
  Streams promote memory and time efficiency. As you are not necessarily reading
  the whole file, you can extract the data you need quickly and you are not
  putting the whole file in memory.

  When read as a stream, the file is broken up into smaller chunks. Readable
  streams raise data events and pass the chunks to our callbacks. Hence we don't
  have to wait for the whole file before registering data chunks.

  ## Implementation

  Instead of, e.g :

  ```js
  fs.readFile("./lorem.md", "UTF-8", function (err, fileContents) {
    console.log(fileContents);
  });
  ```

  We would:

  ```js
  let stream = fs.createReadStream("./lorem.md", "UTF-8");

  let data;

  stream.once("data", (chunk) => {
    console.log("read stream started");
  });

  stream.on("data", (chunk) => {
    data += chunk;
    console.log(data);
  });

  stream.on("end", () => console.log("stream ends"));
  ```

  We use `once` to do something at the moment the stream starts to be read.

  We use `on` to target the `"data"` and `"end"` events. We use this to log the
  chunks via the `data` variable.

  ## Writing streams

  When we write as a stream, we break data up and pass this to a reader as a
  stream. It is what creates a stream in the first place.

  For example we could create a stream that handles `stdin` and writes this to a
  file in chunks:

  ```js
  var stream = fs.createWriteStream("lorem.txt")

  process.stdin.once("data", (data) => {
      stream.write(data, function() => {
          // data is now written.
      })
  })

  process.on("exit", function () {
      stream.close()
  })
  ```

  Note that we always have to close the stream on exit


path: Strings_in_Bash.md
sortable_path: Strings_in_Bash.md
title: Strings in bash
lead: ## Return a substring by index
checksum: 2530f2db53c8899fd0d79f4d754052143e4479eb4e64620e2cc4cc233aca8321
created: 2025-10-01 13:31:01.778318468+00:00
modified: 2025-08-13 15:57:14.090076204+00:00
metadata: {"tags":["shell","data-types"]}
body:
  ## Return a substring by index

  ```bash
  myString="hello"
  substring=${myString:0:3}
  # hel
  ```

  This is often used when looping through each character in a string.

  ## Loop through each character in a string

  ```bash
  str="hallelujah"
  stringLength=$(expr length str)

  for (( i=0; i<=${stringLength}; i++ )); do
      echo "${str:$i:1}"
  done
  ```

raw_content:
  ---
  tags:
    - shell
    - data-types
  ---

  # Strings in bash

  ## Return a substring by index

  ```bash
  myString="hello"
  substring=${myString:0:3}
  # hel
  ```

  This is often used when looping through each character in a string.

  ## Loop through each character in a string

  ```bash
  str="hallelujah"
  stringLength=$(expr length str)

  for (( i=0; i<=${stringLength}; i++ )); do
      echo "${str:$i:1}"
  done
  ```


path: Strings_in_Python.md
sortable_path: Strings_in_Python.md
title: Strings in Python
checksum: 3281a0dcc11d012d63621b70be662326f2a19865f9e34b84f32e7679bff270a7
created: 2025-10-01 13:31:01.785702585+00:00
modified: 2025-08-13 15:57:13.969960617+00:00
metadata: {"tags":["python","data-types"]}
lead:
  > Generally, anything that changes a string will be a method on the `str` class,
  > rather than a built-in function like `len()`, as such it will use dot notation

body:
  > Generally, anything that changes a string will be a method on the `str` class,
  > rather than a built-in function like `len()`, as such it will use dot notation

  - Strings are **immutable**: string operations produce a new string.

  ```python
  # Working with Strings

  my_variable = 'Bob'
  print(my_variable)
  # Bob

  my_variable = "Eloise"
  print(my_variable)
  # Eloise

  # A multi line string
  my_variable = """
  Hello
    World
  """
  print(my_variable)
  """
  Hello
    World
  """

  my_string = 'Hello World'
  print(len(my_string))
  # 11

  string_1 = 'Good'
  string_2 = " day"
  string_3 = string_1 + string_2
  print(string_3)
  # Good day

  msg = 'Hello Lloyd you are ' + str(21)
  print(msg)
  # Hello Lloyd you are 21

  # Range of String operations
  msg = 'Hello World'
  print(msg.replace("Hello", "Goodbye"))
  # Goodbye World

  print('Edward Alan Rawlings'.find('Alan'))
  # 7

  print('Edward John Rawlings'.find('Alan'))
  # -1

  print('James' == 'James') # prints True
  print('James' != 'John') # prints True

  print("msg.startswith('H')", msg.startswith('H'))
  # msg.startswith('H') True

  print("msg.endswith('d')", msg.endswith('d'))
  # msg.endswith('d') TRUE

  print('some_string.upper()', msg.upper())
  # some_string.upper() HELLO WORLD

  print('sub string: ', 'Hello-World'[1:5])
  # sub string: ello

  # String interpolation
  user_age = input("Please enter your age: ")
  print(f'You are {user_age}')
  ```

  ## `str.split()`

  The `split()` function in Python is used to divide a string into multiple parts
  at the occurrence of a given separator. This function returns a
  [list](Lists_in_Python.md) of substrings.

  ### General syntax

  ```py
  str.split(separator, maxsplit)
  ```

  - The `separator` is optional. It specifies the separator to use when splitting
    the string. If no separator is provided, it will default to using whitespace.

  - `maxsplit` is also optional and specifies how many splits to do. Default value
    is -1, which is "all occurrences".

  ### Examples

  ```py

  text = "Hello world, how are you?"

  # Default usage:
  x = text.split()
  print(x)
  # ['Hello', 'world', 'how', 'are', 'you?']

  # Using a specific separator
  x = text.split(",")
  print(x)
  # ['Hello', 'world how are you?']

  # Specifiying a maxplit value
  x = text.split(" ", 1)
  print(x)
  # ['Hello']
  ```

raw_content:
  ---
  tags: [python, data-types]
  ---

  # Strings in Python

  > Generally, anything that changes a string will be a method on the `str` class,
  > rather than a built-in function like `len()`, as such it will use dot notation

  - Strings are **immutable**: string operations produce a new string.

  ```python
  # Working with Strings

  my_variable = 'Bob'
  print(my_variable)
  # Bob

  my_variable = "Eloise"
  print(my_variable)
  # Eloise

  # A multi line string
  my_variable = """
  Hello
    World
  """
  print(my_variable)
  """
  Hello
    World
  """

  my_string = 'Hello World'
  print(len(my_string))
  # 11

  string_1 = 'Good'
  string_2 = " day"
  string_3 = string_1 + string_2
  print(string_3)
  # Good day

  msg = 'Hello Lloyd you are ' + str(21)
  print(msg)
  # Hello Lloyd you are 21

  # Range of String operations
  msg = 'Hello World'
  print(msg.replace("Hello", "Goodbye"))
  # Goodbye World

  print('Edward Alan Rawlings'.find('Alan'))
  # 7

  print('Edward John Rawlings'.find('Alan'))
  # -1

  print('James' == 'James') # prints True
  print('James' != 'John') # prints True

  print("msg.startswith('H')", msg.startswith('H'))
  # msg.startswith('H') True

  print("msg.endswith('d')", msg.endswith('d'))
  # msg.endswith('d') TRUE

  print('some_string.upper()', msg.upper())
  # some_string.upper() HELLO WORLD

  print('sub string: ', 'Hello-World'[1:5])
  # sub string: ello

  # String interpolation
  user_age = input("Please enter your age: ")
  print(f'You are {user_age}')
  ```

  ## `str.split()`

  The `split()` function in Python is used to divide a string into multiple parts
  at the occurrence of a given separator. This function returns a
  [list](Lists_in_Python.md) of substrings.

  ### General syntax

  ```py
  str.split(separator, maxsplit)
  ```

  - The `separator` is optional. It specifies the separator to use when splitting
    the string. If no separator is provided, it will default to using whitespace.

  - `maxsplit` is also optional and specifies how many splits to do. Default value
    is -1, which is "all occurrences".

  ### Examples

  ```py

  text = "Hello world, how are you?"

  # Default usage:
  x = text.split()
  print(x)
  # ['Hello', 'world', 'how', 'are', 'you?']

  # Using a specific separator
  x = text.split(",")
  print(x)
  # ['Hello', 'world how are you?']

  # Specifiying a maxplit value
  x = text.split(" ", 1)
  print(x)
  # ['Hello']
  ```


path: Structuring_Express_applications.md
sortable_path: Structuring_Express_applications.md
title: Structuring Express applications
lead: In general, the following application structure works well:
checksum: 7a70ba15689a6e7e95f95149fa25048890b62d3003d249060f54bf4a76b99b6d
created: 2025-10-01 13:31:01.797200954+00:00
modified: 2025-08-13 15:57:13.89272035+00:00
metadata: {"tags":["node-js","REST","APIs"]}
body:
  In general, the following application structure works well:

  ```
  \
      routes\
      models\
      middlewear\
      config\
      public\
    index.js

  ```

  - **`routes/`**

    - This should house all our RESTful API endpoints for a given resource which
      we define as a module. For instance if we are handling API requests to
      `/api/courses/` we would have a module at `/routes/courses.js`

  - **`models/`**
    - Here we define our database schemas: definitions for the structure of the
      data objects we want to return from the database.
    - In the case of Mongo we would have a file for each of our Mongoose models
      which will belong to an overall collection of which there are document
      instances.
    - Our model files should map onto our route modules; there should generally be
      at least one model file for each module. The model stands as an abstract
      representation of the data that we will be handling with our request paths
      in the routes file.

raw_content:
  ---
  tags:
    - node-js
    - REST
    - APIs
  ---

  # Structuring Express applications

  In general, the following application structure works well:

  ```
  \
      routes\
      models\
      middlewear\
      config\
      public\
    index.js

  ```

  - **`routes/`**

    - This should house all our RESTful API endpoints for a given resource which
      we define as a module. For instance if we are handling API requests to
      `/api/courses/` we would have a module at `/routes/courses.js`

  - **`models/`**
    - Here we define our database schemas: definitions for the structure of the
      data objects we want to return from the database.
    - In the case of Mongo we would have a file for each of our Mongoose models
      which will belong to an overall collection of which there are document
      instances.
    - Our model files should map onto our route modules; there should generally be
      at least one model file for each module. The model stands as an abstract
      representation of the data that we will be handling with our request paths
      in the routes file.


path: Surveillance_capitalism_not_necessary.md
sortable_path: Surveillance_capitalism_not_necessary.md
title: a247fa9b_surveillance_capitalism_not_necessary
checksum: 6982996ba7fd461a4139427f331faafde8db2ffe30532126b850a052b8f777d5
created: 2025-10-01 13:31:01.809390572+00:00
modified: 2025-08-13 15:57:14.004522945+00:00
metadata: {"created":"Sunday, December 01, 2024","tags":["ecopolsoc","internet","surveillance-capitalism"]}
lead:
  _Surveillance capitalism is neither a necessary development of information
  capitalism nor a necessary product of digital technology or the internet. It is
  a specifically constructed human choice, an unprecedented market form, an
  original solution to an emergency, and the underlying mechanism through which a
  new asset class is created on the cheap and converted to revenue._

body:
  _Surveillance capitalism is neither a necessary development of information
  capitalism nor a necessary product of digital technology or the internet. It is
  a specifically constructed human choice, an unprecedented market form, an
  original solution to an emergency, and the underlying mechanism through which a
  new asset class is created on the cheap and converted to revenue._

  _The Age of Surveillance Capitalism_ (2019): 91

raw_content:
  ---
  tags: [ecopolsoc, internet, surveillance-capitalism]
  created: Sunday, December 01, 2024
  ---

  # a247fa9b_surveillance_capitalism_not_necessary

  _Surveillance capitalism is neither a necessary development of information
  capitalism nor a necessary product of digital technology or the internet. It is
  a specifically constructed human choice, an unprecedented market form, an
  original solution to an emergency, and the underlying mechanism through which a
  new asset class is created on the cheap and converted to revenue._

  _The Age of Surveillance Capitalism_ (2019): 91


path: Swap_space.md
sortable_path: Swap_space.md
title: Swap space
checksum: 97e8698380c0d8084842f783b7b5347d4c22bff686d8e8150f37e223ec3d98d8
created: 2025-10-01 13:31:01.81405645+00:00
modified: 2025-08-13 15:57:13.958774554+00:00
metadata: {"tags":["memory","disks","Linux","operating-systems"]}
lead:
  A swap partition is a partition on a disk that is not intended to be used as a
  filesystem. Instead, it is a part of the disk that is used to augment the main
  memory.

body:
  A swap partition is a partition on a disk that is not intended to be used as a
  filesystem. Instead, it is a part of the disk that is used to augment the main
  memory.

  If you run out of memory and have set up a swap partition, the OS will be able
  to move pieces of memory to and from disk storage. This is called _swapping_
  because pieces of idle programs are swapped to the disk in exchange for active
  pieces residing on the disk.

  ## View current swap usage

  If you have a swap space established, the command `free` will show current
  usage:

  ```bash
  free
                 total        used        free      shared  buff/cache   available
  Mem:        16099420     3031572    10157652     1153144     2910196    11605820
  Swap:        3145724           0     3145724
  ```

  ## Create a swap partition

  To use an existing disk partition as a swap you can run the command
  `mkswap [device]` and then `swapon [device]` to register the space with the
  [kernel](The_kernel.md).

  ### Add to `fstab`

  You will want the swap to be activated every time the OS boots so add the
  following line to the [fstab](Filesystems.md#fstab),
  where `/sda3` is used as the example partition:

  ```bash
  /dev/sda3e none swap sw 0 0
  ```

  ## Create a swap file

  // Add info here

raw_content:
  ---
  tags:
    - memory
    - disks
    - Linux
    - operating-systems
  ---

  # Swap space

  A swap partition is a partition on a disk that is not intended to be used as a
  filesystem. Instead, it is a part of the disk that is used to augment the main
  memory.

  If you run out of memory and have set up a swap partition, the OS will be able
  to move pieces of memory to and from disk storage. This is called _swapping_
  because pieces of idle programs are swapped to the disk in exchange for active
  pieces residing on the disk.

  ## View current swap usage

  If you have a swap space established, the command `free` will show current
  usage:

  ```bash
  free
                 total        used        free      shared  buff/cache   available
  Mem:        16099420     3031572    10157652     1153144     2910196    11605820
  Swap:        3145724           0     3145724
  ```

  ## Create a swap partition

  To use an existing disk partition as a swap you can run the command
  `mkswap [device]` and then `swapon [device]` to register the space with the
  [kernel](The_kernel.md).

  ### Add to `fstab`

  You will want the swap to be activated every time the OS boots so add the
  following line to the [fstab](Filesystems.md#fstab),
  where `/sda3` is used as the example partition:

  ```bash
  /dev/sda3e none swap sw 0 0
  ```

  ## Create a swap file

  // Add info here


path: Syllogism.md
sortable_path: Syllogism.md
title: Syllogism
checksum: efa3d071823652b3ce7959d58ea9a4b400250d7f5cb9fe963479e825295704cd
created: 2025-10-01 13:31:01.840092771+00:00
modified: 2025-08-13 15:57:14.092943204+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  In order to make assertions about the relative
  [consistency](Logical_consistency.md) or inconsistency
  of a set of propositions we advance arguments. Consider everyday life: if we are
  having an argument with someone, we believe that they are wrong. A more logical
  way to say this is that we believe that their beliefs are inconsistent. In order
  to change their viewpoint or point out why they are wrong we advance an argument
  intended to show that belief A conflicts with belief B. Or if C is true, then
  you cannot believe that D.

body:
  In order to make assertions about the relative
  [consistency](Logical_consistency.md) or inconsistency
  of a set of propositions we advance arguments. Consider everyday life: if we are
  having an argument with someone, we believe that they are wrong. A more logical
  way to say this is that we believe that their beliefs are inconsistent. In order
  to change their viewpoint or point out why they are wrong we advance an argument
  intended to show that belief A conflicts with belief B. Or if C is true, then
  you cannot believe that D.

  In formal terms **an argument is a set of propositions comprising one or more
  premises and a conclusion. The conclusion is taken to be supported by the
  premises.**

  > The terms **argument** and **syllogism** are used interchangeably in logic to
  > describe the above feature of a set of propositions.

  ### Demonstration

  ```
  (P1) All men are mortal.
  (P2) Socrates is a man.
  _____________________
  (C) Socrates is mortal
  ```

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Syllogism

  In order to make assertions about the relative
  [consistency](Logical_consistency.md) or inconsistency
  of a set of propositions we advance arguments. Consider everyday life: if we are
  having an argument with someone, we believe that they are wrong. A more logical
  way to say this is that we believe that their beliefs are inconsistent. In order
  to change their viewpoint or point out why they are wrong we advance an argument
  intended to show that belief A conflicts with belief B. Or if C is true, then
  you cannot believe that D.

  In formal terms **an argument is a set of propositions comprising one or more
  premises and a conclusion. The conclusion is taken to be supported by the
  premises.**

  > The terms **argument** and **syllogism** are used interchangeably in logic to
  > describe the above feature of a set of propositions.

  ### Demonstration

  ```
  (P1) All men are mortal.
  (P2) Socrates is a man.
  _____________________
  (C) Socrates is mortal
  ```


path: Symlinks.md
sortable_path: Symlinks.md
title: Syntax
lead:
  ```
  ln -s -f ~/[existing_file] ~/.[file_you_want_to_symbolise]
  ```

checksum: 4b58c5eb0129339e5fe3e417845dbf94a8b384f545a3b991c3e52bc6659425ca
created: 2025-10-01 13:31:01.853032925+00:00
modified: 2025-08-13 15:57:13.836358963+00:00
metadata: {"tags":["shell","Linux","procedural"]}
body:
  ```
  ln -s -f ~/[existing_file] ~/.[file_you_want_to_symbolise]
  ```

  Real example:

  ```
  ln -s -f ~/dotfiles/.vimrc ~/.vimrc
  ```

raw_content:
  ---
  tags:
    - shell
    - Linux
    - procedural
  ---

  > A symbolic link, also termed a soft link, is a special kind of file that
  > points to another file. Unlike a hard link, a symbolic link does not contain
  > the data in the target file. It simply points to another entry somewhere in
  > the file system.

  # Syntax

  ```
  ln -s -f ~/[existing_file] ~/.[file_you_want_to_symbolise]
  ```

  Real example:

  ```
  ln -s -f ~/dotfiles/.vimrc ~/.vimrc
  ```


path: Syntax_of_propositional_logic.md
sortable_path: Syntax_of_propositional_logic.md
title: Syntax of propositional logic
lead: ## Syntax of formal languages versus semantics
checksum: 62edccda4593be282121ee3b522e1dc1bd9a989fa7475c32470c54ea34730039
created: 2025-10-01 13:31:01.860326934+00:00
modified: 2025-08-13 15:57:13.943568041+00:00
metadata: {"tags":["propositional-logic","logic"]}
body:
  ## Syntax of formal languages versus semantics

  > The syntactical study of a language is the study of the expressions of the
  > language and the relations among them _without regard_ to the possible
  > interpretations or 'meaning' of these expressions.

  Syntax is talking about the order and placement of propositions relative to
  connectives and what constitutes a well-formed expression in these terms.
  Semantics is about what the connectives mean, in other words: truth-functions
  and truth-values and not just placement and order.

  ## Formal specification of the syntax of the language of Sentential Logic

  ### Vocabulary

  Propositions in SL are capitalised Roman letters (non-bold) with or without
  natural number subscripts. We may call these proposition letters. For example:

  $$
    P, Q, R,... P_{1}, Q_{1}, R_{1}, ...
  $$

  The connectives of SL are the five truth-functional connectives:

  $$
    \lnot, \land, \lor, \rightarrow, \leftrightarrow
  $$

  The punctuation marks of SL consist in the left and right parentheses:

  $$
    ( )
  $$

  ### Grammar

  1. Every letter in a statement is a proposition.
  2. If $P$ is a proposition then $\lnot P$ is a proposition.
  3. If $P$ and $Q$ are propositions, then $P \land Q$ is a proposition
  4. If $P$ and $Q$ are propositions, then $P \lor Q$ is a proposition
  5. If $P$ and $Q$ are propositions, then $P \rightarrow Q$ is a proposition
  6. If $P$ and $Q$ are propositions, then $P \leftrightarrow Q$ is a proposition
  7. Nothing is a proposition unless it can be formed by repeated application of
     rules 1-6

  ### Additional syntactic concepts

  We also distinguish:

  - the **main connective**
  - **immediate sentential components**
  - **sentential components**
  - **atomic components**

  These definitions provide a formal specification of the concepts of
  [atomic and molecular propositions](Atomic_and_molecular_propositions.md)
  introduced previously.

  1. If $P$ is an atomic proposition, $P$ contains no connectives and hence does
     not have a main connective. $P$ has no immediate propositional components.
  1. If $P$ is of the form $\lnot Q$ where $Q$ is a proposition, then the main
     connective of $P$ is the negation symbol that occurs before $Q$ and $Q$ is
     the immediate propositional component of $P$
  1. If P is of the form:
     1. $Q \land R$
     1. $Q \lor R$
     1. $Q \rightarrow R$
     1. $Q \leftrightarrow R$

  where $Q$ and $R$ are propositions, then the main connective of $P$ is the
  connective that occurs between $Q$ and $R$ and $Q$ and $R$ are the immediate
  propositional components of $P$.

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Syntax of propositional logic

  ## Syntax of formal languages versus semantics

  > The syntactical study of a language is the study of the expressions of the
  > language and the relations among them _without regard_ to the possible
  > interpretations or 'meaning' of these expressions.

  Syntax is talking about the order and placement of propositions relative to
  connectives and what constitutes a well-formed expression in these terms.
  Semantics is about what the connectives mean, in other words: truth-functions
  and truth-values and not just placement and order.

  ## Formal specification of the syntax of the language of Sentential Logic

  ### Vocabulary

  Propositions in SL are capitalised Roman letters (non-bold) with or without
  natural number subscripts. We may call these proposition letters. For example:

  $$
    P, Q, R,... P_{1}, Q_{1}, R_{1}, ...
  $$

  The connectives of SL are the five truth-functional connectives:

  $$
    \lnot, \land, \lor, \rightarrow, \leftrightarrow
  $$

  The punctuation marks of SL consist in the left and right parentheses:

  $$
    ( )
  $$

  ### Grammar

  1. Every letter in a statement is a proposition.
  2. If $P$ is a proposition then $\lnot P$ is a proposition.
  3. If $P$ and $Q$ are propositions, then $P \land Q$ is a proposition
  4. If $P$ and $Q$ are propositions, then $P \lor Q$ is a proposition
  5. If $P$ and $Q$ are propositions, then $P \rightarrow Q$ is a proposition
  6. If $P$ and $Q$ are propositions, then $P \leftrightarrow Q$ is a proposition
  7. Nothing is a proposition unless it can be formed by repeated application of
     rules 1-6

  ### Additional syntactic concepts

  We also distinguish:

  - the **main connective**
  - **immediate sentential components**
  - **sentential components**
  - **atomic components**

  These definitions provide a formal specification of the concepts of
  [atomic and molecular propositions](Atomic_and_molecular_propositions.md)
  introduced previously.

  1. If $P$ is an atomic proposition, $P$ contains no connectives and hence does
     not have a main connective. $P$ has no immediate propositional components.
  1. If $P$ is of the form $\lnot Q$ where $Q$ is a proposition, then the main
     connective of $P$ is the negation symbol that occurs before $Q$ and $Q$ is
     the immediate propositional component of $P$
  1. If P is of the form:
     1. $Q \land R$
     1. $Q \lor R$
     1. $Q \rightarrow R$
     1. $Q \leftrightarrow R$

  where $Q$ and $R$ are propositions, then the main connective of $P$ is the
  connective that occurs between $Q$ and $R$ and $Q$ and $R$ are the immediate
  propositional components of $P$.


path: TIMPs.md
sortable_path: TIMPs.md
title: c8173d17_TIMPs
checksum: 8229d083e090ae2c63d332d19e6c4c8f093943706d8e20948dbdc7310a98fbef
created: 2025-10-01 13:31:01.872728312+00:00
modified: 2025-08-13 15:57:14.033444442+00:00
metadata: {"created":"Friday, October 25, 2024","tags":["ARPANET","networks","computer-history"]}
lead:
  After the initial ARPANET was complete, the next major milestone was to enable
  access to the network regardless of one's proximity to a host node with an IMP
  connection.

body:
  After the initial ARPANET was complete, the next major milestone was to enable
  access to the network regardless of one's proximity to a host node with an IMP
  connection.

  The idea was to allow a computer to access resources on another computer
  directly without connecting to a host first. This connective computer would
  connect to an IMP directly (but still transparently) as a 'dumb terminal' as
  with time-sharing and would not be a fully-fledged computing device. These
  devices would be called _Terminal_ Information Processors (TIMPs) for this
  reason.

  The development of TIMPs makes it clearer that the host machines on the ARPANET
  were not just connection and transmission nodes for their own purpose, they were
  loci for other non-host computers to gain access to an IMP, and thereby, the
  broader network. In other words computers would connect to a host which
  sustained a connection to an IMP.

raw_content:
  ---
  tags: [ARPANET, networks, computer-history]
  created: Friday, October 25, 2024
  ---

  # c8173d17_TIMPs

  After the initial ARPANET was complete, the next major milestone was to enable
  access to the network regardless of one's proximity to a host node with an IMP
  connection.

  The idea was to allow a computer to access resources on another computer
  directly without connecting to a host first. This connective computer would
  connect to an IMP directly (but still transparently) as a 'dumb terminal' as
  with time-sharing and would not be a fully-fledged computing device. These
  devices would be called _Terminal_ Information Processors (TIMPs) for this
  reason.

  The development of TIMPs makes it clearer that the host machines on the ARPANET
  were not just connection and transmission nodes for their own purpose, they were
  loci for other non-host computers to gain access to an IMP, and thereby, the
  broader network. In other words computers would connect to a host which
  sustained a connection to an IMP.


path: Tags_in_Git.md
sortable_path: Tags_in_Git.md
title: Tags in Git
checksum: b684f8ef44eb962d1c0262ba8934999fc798aa1d14d52b9dacc2cb0b680da031
created: 2025-10-01 13:31:01.880786083+00:00
modified: 2025-08-13 15:57:13.743028245+00:00
metadata: {"tags":["git"]}
lead:
  Tags exist to reference specific commits in a more meaningful way than using a
  SHA reference. They are a **named reference to a commit**. As they refer to a
  singular commit they do not update but are a fixed point in the Git history.

body:
  Tags exist to reference specific commits in a more meaningful way than using a
  SHA reference. They are a **named reference to a commit**. As they refer to a
  singular commit they do not update but are a fixed point in the Git history.

  > Tags do not care about branches. They are an objective reference to a commit
  > regardless of which branch it occured on. You can reference a tag without
  > needing to be in the branch where the tagging originally took place.

  Tags are frequently used to mark software release versions. But they can also be
  used for other designations such as the addition of a key feature or bug.

  ## Creating a tag

  ```
  git tag -am "Version 1.0" v1.0 dd5c4539a0
  ```

  `am` is for "annotated + message". The actual tag name is `v1.0`

  Now you can refer to that commit with `v1.0` rather than use the SHA

  ## Deleting a tag

  ```
  git tag -d v1.0
  ```

  > When you delete the tag you are only deleting the tag reference not the commit
  > that it refers to.

  ## Listing tags

  ```
  git tag --list
  ```

  ## Examples of use

  ```
  git show v1.1

  git diff v1.0..v1.1
  ```

  ## Pushing tags to a remote

  In the examples so far, all the tags have been **local tags**. They only exist
  in our local workflow and are not accessible to our collaborators. If we do a
  `git push`, this doesn't transfer our local tags. You have to do:

  ```
  git push origin [tag-name]
  ```

  Push all tags:

  ```
  git push origin --tags

  # Delete remote tag

  git push -d origin [tag-name]
  ```

  ## Getting tags from the remote

  ```
  git fetch
  ```

  This automatically fetches tags too.

raw_content:
  ---
  tags: [git]
  ---

  # Tags in Git

  Tags exist to reference specific commits in a more meaningful way than using a
  SHA reference. They are a **named reference to a commit**. As they refer to a
  singular commit they do not update but are a fixed point in the Git history.

  > Tags do not care about branches. They are an objective reference to a commit
  > regardless of which branch it occured on. You can reference a tag without
  > needing to be in the branch where the tagging originally took place.

  Tags are frequently used to mark software release versions. But they can also be
  used for other designations such as the addition of a key feature or bug.

  ## Creating a tag

  ```
  git tag -am "Version 1.0" v1.0 dd5c4539a0
  ```

  `am` is for "annotated + message". The actual tag name is `v1.0`

  Now you can refer to that commit with `v1.0` rather than use the SHA

  ## Deleting a tag

  ```
  git tag -d v1.0
  ```

  > When you delete the tag you are only deleting the tag reference not the commit
  > that it refers to.

  ## Listing tags

  ```
  git tag --list
  ```

  ## Examples of use

  ```
  git show v1.1

  git diff v1.0..v1.1
  ```

  ## Pushing tags to a remote

  In the examples so far, all the tags have been **local tags**. They only exist
  in our local workflow and are not accessible to our collaborators. If we do a
  `git push`, this doesn't transfer our local tags. You have to do:

  ```
  git push origin [tag-name]
  ```

  Push all tags:

  ```
  git push origin --tags

  # Delete remote tag

  git push -d origin [tag-name]
  ```

  ## Getting tags from the remote

  ```
  git fetch
  ```

  This automatically fetches tags too.


path: Terraform.md
sortable_path: Terraform.md
title: Terraform
lead: ## Authentication
checksum: 389c99f29af9b45f5cfbb202a791cf8718bb9492927bac18062032aaf822e9fe
created: 2025-10-01 13:31:01.888740909+00:00
modified: 2025-08-13 15:57:14.051938288+00:00
metadata: {"created":"Saturday, November 16, 2024","tags":["AWS","IaC"]}
body:
  ## Authentication

  Terraform will default to the currently active AWS user providing you have your
  [AWS credentials](/zk/AWS_CLI.md) set-up.

  ## Creating resources

  > Resources are specified in the `main.tf` file.

  Create a new S3 bucket:

  ```hcl
  resource "aws_s3_bucket" "my_new_bucket" {
      bucket = "my-new-bucket"
  }
  ```

  At this point the resource doesn't exist so there is no ARN or ID. When the
  resource is created (through `terraform apply`) Terraform receives this info
  from AWS and store it in a state file.

  This info can be retrieved with `terraform show`, e.g:

  ```
  resource "aws_s3_bucket" "my_new_bucket" {
      id                    = "my-new-bucket"
      arn                   = "arn:aws:s3:::my-new-bucket"
      bucket                = "my-new-bucket"
      bucket_domain_name    = "my-new-bucket.s3.amazonaws.com"
   }

  ```

  ## Standard sequence

  Make your `terraform` directory, then `cd` and:

  ### Initialise project

  ```
  terraform init
  ```

  This will create the file `.terraform.lock.hcl` which should be committed. Stuff
  in `.terraform/` can be git-ignored.

  ### Check changes

  ```
  terraform plan
  ```

  Providing you have written a `main.tf`. The command will list the proposed
  changes to your resources.

  ### Apply changes

  ```
  terraform apply
  ```

  ### Destroy the resource

  Typically when you want to do a clean recreate.

  ```
  terraform destoy
  ```

  ## Run with debug/logger

  ```
  TF_LOG=DEBUG terraform apply
  ```

  Or use with other Terraform command, e.g. `destroy`, `refresh` - whatever is
  causing the problem.

raw_content:
  ---
  tags: [AWS, IaC]
  created: Saturday, November 16, 2024
  ---

  # Terraform

  ## Authentication

  Terraform will default to the currently active AWS user providing you have your
  [AWS credentials](/zk/AWS_CLI.md) set-up.

  ## Creating resources

  > Resources are specified in the `main.tf` file.

  Create a new S3 bucket:

  ```hcl
  resource "aws_s3_bucket" "my_new_bucket" {
      bucket = "my-new-bucket"
  }
  ```

  At this point the resource doesn't exist so there is no ARN or ID. When the
  resource is created (through `terraform apply`) Terraform receives this info
  from AWS and store it in a state file.

  This info can be retrieved with `terraform show`, e.g:

  ```
  resource "aws_s3_bucket" "my_new_bucket" {
      id                    = "my-new-bucket"
      arn                   = "arn:aws:s3:::my-new-bucket"
      bucket                = "my-new-bucket"
      bucket_domain_name    = "my-new-bucket.s3.amazonaws.com"
   }

  ```

  ## Standard sequence

  Make your `terraform` directory, then `cd` and:

  ### Initialise project

  ```
  terraform init
  ```

  This will create the file `.terraform.lock.hcl` which should be committed. Stuff
  in `.terraform/` can be git-ignored.

  ### Check changes

  ```
  terraform plan
  ```

  Providing you have written a `main.tf`. The command will list the proposed
  changes to your resources.

  ### Apply changes

  ```
  terraform apply
  ```

  ### Destroy the resource

  Typically when you want to do a clean recreate.

  ```
  terraform destoy
  ```

  ## Run with debug/logger

  ```
  TF_LOG=DEBUG terraform apply
  ```

  Or use with other Terraform command, e.g. `destroy`, `refresh` - whatever is
  causing the problem.


path: Test_values_in_Bash.md
sortable_path: Test_values_in_Bash.md
title: Test values in Bash
checksum: d30c66e930746ebe15a95780057d6e630825c2911fddc143375e875c159e3141
created: 2025-10-01 13:31:01.905280049+00:00
modified: 2025-08-13 15:57:13.89679232+00:00
metadata: {"tags":["shell","regex"]}
lead:
  `test` is a built-in command that is used to compare values or determine whether
  something is the case.

body:
  `test` is a built-in command that is used to compare values or determine whether
  something is the case.

  We can use the command `test` but it is more common to test a condition
  implicity by using square brackets. The square brackets are an alias for `test`.
  We use this alias when we use `IF` logic.

  When we run a test the result we get back is a return status of a `0` or a `1`.
  `0` indicates that the test was a success and `1` means failure. (Bear in mind
  this is in contrast to most all other programming languages.)

  If we run a test in the command line we won't get a `0` or a `1` or back, there
  will just be silence from the shell. We can explicitly invoke the return value
  with variable `$?`, e.g:

  ```bash
  [ -d ~ ] # is the home directory a directory?
  echo $?
  0   # yes

  [ -d /bin/zsh ] # is this binary a directory?
  echo $
  1  # no
  ```

  ## Test structures

  Many tests can be run with flags as a shorthand like we saw above:

  ### File operators

  ```
  -a FILE        True if file exists.
  -d FILE        True if file is a directory.
  -e FILE        True if file exists.
  -h FILE        True if file is a symbolic link
  -s FILE        True if file exists and is not empty.
  -N FILE        True if the file has been modified since it was last read.
  ```

  ### String operators

  ```
  -z STRING      True if string is empty.
  -n STRING      True if string is not empty.
  ```

  ## Differences between comparing numbers and strings

  - `=` is reserved for comparing strings
  - For numbers we use, e.g, `[ 4 -lt 5 ]`

  ## Negation

  We can negate a test condition with `!`:

  ```bash
  [ ! 4 -lt 3 ]; echo $?
  0
  ```

  ## Running process if test succeeds or fails

  We can use the following structure to run a process if a test condition obtains:

  ```bash
  [[ -d ~ ]] && echo "/home is a directory"
  ```

  ## Extended test: `[[...]]`

  When we use **double brackets** we are using _extended_ `test`.

  The extended test supports the standard `test` comparisons and adds other
  features:

  - The use of Boolean operators:
    ```
    [[ -d ~ || -a /bin/mash ]]; echo $?
    ```

  ### Using regular expressions

  Extended test also allows us to use regular expressions as part of our test
  conditions. In order to test against a regular expression we use `=~` as the
  comparison operator.

  ```bash
  [[ "thomas" =~ t.* ]]; echo $?
  ```

  Here the test succeeds because "thomas" begins with "t" followed by any other
  character.

  ## Further examples

  **Test if a character exists in two strings**

  ```bash
  string1="hello"
  string2="world"
  char="l"

  if [[ "$string1" == *"$char"* ]] && [[ "$string2" == *"$char"* ]]; then
      echo "Character '$char' exists in both strings."
  else
      echo "Character '$char' does not exist in both strings."
  fi
  ```

  > Note: this syntax can also be used to test if a given element exists in an
  > [array](Lists_and_arrays_in_Bash.md).

raw_content:
  ---
  tags:
    - shell
    - regex
  ---

  # Test values in Bash

  `test` is a built-in command that is used to compare values or determine whether
  something is the case.

  We can use the command `test` but it is more common to test a condition
  implicity by using square brackets. The square brackets are an alias for `test`.
  We use this alias when we use `IF` logic.

  When we run a test the result we get back is a return status of a `0` or a `1`.
  `0` indicates that the test was a success and `1` means failure. (Bear in mind
  this is in contrast to most all other programming languages.)

  If we run a test in the command line we won't get a `0` or a `1` or back, there
  will just be silence from the shell. We can explicitly invoke the return value
  with variable `$?`, e.g:

  ```bash
  [ -d ~ ] # is the home directory a directory?
  echo $?
  0   # yes

  [ -d /bin/zsh ] # is this binary a directory?
  echo $
  1  # no
  ```

  ## Test structures

  Many tests can be run with flags as a shorthand like we saw above:

  ### File operators

  ```
  -a FILE        True if file exists.
  -d FILE        True if file is a directory.
  -e FILE        True if file exists.
  -h FILE        True if file is a symbolic link
  -s FILE        True if file exists and is not empty.
  -N FILE        True if the file has been modified since it was last read.
  ```

  ### String operators

  ```
  -z STRING      True if string is empty.
  -n STRING      True if string is not empty.
  ```

  ## Differences between comparing numbers and strings

  - `=` is reserved for comparing strings
  - For numbers we use, e.g, `[ 4 -lt 5 ]`

  ## Negation

  We can negate a test condition with `!`:

  ```bash
  [ ! 4 -lt 3 ]; echo $?
  0
  ```

  ## Running process if test succeeds or fails

  We can use the following structure to run a process if a test condition obtains:

  ```bash
  [[ -d ~ ]] && echo "/home is a directory"
  ```

  ## Extended test: `[[...]]`

  When we use **double brackets** we are using _extended_ `test`.

  The extended test supports the standard `test` comparisons and adds other
  features:

  - The use of Boolean operators:
    ```
    [[ -d ~ || -a /bin/mash ]]; echo $?
    ```

  ### Using regular expressions

  Extended test also allows us to use regular expressions as part of our test
  conditions. In order to test against a regular expression we use `=~` as the
  comparison operator.

  ```bash
  [[ "thomas" =~ t.* ]]; echo $?
  ```

  Here the test succeeds because "thomas" begins with "t" followed by any other
  character.

  ## Further examples

  **Test if a character exists in two strings**

  ```bash
  string1="hello"
  string2="world"
  char="l"

  if [[ "$string1" == *"$char"* ]] && [[ "$string2" == *"$char"* ]]; then
      echo "Character '$char' exists in both strings."
  else
      echo "Character '$char' does not exist in both strings."
  fi
  ```

  > Note: this syntax can also be used to test if a given element exists in an
  > [array](Lists_and_arrays_in_Bash.md).


path: Testing_Python_code.md
sortable_path: Testing_Python_code.md
title: Testing Python code
lead: ## `pytest`
checksum: f21ee764b3d4c67afa18fdc25fbee482906dd1434c85f34660de4f8726cbaeab
created: 2025-10-01 13:31:01.917777664+00:00
modified: 2025-08-13 15:57:13.852315112+00:00
metadata: {"tags":["python","testing"]}
body:
  ## `pytest`

  Pytest is the most popular testing library for Python. It is not included with
  the Python standard library so it must be installed with
  [pip](Python_package_management.md).
  While it does not include a declaration library, it is robust enough to handle
  most scenarios having a rich and expressive set of constructs and decorators
  that let you declare what your tests should do, under what conditions they
  should run, and how they should interact with the rest of your system.

  ### Using `pytest`

  - Pytest will automatically detect test files so long as they are named
    appropriately. E.g. for a module called `lorem`, it will detzect the unit test
    files `lorem_test.py` and `test_lorem.py`.
  - In order to detect tests it should be run from a directory level above them

  ### Examples

  Here is a basic example of using pytest for a local module callled `palindrome`:

  ```py
  import palindrome

  def test_is_palindrome():
      assert palindrome.is_palindrome('soros')
      assert palindrome.is_palindrome('torot')
      assert not palindrome.is_palindrome('chair')
  ```

  ## Mocking

  `patch()` and `Mock` enable us to mock objects whilst testing (classes,
  functions, methods and properties belonging). They are used in combination.

  The `@patch` decorator temporarily replaces a specified object in your code with
  a mock object and restores the original object after the test is complete

  A Mock object simulates the object it replaces so that the object behaves as
  expected during testing. For example, if your code calls
  `some_function.some_method()`, and `some_method` is mocked, calling
  `some_method` will not execute real logic but will interact with the Mock object
  instead. Mock objects record details about how they have been used, like what
  methods have been called, with what arguments, etc., allowing you to make
  assertions about how they have been used.

  > `@patch` and `Mock` work together because a patch is used to replace an object
  > or attribute with a Mock object. `Mock` handles the simulated functionality,
  > and `@patch` designates the real value we are replacing with the mock.

  ### Example case

  I will use the following example from one my projects:

  ```py
  # get_articles.py
  def get_articles(article_type: str) -> Optional[Dict[str, Any]]:
      """Retrieve articles from Pocket API"""

      if POCKET_LAMBDA_ENDPOINT is None:
          logging.error("Error: POCKET_LAMBDA_ENDPOINT envinronment variable is not set")
          return None
      else:
          # Interpolate the article_type into the Pocket request URL
          endpoint = POCKET_LAMBDA_ENDPOINT.format(article_type=article_type)

      try:
          response = requests.get(endpoint)
          response.raise_for_status()
          return response.json()

      except RequestException as e:
          print(f"An error occurred: {e}")
          return None
  ```

  This function: sources a URL from an environment variable, interpolates a query
  string into the URL (which comes in as a parameter), makes a request to the URL,
  and returns the response as JSON.

  It has some safeguards in place:

  - It checks that the environment variable is set
  - It checks that the request was successful

  In the example we could use a Mock object to simulate the response from the
  Pocket API. This would allow us to test the function without having to make a
  real request to the API:

  ```py
  def test_successful_request():
      # Replace the requests.get function with a Mock object (mock_get)
      with patch("requests.get") as mock_get:
          # Specify the return value of the mock_get object)
          mock_get.return_value = Mock(ok=true)
          mock_get.return_value.json.return_value = {"value": "test"}

          # Call the function under test
          result = get_articles("gaby")

      # Assert expected outcomes
      mock_get.assert_called_once_with(endpoint)
      assert result == mock_json_response
  ```

  The example above follows the **Arrange, Act, Assert** pattern:

  | Stage   | Action                                                                          |
  | ------- | ------------------------------------------------------------------------------- |
  | Arrange | Replace the `requests.get` function with `patch` and set properties with `Mock` |
  | Act     | Call the function under test                                                    |
  | Assert  | Assert that the function under test behaved as expected                         |

  ### Alternative mock syntax

  The `with patch(...) as mock_name` syntax is fine for small-scale mocking but
  can become cumbersome when you are mocking several dependencies.

  There is another syntax (which I actually find clearer). Say we have a function
  with three dependencies: `update_worksheet`, `process_articles`, `get_articles`.
  We could mock like so:

  ```py
  @patch("app.update_worksheet")
  @patch("app.process_articles")
  @patch("app.get_articles")
  def test_success(
      mock_get_articles, mock_process_articles, mock_update_worksheet
  ):
      mock_get_articles.return_value = [1, 2, 3]
  ```

  Here the patching is done by the decorator and the mocks are defined as
  parameters to the test function (always in reverse order)

  ### Mock assertion lexicon

  #### `return_value`

  State what the mock should return

  ```py
  my_mocked_function.return_value = ['one', 'two', 'three']
  ```

  #### `call_count`

  Test how many times a dependent function is called

  ```py
  assert my_mocked_function.call_count = 3
  ```

  #### `assert_any_call()`

  Test that a given mock is called at least once during the execution of the
  function under test

  ```py
  my_mocked_function.assert_any_call(some_mocked_return_value)
  ```

  When the output of one function is used as a parameter to another, and we don't
  particularly care about the details of what is concerned we can just pass the
  executed function, e.g:

  ```py
  my_mocked_function.assert_any_call(preceding_function())
  ```

  #### `call_args_list`

  Get a list of all the arguments that a mock object was called with during the
  test.

  `call_args_list` is useful when you want to check the arguments that a mock
  object was called with during the test, especially if the mock object was called
  multiple times with different arguments. You can use it to inspect the arguments
  of each call and make assertions based on them.

  ```py
  second_my_mocked_function_call = my_mocked_function.call_args_list[1]

  # check the first argument of the second call:

  assert second_my_mocked_function_call[0][0] == "expected arg"
  ```

  #### `side_effect`

  Use to trigger a side effect when returning a value from a mock. Most useful for
  mocking exceptions.

  ```py
  my_mocked_function.side_effect = Exception("Some exception raised")
  ```

  ## Testing exceptions with `raises`

  Testing exceptions is quite straightforward. You can use the `raises` helper
  provided by pytest, and combine this with `excinfo` ("exception info") to
  inspect the exception message.

  ```py
  if POCKET_LAMBDA_ENDPOINT is None:
      raise ValueError(
          "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set"
      )
  ```

  Then to test this, we would use pytest's `excinfo` fixture along with `raises`:

  ```py
   with pytest.raises(ValueError) as excinfo:  # Watch for the ValueError
      get_articles("some_type")

      assert "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set" in str(
          excinfo.value
      )
  ```

  We could actually simplify the above test by using the `match` parameter with
  `raise`. This way we do not need the separate assertion:

  ```py
  with pytest.raises(ValueError, match="Error: POCKET_LAMBDA_ENDPOINT environment variable is not set"):
      get_articles("some_type")
  ```

  Note that `excinfo` is best used for testing the exception text that you the
  developer explicitly `raise`. For exceptions tha may occur naturaly in the code
  you are testing, you should use `caplog` or `capsys` (see below).

  ## Before-each and after-each

  When testing functions, we achieve this in Python using `setup_function` and
  `teardown_function` methods. These methods are called before and after each test
  method respectively.

  To apply a "before each" to _every test_ just put your setup function and/or
  teardown function at the top level of your test module.

  For example, below we set and remove an env var before and after each test:

  ```py
  @pytest.fixture(scope="function") # specify that this fixture should be run before each function test
  def setup_function():
      print("Setting up test environment...")
      os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}"


  def teardown_function():
      print("Tearing down test environment...")
      del os.environ["POCKET_LAMBDA_ENDPOINT"]
  ```

  If the setup/teardown should only be applied to a subset of tests, just pass the
  name of the fixture as a parameter to the test function:

  ```py
  def some_function(setup_function):
      # setup_function will be run before this test
  ```

  You don't need to use the names `setup_function` and `teardown_function` so long
  as you are passing the fixture as a parameter.

  You can also use `yield` to combine the setup and teardown into a single
  function:

  ```py
  @pytest.fixture(scope="function")
  def setup_function():
      os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}"
      yield
      del os.environ["POCKET_LAMBDA_ENDPOINT"]
  ```

  ### Another example:

  The following test suite uses the same three mocked functions in every test. The
  following set-up assigns the mocks before each test and resets after each
  individual test has run:

  ```py
  @pytest.fixture(scope="function")
  def setup_function():
      with patch("app.get_articles") as mock_get_articles, patch(
          "app.process_articles"
      ) as mock_process_articles, patch("app.update_worksheet") as mock_update_worksheet:
          yield mock_get_articles, mock_process_articles, mock_update_worksheet
  ```

  Then to use:

  ```py
  def individual_test(setup_function):
      mock_get_articles, mock_process_articles, mock_update_worksheet = setup_function
      # Now each mock can be referenced using the vars above

  ```

  ## Parameterized tests

  For a sequence of tests that are repetitive, to avoid repeating the same code
  over and over again, we can use parameterized tests. This is where we pass in a
  list of parameters to the test function and the test function is run once for
  each parameter.

  For example, in the function below I am handling numerous possible Exceptions
  that could be raised by the `requests.get` method:

  ```py
      try:
          response = requests.get(endpoint)
          response.raise_for_status()
          return response.json()

      except HTTPError as http_err:
          logging.error(f"HTTP Error occurred: {http_err}")

      except ConnectionError as conn_err:
          logging.error(f"Connection Error occurred: {conn_err}")

      except Timeout as timeout_err:
          logging.error(f"Timeout Error occurred: {timeout_err}")

      except RequestException as e:
          logging.error(f"Request Exception occurred: {e}")

      return None
  ```

  Instead of writing something like the following for each of the four exceptions:

  ```py

   def test_exception_generic(caplog):
      with patch("requests.get", side_effect=RequestException("Some error")):
          result = get_articles("some_type")

      assert "Request Exception occurred" in caplog.text
      assert result is None
  ```

  I could parameterize like so:

  ```py
  @pytest.mark.parametrize(
      "exception_type, log_message",
      [
          (RequestException, "Request Exception occurred: "),
          (HTTPError, "HTTP Error occurred: "),
          (Timeout, "Timeout Error occurred: "),
          (ConnectionError, "Connection Error occurred: "),
      ],
  )
  def test_exceptions(caplog, exception_type, log_message):
      with patch("requests.get", side_effect=exception_type("Some error")):
          result = get_articles("some_type")

      assert log_message in caplog.text
      assert result is None
  ```

  ## Caplog, syslog, excinfo

  `caplog` and `capsys` are built-in pytest fixtures. `caplog` lets you test log
  messages. `capsys` lets you test stdout and stderr. As such they are very useful
  when testing that error messages are logged correctly.

  ### `caplog`

  In our example, if the endpoing environment is not set, we log an error message.
  We can test that this message is logged correctly using `caplog`:

  ```py
  def test_no_endpoint_env_var(caplog):
      os.environ.pop("POCKET_LAMBDA_ENDPOINT", None)  # Remove env variable if it exists

      with caplog.at_level(logging.ERROR):
          result = get_articles("some_type")

      assert (
          "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set" in caplog.text
      )
      assert result is None
  ```

  > Note tha we pass in `caplog` as a parameter to the test function. This is how
  > pytest knows to use it as a fixture.

  ### `capsys`

  In our example, if the request is unsuccessful, we log an error message with
  `print` rather than `logging`. We can test that this message is printed
  correctly using `capsys` to check the stdout:

  ```py
  def test_http_error(capsys):
      with patch("requests.get") as mock_get:
          mock_get.return_value = Mock(ok=False, status_code=404)

          # Raise an HTTP error when raise_for_status is called
          mock_get.return_value.raise_for_status.side_effect = RequestException()

          result = get_articles("some_type")

      captured = capsys.readouterr()
      assert "An error occurred" in captured.out
  ```

  ### `excinfo`

raw_content:
  ---
  tags: [python, testing]
  ---

  # Testing Python code

  ## `pytest`

  Pytest is the most popular testing library for Python. It is not included with
  the Python standard library so it must be installed with
  [pip](Python_package_management.md).
  While it does not include a declaration library, it is robust enough to handle
  most scenarios having a rich and expressive set of constructs and decorators
  that let you declare what your tests should do, under what conditions they
  should run, and how they should interact with the rest of your system.

  ### Using `pytest`

  - Pytest will automatically detect test files so long as they are named
    appropriately. E.g. for a module called `lorem`, it will detzect the unit test
    files `lorem_test.py` and `test_lorem.py`.
  - In order to detect tests it should be run from a directory level above them

  ### Examples

  Here is a basic example of using pytest for a local module callled `palindrome`:

  ```py
  import palindrome

  def test_is_palindrome():
      assert palindrome.is_palindrome('soros')
      assert palindrome.is_palindrome('torot')
      assert not palindrome.is_palindrome('chair')
  ```

  ## Mocking

  `patch()` and `Mock` enable us to mock objects whilst testing (classes,
  functions, methods and properties belonging). They are used in combination.

  The `@patch` decorator temporarily replaces a specified object in your code with
  a mock object and restores the original object after the test is complete

  A Mock object simulates the object it replaces so that the object behaves as
  expected during testing. For example, if your code calls
  `some_function.some_method()`, and `some_method` is mocked, calling
  `some_method` will not execute real logic but will interact with the Mock object
  instead. Mock objects record details about how they have been used, like what
  methods have been called, with what arguments, etc., allowing you to make
  assertions about how they have been used.

  > `@patch` and `Mock` work together because a patch is used to replace an object
  > or attribute with a Mock object. `Mock` handles the simulated functionality,
  > and `@patch` designates the real value we are replacing with the mock.

  ### Example case

  I will use the following example from one my projects:

  ```py
  # get_articles.py
  def get_articles(article_type: str) -> Optional[Dict[str, Any]]:
      """Retrieve articles from Pocket API"""

      if POCKET_LAMBDA_ENDPOINT is None:
          logging.error("Error: POCKET_LAMBDA_ENDPOINT envinronment variable is not set")
          return None
      else:
          # Interpolate the article_type into the Pocket request URL
          endpoint = POCKET_LAMBDA_ENDPOINT.format(article_type=article_type)

      try:
          response = requests.get(endpoint)
          response.raise_for_status()
          return response.json()

      except RequestException as e:
          print(f"An error occurred: {e}")
          return None
  ```

  This function: sources a URL from an environment variable, interpolates a query
  string into the URL (which comes in as a parameter), makes a request to the URL,
  and returns the response as JSON.

  It has some safeguards in place:

  - It checks that the environment variable is set
  - It checks that the request was successful

  In the example we could use a Mock object to simulate the response from the
  Pocket API. This would allow us to test the function without having to make a
  real request to the API:

  ```py
  def test_successful_request():
      # Replace the requests.get function with a Mock object (mock_get)
      with patch("requests.get") as mock_get:
          # Specify the return value of the mock_get object)
          mock_get.return_value = Mock(ok=true)
          mock_get.return_value.json.return_value = {"value": "test"}

          # Call the function under test
          result = get_articles("gaby")

      # Assert expected outcomes
      mock_get.assert_called_once_with(endpoint)
      assert result == mock_json_response
  ```

  The example above follows the **Arrange, Act, Assert** pattern:

  | Stage   | Action                                                                          |
  | ------- | ------------------------------------------------------------------------------- |
  | Arrange | Replace the `requests.get` function with `patch` and set properties with `Mock` |
  | Act     | Call the function under test                                                    |
  | Assert  | Assert that the function under test behaved as expected                         |

  ### Alternative mock syntax

  The `with patch(...) as mock_name` syntax is fine for small-scale mocking but
  can become cumbersome when you are mocking several dependencies.

  There is another syntax (which I actually find clearer). Say we have a function
  with three dependencies: `update_worksheet`, `process_articles`, `get_articles`.
  We could mock like so:

  ```py
  @patch("app.update_worksheet")
  @patch("app.process_articles")
  @patch("app.get_articles")
  def test_success(
      mock_get_articles, mock_process_articles, mock_update_worksheet
  ):
      mock_get_articles.return_value = [1, 2, 3]
  ```

  Here the patching is done by the decorator and the mocks are defined as
  parameters to the test function (always in reverse order)

  ### Mock assertion lexicon

  #### `return_value`

  State what the mock should return

  ```py
  my_mocked_function.return_value = ['one', 'two', 'three']
  ```

  #### `call_count`

  Test how many times a dependent function is called

  ```py
  assert my_mocked_function.call_count = 3
  ```

  #### `assert_any_call()`

  Test that a given mock is called at least once during the execution of the
  function under test

  ```py
  my_mocked_function.assert_any_call(some_mocked_return_value)
  ```

  When the output of one function is used as a parameter to another, and we don't
  particularly care about the details of what is concerned we can just pass the
  executed function, e.g:

  ```py
  my_mocked_function.assert_any_call(preceding_function())
  ```

  #### `call_args_list`

  Get a list of all the arguments that a mock object was called with during the
  test.

  `call_args_list` is useful when you want to check the arguments that a mock
  object was called with during the test, especially if the mock object was called
  multiple times with different arguments. You can use it to inspect the arguments
  of each call and make assertions based on them.

  ```py
  second_my_mocked_function_call = my_mocked_function.call_args_list[1]

  # check the first argument of the second call:

  assert second_my_mocked_function_call[0][0] == "expected arg"
  ```

  #### `side_effect`

  Use to trigger a side effect when returning a value from a mock. Most useful for
  mocking exceptions.

  ```py
  my_mocked_function.side_effect = Exception("Some exception raised")
  ```

  ## Testing exceptions with `raises`

  Testing exceptions is quite straightforward. You can use the `raises` helper
  provided by pytest, and combine this with `excinfo` ("exception info") to
  inspect the exception message.

  ```py
  if POCKET_LAMBDA_ENDPOINT is None:
      raise ValueError(
          "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set"
      )
  ```

  Then to test this, we would use pytest's `excinfo` fixture along with `raises`:

  ```py
   with pytest.raises(ValueError) as excinfo:  # Watch for the ValueError
      get_articles("some_type")

      assert "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set" in str(
          excinfo.value
      )
  ```

  We could actually simplify the above test by using the `match` parameter with
  `raise`. This way we do not need the separate assertion:

  ```py
  with pytest.raises(ValueError, match="Error: POCKET_LAMBDA_ENDPOINT environment variable is not set"):
      get_articles("some_type")
  ```

  Note that `excinfo` is best used for testing the exception text that you the
  developer explicitly `raise`. For exceptions tha may occur naturaly in the code
  you are testing, you should use `caplog` or `capsys` (see below).

  ## Before-each and after-each

  When testing functions, we achieve this in Python using `setup_function` and
  `teardown_function` methods. These methods are called before and after each test
  method respectively.

  To apply a "before each" to _every test_ just put your setup function and/or
  teardown function at the top level of your test module.

  For example, below we set and remove an env var before and after each test:

  ```py
  @pytest.fixture(scope="function") # specify that this fixture should be run before each function test
  def setup_function():
      print("Setting up test environment...")
      os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}"


  def teardown_function():
      print("Tearing down test environment...")
      del os.environ["POCKET_LAMBDA_ENDPOINT"]
  ```

  If the setup/teardown should only be applied to a subset of tests, just pass the
  name of the fixture as a parameter to the test function:

  ```py
  def some_function(setup_function):
      # setup_function will be run before this test
  ```

  You don't need to use the names `setup_function` and `teardown_function` so long
  as you are passing the fixture as a parameter.

  You can also use `yield` to combine the setup and teardown into a single
  function:

  ```py
  @pytest.fixture(scope="function")
  def setup_function():
      os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}"
      yield
      del os.environ["POCKET_LAMBDA_ENDPOINT"]
  ```

  ### Another example:

  The following test suite uses the same three mocked functions in every test. The
  following set-up assigns the mocks before each test and resets after each
  individual test has run:

  ```py
  @pytest.fixture(scope="function")
  def setup_function():
      with patch("app.get_articles") as mock_get_articles, patch(
          "app.process_articles"
      ) as mock_process_articles, patch("app.update_worksheet") as mock_update_worksheet:
          yield mock_get_articles, mock_process_articles, mock_update_worksheet
  ```

  Then to use:

  ```py
  def individual_test(setup_function):
      mock_get_articles, mock_process_articles, mock_update_worksheet = setup_function
      # Now each mock can be referenced using the vars above

  ```

  ## Parameterized tests

  For a sequence of tests that are repetitive, to avoid repeating the same code
  over and over again, we can use parameterized tests. This is where we pass in a
  list of parameters to the test function and the test function is run once for
  each parameter.

  For example, in the function below I am handling numerous possible Exceptions
  that could be raised by the `requests.get` method:

  ```py
      try:
          response = requests.get(endpoint)
          response.raise_for_status()
          return response.json()

      except HTTPError as http_err:
          logging.error(f"HTTP Error occurred: {http_err}")

      except ConnectionError as conn_err:
          logging.error(f"Connection Error occurred: {conn_err}")

      except Timeout as timeout_err:
          logging.error(f"Timeout Error occurred: {timeout_err}")

      except RequestException as e:
          logging.error(f"Request Exception occurred: {e}")

      return None
  ```

  Instead of writing something like the following for each of the four exceptions:

  ```py

   def test_exception_generic(caplog):
      with patch("requests.get", side_effect=RequestException("Some error")):
          result = get_articles("some_type")

      assert "Request Exception occurred" in caplog.text
      assert result is None
  ```

  I could parameterize like so:

  ```py
  @pytest.mark.parametrize(
      "exception_type, log_message",
      [
          (RequestException, "Request Exception occurred: "),
          (HTTPError, "HTTP Error occurred: "),
          (Timeout, "Timeout Error occurred: "),
          (ConnectionError, "Connection Error occurred: "),
      ],
  )
  def test_exceptions(caplog, exception_type, log_message):
      with patch("requests.get", side_effect=exception_type("Some error")):
          result = get_articles("some_type")

      assert log_message in caplog.text
      assert result is None
  ```

  ## Caplog, syslog, excinfo

  `caplog` and `capsys` are built-in pytest fixtures. `caplog` lets you test log
  messages. `capsys` lets you test stdout and stderr. As such they are very useful
  when testing that error messages are logged correctly.

  ### `caplog`

  In our example, if the endpoing environment is not set, we log an error message.
  We can test that this message is logged correctly using `caplog`:

  ```py
  def test_no_endpoint_env_var(caplog):
      os.environ.pop("POCKET_LAMBDA_ENDPOINT", None)  # Remove env variable if it exists

      with caplog.at_level(logging.ERROR):
          result = get_articles("some_type")

      assert (
          "Error: POCKET_LAMBDA_ENDPOINT environment variable is not set" in caplog.text
      )
      assert result is None
  ```

  > Note tha we pass in `caplog` as a parameter to the test function. This is how
  > pytest knows to use it as a fixture.

  ### `capsys`

  In our example, if the request is unsuccessful, we log an error message with
  `print` rather than `logging`. We can test that this message is printed
  correctly using `capsys` to check the stdout:

  ```py
  def test_http_error(capsys):
      with patch("requests.get") as mock_get:
          mock_get.return_value = Mock(ok=False, status_code=404)

          # Raise an HTTP error when raise_for_status is called
          mock_get.return_value.raise_for_status.side_effect = RequestException()

          result = get_articles("some_type")

      captured = capsys.readouterr()
      assert "An error occurred" in captured.out
  ```

  ### `excinfo`


path: Testing_basic_prop_passing_in_React.md
sortable_path: Testing_basic_prop_passing_in_React.md
title: Testing basic prop passing in React
lead:
  ```js
  import { render, screen } from "@testing-library/react";

checksum: 34575567e1cfa73384ee771f05a338abb12b0e70f2b64ff32c0317a0e85fdbb8
created: 2025-10-01 13:31:01.930831737+00:00
modified: 2025-08-13 15:57:13.941619893+00:00
metadata: {"tags":["javascript","react","testing"]}
body:
  ```js
  import { render, screen } from "@testing-library/react";

  describe("<CertificateHtml />", () => {
    it("should render the values passed as props", () => {
      // Arrange:
      const stub = {
        titleOfActivityOrProgramme: "Filming",
        nameOfDepartment: "The film department",
      };

      // Act:
      render(<CertificateHtml {...stub} />);

      // Assert:
      expect(screen.getByText("Filming")).toBeInTheDocument();
      expect(screen.getByText("The film department")).toBeInTheDocument();
    });
  });
  ```

  Or, loop:

  ```js
  for (const entry of Object.values(stub)) {
    expect(screen.getByText(entry)).toBeInTheDocument();
  }
  ```

raw_content:
  ---
  tags:
    - javascript
    - react
    - testing
  ---

  # Testing basic prop passing in React

  ```js
  import { render, screen } from "@testing-library/react";

  describe("<CertificateHtml />", () => {
    it("should render the values passed as props", () => {
      // Arrange:
      const stub = {
        titleOfActivityOrProgramme: "Filming",
        nameOfDepartment: "The film department",
      };

      // Act:
      render(<CertificateHtml {...stub} />);

      // Assert:
      expect(screen.getByText("Filming")).toBeInTheDocument();
      expect(screen.getByText("The film department")).toBeInTheDocument();
    });
  });
  ```

  Or, loop:

  ```js
  for (const entry of Object.values(stub)) {
    expect(screen.getByText(entry)).toBeInTheDocument();
  }
  ```


path: Testing_with_Jest.md
sortable_path: Testing_with_Jest.md
title: Testing with Jest
lead: ## Mocking classes/modules
checksum: 073f7ee1dd7b138b9e24d7765cf0ae3b60643eb0ef5c9f391187fd72c1e98968
created: 2025-10-01 13:31:01.936102025+00:00
modified: 2025-08-13 15:57:13.977370835+00:00
metadata: {"tags":["javascript","testing","jest"]}
body:
  ## Mocking classes/modules

  ### Classes

  Let' say we have this class:

  ```js
  // database.js
  class Database {
    connect() {}
    save(data) {}
  }
  ```

  Then to mock:

  ```js
  import Database from "./database";

  // This will mock the whole Database class, replacing all methods with jest mock functions.
  jest.mock("./database");

  test("should use mocked save method", () => {
    const dbInstance = new Database();

    // Mocking the save method with a specific return value
    dbInstance.save.mockReturnValue(true);

    const result = dbInstance.save({ key: "value" });

    expect(result).toBe(true);
    expect(dbInstance.save).toHaveBeenCalledWith({ key: "value" });

    // The connect method is still a mock function (but without a specific behavior).
    dbInstance.connect();
    expect(dbInstance.connect).toHaveBeenCalled();
  });
  ```

  ### Modules

  Say we have the following module file:

  ```js
  // utils.js
  export const doSomething = () => {
    // ...
  };

  export const fetchUserData = async (userId) => {
    const response = await axios.get(`/api/users/${userId}`);
    return response.data;
  };
  ```

  Mocked:

  ```js
  jest.mock("./utils", () => {
    return {
      doSomething: jest.fn(() => "mocked doSomething"),
      fetchUserData: jest.fn((userId) =>
        Promise.resolve({ id: userId, name: "Mock User" })
      ),
    };
  });

  test("should use mocked module functions", () => {
    expect(utils.doSomething()).toBe("mocked doSomething");
    expect(utils.doSomething).toHaveBeenCalled();

    const result = await utils.fetchUserData(123);

    expect(result).toEqual({ id: 123, name: "Mock User" });
    expect(utils.fetchUserData).toHaveBeenCalledWith(123);

  });
  ```

  ### Inline mocking versus "per test" mocking

  There are two different architectures that we can use when mocking modules and
  classes: **inline** and **per test** mocking.

  Here is the inline case:

  ```js
  jest.mock("./some_module.js", () => {
    return {
      someFunction: jest.fn(() => "value"),
      someFunctionWithParam: jest.fn((param) => ({
        property: param,
      })),
      someAsyncFunction: jest.fn(() => Promise.resolve("value")),
    };
  });
  ```

  Here is the per test case:

  ```js
  import { someModule } from "./some_module.js";

  let someModuleMock;

  someModuleMock = {
    someFunction: jest.fn(() => "value"),
    someFunctionWithParam: jest.fn((param) => ({
      property: param,
    })),
    someAsyncFunction: jest.fn(() => Promise.resolve("value")),
  };

  someModule.mockImplementation(() => someModuleMock);

  it("should do something", () => {
    const newValue = "new value";
    someModule.someFunction.mockReturnValue(newValue);
  });
  ```

  The benefits of inline:

  - Inline is good because everything is set up in one place
  - Inline keeps consistency accross tests: every test case in the file will use
    the same mocked function unless overwritten within a test
  - It lends itself to being a _global_ mock that can be used accross test files
    in a `__mocks__/` directory

  The benefits of per-test:

  - You can very mock implementations within the file, providing more granular
    control. You can redefine `someModuleMock` or parts of it
    (`someModule.someFunction`) throughout your test file to accomodate varied
    requirements between tests
  - It’s beneficial when your tests have divergent requirements, as you can
    perform more detailed setups and overrides for each individual test case or
    suite, ensuring mocks are configured exactly as required.

  #### Overriding inline mocks

  Per test mocking makes it straightforward to change the test parameters of the
  mocked module or class but you can also override inline mocks.

  If we were using the `someModule` inline mock and we wanted to override the
  `someFunction` function that we have defined inline, we would first import the
  `someFunction` function and then use `mockImplementation` against it:

  ```js
  import { someFunction } from "./some_module.js";
  someFunction.mockImplementation(() => "custom value");
  expect(someFunction()).toBe("custom value");

  // Optional: Restore the original mock implementation after the test
  someFunction.mockRestore();
  ```

  Note: although we are importing `someFunction` we are not actually importing the
  real function tha belongs to the module. Because Jest mocks all of its
  properties and methods with the inline syntax, we are actually just importing
  that which Jest has aready mocked, but the syntax is a bit misleading.

  #### Applied to classes

  The same approaches (with minor differences) can be used with classes:

  Using inline (where the class is not the default export):

  ```js
  jest.mock("./SomeClass", () => {
    return {
      SomeClass: jest.fn().mockImplementation(() => {
        return {
          someFunction: jest.fn(() => "value"),
          someFunctionWithParam: jest.fn((param) => ({ property: param })),
          someAsyncFunction: jest.fn(() => Promise.resolve("value")),
          someOtherFunctionThatResolves: jest.fn().mockResolvedValue("some data"),
        };
      }),
    };
  });
  ```

  Using per test:

  ```js
  import SomeClass from "./someClass";

  jest.mock("./someClass");

  let someClassMock = {
    someFunction: jest.fn(() => "value"),
    someFunctionWithParam: jest.fn((param) => ({ property: param })),
    someAsyncFunction: jest.fn(() => Promise.resolve("value")),
  };

  // Mock class implementation
  SomeClass.mockImplementation(() => someClassMock);

  it("should do something", () => {
    const newValue = "new value";
    someClassMock.someFunction.mockReturnValue(newValue);
  });
  ```

  ## Check that a function has been called within another function

  ```js
  function toBeCalledFunction() {
    console.log("Original function called");
  }

  function callerFunction() {
    toBeCalledFunction();
  }
  ```

  ```js
  test("spy on toBeCalledFunction", () => {
    const spy = jest.spyOn(global, "toBeCalledFunction"); // Replace `global` with the appropriate object/context if the function is not global
    callerFunction();
    expect(spy).toHaveBeenCalled();
    spy.mockRestore(); // Restore the original function after spying
  });
  ```

  ## Mock a function that needs to resolve to something within another function

  We have two functions, one that gets data and another that processes it. We want
  to mock the function that gets data and return a value that the processing
  function can use.

  ```js
  async function getData() {
    // ... Fetch some data from an API or database
    return fetchedData;
  }

  async function processData() {
    const data = await getData();
    // ... Process the data
    return processedData;
  }
  ```

  The mocking part:

  ```js
  const mockData = { key: "value" }; // Mocked data

  jest.mock("./path-to-file-where-getData-is", () => ({
    getData: jest.fn().mockResolvedValue(mockData),
  }));

  test("test processData function", async () => {
    const result = await processData();
    // Now, result contains the processed version of mockData
    expect(result).toEqual(/* expected processed data based on mockData */);
  });
  ```

  We could also combine the above with a spy to check that the `getData` function
  was called:

  ```js
  const getDataSpy = jest
    .spyOn(moduleContainingGetData, "getData")
    .mockResolvedValue(mockData);

  const result = await processData();
  expect(getDataSpy).toHaveBeenCalled();
  expect(result).toEqual(/* expected processed data based on mockData */);
  getDataSpy.mockRestore();
  ```

  ## Mock a function that takes arguments

  ```js
  function addPrefix(str) {
    return `prefix-${str}`;
  }
  ```

  ```js
  test("dynamic mock for addPrefix function", () => {
    const mockFunction = jest.fn((str) => `mock-${str}`);

    // Example usage of mockFunction
    const result1 = mockFunction("test");
    const result2 = mockFunction("example");

    expect(result1).toBe("mock-test");
    expect(result2).toBe("mock-example");
  });
  ```

  ## Mocking network requests

  ### Mocking Axios

  ```js
  jest.mock("axios", () => ({
    get: jest.fn().mockResolvedValue(mockData),
    post: jest.fn().mockResolvedValue(mockData),
  }));
  ```

  Or we could implement this way:

  ```js
  jest.mock("axios");
  axios.get.mockResolvedValue({ data: "mockedData" });
  axios.post.mockResolvedValue({ data: "mockedData" });
  ```

  Then we can use the mocked axios functions in our tests:

  ```js
  const result = await fetchData(); // the function that uses Axios `get``
  expect(result).toBe("mockedGetData");

  const result = await sendData({ key: "value" }); // the function tha uses Axios `post`
  expect(result).toBe("mockedPostData");
  ```

  ### `mockImplementation`

  For more configurable cases we can use `mockImplementation`:

  ```js
  it("sends data", async () => {
    // Mock axios.post using mockImplementation
    axios.post.mockImplementation((url, data) => {
      if (data.key === "value") {
        return Promise.resolve({ data: "mockedPostData" });
      } else {
        return Promise.reject({ error: "An error occurred" });
      }
    });

    const result = await sendData({ key: "value" });

    expect(result).toBe("mockedPostData");
  });
  ```

  If we want to change the `get` and `post` values in different tests, we can do
  so by using `mockImplementation`:

  ## Mocking exceptions

  Again we use `mockImplementation`:

  Say we have the following function:

  ```js
  // fetchData.js
  import axios from "axios";

  const fetchData = async (url) => {
    try {
      const response = await axios.get(url);
      return response.data;
    } catch (error) {
      throw new Error("Error fetching data");
    }
  };

  export default fetchData;
  ```

  We would mock the success and the error as follows:

  ```js
  import axios from "axios";
  import fetchData from "./fetchData";

  jest.mock("axios");

  describe("fetchData", () => {
    it("fetches data successfully", async () => {
      axios.get.mockResolvedValue({ data: "mockedData" });

      const result = await fetchData("https://api.example.com/data");

      expect(result).toBe("mockedData");
    });

    it("throws an error when fetching fails", async () => {
      axios.get.mockImplementation(() => {
        throw new Error("API error");
      });

      // We use an asynchronous assertion here because we're expecting a promise to reject
      await expect(fetchData("https://api.example.com/data")).rejects.toThrow(
        "Error fetching data"
      );
    });
  });
  ```

  ## Parameterization

  The following offers a good opportunity for parameterisation:

  ```js
  it("should return page for deletion from `ipages-live`", async () => {
    // preview = false, isInternal = false
    await deletePageFromS3("url", false, false);
    const deleteObjectCommand = s3ClientMock.calls()[0].args[0];
    expect(deleteObjectCommand.input).toEqual({
      Bucket: "bbc-ise-ipages-live",
      Key: "url/index.html",
    });
  });

  it("should return page for deletion from `preview`", async () => {
    // preview = true, isInternal = false
    await deletePageFromS3("url", true, false);
    const deleteObjectCommand = s3ClientMock.calls()[0].args[0];
    expect(deleteObjectCommand.input).toEqual({
      Bucket: "staff.bbc.com-preview",
      Key: "preview/url/index.html",
    });
  });

  ...
  ```

  Each time we are passing in three parameters to the `deletePageFromS3` function
  which is the object under test. Each time there are different variations in the
  object that is output.

  To parameterize the process rather than use repeated `it` blocks we can combine
  the input paramters and outputs into an array:

  ```js
  const testParams = [
    {
      preview: false,
      isInternal: false,
      bucket: "ipages-live",
      key: "url/index.html",
    },
    {
      preview: true,
      isInternal: false,
      bucket: "staff.com-preview",
      key: "preview/url/index.html",
    },
  ];
  ```

  Then use `it.each` to loop through all possible parameter combinations:

  ```js
  it.each(testParams)(
    "should return page for deletion from %s",
    async ({ preview, isInternal, bucket, key }) => {
      await deletePageFromS3("url", preview, isInternal);
      const deleteObjectCommand = s3ClientMock.calls()[0].args[0];
      expect(deleteObjectCommand.input).toEqual({
        Bucket: bucket,
        Key: key,
      });
    }
  );
  ```

  This uses the `%s` variable to print the parameters from each test, which
  outputs:

  ```
    ✓ should return page for deletion from {
    preview: false,
    isInternal: false,
    bucket: 'ipages-live',
    key: 'url/index.html'
  } (1 ms)
      ✓ should return page for deletion from {
    preview: true,
    isInternal: false,
    bucket: 'staff.com-preview',
    key: 'preview/url/index.html'
  }
  ```

raw_content:
  ---
  tags: [javascript, testing, jest]
  ---

  # Testing with Jest

  ## Mocking classes/modules

  ### Classes

  Let' say we have this class:

  ```js
  // database.js
  class Database {
    connect() {}
    save(data) {}
  }
  ```

  Then to mock:

  ```js
  import Database from "./database";

  // This will mock the whole Database class, replacing all methods with jest mock functions.
  jest.mock("./database");

  test("should use mocked save method", () => {
    const dbInstance = new Database();

    // Mocking the save method with a specific return value
    dbInstance.save.mockReturnValue(true);

    const result = dbInstance.save({ key: "value" });

    expect(result).toBe(true);
    expect(dbInstance.save).toHaveBeenCalledWith({ key: "value" });

    // The connect method is still a mock function (but without a specific behavior).
    dbInstance.connect();
    expect(dbInstance.connect).toHaveBeenCalled();
  });
  ```

  ### Modules

  Say we have the following module file:

  ```js
  // utils.js
  export const doSomething = () => {
    // ...
  };

  export const fetchUserData = async (userId) => {
    const response = await axios.get(`/api/users/${userId}`);
    return response.data;
  };
  ```

  Mocked:

  ```js
  jest.mock("./utils", () => {
    return {
      doSomething: jest.fn(() => "mocked doSomething"),
      fetchUserData: jest.fn((userId) =>
        Promise.resolve({ id: userId, name: "Mock User" })
      ),
    };
  });

  test("should use mocked module functions", () => {
    expect(utils.doSomething()).toBe("mocked doSomething");
    expect(utils.doSomething).toHaveBeenCalled();

    const result = await utils.fetchUserData(123);

    expect(result).toEqual({ id: 123, name: "Mock User" });
    expect(utils.fetchUserData).toHaveBeenCalledWith(123);

  });
  ```

  ### Inline mocking versus "per test" mocking

  There are two different architectures that we can use when mocking modules and
  classes: **inline** and **per test** mocking.

  Here is the inline case:

  ```js
  jest.mock("./some_module.js", () => {
    return {
      someFunction: jest.fn(() => "value"),
      someFunctionWithParam: jest.fn((param) => ({
        property: param,
      })),
      someAsyncFunction: jest.fn(() => Promise.resolve("value")),
    };
  });
  ```

  Here is the per test case:

  ```js
  import { someModule } from "./some_module.js";

  let someModuleMock;

  someModuleMock = {
    someFunction: jest.fn(() => "value"),
    someFunctionWithParam: jest.fn((param) => ({
      property: param,
    })),
    someAsyncFunction: jest.fn(() => Promise.resolve("value")),
  };

  someModule.mockImplementation(() => someModuleMock);

  it("should do something", () => {
    const newValue = "new value";
    someModule.someFunction.mockReturnValue(newValue);
  });
  ```

  The benefits of inline:

  - Inline is good because everything is set up in one place
  - Inline keeps consistency accross tests: every test case in the file will use
    the same mocked function unless overwritten within a test
  - It lends itself to being a _global_ mock that can be used accross test files
    in a `__mocks__/` directory

  The benefits of per-test:

  - You can very mock implementations within the file, providing more granular
    control. You can redefine `someModuleMock` or parts of it
    (`someModule.someFunction`) throughout your test file to accomodate varied
    requirements between tests
  - It’s beneficial when your tests have divergent requirements, as you can
    perform more detailed setups and overrides for each individual test case or
    suite, ensuring mocks are configured exactly as required.

  #### Overriding inline mocks

  Per test mocking makes it straightforward to change the test parameters of the
  mocked module or class but you can also override inline mocks.

  If we were using the `someModule` inline mock and we wanted to override the
  `someFunction` function that we have defined inline, we would first import the
  `someFunction` function and then use `mockImplementation` against it:

  ```js
  import { someFunction } from "./some_module.js";
  someFunction.mockImplementation(() => "custom value");
  expect(someFunction()).toBe("custom value");

  // Optional: Restore the original mock implementation after the test
  someFunction.mockRestore();
  ```

  Note: although we are importing `someFunction` we are not actually importing the
  real function tha belongs to the module. Because Jest mocks all of its
  properties and methods with the inline syntax, we are actually just importing
  that which Jest has aready mocked, but the syntax is a bit misleading.

  #### Applied to classes

  The same approaches (with minor differences) can be used with classes:

  Using inline (where the class is not the default export):

  ```js
  jest.mock("./SomeClass", () => {
    return {
      SomeClass: jest.fn().mockImplementation(() => {
        return {
          someFunction: jest.fn(() => "value"),
          someFunctionWithParam: jest.fn((param) => ({ property: param })),
          someAsyncFunction: jest.fn(() => Promise.resolve("value")),
          someOtherFunctionThatResolves: jest.fn().mockResolvedValue("some data"),
        };
      }),
    };
  });
  ```

  Using per test:

  ```js
  import SomeClass from "./someClass";

  jest.mock("./someClass");

  let someClassMock = {
    someFunction: jest.fn(() => "value"),
    someFunctionWithParam: jest.fn((param) => ({ property: param })),
    someAsyncFunction: jest.fn(() => Promise.resolve("value")),
  };

  // Mock class implementation
  SomeClass.mockImplementation(() => someClassMock);

  it("should do something", () => {
    const newValue = "new value";
    someClassMock.someFunction.mockReturnValue(newValue);
  });
  ```

  ## Check that a function has been called within another function

  ```js
  function toBeCalledFunction() {
    console.log("Original function called");
  }

  function callerFunction() {
    toBeCalledFunction();
  }
  ```

  ```js
  test("spy on toBeCalledFunction", () => {
    const spy = jest.spyOn(global, "toBeCalledFunction"); // Replace `global` with the appropriate object/context if the function is not global
    callerFunction();
    expect(spy).toHaveBeenCalled();
    spy.mockRestore(); // Restore the original function after spying
  });
  ```

  ## Mock a function that needs to resolve to something within another function

  We have two functions, one that gets data and another that processes it. We want
  to mock the function that gets data and return a value that the processing
  function can use.

  ```js
  async function getData() {
    // ... Fetch some data from an API or database
    return fetchedData;
  }

  async function processData() {
    const data = await getData();
    // ... Process the data
    return processedData;
  }
  ```

  The mocking part:

  ```js
  const mockData = { key: "value" }; // Mocked data

  jest.mock("./path-to-file-where-getData-is", () => ({
    getData: jest.fn().mockResolvedValue(mockData),
  }));

  test("test processData function", async () => {
    const result = await processData();
    // Now, result contains the processed version of mockData
    expect(result).toEqual(/* expected processed data based on mockData */);
  });
  ```

  We could also combine the above with a spy to check that the `getData` function
  was called:

  ```js
  const getDataSpy = jest
    .spyOn(moduleContainingGetData, "getData")
    .mockResolvedValue(mockData);

  const result = await processData();
  expect(getDataSpy).toHaveBeenCalled();
  expect(result).toEqual(/* expected processed data based on mockData */);
  getDataSpy.mockRestore();
  ```

  ## Mock a function that takes arguments

  ```js
  function addPrefix(str) {
    return `prefix-${str}`;
  }
  ```

  ```js
  test("dynamic mock for addPrefix function", () => {
    const mockFunction = jest.fn((str) => `mock-${str}`);

    // Example usage of mockFunction
    const result1 = mockFunction("test");
    const result2 = mockFunction("example");

    expect(result1).toBe("mock-test");
    expect(result2).toBe("mock-example");
  });
  ```

  ## Mocking network requests

  ### Mocking Axios

  ```js
  jest.mock("axios", () => ({
    get: jest.fn().mockResolvedValue(mockData),
    post: jest.fn().mockResolvedValue(mockData),
  }));
  ```

  Or we could implement this way:

  ```js
  jest.mock("axios");
  axios.get.mockResolvedValue({ data: "mockedData" });
  axios.post.mockResolvedValue({ data: "mockedData" });
  ```

  Then we can use the mocked axios functions in our tests:

  ```js
  const result = await fetchData(); // the function that uses Axios `get``
  expect(result).toBe("mockedGetData");

  const result = await sendData({ key: "value" }); // the function tha uses Axios `post`
  expect(result).toBe("mockedPostData");
  ```

  ### `mockImplementation`

  For more configurable cases we can use `mockImplementation`:

  ```js
  it("sends data", async () => {
    // Mock axios.post using mockImplementation
    axios.post.mockImplementation((url, data) => {
      if (data.key === "value") {
        return Promise.resolve({ data: "mockedPostData" });
      } else {
        return Promise.reject({ error: "An error occurred" });
      }
    });

    const result = await sendData({ key: "value" });

    expect(result).toBe("mockedPostData");
  });
  ```

  If we want to change the `get` and `post` values in different tests, we can do
  so by using `mockImplementation`:

  ## Mocking exceptions

  Again we use `mockImplementation`:

  Say we have the following function:

  ```js
  // fetchData.js
  import axios from "axios";

  const fetchData = async (url) => {
    try {
      const response = await axios.get(url);
      return response.data;
    } catch (error) {
      throw new Error("Error fetching data");
    }
  };

  export default fetchData;
  ```

  We would mock the success and the error as follows:

  ```js
  import axios from "axios";
  import fetchData from "./fetchData";

  jest.mock("axios");

  describe("fetchData", () => {
    it("fetches data successfully", async () => {
      axios.get.mockResolvedValue({ data: "mockedData" });

      const result = await fetchData("https://api.example.com/data");

      expect(result).toBe("mockedData");
    });

    it("throws an error when fetching fails", async () => {
      axios.get.mockImplementation(() => {
        throw new Error("API error");
      });

      // We use an asynchronous assertion here because we're expecting a promise to reject
      await expect(fetchData("https://api.example.com/data")).rejects.toThrow(
        "Error fetching data"
      );
    });
  });
  ```

  ## Parameterization

  The following offers a good opportunity for parameterisation:

  ```js
  it("should return page for deletion from `ipages-live`", async () => {
    // preview = false, isInternal = false
    await deletePageFromS3("url", false, false);
    const deleteObjectCommand = s3ClientMock.calls()[0].args[0];
    expect(deleteObjectCommand.input).toEqual({
      Bucket: "bbc-ise-ipages-live",
      Key: "url/index.html",
    });
  });

  it("should return page for deletion from `preview`", async () => {
    // preview = true, isInternal = false
    await deletePageFromS3("url", true, false);
    const deleteObjectCommand = s3ClientMock.calls()[0].args[0];
    expect(deleteObjectCommand.input).toEqual({
      Bucket: "staff.bbc.com-preview",
      Key: "preview/url/index.html",
    });
  });

  ...
  ```

  Each time we are passing in three parameters to the `deletePageFromS3` function
  which is the object under test. Each time there are different variations in the
  object that is output.

  To parameterize the process rather than use repeated `it` blocks we can combine
  the input paramters and outputs into an array:

  ```js
  const testParams = [
    {
      preview: false,
      isInternal: false,
      bucket: "ipages-live",
      key: "url/index.html",
    },
    {
      preview: true,
      isInternal: false,
      bucket: "staff.com-preview",
      key: "preview/url/index.html",
    },
  ];
  ```

  Then use `it.each` to loop through all possible parameter combinations:

  ```js
  it.each(testParams)(
    "should return page for deletion from %s",
    async ({ preview, isInternal, bucket, key }) => {
      await deletePageFromS3("url", preview, isInternal);
      const deleteObjectCommand = s3ClientMock.calls()[0].args[0];
      expect(deleteObjectCommand.input).toEqual({
        Bucket: bucket,
        Key: key,
      });
    }
  );
  ```

  This uses the `%s` variable to print the parameters from each test, which
  outputs:

  ```
    ✓ should return page for deletion from {
    preview: false,
    isInternal: false,
    bucket: 'ipages-live',
    key: 'url/index.html'
  } (1 ms)
      ✓ should return page for deletion from {
    preview: true,
    isInternal: false,
    bucket: 'staff.com-preview',
    key: 'preview/url/index.html'
  }
  ```


path: Text_manipulation_in_Bash.md
sortable_path: Text_manipulation_in_Bash.md
title: Text manipulation
lead: ## Sorting strings: `sort`
checksum: 16bd858534a5e8bb07228d40c6a8984bad8a4b74cf7a8c1b32bae9ae2ba8dfe1
created: 2025-10-01 13:31:01.942920623+00:00
modified: 2025-08-13 15:57:13.903573874+00:00
metadata: {"tags":["shell"]}
body:
  ## Sorting strings: `sort`

  If you have a `.txt` file containing text strings, each on a new line you can
  use the sort function to quickly put them in alphabetical order:

  ```bash
  sort file.txt
  ```

  Note that this will not save the sort, it only presents it as a standard output.
  To save the sort you need to direct the sort to a file in the standard way:

  ```bash
  sort file.txt > output.txt
  ```

  ### Options

  - `-r`
    - reverse sort
  - `c`
    - check if file is already sorted. If not, it will highlight the strings which
      are not sorted

  ## Find and replace: `sed`

  The `sed` programme can be used to implement find and replace procedures. In
  `sed`, find and replace are covered by the substitution option: `/s` :

  ```bash
  sed ‘s/word/replacement word/’ file.txt
  ```

  This however will only change the first instance of word to be replaced, in
  order to apply to every instance you need to add the global option: `-g` .

  As sed is a stream editor, any changes you make using it, will only occur within
  the standard output , they will not be saved to file. In order to save to file
  you need to specify a new file output (using `> output.txt`) in addition to the
  original file. This hasthe benefit of leaving the original file untouched whilst
  ensuring the desired outcome is stored permanently.

  Alternatively, you can use the `-i` option which will make the changes take
  place in the source file as well as in standard input.

  Note that this will overwrite the original version of the file and it cannot be
  regained. If this is an issue then it is recommended to include a backup command
  in the overall argument like so:

  ```bash
  sed -i.bak ‘s/word/replacement word/’ file.txt
  ```

  This will create the file `file.txt.bak` in the directory you are working within
  which is the original file before the replacement was carried out.

  ### Remove duplicates

  We can use the `sort -u` command can be used to remove duplicates:

  ```bash
  sort -u file.txt
  ```

  It is important to sort before attempting to remove duplicates since the `-u`
  flag works on the basis of the strings being adjacent.

  ## Split a large file into multiple smaller files: `split`

  Suppose you have a file containing 1000 lines. You want to break the file up
  into five separate files, each containing two hundred lines. You can use `split`
  to accomplish this, like so:

  ```bash
  split -l 200 big-file.txt new-files
  ```

  `split` will categorise the resulting five files as follows:

  - new-file-aa,
  - new-file-ab
  - new-file-ac,
  - newfile-ad,
  - new-file-ae.

  If you would rather have numeric suffixes, use the option `-d` . You can also
  split a file by its number of bytes, using the option `-b` and specifying a
  constituent file size.

  ## Merge multiple files into one with `cat`

  We can use `cat` read multiple files at once and then append a redirect to save
  them to a file:

  ```bash
  cat file_a.txt file_b.txt file_c.txt > merged-file.txt
  ```

  ## Count lines, words, etc: `wc`

  To count words:

  ```bash
  wc file.txt
  ```

  When we use the command three numbers are outputted, in order: lines, words,
  bytes.

  You can use modifiers to get just one of the numbers: `-l`, `-w` , `-b` .

raw_content:
  ---
  tags:
    - shell
  ---

  # Text manipulation

  ## Sorting strings: `sort`

  If you have a `.txt` file containing text strings, each on a new line you can
  use the sort function to quickly put them in alphabetical order:

  ```bash
  sort file.txt
  ```

  Note that this will not save the sort, it only presents it as a standard output.
  To save the sort you need to direct the sort to a file in the standard way:

  ```bash
  sort file.txt > output.txt
  ```

  ### Options

  - `-r`
    - reverse sort
  - `c`
    - check if file is already sorted. If not, it will highlight the strings which
      are not sorted

  ## Find and replace: `sed`

  The `sed` programme can be used to implement find and replace procedures. In
  `sed`, find and replace are covered by the substitution option: `/s` :

  ```bash
  sed ‘s/word/replacement word/’ file.txt
  ```

  This however will only change the first instance of word to be replaced, in
  order to apply to every instance you need to add the global option: `-g` .

  As sed is a stream editor, any changes you make using it, will only occur within
  the standard output , they will not be saved to file. In order to save to file
  you need to specify a new file output (using `> output.txt`) in addition to the
  original file. This hasthe benefit of leaving the original file untouched whilst
  ensuring the desired outcome is stored permanently.

  Alternatively, you can use the `-i` option which will make the changes take
  place in the source file as well as in standard input.

  Note that this will overwrite the original version of the file and it cannot be
  regained. If this is an issue then it is recommended to include a backup command
  in the overall argument like so:

  ```bash
  sed -i.bak ‘s/word/replacement word/’ file.txt
  ```

  This will create the file `file.txt.bak` in the directory you are working within
  which is the original file before the replacement was carried out.

  ### Remove duplicates

  We can use the `sort -u` command can be used to remove duplicates:

  ```bash
  sort -u file.txt
  ```

  It is important to sort before attempting to remove duplicates since the `-u`
  flag works on the basis of the strings being adjacent.

  ## Split a large file into multiple smaller files: `split`

  Suppose you have a file containing 1000 lines. You want to break the file up
  into five separate files, each containing two hundred lines. You can use `split`
  to accomplish this, like so:

  ```bash
  split -l 200 big-file.txt new-files
  ```

  `split` will categorise the resulting five files as follows:

  - new-file-aa,
  - new-file-ab
  - new-file-ac,
  - newfile-ad,
  - new-file-ae.

  If you would rather have numeric suffixes, use the option `-d` . You can also
  split a file by its number of bytes, using the option `-b` and specifying a
  constituent file size.

  ## Merge multiple files into one with `cat`

  We can use `cat` read multiple files at once and then append a redirect to save
  them to a file:

  ```bash
  cat file_a.txt file_b.txt file_c.txt > merged-file.txt
  ```

  ## Count lines, words, etc: `wc`

  To count words:

  ```bash
  wc file.txt
  ```

  When we use the command three numbers are outputted, in order: lines, words,
  bytes.

  You can use modifiers to get just one of the numbers: `-l`, `-w` , `-b` .


path: The_History_of_Computing_Swade.md
sortable_path: The_History_of_Computing_Swade.md
title: History of Computing (Swade, 2022 )
checksum: 74218045b9ac4980e8b4c9e5813ac1e0dd6a8fb48516ad0a52fa92aa50c9959c
created: 2025-10-01 13:31:01.949829422+00:00
modified: 2025-08-13 15:57:14.008073771+00:00
metadata: {"created":"Friday, August 23, 2024","tags":["literature","computer-history"]}
lead:
  | Title                                               | Author      | Publication date | Resource type |
  | --------------------------------------------------- | ----------- | ---------------- | ------------- |
  | The History of Computing: A Very Short Introduction | Doron Swade | 2022             | Book          |

body:
  | Title                                               | Author      | Publication date | Resource type |
  | --------------------------------------------------- | ----------- | ---------------- | ------------- |
  | The History of Computing: A Very Short Introduction | Doron Swade | 2022             | Book          |

  ## Timeline

  A typical timeline approach rooted in major innovations.

  - Ancient aids to counting: knotted cords and notched sticks
  - Ancient aids to calculation: counting boards and abacii
  - Early mechanical calculator devices in the 17th century (number wheels,
    Pascal, Leibniz)
  - Modern aids to calculation: slide rules following the discovery of
    [logarithms](Logarithms.md)
  - Mechanised, automated calculating engines of Babbage in the 19th century
  - Punched-card machines leading to IBM in the early 20th century
  - Analogue and electro-mechanical computers of the early 20th century inclusive
    of wartime computers
  - Early valve-based (vacuum-tubed) digital computers (again wartime and early
    Cold War)
  - The invention of the transistor and first fully-digital computers
  - The invention of [integrated_circuits](Integrated_circuits.md)
  - Supercomputers
  - Minicomputers
  - Consumer personal computers
  - Internet and later, Web
  - Smart phones

  First three phases of digital electronic computers:

  - Wartime up to 1950s vacuum-tube era
  - Transistor era up to 1963
  - First microchip era ending in early 1970s

  ## Terms

  | Term            | Definition                                                                                                                                                                  |
  | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | automatic       | can execute programs, process data and perform functions on its own once it has been started. Can monitor its own processes and make adjustments without human intervention |
  | general-purpose | a computer designed to be able to solve a wide variety of problems and perform many different types of tasks as opposed to a computer built for a specific, limited purpose |
  | programmable    | the ability to change the operations a computer peforms without physically altering its hardware                                                                            |

  ## Mechanical calculating devices in the 17th century

  Focus was chiefly on creating a desktop calculator capable of four-function
  arithmetic.

  ![Photograph of Pascaline](../img/pascaline.jpg)

  ![Photograph of replica of Leibniz stepped drum machine](../img/leibniz-stepped-drum.jpg)

  The main contenders were the Pascaline of Pascal (which only did cumulative
  addition) and the wheel or "stepped drum" calculator of Leibniz that could do
  all operations (in theory).

  Subsequent designs were based on these artefacts. In practice, neither worked
  consistently well with the carriage of tens remaining a sticking point.

  ![Photograph of Arithmometer](../img/arithmometer.jpg)

  ![Photograph of Comptometer](../img/comptometer.png)

  The arithmometer (crank driven) and comptometer (key-driven) were descendents of
  the Leibniz design that became commercially viable by the 19th century along
  with other mechanical calculators. In the US, Burroughs dominated the market.

  ## Babbage: mechanized, automated calculation

  > I wish to God these calculations had been executed by Steam (Babbage)

  With Babbage's machines we see an approach to computation that can only be
  understood against the backdrop of the Industrial Revolution in which they were
  conceived.

  The idea is that the machine is a factory and number is the product. In the same
  way as the mechanised looms created textiles. It is the extension of a model of
  industrial production from goods/commodities to information.

  Babbage conceived two machines: the Difference Engine (DE) and the Analytical
  Engine (AE). Neither were successfully built in his lifetime. The DE preceded
  the AE and was basically an advanced mechanical calculator whereas the AE
  approximated a general purpose computer.

  Despite this, with the Difference Engine, in contrast to preceding _aids to
  calculation_, the steps of the computational algorithm were no longer directed
  by human intelligence but by internal rules embodied in the mechanism and
  automatically generated.

  ### Difference Engine

  The DE's single purpose was to calculate and output mathematical tables such as
  the results of polynomial equations. The idea was that you would input the
  answer a bell would ring and the machine would _halt_. This influenced Turing
  later. It was non-programmable and designed for a specific set of calculations.

  ### Analytical Engine

  Conceived as a general-purpose computing machine capable of performing a wide
  range of calculations, programmable using punched cards similar to those used
  with Jacquard looms.

  It more resembled modern computers in that Babbage used concepts that would
  later translate into the [von Neumann architecture](CPU_architecture.md) There
  was a "mill" (CPU), "store" (memory) and input/output mechanisms. It also had a
  concept of looping and conditional branching.

  ### Lovelace's insight

  A central idea of Ada Lovelace, expressed in her notes on the Analytical Engine
  is that **number can represent entities other than quantity**.

  If we assign meaning to number, results arrived at by operating on number
  according to rules can say things about the world when mapped back onto the
  world using the meanings assigned to them.

  Lovelace's insight was that the potential of computing lay in the power of
  machines to manipulate representations of the world contained in symbols.

  ## Analogue computers

  Both digital and analogue computers are automatic. They differ in _how they
  represent quantities_ and how their outputs are derived.

  With digital machines, quantity is represented as a string of discrete digits.

  With analogue machines, quantity is a physical property _in itself_ rather than
  a representation. This could be, for example: the lowering of a weight, the flow
  of a liquid, or an electrical charge. This physical behaviour is **analagous**
  to the system that is being modelled. Quantities are continuously variable
  values rather than discrete (discontinuous values).

  Digital machines produce results by _calculation_ whereas analogue machines
  produce results by _measurement_, e.g. the height of liquid in a tank or the
  time it taks for a tank to be emptied.

  ### Examples of analogue computers

  #### The Phillips Hydraulic Computer

  This used fluid to model the workings of the British economy. It consisted of a
  series of transparent plastic tanks and pipes which were fastened to a wooden
  board.

  ![Philips Hydraulic Computer](../img/hydraulic_computer.jpg)

  Each tank represented some aspect of the UK national economy and the flow of
  money around the economy was illustrated by coloured water. At the top of the
  board was a large tank called the treasury. Water (representing money) flowed
  from the treasury to other tanks representing the various ways in which a
  country could spend its money. For example, there were tanks for health and
  education. To increase spending on health care a tap could be opened to drain
  water from the treasury to the tank which represented health spending.

  #### Bush's Differential Analyser

  This was designed to solve differential equations by integration. In contrast to
  the Philips Computer it was general enough to be used to solve problems from
  different contexts. Examples of these contexts: heat flow, ballistics,
  mechanics, population growth, chemical interactions, astronomy.

  ![Differential Analyser](../img/differential_analyser_bush.jpg)

  It was about the size of a room and used shafts, motors, discs and wheels to
  work.

  ### Historiography

  There is a tendency in the history of computing to downplay or diminish the
  contribution of analogue computing devices and to present them as just an
  inferior precursor to the inevitable dominance of digital electronic computers.

  This is ahistorical and inaccurate.

  Analogue (and electromechanical devices) overlapped with and coexisted with
  digital devices for 40 years, spanning the first three generations of digital
  electronic devices. The term "analogue" itself only came about when the need
  arose to distinguish digital devices from other types of computer. They were not
  "rivals" before this.

  ### Electro-mechanical computers

  Electro-mechanical devices (also known as "electronic analogue computers") are a
  sort of midway between full digital devices and analogue computers, forming a
  bridge between the two eras.

  Their heyday was roughly 1935 - 1945.

  Their key components were:

  - [relays](Relays.md) for logic operations
  - rotating shafts and gears for performing calculations
  - punched cards or paper tape for input instructions and outputs

  They were slower than fully digital computers as they were limited by the speed
  of moving components rather than the flow of electric charge. In addition the
  various mechanical parts were prone to wear and needed frequent replacement.

  ### Examples of electro-mechanical computers

  #### Harvard Mark 1 (1937-1944) - Aikin, IBM

  Designed by Howard Aiken and built by IBM (1937-1944). Also known as the
  _Automatic Sequence Controlled Calculator_ (ASCC). A general-purpose
  electro-mechanical computer it was most famously used at Los Alamos by von
  Neumann to calculate the blast yield of the atomic bomb.

  ![Harvard Mark One](../img/Harvard_Mark_1.jpg)

  It was more than 15m in length and weighed 5 tonnes comprising over 750,000
  parts. It used paper tape and punched cards for input/output.

  #### Complex Number Calculator (1940) - Bell Labs, Stibitz

  Not general purpose nor was it programmable. It was hardwired to perform a
  specific set of operations on numbers and nothing else. It used relays like the
  others. Its distinguishing feature was that it used a teletype for input rather
  than cards or paper tape.

  ![Calculating panel of the CMC](../img/complex-number-calculator-bell-labs.jpg)

  ![Teletype input of CMC](../img/stibitz-teletype.jpg)

  It comprised a panel (the calculating unit) and teletype (the input). One could
  remotely access the computer from the teletype in another location, providing it
  was connected to Bell Lab's telephone network.

  #### Z3 (1941) - Konrad Zuse

  ![Z3](../img/Zuse-Z3.jpeg)

  First designed in 1938 and completed in 1941. Considered the first fully
  automatic, programmable digital computer although relay-based. Programs were
  stored on external punched tape and binary was used as the number system.
  Destroyed by Allied bombing in the War but a replica (pictured) was built
  in 1998.

  ## Vacuum-tube devices

  Vacuum-tube devices were the first fully electronic digital devices as they used
  vacuum-tubes for implementing binary logic rather than relay switches.

  Canonical examples from this era are the ABC computer, the Collossus and most
  famously, the ENIAC.

  ### The Antanasoff-Berry Computer (1939 - 1942)

  ![ABC Computer](../img/abc-computer.jpg)

  Fully automatic, digital electronic computer but not programmable or really
  general-purpose, being created to do linear equations. It took decimal input and
  output but operated internally using binary arithmetic. The input was punched
  cards and the output was via a front-panel display.

  Its importance stems from the fact that it was the first to use vacuum-tubes for
  the arithmetic calculations of the processor.

  ### Colossus (1943-1945) - Tommy Flowers/Max Newman

  Designed and built at the Post Office Research Station at Dollis Hill. Used for
  cracking the Lorenz messages of the German high command.

  Mainly important (beyond its role in the War) for establishing the viability of
  vacuum-tubes over relays. It comprised 2.4 thousand tubes.

  Debatable the extent to which the Collosus can be called the _first digital
  computer_ since it was not general purpose and not fully programmable.

  Furthermore it was not even a complete solution in its context of application.
  The codebreakers would be given a certain advantage using the Collosus
  (narrowing the field of possible deciphering keys) but still need to use manual
  methods to break the code. It made decipher time shorter rather than executing
  the full diciphering. In contrast to, say, the Bombe computer that would
  identify the key for the days transmissions.

  It's actual influence is also hard to gauge because of the secrecy that remained
  in place after the War. Apart from those who worked on it, its technical
  advancements were not public knowledge until the late 1970s.

  ### ENIAC (1945) John Mauchly/J.P Eckert

  The _Electronic Numerical Calculator and Integrator_

  The main motivation for creating the ENIAC was military: the US Army needed
  speed and accuracy when calculating artiliary firing tables and munition
  trajectory so that shells could hit their intended targets. Accuracy depended on
  myriad factors: gun elevation, shell shape and weight, explosive charge,
  distance, wind, temperature etc. Previously this had been done by human
  computers under the aegis of the Ballistic Research Laboratory.

  ![The main control panel of the ENIAC](../img/eniac.jpg)

  It was not completed until after VE day but was used at Los Alamos after the War
  and retired in 1955.

  The BRL commissioned the Moore School of Engineering at the University of
  Pennsylvania for the construction of an automatic electronic calculator.

  It used vacuum-tubes, along with switches and realys. It used decimal rather
  than binary and had punch card input and output. It was crudely programmable and
  could be set up to work on different problems by rearranging the different
  components using switches and plugboards. But this took roughly two days.

  Its design was very influential because it was made public and not classified.

  ### EDVAC (1944-49) John Mauchly/J.P Eckert/ John von Neumann

  Built at the Moore School for the BRL as a successor to ENIAC by Mauchly and
  Eckert whilst ENIAC was still operational. It's architecture arose out of
  discussions between von Neumann and Mauchly and Eckert and was necessitated by
  the need to calculate the behaviour of the fissile materials in the atomic bomb.

  It resulted in the famous _First Draft of a Report on the EDVAC_ by von Neumann
  which is seen as the definitive statement of the modern **digital programmable
  general-purpose computer**.

  As a result of the paper, von Neumann effectively got the credit for the
  invention even though it synthesised ideas from the ENIAC and the contributions
  of others to the EDVAC.

  There was lots of disputed provenance and claims of plagiarism generally.
  Antanasoff won a legal claim against Eckert and Mauchly for stealing ideas from
  the ABC to make ENIAC.

  // Add more on the hardware.

  ### Concept of the _internal stored program_

  - Key innovation of the EDVAC was the _internal stored program_ (ISP).

  - Solution to the problem of preceding computers requiring down-time when
    switching between applications and problem sets.

  - Circuitry would need to be reconfigured before the computer could run on the
    next problem

  - ISP removed the friction:

    - the instructions comprising the program would be prepared on tape or punched
      cards and read into electronic memory
    - the hardware configuration of the machine would remain the same accross
      different programs and require no reconfiguration, only the input program
      data would change

  - This was basically the invention of software. Both instructions and data
    shared the same memory space once they were read-in. Before, the data would be
    stored in memory but the instructions would be read one-by-one from the
    external storage media.

  - Key consequences:
    - flexibility (no need for re-wiring)
    - speed: fetching instructions from memory is quicker than fetching from
      external storage devicde
    - self-modification: since they were in memory, programs could modify
      themselves during their execution (e.g. use branching conditions) and
      treating one program as data for another. This enabled more complex
      algorithms and also opened the possibility of higher-level languages and
      compilers
    - simplified architecture: both programs and data in memory
    - generalisation: computers could switch between different tasks simply by
      loading different programs

  ### Post-EDVAC devices

  There were several notable devices which attempted to implement the architecture
  described in the _First Draft_ using vacuum tubes for logic operations and a
  variety of different electronic methods for memory:

  - the Manchester "Baby"
  - the Cambridge EDSAC
  - Turing's ACE computer

  #### Manchester Baby (1948)

  ![The Manchester Baby Computer](../img/manchester_baby.png)

  - An experimental computer intended to create the
    [von Neumann architecture](CPU_architecture.md) using
    [Williams_Tube_RAM](Williams_Tube_RAM.md)

  - Developed at the Univesity of Manchester and completed in 1948.

  - Considered the first electronic stored-program computer and first to contain
    all the elements of a modern electronic digital computer.

  #### EDSAC (1949) J.Wilkes et al.

  ![EDSAC](../img/EDSAC.jpg)

  - _Electronic Delay Storge Automatic Computer_

  - Constructed by Maurice Wilkes and others at the Mathematical Laboratory of the
    University of Cambridge.

  - The second digital stored-program computer after the Manchester Baby.

  - It used vacuum-tubes for the arithmetical operations in the ALU and mercury
    [delay line memory](Delay_line_memory.md) for the RAM.

  - Designed to be used by relatively non-specialist practitioners from other
    university departments who were expected to program it themselves. To this
    end, a formal programming paradigm was forged for the EDSAC which established
    the following:

    - subroutines as a library of common procedures available to programmers (e.g.
      printing a result, reading input tape, program checking, mathematical
      operations)
    - diagnostics: techniques for verifying program code and its correctness

  ![Programming Digital Electronic Computer book](../img/programming_a_digital_electronic_computter.jpg)

  - The key players wrote the first textbook on programming in 1951: _The
    Preparation of Programs for an Electronic Digital Computer_ (Wilkes, Wheeler,
    and Gill)

  #### The Automatic Computing Engine (ACE) (1946) Alan Turing

  - Technical design provided by Alan Turing working at the Mathematics Division
    of the National Physical Laboratory. The product of Turing's theoretical work
    in "On Computable Numbers" where he proposes the concept of a
    [Turing_machine](Turing_machines.md) and based on his experience with early
    single-purpose computing devices at Bletchley.

  - Due to secrecy over wartime work, it was hard for get Turing to build a fully
    electronic implementation since this would disclose advances made during the
    War. Tommy Flowers was intended to be brought in to build it but again this
    was scuppered over the classification of wartime technology. Had this not been
    the case, it is likely it would've been seen as on par with the EDVAC in its
    design, if not dwarfing it.

  - Turing proposed a stored program architecture with high-speed memory. It would
    be more perfomant than the EDVAC as a result.

  ![The Pilot ACE computer](../img/pilot-ace.jpg)

  - Turing's actual design was not implemented as it was thought too ambitious
    given how powerful it would need to be. Instead a smaller prototype was made,
    the Pilot ACE. It used vacuum-tubes for logic, and mercury delay lines for
    memory. Data and memory was input via punched cards.

  ### Further applications of the EDSAC architecture

  ### LEO I

  - Built by Lyons Tea Company to manage business affairs (payroll, inventory,
    stock management)

  - Marked a shift from military and academic contexts for computers to business
    and data management.

  ### IAS machines

  ![The MANIAC computer](../img/MANIAC_computer.jpg)

  Several machines were built at the Institute for Advanced Study utilising the
  "von Neumann" architecture and associated advancements such as vacuum-tubes and
  Williams' tubes. There was the original IAS machine (1952) as well as the
  JOHNNIAC (1954) and MANIAC (1956).

  Although their purpose was military (Los Alamos), their designs were public and
  widely studied making them influential outside of academia.

  ### UNIVAC (1951)

  ![UNIVAC_computer](../img/univac-1.jpg)

  Mauchley and Eckert, who had designed the ENIAC left the Moore School and went
  into business: Eckert-Mauchley Computer Corporation. This was bought by
  Remington Rand and in subsequent years became the main competitor to IBM.

  In this capacity they built the UNIVAC: _Universal Automatic Computer_. It's
  name being an embodiment of its nature as a general-purpose, electronic digital
  computer.

  It used vacuum-tubes for logic and mercury delay lines for memory. It had
  multiple means of input/output including: directly via an operator console
  (basically a typewriter keyboard), magnetic tape for input and output, along
  with punched cards.

  ![UNIVAC operator console](../img/univac-operator-console.jpeg)

  It was the first computer specifically designed to include business and
  administrative use. This was underscored by its first client: the US Census
  Bureau.

  A key event was its succesful prediction of the 1952 general election. It
  correctly predicted a landslide for Eisenhower (against expectations). (This was
  so unlikely, they actually fudged the data because they thought the machine was
  way off.) It was a novelty on the results night but it cemented a certain
  concept of the computer in the public imagination - large, room sized machines
  with blinking lights.

  Swade notes that Eckert and Mauchley effectively launched the US commercial
  computer industry with the UNIVAC.

  ## IBM

  - First created in 1911 as a merger of four companies all involved in some
    capacity in business data processing. The most significant of these was the
    Hollerith Tabulating Machine Company who's punched card machines had been used
    in the 1890 census, greatly reducing its collation time

  - Named IBM in 1924

  - UNIVAC represented the encroachment of computing devices into IBM's market of
    punched-card based data-processing and effectively put IBM on notice.

  - To attack the threat IBM brought out three crucial machines: the 701, 702
    and 650.

  - IBM's approach to marketing their computers was to emphasise the comfort of
    continuity over the shock of the new represented by the UNIVAC. IBM had
    already captured the business market. Their computers would be more of the
    same, just faster and more powerful than their punched card machines due to
    the electronics. To this end they initially did not call their devices
    'computers'.

  - IBM were ultimately totally successful in their pivot to the computing market.
    By 1960 it owned over 70% of the data processing and computer market.

  ### IBM 701 ("Defense Calculator") (1952)

  ![Control unit of the IBM 701](../img/ibm-701-control-unit.jpeg)

  IBM's first electronic computer. It directly competed with the UNIVAC for
  government contracts. It followed the prevailing approach of vaccuum tubes for
  logic and had a variety of storage methods: Williams tubes,
  [magnetic tape](Magnetic_tape.md) , and magentic drums.

  701s were used at Los Alamos and avionics companies. Some were also used to
  manage payroll in business contexts.

  ### IBM 702 ("Tape Processing Machine") (1953)

  ![Advertisement for the IBM 702](../img/ibm-add.jpeg)

  Focused primarily on business applications and targetted at businesses rather
  than government contracts. Less powerful than the 701.

  ### IBM 650 (1953)

  ![IBM 650](../img/ibm-650.jpg)

  Low-cost general purpose machine using magnetic drum memory. Marketed as slower
  but more affordable than the 700 range. It was mass-produced unlike the others
  which were built for specific customers. It was in fact the first mass-produced
  computer in the world. It proved the breakout star in IBMs initial line up and
  sometimes called "IBM's Model T".

  ![IBM_650 console panel](../img/ibm-650-console-panel.jpg)

  IBM offerred 650s to univesities at a 60% discount on the condition that the
  universities would establish courses in computing. This was shrewd as it meant
  that a whole generation of engineers and computer scientists learned on 650s and
  would prefer them in their research. It also created a pool of skilled users
  that would go on to work at IBM.

  Donald Knuth dedicated _The Art of Computer Programming_ to the 650.

  It used [magnetic drum memory](./Magnetic_drum_memory.md) along with
  vacuum-tubes.

  ### IBM 1401 (1959)

  ![IBM 1401](../img/ibm-1401.jpg)

  Another IBM mainframe. Transistors replaced vacuum-tubes. Magnetic core storage
  replaced magnetic drum storage. It was housed in rectangular light-blue cabinets
  and the ubiquity of the 1401 in industry earned IBM the moniker 'Big Blue'.

  ### IBM System/360 (1964)

  ![IBM 360](../img/ibm-360.jpg)

  Named to suggest all-round compatibility - a family of mainframes designed to
  cover commercial and scientific applications. Considered one of history's most
  successful computers.

  Up until this point all IBM computers had a programming language unique to the
  specific processor. At that point there were about seven IBM computer models in
  active use and they were all incompatible with each other.

  ![IBM 360 promo poster](../img/ibm-360-in-use.jpeg)

  In contrast the 360 computers all used the same programming language. This meant
  they were interoperable with each others. Because the there were variants in the
  360 ranges, customers could purchase a smaller system knowing they could expand
  it if their needs grew, without reprogramming their application software.

  ## Magnetic core devices: Whirlwind and SAGE

  Two devices that leveraged the new technology of
  [magnetic_core_memory](Magnetic_core_memory.md) where the Whirlwind and SAGE
  computers. The ENIAC was also updated to use magnetic cores.

  ![Whirlwind magnetic core memory](../img/whirlwind-magnetic-cores.JPG)

  The Whirlwind computer (1953) was a flight simulator and the first to use
  magnetic cores. Crucially was able to operate in realtime for output.

  The SAGE computer (_Semi-Automatic Ground Environment_) emerged as a response to
  the Soviet acquisition of the atomic bomb in 1949. The Americans realised they
  needed a much better early warning and air defence system that would allow them
  to identify bombers in their airspace and dispatch fighters.

  To achieve this, it was necessary to detect enemy bombers with radar and compute
  an interception course. Alterations in the bomber flight path required
  continuous updates and real-time computation of directions for the responding
  fighter.

  The resulting computer was SAGE (made by IBM) which was modelled on Whirlwind,
  using magnetic cores. SAGE computers were spread accross the continental US in
  sectors and managed by NORAD.

  ![SAGE command post](../img/SAGE_command_post.jpg)

  The SAGE system was operation between 1958-1984, receiving continual
  improvements and updates. In addition to magnetic cores it introduced many
  technological innovations that influenced computers generally: interactive
  screens, printed circuit boards, mass-storage devices, digital communication
  over telephone lines, time-sharing, along with advances in software.

  Because so many private companies worked as contractors on SAGE (IBM, Burroughs,
  Bell Labs, RAND Corp), the military technology (both hardware and software) made
  its way from the military context to business and consumer computing.

  An example of a civilian application of SAGE technology was airline booking
  systems, where booking reservation data needed to be processed in realtime. IBM
  worked with American Airlines to introduce this.

  ## Transistor and integrated circuit revolution

  The arc of modern computing history (and its main eras) can be presented as

  - vacuum-tubes
  - [transistors](Transistors.md)
  - [integrated_circuits](Integrated_circuits.md)

  The transistor was invented at Bell Labs in 1947 by William Shockley and others.
  They were not created with computers in mind. Instead they were first used in
  hearing aids (1953) and transistor radios (1954).

  From the mid-1950s onwards they started being used in military computers.

  They had several advantages over vacuum-tubes: they were durable and reliable
  with no required startup time, they were power efficient and small.

  The actual period where transistors alone were supreme in the form of a
  transistor board was relatively short-lived and rapidly gave way to ICs.

  Computers of the transistor era had the following components separate from each
  other: the transistors, wired connections, resistors and capacitors. Integrated
  circuits put these all on the same piece of silicon.

  They were first manufactured by Fairchild Semiconductor which was an offshoot of
  Shockley's Shockley Semiconductor Laboratory.

  The initial marked for ICs was military and scientific: the Minuteman ICBM and
  computers of the Apollo spacecraft. This laid the groundworkd for later
  commercial use and the personal computer revolution.

  ## Mini-computers

  Mini computers did not threaten mainframes but they opened up a new class of
  user. They were vastly cheaper than a mainframe and only the size of a small
  fridge.

  The main player was DEC (Digital Equipment Corporation) starting in the
  mid-1960s. The introduced the PDP-8 in 1965 (Programmed Data Processor). The
  PDP-8 used transisotrs and magnetic core memory. It was affordable to smaller
  businesses if not yet, consumers.

  ![The PDP-8](<../img/540px-PDP-8_(1).jpg>)

  The internals were made public and DEC encouraged making the machine extensible
  by users being permitted to create their own programs and specialised
  applications. This was a very different culture to IBM where technicians were
  required and machines were typically leased.

  By 1988, DEC was the second largest computer company after IBM. This led to
  additional companies entering the mini-computer market: Data General, Honeywell,
  Hewlett-Packard.

  Mini-computers died as a category in the mid-1990s in the wake of the PC.

  ## The Personal Computer

  The PC emerged out of advances in IC chip design - this was the engine of the
  PC.

  Intel (newly founded by Robert Noyce and Gordon Moore) were charged with making
  custom chips for a Japanese calculator company. Instead of creating the custom
  chips, they advised the company to use a general purpose chip that could handle
  general computing as well as the basic calculator functions. This generic chip -
  the Intel 4004 - was the microprocessor combined with three other ICs
  (comprising 2.3k transistors) that comprised the fundamental building blocks of
  the von Neumann architecture.

  ![Intel 4004 chip](../img/intel-4004.jpg)

  However it took until the Apple II in 1977 for the phrase "personal computer" to
  enter the lexicon and become an identifiable class.

  The broader context for this was the computer hobbyists in Silicon Valley/Menlo
  Park (particularly the Homebrew Club of amateur computer engineers) along with
  idealistic figures from the 60s counterculture (such as Stuart Brand) who began
  associating computing technology as a tool of personal liberty and fulfillment.

  ### Microsoft and Apple

  The PC "boom" began with the Altair 8800. This was a mail-order kit that
  required manual assembly. It was released by Micro Instrumentation Telemetry
  Systems (MITS) in 1975. It couldn't do much but had expansion capability
  (memory, teletype interface, casette player for data storage).

  ![Altair 8800](../img/altair-8800.jpg)

  Bill Gates and Paul Allen made a proposal to MITS: they would write software
  that would allow users to program the Altair in BASIC. They agreed and were
  prepared to distribute the software. As a result Gates and Allen founded
  Microsoft in 1975.

  Around the same time, Steve Wozniak (member of the Homebrew club) built the
  Apple I as a single-board hobbyist project. He made this available to buy via
  mail order and formed Apple with Steve Jobs in 1976 to manage the enterprise.

  ![Apple I](../img/apple1.jpg)

  By 1977 they had investment capital and brought out the Apple II. This was sold
  preassembled with casing and required no soldering. It had expansion slots for
  third-party vendors to create compatible devices and had colour graphics.

  The design of the Apple II was published in _Byte_ in order to invite
  crowd-sourced plugins and extensions. One of these was the Z-80 softcard which
  allowed it to run the CP/M OS (created by the company Digital Research) giving
  ready-made access to software such as a word-processor, spreadsheets and
  databases.

  ![Apple 2](../img/apple2.jpg)

  Other competitors in the PC market at this time were Radioshack's TRS-80 and the
  Commodore PET.

  ### IBM

  All the while it was hobbyist and tinkerers driving the PC market, IBM had no
  real interest. When PCSs started being used by businesses and their capacity
  grew to include typical business applications (databases, spreadsheets) it
  sensed an incursion into its market.

  ![IBM PC](../img/ibm-pc.jpg)

  Thus, in 1981, IBM launched the "IBM Personal Computer" which rapidly became the
  industry standard. This had the effect of legitimising the concept of a PC.
  There was overwhelming demand. In their marketing IBM melded home and office use
  to capture both markets at once.

  The internal specification of the IBM PC was freely available (dubbed 'open
  architecture'), in contrast to IBM's business computers, to encourage software
  expansion. Cards could be created to fit in its expansion slots adding graphics,
  sound, additional memory and networking capability.

  IBM originally contracted Digital Research to create software based on CP/M but
  this fell through. Microsoft got the deal to supply the OS which became MS-DOS
  (Microsoft Disk Operating System). Henceforth every PC and PC-clone came with
  DOS bundled.

raw_content:
  ---
  tags: [literature, computer-history]
  created: Friday, August 23, 2024
  ---

  # History of Computing (Swade, 2022 )

  | Title                                               | Author      | Publication date | Resource type |
  | --------------------------------------------------- | ----------- | ---------------- | ------------- |
  | The History of Computing: A Very Short Introduction | Doron Swade | 2022             | Book          |

  ## Timeline

  A typical timeline approach rooted in major innovations.

  - Ancient aids to counting: knotted cords and notched sticks
  - Ancient aids to calculation: counting boards and abacii
  - Early mechanical calculator devices in the 17th century (number wheels,
    Pascal, Leibniz)
  - Modern aids to calculation: slide rules following the discovery of
    [logarithms](Logarithms.md)
  - Mechanised, automated calculating engines of Babbage in the 19th century
  - Punched-card machines leading to IBM in the early 20th century
  - Analogue and electro-mechanical computers of the early 20th century inclusive
    of wartime computers
  - Early valve-based (vacuum-tubed) digital computers (again wartime and early
    Cold War)
  - The invention of the transistor and first fully-digital computers
  - The invention of [integrated_circuits](Integrated_circuits.md)
  - Supercomputers
  - Minicomputers
  - Consumer personal computers
  - Internet and later, Web
  - Smart phones

  First three phases of digital electronic computers:

  - Wartime up to 1950s vacuum-tube era
  - Transistor era up to 1963
  - First microchip era ending in early 1970s

  ## Terms

  | Term            | Definition                                                                                                                                                                  |
  | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | automatic       | can execute programs, process data and perform functions on its own once it has been started. Can monitor its own processes and make adjustments without human intervention |
  | general-purpose | a computer designed to be able to solve a wide variety of problems and perform many different types of tasks as opposed to a computer built for a specific, limited purpose |
  | programmable    | the ability to change the operations a computer peforms without physically altering its hardware                                                                            |

  ## Mechanical calculating devices in the 17th century

  Focus was chiefly on creating a desktop calculator capable of four-function
  arithmetic.

  ![Photograph of Pascaline](../img/pascaline.jpg)

  ![Photograph of replica of Leibniz stepped drum machine](../img/leibniz-stepped-drum.jpg)

  The main contenders were the Pascaline of Pascal (which only did cumulative
  addition) and the wheel or "stepped drum" calculator of Leibniz that could do
  all operations (in theory).

  Subsequent designs were based on these artefacts. In practice, neither worked
  consistently well with the carriage of tens remaining a sticking point.

  ![Photograph of Arithmometer](../img/arithmometer.jpg)

  ![Photograph of Comptometer](../img/comptometer.png)

  The arithmometer (crank driven) and comptometer (key-driven) were descendents of
  the Leibniz design that became commercially viable by the 19th century along
  with other mechanical calculators. In the US, Burroughs dominated the market.

  ## Babbage: mechanized, automated calculation

  > I wish to God these calculations had been executed by Steam (Babbage)

  With Babbage's machines we see an approach to computation that can only be
  understood against the backdrop of the Industrial Revolution in which they were
  conceived.

  The idea is that the machine is a factory and number is the product. In the same
  way as the mechanised looms created textiles. It is the extension of a model of
  industrial production from goods/commodities to information.

  Babbage conceived two machines: the Difference Engine (DE) and the Analytical
  Engine (AE). Neither were successfully built in his lifetime. The DE preceded
  the AE and was basically an advanced mechanical calculator whereas the AE
  approximated a general purpose computer.

  Despite this, with the Difference Engine, in contrast to preceding _aids to
  calculation_, the steps of the computational algorithm were no longer directed
  by human intelligence but by internal rules embodied in the mechanism and
  automatically generated.

  ### Difference Engine

  The DE's single purpose was to calculate and output mathematical tables such as
  the results of polynomial equations. The idea was that you would input the
  answer a bell would ring and the machine would _halt_. This influenced Turing
  later. It was non-programmable and designed for a specific set of calculations.

  ### Analytical Engine

  Conceived as a general-purpose computing machine capable of performing a wide
  range of calculations, programmable using punched cards similar to those used
  with Jacquard looms.

  It more resembled modern computers in that Babbage used concepts that would
  later translate into the [von Neumann architecture](CPU_architecture.md) There
  was a "mill" (CPU), "store" (memory) and input/output mechanisms. It also had a
  concept of looping and conditional branching.

  ### Lovelace's insight

  A central idea of Ada Lovelace, expressed in her notes on the Analytical Engine
  is that **number can represent entities other than quantity**.

  If we assign meaning to number, results arrived at by operating on number
  according to rules can say things about the world when mapped back onto the
  world using the meanings assigned to them.

  Lovelace's insight was that the potential of computing lay in the power of
  machines to manipulate representations of the world contained in symbols.

  ## Analogue computers

  Both digital and analogue computers are automatic. They differ in _how they
  represent quantities_ and how their outputs are derived.

  With digital machines, quantity is represented as a string of discrete digits.

  With analogue machines, quantity is a physical property _in itself_ rather than
  a representation. This could be, for example: the lowering of a weight, the flow
  of a liquid, or an electrical charge. This physical behaviour is **analagous**
  to the system that is being modelled. Quantities are continuously variable
  values rather than discrete (discontinuous values).

  Digital machines produce results by _calculation_ whereas analogue machines
  produce results by _measurement_, e.g. the height of liquid in a tank or the
  time it taks for a tank to be emptied.

  ### Examples of analogue computers

  #### The Phillips Hydraulic Computer

  This used fluid to model the workings of the British economy. It consisted of a
  series of transparent plastic tanks and pipes which were fastened to a wooden
  board.

  ![Philips Hydraulic Computer](../img/hydraulic_computer.jpg)

  Each tank represented some aspect of the UK national economy and the flow of
  money around the economy was illustrated by coloured water. At the top of the
  board was a large tank called the treasury. Water (representing money) flowed
  from the treasury to other tanks representing the various ways in which a
  country could spend its money. For example, there were tanks for health and
  education. To increase spending on health care a tap could be opened to drain
  water from the treasury to the tank which represented health spending.

  #### Bush's Differential Analyser

  This was designed to solve differential equations by integration. In contrast to
  the Philips Computer it was general enough to be used to solve problems from
  different contexts. Examples of these contexts: heat flow, ballistics,
  mechanics, population growth, chemical interactions, astronomy.

  ![Differential Analyser](../img/differential_analyser_bush.jpg)

  It was about the size of a room and used shafts, motors, discs and wheels to
  work.

  ### Historiography

  There is a tendency in the history of computing to downplay or diminish the
  contribution of analogue computing devices and to present them as just an
  inferior precursor to the inevitable dominance of digital electronic computers.

  This is ahistorical and inaccurate.

  Analogue (and electromechanical devices) overlapped with and coexisted with
  digital devices for 40 years, spanning the first three generations of digital
  electronic devices. The term "analogue" itself only came about when the need
  arose to distinguish digital devices from other types of computer. They were not
  "rivals" before this.

  ### Electro-mechanical computers

  Electro-mechanical devices (also known as "electronic analogue computers") are a
  sort of midway between full digital devices and analogue computers, forming a
  bridge between the two eras.

  Their heyday was roughly 1935 - 1945.

  Their key components were:

  - [relays](Relays.md) for logic operations
  - rotating shafts and gears for performing calculations
  - punched cards or paper tape for input instructions and outputs

  They were slower than fully digital computers as they were limited by the speed
  of moving components rather than the flow of electric charge. In addition the
  various mechanical parts were prone to wear and needed frequent replacement.

  ### Examples of electro-mechanical computers

  #### Harvard Mark 1 (1937-1944) - Aikin, IBM

  Designed by Howard Aiken and built by IBM (1937-1944). Also known as the
  _Automatic Sequence Controlled Calculator_ (ASCC). A general-purpose
  electro-mechanical computer it was most famously used at Los Alamos by von
  Neumann to calculate the blast yield of the atomic bomb.

  ![Harvard Mark One](../img/Harvard_Mark_1.jpg)

  It was more than 15m in length and weighed 5 tonnes comprising over 750,000
  parts. It used paper tape and punched cards for input/output.

  #### Complex Number Calculator (1940) - Bell Labs, Stibitz

  Not general purpose nor was it programmable. It was hardwired to perform a
  specific set of operations on numbers and nothing else. It used relays like the
  others. Its distinguishing feature was that it used a teletype for input rather
  than cards or paper tape.

  ![Calculating panel of the CMC](../img/complex-number-calculator-bell-labs.jpg)

  ![Teletype input of CMC](../img/stibitz-teletype.jpg)

  It comprised a panel (the calculating unit) and teletype (the input). One could
  remotely access the computer from the teletype in another location, providing it
  was connected to Bell Lab's telephone network.

  #### Z3 (1941) - Konrad Zuse

  ![Z3](../img/Zuse-Z3.jpeg)

  First designed in 1938 and completed in 1941. Considered the first fully
  automatic, programmable digital computer although relay-based. Programs were
  stored on external punched tape and binary was used as the number system.
  Destroyed by Allied bombing in the War but a replica (pictured) was built
  in 1998.

  ## Vacuum-tube devices

  Vacuum-tube devices were the first fully electronic digital devices as they used
  vacuum-tubes for implementing binary logic rather than relay switches.

  Canonical examples from this era are the ABC computer, the Collossus and most
  famously, the ENIAC.

  ### The Antanasoff-Berry Computer (1939 - 1942)

  ![ABC Computer](../img/abc-computer.jpg)

  Fully automatic, digital electronic computer but not programmable or really
  general-purpose, being created to do linear equations. It took decimal input and
  output but operated internally using binary arithmetic. The input was punched
  cards and the output was via a front-panel display.

  Its importance stems from the fact that it was the first to use vacuum-tubes for
  the arithmetic calculations of the processor.

  ### Colossus (1943-1945) - Tommy Flowers/Max Newman

  Designed and built at the Post Office Research Station at Dollis Hill. Used for
  cracking the Lorenz messages of the German high command.

  Mainly important (beyond its role in the War) for establishing the viability of
  vacuum-tubes over relays. It comprised 2.4 thousand tubes.

  Debatable the extent to which the Collosus can be called the _first digital
  computer_ since it was not general purpose and not fully programmable.

  Furthermore it was not even a complete solution in its context of application.
  The codebreakers would be given a certain advantage using the Collosus
  (narrowing the field of possible deciphering keys) but still need to use manual
  methods to break the code. It made decipher time shorter rather than executing
  the full diciphering. In contrast to, say, the Bombe computer that would
  identify the key for the days transmissions.

  It's actual influence is also hard to gauge because of the secrecy that remained
  in place after the War. Apart from those who worked on it, its technical
  advancements were not public knowledge until the late 1970s.

  ### ENIAC (1945) John Mauchly/J.P Eckert

  The _Electronic Numerical Calculator and Integrator_

  The main motivation for creating the ENIAC was military: the US Army needed
  speed and accuracy when calculating artiliary firing tables and munition
  trajectory so that shells could hit their intended targets. Accuracy depended on
  myriad factors: gun elevation, shell shape and weight, explosive charge,
  distance, wind, temperature etc. Previously this had been done by human
  computers under the aegis of the Ballistic Research Laboratory.

  ![The main control panel of the ENIAC](../img/eniac.jpg)

  It was not completed until after VE day but was used at Los Alamos after the War
  and retired in 1955.

  The BRL commissioned the Moore School of Engineering at the University of
  Pennsylvania for the construction of an automatic electronic calculator.

  It used vacuum-tubes, along with switches and realys. It used decimal rather
  than binary and had punch card input and output. It was crudely programmable and
  could be set up to work on different problems by rearranging the different
  components using switches and plugboards. But this took roughly two days.

  Its design was very influential because it was made public and not classified.

  ### EDVAC (1944-49) John Mauchly/J.P Eckert/ John von Neumann

  Built at the Moore School for the BRL as a successor to ENIAC by Mauchly and
  Eckert whilst ENIAC was still operational. It's architecture arose out of
  discussions between von Neumann and Mauchly and Eckert and was necessitated by
  the need to calculate the behaviour of the fissile materials in the atomic bomb.

  It resulted in the famous _First Draft of a Report on the EDVAC_ by von Neumann
  which is seen as the definitive statement of the modern **digital programmable
  general-purpose computer**.

  As a result of the paper, von Neumann effectively got the credit for the
  invention even though it synthesised ideas from the ENIAC and the contributions
  of others to the EDVAC.

  There was lots of disputed provenance and claims of plagiarism generally.
  Antanasoff won a legal claim against Eckert and Mauchly for stealing ideas from
  the ABC to make ENIAC.

  // Add more on the hardware.

  ### Concept of the _internal stored program_

  - Key innovation of the EDVAC was the _internal stored program_ (ISP).

  - Solution to the problem of preceding computers requiring down-time when
    switching between applications and problem sets.

  - Circuitry would need to be reconfigured before the computer could run on the
    next problem

  - ISP removed the friction:

    - the instructions comprising the program would be prepared on tape or punched
      cards and read into electronic memory
    - the hardware configuration of the machine would remain the same accross
      different programs and require no reconfiguration, only the input program
      data would change

  - This was basically the invention of software. Both instructions and data
    shared the same memory space once they were read-in. Before, the data would be
    stored in memory but the instructions would be read one-by-one from the
    external storage media.

  - Key consequences:
    - flexibility (no need for re-wiring)
    - speed: fetching instructions from memory is quicker than fetching from
      external storage devicde
    - self-modification: since they were in memory, programs could modify
      themselves during their execution (e.g. use branching conditions) and
      treating one program as data for another. This enabled more complex
      algorithms and also opened the possibility of higher-level languages and
      compilers
    - simplified architecture: both programs and data in memory
    - generalisation: computers could switch between different tasks simply by
      loading different programs

  ### Post-EDVAC devices

  There were several notable devices which attempted to implement the architecture
  described in the _First Draft_ using vacuum tubes for logic operations and a
  variety of different electronic methods for memory:

  - the Manchester "Baby"
  - the Cambridge EDSAC
  - Turing's ACE computer

  #### Manchester Baby (1948)

  ![The Manchester Baby Computer](../img/manchester_baby.png)

  - An experimental computer intended to create the
    [von Neumann architecture](CPU_architecture.md) using
    [Williams_Tube_RAM](Williams_Tube_RAM.md)

  - Developed at the Univesity of Manchester and completed in 1948.

  - Considered the first electronic stored-program computer and first to contain
    all the elements of a modern electronic digital computer.

  #### EDSAC (1949) J.Wilkes et al.

  ![EDSAC](../img/EDSAC.jpg)

  - _Electronic Delay Storge Automatic Computer_

  - Constructed by Maurice Wilkes and others at the Mathematical Laboratory of the
    University of Cambridge.

  - The second digital stored-program computer after the Manchester Baby.

  - It used vacuum-tubes for the arithmetical operations in the ALU and mercury
    [delay line memory](Delay_line_memory.md) for the RAM.

  - Designed to be used by relatively non-specialist practitioners from other
    university departments who were expected to program it themselves. To this
    end, a formal programming paradigm was forged for the EDSAC which established
    the following:

    - subroutines as a library of common procedures available to programmers (e.g.
      printing a result, reading input tape, program checking, mathematical
      operations)
    - diagnostics: techniques for verifying program code and its correctness

  ![Programming Digital Electronic Computer book](../img/programming_a_digital_electronic_computter.jpg)

  - The key players wrote the first textbook on programming in 1951: _The
    Preparation of Programs for an Electronic Digital Computer_ (Wilkes, Wheeler,
    and Gill)

  #### The Automatic Computing Engine (ACE) (1946) Alan Turing

  - Technical design provided by Alan Turing working at the Mathematics Division
    of the National Physical Laboratory. The product of Turing's theoretical work
    in "On Computable Numbers" where he proposes the concept of a
    [Turing_machine](Turing_machines.md) and based on his experience with early
    single-purpose computing devices at Bletchley.

  - Due to secrecy over wartime work, it was hard for get Turing to build a fully
    electronic implementation since this would disclose advances made during the
    War. Tommy Flowers was intended to be brought in to build it but again this
    was scuppered over the classification of wartime technology. Had this not been
    the case, it is likely it would've been seen as on par with the EDVAC in its
    design, if not dwarfing it.

  - Turing proposed a stored program architecture with high-speed memory. It would
    be more perfomant than the EDVAC as a result.

  ![The Pilot ACE computer](../img/pilot-ace.jpg)

  - Turing's actual design was not implemented as it was thought too ambitious
    given how powerful it would need to be. Instead a smaller prototype was made,
    the Pilot ACE. It used vacuum-tubes for logic, and mercury delay lines for
    memory. Data and memory was input via punched cards.

  ### Further applications of the EDSAC architecture

  ### LEO I

  - Built by Lyons Tea Company to manage business affairs (payroll, inventory,
    stock management)

  - Marked a shift from military and academic contexts for computers to business
    and data management.

  ### IAS machines

  ![The MANIAC computer](../img/MANIAC_computer.jpg)

  Several machines were built at the Institute for Advanced Study utilising the
  "von Neumann" architecture and associated advancements such as vacuum-tubes and
  Williams' tubes. There was the original IAS machine (1952) as well as the
  JOHNNIAC (1954) and MANIAC (1956).

  Although their purpose was military (Los Alamos), their designs were public and
  widely studied making them influential outside of academia.

  ### UNIVAC (1951)

  ![UNIVAC_computer](../img/univac-1.jpg)

  Mauchley and Eckert, who had designed the ENIAC left the Moore School and went
  into business: Eckert-Mauchley Computer Corporation. This was bought by
  Remington Rand and in subsequent years became the main competitor to IBM.

  In this capacity they built the UNIVAC: _Universal Automatic Computer_. It's
  name being an embodiment of its nature as a general-purpose, electronic digital
  computer.

  It used vacuum-tubes for logic and mercury delay lines for memory. It had
  multiple means of input/output including: directly via an operator console
  (basically a typewriter keyboard), magnetic tape for input and output, along
  with punched cards.

  ![UNIVAC operator console](../img/univac-operator-console.jpeg)

  It was the first computer specifically designed to include business and
  administrative use. This was underscored by its first client: the US Census
  Bureau.

  A key event was its succesful prediction of the 1952 general election. It
  correctly predicted a landslide for Eisenhower (against expectations). (This was
  so unlikely, they actually fudged the data because they thought the machine was
  way off.) It was a novelty on the results night but it cemented a certain
  concept of the computer in the public imagination - large, room sized machines
  with blinking lights.

  Swade notes that Eckert and Mauchley effectively launched the US commercial
  computer industry with the UNIVAC.

  ## IBM

  - First created in 1911 as a merger of four companies all involved in some
    capacity in business data processing. The most significant of these was the
    Hollerith Tabulating Machine Company who's punched card machines had been used
    in the 1890 census, greatly reducing its collation time

  - Named IBM in 1924

  - UNIVAC represented the encroachment of computing devices into IBM's market of
    punched-card based data-processing and effectively put IBM on notice.

  - To attack the threat IBM brought out three crucial machines: the 701, 702
    and 650.

  - IBM's approach to marketing their computers was to emphasise the comfort of
    continuity over the shock of the new represented by the UNIVAC. IBM had
    already captured the business market. Their computers would be more of the
    same, just faster and more powerful than their punched card machines due to
    the electronics. To this end they initially did not call their devices
    'computers'.

  - IBM were ultimately totally successful in their pivot to the computing market.
    By 1960 it owned over 70% of the data processing and computer market.

  ### IBM 701 ("Defense Calculator") (1952)

  ![Control unit of the IBM 701](../img/ibm-701-control-unit.jpeg)

  IBM's first electronic computer. It directly competed with the UNIVAC for
  government contracts. It followed the prevailing approach of vaccuum tubes for
  logic and had a variety of storage methods: Williams tubes,
  [magnetic tape](Magnetic_tape.md) , and magentic drums.

  701s were used at Los Alamos and avionics companies. Some were also used to
  manage payroll in business contexts.

  ### IBM 702 ("Tape Processing Machine") (1953)

  ![Advertisement for the IBM 702](../img/ibm-add.jpeg)

  Focused primarily on business applications and targetted at businesses rather
  than government contracts. Less powerful than the 701.

  ### IBM 650 (1953)

  ![IBM 650](../img/ibm-650.jpg)

  Low-cost general purpose machine using magnetic drum memory. Marketed as slower
  but more affordable than the 700 range. It was mass-produced unlike the others
  which were built for specific customers. It was in fact the first mass-produced
  computer in the world. It proved the breakout star in IBMs initial line up and
  sometimes called "IBM's Model T".

  ![IBM_650 console panel](../img/ibm-650-console-panel.jpg)

  IBM offerred 650s to univesities at a 60% discount on the condition that the
  universities would establish courses in computing. This was shrewd as it meant
  that a whole generation of engineers and computer scientists learned on 650s and
  would prefer them in their research. It also created a pool of skilled users
  that would go on to work at IBM.

  Donald Knuth dedicated _The Art of Computer Programming_ to the 650.

  It used [magnetic drum memory](./Magnetic_drum_memory.md) along with
  vacuum-tubes.

  ### IBM 1401 (1959)

  ![IBM 1401](../img/ibm-1401.jpg)

  Another IBM mainframe. Transistors replaced vacuum-tubes. Magnetic core storage
  replaced magnetic drum storage. It was housed in rectangular light-blue cabinets
  and the ubiquity of the 1401 in industry earned IBM the moniker 'Big Blue'.

  ### IBM System/360 (1964)

  ![IBM 360](../img/ibm-360.jpg)

  Named to suggest all-round compatibility - a family of mainframes designed to
  cover commercial and scientific applications. Considered one of history's most
  successful computers.

  Up until this point all IBM computers had a programming language unique to the
  specific processor. At that point there were about seven IBM computer models in
  active use and they were all incompatible with each other.

  ![IBM 360 promo poster](../img/ibm-360-in-use.jpeg)

  In contrast the 360 computers all used the same programming language. This meant
  they were interoperable with each others. Because the there were variants in the
  360 ranges, customers could purchase a smaller system knowing they could expand
  it if their needs grew, without reprogramming their application software.

  ## Magnetic core devices: Whirlwind and SAGE

  Two devices that leveraged the new technology of
  [magnetic_core_memory](Magnetic_core_memory.md) where the Whirlwind and SAGE
  computers. The ENIAC was also updated to use magnetic cores.

  ![Whirlwind magnetic core memory](../img/whirlwind-magnetic-cores.JPG)

  The Whirlwind computer (1953) was a flight simulator and the first to use
  magnetic cores. Crucially was able to operate in realtime for output.

  The SAGE computer (_Semi-Automatic Ground Environment_) emerged as a response to
  the Soviet acquisition of the atomic bomb in 1949. The Americans realised they
  needed a much better early warning and air defence system that would allow them
  to identify bombers in their airspace and dispatch fighters.

  To achieve this, it was necessary to detect enemy bombers with radar and compute
  an interception course. Alterations in the bomber flight path required
  continuous updates and real-time computation of directions for the responding
  fighter.

  The resulting computer was SAGE (made by IBM) which was modelled on Whirlwind,
  using magnetic cores. SAGE computers were spread accross the continental US in
  sectors and managed by NORAD.

  ![SAGE command post](../img/SAGE_command_post.jpg)

  The SAGE system was operation between 1958-1984, receiving continual
  improvements and updates. In addition to magnetic cores it introduced many
  technological innovations that influenced computers generally: interactive
  screens, printed circuit boards, mass-storage devices, digital communication
  over telephone lines, time-sharing, along with advances in software.

  Because so many private companies worked as contractors on SAGE (IBM, Burroughs,
  Bell Labs, RAND Corp), the military technology (both hardware and software) made
  its way from the military context to business and consumer computing.

  An example of a civilian application of SAGE technology was airline booking
  systems, where booking reservation data needed to be processed in realtime. IBM
  worked with American Airlines to introduce this.

  ## Transistor and integrated circuit revolution

  The arc of modern computing history (and its main eras) can be presented as

  - vacuum-tubes
  - [transistors](Transistors.md)
  - [integrated_circuits](Integrated_circuits.md)

  The transistor was invented at Bell Labs in 1947 by William Shockley and others.
  They were not created with computers in mind. Instead they were first used in
  hearing aids (1953) and transistor radios (1954).

  From the mid-1950s onwards they started being used in military computers.

  They had several advantages over vacuum-tubes: they were durable and reliable
  with no required startup time, they were power efficient and small.

  The actual period where transistors alone were supreme in the form of a
  transistor board was relatively short-lived and rapidly gave way to ICs.

  Computers of the transistor era had the following components separate from each
  other: the transistors, wired connections, resistors and capacitors. Integrated
  circuits put these all on the same piece of silicon.

  They were first manufactured by Fairchild Semiconductor which was an offshoot of
  Shockley's Shockley Semiconductor Laboratory.

  The initial marked for ICs was military and scientific: the Minuteman ICBM and
  computers of the Apollo spacecraft. This laid the groundworkd for later
  commercial use and the personal computer revolution.

  ## Mini-computers

  Mini computers did not threaten mainframes but they opened up a new class of
  user. They were vastly cheaper than a mainframe and only the size of a small
  fridge.

  The main player was DEC (Digital Equipment Corporation) starting in the
  mid-1960s. The introduced the PDP-8 in 1965 (Programmed Data Processor). The
  PDP-8 used transisotrs and magnetic core memory. It was affordable to smaller
  businesses if not yet, consumers.

  ![The PDP-8](<../img/540px-PDP-8_(1).jpg>)

  The internals were made public and DEC encouraged making the machine extensible
  by users being permitted to create their own programs and specialised
  applications. This was a very different culture to IBM where technicians were
  required and machines were typically leased.

  By 1988, DEC was the second largest computer company after IBM. This led to
  additional companies entering the mini-computer market: Data General, Honeywell,
  Hewlett-Packard.

  Mini-computers died as a category in the mid-1990s in the wake of the PC.

  ## The Personal Computer

  The PC emerged out of advances in IC chip design - this was the engine of the
  PC.

  Intel (newly founded by Robert Noyce and Gordon Moore) were charged with making
  custom chips for a Japanese calculator company. Instead of creating the custom
  chips, they advised the company to use a general purpose chip that could handle
  general computing as well as the basic calculator functions. This generic chip -
  the Intel 4004 - was the microprocessor combined with three other ICs
  (comprising 2.3k transistors) that comprised the fundamental building blocks of
  the von Neumann architecture.

  ![Intel 4004 chip](../img/intel-4004.jpg)

  However it took until the Apple II in 1977 for the phrase "personal computer" to
  enter the lexicon and become an identifiable class.

  The broader context for this was the computer hobbyists in Silicon Valley/Menlo
  Park (particularly the Homebrew Club of amateur computer engineers) along with
  idealistic figures from the 60s counterculture (such as Stuart Brand) who began
  associating computing technology as a tool of personal liberty and fulfillment.

  ### Microsoft and Apple

  The PC "boom" began with the Altair 8800. This was a mail-order kit that
  required manual assembly. It was released by Micro Instrumentation Telemetry
  Systems (MITS) in 1975. It couldn't do much but had expansion capability
  (memory, teletype interface, casette player for data storage).

  ![Altair 8800](../img/altair-8800.jpg)

  Bill Gates and Paul Allen made a proposal to MITS: they would write software
  that would allow users to program the Altair in BASIC. They agreed and were
  prepared to distribute the software. As a result Gates and Allen founded
  Microsoft in 1975.

  Around the same time, Steve Wozniak (member of the Homebrew club) built the
  Apple I as a single-board hobbyist project. He made this available to buy via
  mail order and formed Apple with Steve Jobs in 1976 to manage the enterprise.

  ![Apple I](../img/apple1.jpg)

  By 1977 they had investment capital and brought out the Apple II. This was sold
  preassembled with casing and required no soldering. It had expansion slots for
  third-party vendors to create compatible devices and had colour graphics.

  The design of the Apple II was published in _Byte_ in order to invite
  crowd-sourced plugins and extensions. One of these was the Z-80 softcard which
  allowed it to run the CP/M OS (created by the company Digital Research) giving
  ready-made access to software such as a word-processor, spreadsheets and
  databases.

  ![Apple 2](../img/apple2.jpg)

  Other competitors in the PC market at this time were Radioshack's TRS-80 and the
  Commodore PET.

  ### IBM

  All the while it was hobbyist and tinkerers driving the PC market, IBM had no
  real interest. When PCSs started being used by businesses and their capacity
  grew to include typical business applications (databases, spreadsheets) it
  sensed an incursion into its market.

  ![IBM PC](../img/ibm-pc.jpg)

  Thus, in 1981, IBM launched the "IBM Personal Computer" which rapidly became the
  industry standard. This had the effect of legitimising the concept of a PC.
  There was overwhelming demand. In their marketing IBM melded home and office use
  to capture both markets at once.

  The internal specification of the IBM PC was freely available (dubbed 'open
  architecture'), in contrast to IBM's business computers, to encourage software
  expansion. Cards could be created to fit in its expansion slots adding graphics,
  sound, additional memory and networking capability.

  IBM originally contracted Digital Research to create software based on CP/M but
  this fell through. Microsoft got the deal to supply the OS which became MS-DOS
  (Microsoft Disk Operating System). Henceforth every PC and PC-clone came with
  DOS bundled.


path: The_Little_Man_computer.md
sortable_path: The_Little_Man_computer.md
title: The Little Man Computer
checksum: 7ade3a76827f3f0535ca49bfb1e97fd63aba137e7816612848aa576e8cf04234
created: 2025-10-01 13:31:02.03716534+00:00
modified: 2025-08-13 15:57:13.791011391+00:00
metadata: {"tags":["CPU"]}
lead:
  The [Little Man Computer]() is a simplified computer that works on Von Neuman
  principles. It has all the CPU components we have detailed above. It is
  programmed in machine code but for simplicity it uses the denary rather than the
  binary number system.

body:
  The [Little Man Computer]() is a simplified computer that works on Von Neuman
  principles. It has all the CPU components we have detailed above. It is
  programmed in machine code but for simplicity it uses the denary rather than the
  binary number system.

  ![LMC_5.gif](LMC_5.gif)

  On the left is the instruction set. Each number constitutes and execution
  routine and the `xx` stand for the address in RAM that the execution will work
  on.

  Each row of the RAM has a denary address, 1 through to 99. Each address can hold
  three digits.

  - So the instruction `560` would mean _load the number at address 60._
  - The instruction `340` would mean _store a datum at address 40_

  ### Working through a basic computation

  We are going to add two numbers together as a basic example.

  1. First we need to place the two numbers in RAM we are going to use `5` and `3`
     - At address `60` we will put the number `5` and at address `61` we will put
       the number `3`
     - We are going to start at address `0` in the top left of the RAM grid
  1. The first instruction will be _load address 60_ which in the assembly will be
     `560` . We put this in address `0`, our starting point.
  1. This first instruction is now stored in the accumulator.
  1. Now we want to _add this number (in the accumulator) to the number in address
     61_
  1. This second instruction is `161` . We write this in address `1`
  1. Finally we want to store the output of the calculation in the RAM, let's say
     at address `62`
  1. So we store the command `362` at address `2`

raw_content:
  ---
  tags: [CPU]
  ---

  # The Little Man Computer

  The [Little Man Computer]() is a simplified computer that works on Von Neuman
  principles. It has all the CPU components we have detailed above. It is
  programmed in machine code but for simplicity it uses the denary rather than the
  binary number system.

  ![LMC_5.gif](LMC_5.gif)

  On the left is the instruction set. Each number constitutes and execution
  routine and the `xx` stand for the address in RAM that the execution will work
  on.

  Each row of the RAM has a denary address, 1 through to 99. Each address can hold
  three digits.

  - So the instruction `560` would mean _load the number at address 60._
  - The instruction `340` would mean _store a datum at address 40_

  ### Working through a basic computation

  We are going to add two numbers together as a basic example.

  1. First we need to place the two numbers in RAM we are going to use `5` and `3`
     - At address `60` we will put the number `5` and at address `61` we will put
       the number `3`
     - We are going to start at address `0` in the top left of the RAM grid
  1. The first instruction will be _load address 60_ which in the assembly will be
     `560` . We put this in address `0`, our starting point.
  1. This first instruction is now stored in the accumulator.
  1. Now we want to _add this number (in the accumulator) to the number in address
     61_
  1. This second instruction is `161` . We write this in address `1`
  1. Finally we want to store the output of the calculation in the RAM, let's say
     at address `62`
  1. So we store the command `362` at address `2`


path: The_PATH.md
sortable_path: The_PATH.md
title: The $PATH
checksum: 4f73dff47c7baf9d241cb89b6685f5c22038ece2bf4e6585bfc08810220df656
created: 2025-10-01 13:31:02.044958754+00:00
modified: 2025-08-13 15:57:14.083137172+00:00
metadata: {"tags":["shell"]}
lead:
  We know that `$PATH` is an
  [environment variable](Environmental_and_shell_variables.md).
  This variable keeps track of directories **where executables are found**.

body:
  We know that `$PATH` is an
  [environment variable](Environmental_and_shell_variables.md).
  This variable keeps track of directories **where executables are found**.

  Whenever any command is run, the shell looks up the directories contained in the
  `PATH` for the target executable file and runs it. We can see this is the case
  by using the `which` command which traces the executable of bash commands. Take
  the `echo` program:

  ```bash
  which echo
  # echo: shell built-in command
  ```

  Or `npm` :

  ```bash
  which npm
  /home/thomas/.nvm/versions/node/v19.4.0/bin/npm
  ```

  By default the path will always contain the following locations:

  - `/usr/bin`
  - `/usr/sbin`
  - `/usr/local/bin`
  - `/usr/local/sbin`
  - `/bin`
  - `/sbin`

  All the inbuilt terminal programs reside at these locations and most of them are
  at `/usr/bin`. This is why they run automatically without error. If you attempt
  to run a program that doesn’t reside at these locations then you will get an
  error along the lines of `program x is not found in PATH`.

  ## Structure of the PATH

  ```bash
  /home/thomas/.nvm/versions/node/v19.4.0/bin:
  /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:
  /usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
  ```

  ## Adding to the PATH

  Only the default directories load to the PATH on every session. How then can we
  add custom directories to the path without them being lost every time we start a
  new session? Remember that the user config `.bashrc` loads on init for every
  bash session. Therefore, if we set the custom path in this file, it will be
  created every time we start a session. This is why when you add a new program it
  often ask you to append a script to the bottom of your `.bashrc` .

  For example, at the bottom of my `.zshrc` on my work computer I have:

  ```bash
  export CHROME_BIN=/mnt/c/Program\\ Files\\ \\(x86\\)/Google/Chrome/Application/chrome.exe
  ```

  This enables me to access the Chromium binaries from my terminal session (needed
  for running Angular tests) but it doesn’t add it to the path, it creates an
  environment variable on every session.

  For demonstration, let’s add a user’s desktop directory to the PATH.

  First we go to the `.bashrc` and add the `export` command. Remember that this is
  the command for creating a new environment variable:

  ```bash
  export PATH="$PATH=:~/Desktop"
  ```

  We force a reload of the `.bashrc` with the command:

  ```bash
  source ~/.bashrc
  ```

  Then we can check this directory has been added to the path with an echo

  ```bash
  echo $PATH
  ...:~/Desktop
  ```

raw_content:
  ---
  tags:
    - shell
  ---

  # The `$PATH`

  We know that `$PATH` is an
  [environment variable](Environmental_and_shell_variables.md).
  This variable keeps track of directories **where executables are found**.

  Whenever any command is run, the shell looks up the directories contained in the
  `PATH` for the target executable file and runs it. We can see this is the case
  by using the `which` command which traces the executable of bash commands. Take
  the `echo` program:

  ```bash
  which echo
  # echo: shell built-in command
  ```

  Or `npm` :

  ```bash
  which npm
  /home/thomas/.nvm/versions/node/v19.4.0/bin/npm
  ```

  By default the path will always contain the following locations:

  - `/usr/bin`
  - `/usr/sbin`
  - `/usr/local/bin`
  - `/usr/local/sbin`
  - `/bin`
  - `/sbin`

  All the inbuilt terminal programs reside at these locations and most of them are
  at `/usr/bin`. This is why they run automatically without error. If you attempt
  to run a program that doesn’t reside at these locations then you will get an
  error along the lines of `program x is not found in PATH`.

  ## Structure of the PATH

  ```bash
  /home/thomas/.nvm/versions/node/v19.4.0/bin:
  /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:
  /usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
  ```

  ## Adding to the PATH

  Only the default directories load to the PATH on every session. How then can we
  add custom directories to the path without them being lost every time we start a
  new session? Remember that the user config `.bashrc` loads on init for every
  bash session. Therefore, if we set the custom path in this file, it will be
  created every time we start a session. This is why when you add a new program it
  often ask you to append a script to the bottom of your `.bashrc` .

  For example, at the bottom of my `.zshrc` on my work computer I have:

  ```bash
  export CHROME_BIN=/mnt/c/Program\\ Files\\ \\(x86\\)/Google/Chrome/Application/chrome.exe
  ```

  This enables me to access the Chromium binaries from my terminal session (needed
  for running Angular tests) but it doesn’t add it to the path, it creates an
  environment variable on every session.

  For demonstration, let’s add a user’s desktop directory to the PATH.

  First we go to the `.bashrc` and add the `export` command. Remember that this is
  the command for creating a new environment variable:

  ```bash
  export PATH="$PATH=:~/Desktop"
  ```

  We force a reload of the `.bashrc` with the command:

  ```bash
  source ~/.bashrc
  ```

  Then we can check this directory has been added to the path with an echo

  ```bash
  echo $PATH
  ...:~/Desktop
  ```


path: The_Pragmatic_Programmer_1999.md
sortable_path: The_Pragmatic_Programmer_1999.md
title: The Pragmatic Programmer (Hunt/Thomas, 1999)
lead: ## General
checksum: b889e9eee019a923f22718f398b0395d5f234d3045f4a6c3fcc063893630b0b4
created: 2025-10-01 13:31:02.062604856+00:00
modified: 2025-08-13 15:57:14.049062104+00:00
metadata: {"tags":[]}
body:
  ## General

  ### Meyer's Uniform Access Principle

  > All services offered by a module should be available through a uniform
  > notation, which does not betray whether they are implemented through storage
  > or through computation

  This is a clear recommendation for using getters and setters with classes. You
  should not see method calls outside of the class, they should appear as
  properties of the object.

  ## Don't Repeat Yourself

  > Every piece of knowledge must have a single, unambiguous, authoritative
  > representation within a system

  ## The Principle of Orthogonality

  This notion comes from geometry. Two lines are orthogonal to each other if they
  form a right-angle when they meet.

  Their meeting isn't the important part. Think of a simple x, y graph:

  > If you move along one of the lines, **your position projected onto the other
  > doesn't change**

  In computing this is expressed in terms of **decoupling** and is implemented
  through modular, component-based architectures. As much as possible code should
  be scoped narrowly so that a change in one area does not cause changes in
  others. By keeping components discrete it is easier to make changes, refactor,
  improve and extend the codebase.

  > We want to design components that are self-contained: independent and with a
  > single, well-defined purpose. When components are isolated from one another,
  > you know that you can change one without having to worry about the rest. As
  > long as you don't change that component's external interfaces, you can be
  > comfortable that you won't cause problems that ripple through the entire
  > system.

  ### Benefits of orthogonality: productivity

  - Changes are localised so development time and testing time are reduced
  - Orthogonality promotes reuse: if components have specific, well-defined
    responsibilities, they can be combined with new components in ways that were
    not envisioned by their original implementors. The more loosely coupled your
    systems, the easier they are to reconfigure and reengineer.
  - Assume that one component does _M_ distinct things and another does _N_
    things. If they are orthogonal and you combine them, the result does _M x N_
    things. However if the two components are not orthogonal, there will be
    overlap, and the result will do less. You get more functionality per unit
    effort by combining orthogonal components.

  ### Benefits of orthogonality: reduced risk

  - Diseased sections of code are isolated. If a module is sick, it is less likely
    to spread the symptoms around the rest of the system.
  - Overall the system is less fragile: make small changes to a particular area
    and any problems you generate will be restricted to that area.
  - Orthogonal systems are better tested because it is easier to run and design
    discrete tests on modularised components.

  > Building a unit test us itself a an interesting test of orthogonality: what
  > does it take to build and link a unit test? Do you have to drag in a large
  > percentage of the rest of the system just to get a test to compile or link?
  > **If so, you've found a module that is not well decoupled from the rest of the
  > system**

  ### Relationship between DRY and orthogonality

  With DRY you're looking to minimize duplication within a system, whereas with
  orthogonality, you reduce the interdependency among the system's components. If
  you use the principle of orthogonality combined closely with the DRY principle,
  you'll find that the systems you develop are more flexible, more understandable
  and easier to debug, test, and maintain.

  ### Reversibility

  The principles of orthogonality and DRY result in code that is reversible. This
  means it is able to change in an agile way when the circumstances of its use and
  deployment change. This is important because when developing software in a
  business setting, the best decisions are not always made the first time around.
  By following the principles it should be relatively easy to change your
  program's interfaces, platform and scale. In other words, with the principle of
  orthogonality and DRY, refactoring becomes less of a chore.

  ## Prototyping and Tracer Bullets

  'Tracer bullets' are used by the military for night warfare. They are
  phosphorous bullets that are included in the magazines of guns alongside normal
  bullets. They are not intended to kill but instead light-up the surrounding area
  making it easier to see the terrain and target more efficiently.

  The authors use the notion of tracer bullets as a metaphor for developing
  software at the early stages of a project. This is **not** the same thing as
  prototyping. A tracer bullet model is useful for building things that haven't
  been built before. They exist to 'shed light' on the project's needs and to help
  the client understand what they want.

  They differ from prototypes in that they include integrated overall
  functionality but in a rough state. Whereas prototypes are more for singular,
  specific subcomponents of the project. Because tracer bullet models are
  joined-up in this way, even if they turn out to be inappropriate in some regard,
  they can be adapted and developed into a better form, without losing the core
  functionality.

  > Tracer bullets work because they operate in the same environment and under the
  > same constraints as the real bullets. They get to the target fast, so the
  > gunner gets immediate feedback. And from a practical standpoint they are a
  > relatively cheap solution. To get the same effect in code, we're looking for
  > something that gets us from a requirement to some aspect of the final system
  > quickly, visibly and repeatably.

  > Tracer code is not disposable: you write it for keeps. It contains all the
  > error-checking, structuring, documentation and self-checking that a piece of
  > production code has. It simply is not fully functional. However, once you have
  > made an end-to-end connection among the components of your system, you can
  > check how close to the target you are, adjusting as necessary.

  ### Distinguishing from prototyping

  > Prototyping generates disposable code. Tracer code is lean but complete, and
  > forms part of the skeleton of the final system. Think of prototyping as the
  > reconnaissance and intelligence gathering that takes place before a single
  > tracer bullet is fired.

  ## Design by contract

  To understand DBC we have to think of a computational process as involving two
  stages: the call and the execution of the routine that happens in response to
  the call (henceforth **caller** and **routine**).

  - the caller could be a function expression that invokes a function and passes
    arguments to it expecting a given output. The function that executes is the
    routine
  - the caller could be an object instantiation that calls a method belonging to
    its parent class
  - the caller could be a parent React component that passes props to a child
    component

  Design by contract means specifying clear and inviolable rules detailing what
  must obtain at both the call stage and the routine stage if the process is to
  execute.

  Every function and method in a software system does something. Before it starts
  that something, the routine may have some expectation of the state of the world
  and it may be able to make a statement about the state of the world when it
  concludes. These expectations are defined in terms of preconditions,
  postconditions, and invariants. They form that basis of a **contract** between
  the caller and the routine. Hence _design by contract\*\*._\*\*

  ### Preconditions

  Preconditions specify what must be true in order for the routine to be called.
  In other words, the requirements of the routine. What it needs and what should
  be the case before it even considers executing the task. A **routine should
  never get called when its preconditions would be violated**.

  ### Postconditions

  Providing the preconditions are met, this is what the routine is guaranteed to
  do. In other words: the state of affairs that must obtain after the routine has
  ran.

  ### Invariants

  Once established, the preconditions and postconditions should not change. If
  they need to change, that is a separate process and contract. In the processing
  of a routine, the data may be variant relative to the contract, but by the end
  the overall conditions establish the equilibrium of the contract.

  There is an analogue here with functional programming philosophy: the function
  should always return the same sort of output, without ancillary processes
  happening, i.e side-effects.

  One way to achieve this is to be miserly when setting up the contract, which
  overlaps with orthogonality. Only specify the minimum return on a contract
  rather than multiple postconditions. This only increases the likelihood that the
  contract will be breached at some point. If you need multiple postconditions,
  spread them out an achieve them in a compositional way, with multiple separate
  and modular processes.

  > Be strict in what you will accept before you begin, and promise as little as
  > possible in return. If your contract indicates that you'll accept anything and
  > promise the world in return, then you've got a lot of code to write!

  ### Division of responsibilities

  > If all the routine's preconditions are met by the caller, the routine shall
  > guarantee that all postconditions and invariants will be true when it
  > completes.

  Note that the emphasis of responsibilities is on the caller.

  Imagine that we have a function that returns the count of an array of integers.
  It is not the job of the count routine to verify that it has been passed
  integers and then to execute the count. Or, in the event that it is not passed
  integers, to mutate the data to integers and then execute.

  This should be resolved by the caller: it is the responsibility of the caller to
  pass integers. If it doesn't, the routine simply crashes or raises an exception.
  It doesn't try to accommodate the input because that does not come down on its
  side of the contract. The caller has failed to meet the preconditions. If, due
  to some bug, the routine receives integers and fails to output the sum, then it
  has failed on its side

  ### Example: type checking

  An obvious example of this philosophy is when you perform checks or validation
  within your code (although validation is more of an issue when you are dealing
  with user data, not your own internal code). For instance using type checking
  with dynamically-typed languages.

  When we use the `prop-types` library with React we are specifying preconditions:
  so long at the prop (effectively the caller) passed to the component
  (effectively the routine) is of type X, the component will render invariantly as
  R. If the prop is of type Y, an exception will be raised highlighting a breach
  in the contract.

  Another example would be more advanced type checking with Javascript written
  using Typescript.

  ## The Law of Demeter

  Demeter's Law has applicability chiefly when programming with classes.

  It's a fancy name for a simple principle summarised by 'don't talk to
  strangers'. Demeter's law is violated when code has more than one step between
  classes. You should avoid invoking methods of an object returned by another
  method. You should only use your own methods when dealing with it.

  ### Formal

  A method _m_ of object _O_ may only invoke the methods of the following kinds of
  objects:

  - _O_ itself
  - _m_'s parameters
  - any objects created or instantiated within _m_
  - _O_'s direct component objects (in other words nested objects)
  - a global variable (over and above _O_) accessible by _O_, within the scope of
    _m_

  ## Model, View, Controller design pattern

  The key concept behind the MVC idiom is separating the model from both the GUI
  that represents it and the controls that manage the view.

  - **Model**
    - The abstract data model representing the target object
    - The model has no direct knowledge of any views or controllers
  - **View**
    - A way to interpret the model. It subscribes to changes in the model and
      logical events from the controller
  - **Controller**
    - A way to control the view and provide the model with new data. It publishes
      events to both the model and the view

  For comparison, distinguish React from MVC. In React data is unidirectional: the
  JSX component as controller cannot change the state. The state is passed down to
  the controller. Also MVC lends itself to separation of technologies: code used
  to create the View is different from Code that manages Controller and data
  Model. In React it's all one integrated system.

  ## Refactoring

  > Rewriting, reworking, and re-architecting code is collectively known as
  > refactoring

  ### When to refactor

  - **Duplication**: you've discovered a violation of the DRY principle
  - **Non-orthogonal design**: you've discovered some code or design that could be
    made more orthogonal
  - **Outdated knowledge**: your knowledge about the problem and you skills at
    implementing a solution have changed since the code was first written. Update
    and improve the code to reflect these changes
  - **Performance: y**ou need to move functionality from one area of the system to
    another to improve performance

  ### Tips when refactoring

  - Don't try to refactor and add new functionality at the same time!
  - Make sure you have good tests before you begin refactoring. Run the tests as
    you refactor. That way you will know quickly if your changes have broken
    anything
  - Take short, deliberative steps. Refactoring often involves making many
    localised changes that result in a larger-scale change.

  ## Testing

  > Most developers hate testing. They tend to test-gently, subconsciously knowing
  > where the code will break and avoiding the weak spots. Pragmatic Programmers
  > are different. We are _driven_ to find our bugs _now_, so we don't have to
  > endure the shame of others finding our bugs later.

  ### Unit testing

  A unit test is code that exercises a module. It consists in testing each module
  in isolation to verify its behaviour. Unit testing is the foundation of all
  other forms of testing. If the parts don't work by themselves, they probably
  won't work well together. All the modules you are using must pass their own unit
  tests before you can proceed.

  We can think of unit testing as **testing against contract** (detailed above).
  We want to test that the module delivers the functionality it promises over a
  wide range of test cases and boundary conditions.

  Scope for unit testing should cover:

  - Obviously, returning the expected value/outcome
  - Ensuring that faulty arguments/ types are rejected and initiate error handling
    (deliberately breaking your code to ensure it is handled appropriately)
  - Pass in the boundary and maximum value
  - Pass in values between the zero and the maximum expressible argument to cover
    a range of cases

  Benefits of unit testing include:

  - It creates an example to other developers how to use all of the functionality
    of a given module
  - It is a means to build **regression tests** which can be used to validate any
    future changes to the code. In other words, the future changes should pass the
    older tests to prove they are consistent with the code base

  ### Integration testing

  Integration testing shows that the major subsystems that make up the project
  work and play well with each other.

  Integration testing is really just an extension of the unit testing described,
  only know you're testing how entire subsystems honour their contracts.

  ## Commenting your code

  In general, comments should detail **why** something is done, its purpose and
  its goal. The code already shows _how_ it's done, so commenting on this is
  redundant, and violates the DRY principle.

  > We like to see a simple module-level comment, comments for significant data
  > and type declarations, and a brief class and per-method header describing how
  > the function is used and anything it does that is not obvious

  ```js
  /*
  Find the highest value within a specified data range of samples

  Parameter: aRange = range of dates to search for data
  Parameter: aThreshold = minimum value to consider

  Return: the value, or null if no value found that is greater than or equal to the threshold
  */
  ```

raw_content:
  ---
  tags: []
  ---

  # The Pragmatic Programmer (Hunt/Thomas, 1999)

  ## General

  ### Meyer's Uniform Access Principle

  > All services offered by a module should be available through a uniform
  > notation, which does not betray whether they are implemented through storage
  > or through computation

  This is a clear recommendation for using getters and setters with classes. You
  should not see method calls outside of the class, they should appear as
  properties of the object.

  ## Don't Repeat Yourself

  > Every piece of knowledge must have a single, unambiguous, authoritative
  > representation within a system

  ## The Principle of Orthogonality

  This notion comes from geometry. Two lines are orthogonal to each other if they
  form a right-angle when they meet.

  Their meeting isn't the important part. Think of a simple x, y graph:

  > If you move along one of the lines, **your position projected onto the other
  > doesn't change**

  In computing this is expressed in terms of **decoupling** and is implemented
  through modular, component-based architectures. As much as possible code should
  be scoped narrowly so that a change in one area does not cause changes in
  others. By keeping components discrete it is easier to make changes, refactor,
  improve and extend the codebase.

  > We want to design components that are self-contained: independent and with a
  > single, well-defined purpose. When components are isolated from one another,
  > you know that you can change one without having to worry about the rest. As
  > long as you don't change that component's external interfaces, you can be
  > comfortable that you won't cause problems that ripple through the entire
  > system.

  ### Benefits of orthogonality: productivity

  - Changes are localised so development time and testing time are reduced
  - Orthogonality promotes reuse: if components have specific, well-defined
    responsibilities, they can be combined with new components in ways that were
    not envisioned by their original implementors. The more loosely coupled your
    systems, the easier they are to reconfigure and reengineer.
  - Assume that one component does _M_ distinct things and another does _N_
    things. If they are orthogonal and you combine them, the result does _M x N_
    things. However if the two components are not orthogonal, there will be
    overlap, and the result will do less. You get more functionality per unit
    effort by combining orthogonal components.

  ### Benefits of orthogonality: reduced risk

  - Diseased sections of code are isolated. If a module is sick, it is less likely
    to spread the symptoms around the rest of the system.
  - Overall the system is less fragile: make small changes to a particular area
    and any problems you generate will be restricted to that area.
  - Orthogonal systems are better tested because it is easier to run and design
    discrete tests on modularised components.

  > Building a unit test us itself a an interesting test of orthogonality: what
  > does it take to build and link a unit test? Do you have to drag in a large
  > percentage of the rest of the system just to get a test to compile or link?
  > **If so, you've found a module that is not well decoupled from the rest of the
  > system**

  ### Relationship between DRY and orthogonality

  With DRY you're looking to minimize duplication within a system, whereas with
  orthogonality, you reduce the interdependency among the system's components. If
  you use the principle of orthogonality combined closely with the DRY principle,
  you'll find that the systems you develop are more flexible, more understandable
  and easier to debug, test, and maintain.

  ### Reversibility

  The principles of orthogonality and DRY result in code that is reversible. This
  means it is able to change in an agile way when the circumstances of its use and
  deployment change. This is important because when developing software in a
  business setting, the best decisions are not always made the first time around.
  By following the principles it should be relatively easy to change your
  program's interfaces, platform and scale. In other words, with the principle of
  orthogonality and DRY, refactoring becomes less of a chore.

  ## Prototyping and Tracer Bullets

  'Tracer bullets' are used by the military for night warfare. They are
  phosphorous bullets that are included in the magazines of guns alongside normal
  bullets. They are not intended to kill but instead light-up the surrounding area
  making it easier to see the terrain and target more efficiently.

  The authors use the notion of tracer bullets as a metaphor for developing
  software at the early stages of a project. This is **not** the same thing as
  prototyping. A tracer bullet model is useful for building things that haven't
  been built before. They exist to 'shed light' on the project's needs and to help
  the client understand what they want.

  They differ from prototypes in that they include integrated overall
  functionality but in a rough state. Whereas prototypes are more for singular,
  specific subcomponents of the project. Because tracer bullet models are
  joined-up in this way, even if they turn out to be inappropriate in some regard,
  they can be adapted and developed into a better form, without losing the core
  functionality.

  > Tracer bullets work because they operate in the same environment and under the
  > same constraints as the real bullets. They get to the target fast, so the
  > gunner gets immediate feedback. And from a practical standpoint they are a
  > relatively cheap solution. To get the same effect in code, we're looking for
  > something that gets us from a requirement to some aspect of the final system
  > quickly, visibly and repeatably.

  > Tracer code is not disposable: you write it for keeps. It contains all the
  > error-checking, structuring, documentation and self-checking that a piece of
  > production code has. It simply is not fully functional. However, once you have
  > made an end-to-end connection among the components of your system, you can
  > check how close to the target you are, adjusting as necessary.

  ### Distinguishing from prototyping

  > Prototyping generates disposable code. Tracer code is lean but complete, and
  > forms part of the skeleton of the final system. Think of prototyping as the
  > reconnaissance and intelligence gathering that takes place before a single
  > tracer bullet is fired.

  ## Design by contract

  To understand DBC we have to think of a computational process as involving two
  stages: the call and the execution of the routine that happens in response to
  the call (henceforth **caller** and **routine**).

  - the caller could be a function expression that invokes a function and passes
    arguments to it expecting a given output. The function that executes is the
    routine
  - the caller could be an object instantiation that calls a method belonging to
    its parent class
  - the caller could be a parent React component that passes props to a child
    component

  Design by contract means specifying clear and inviolable rules detailing what
  must obtain at both the call stage and the routine stage if the process is to
  execute.

  Every function and method in a software system does something. Before it starts
  that something, the routine may have some expectation of the state of the world
  and it may be able to make a statement about the state of the world when it
  concludes. These expectations are defined in terms of preconditions,
  postconditions, and invariants. They form that basis of a **contract** between
  the caller and the routine. Hence _design by contract\*\*._\*\*

  ### Preconditions

  Preconditions specify what must be true in order for the routine to be called.
  In other words, the requirements of the routine. What it needs and what should
  be the case before it even considers executing the task. A **routine should
  never get called when its preconditions would be violated**.

  ### Postconditions

  Providing the preconditions are met, this is what the routine is guaranteed to
  do. In other words: the state of affairs that must obtain after the routine has
  ran.

  ### Invariants

  Once established, the preconditions and postconditions should not change. If
  they need to change, that is a separate process and contract. In the processing
  of a routine, the data may be variant relative to the contract, but by the end
  the overall conditions establish the equilibrium of the contract.

  There is an analogue here with functional programming philosophy: the function
  should always return the same sort of output, without ancillary processes
  happening, i.e side-effects.

  One way to achieve this is to be miserly when setting up the contract, which
  overlaps with orthogonality. Only specify the minimum return on a contract
  rather than multiple postconditions. This only increases the likelihood that the
  contract will be breached at some point. If you need multiple postconditions,
  spread them out an achieve them in a compositional way, with multiple separate
  and modular processes.

  > Be strict in what you will accept before you begin, and promise as little as
  > possible in return. If your contract indicates that you'll accept anything and
  > promise the world in return, then you've got a lot of code to write!

  ### Division of responsibilities

  > If all the routine's preconditions are met by the caller, the routine shall
  > guarantee that all postconditions and invariants will be true when it
  > completes.

  Note that the emphasis of responsibilities is on the caller.

  Imagine that we have a function that returns the count of an array of integers.
  It is not the job of the count routine to verify that it has been passed
  integers and then to execute the count. Or, in the event that it is not passed
  integers, to mutate the data to integers and then execute.

  This should be resolved by the caller: it is the responsibility of the caller to
  pass integers. If it doesn't, the routine simply crashes or raises an exception.
  It doesn't try to accommodate the input because that does not come down on its
  side of the contract. The caller has failed to meet the preconditions. If, due
  to some bug, the routine receives integers and fails to output the sum, then it
  has failed on its side

  ### Example: type checking

  An obvious example of this philosophy is when you perform checks or validation
  within your code (although validation is more of an issue when you are dealing
  with user data, not your own internal code). For instance using type checking
  with dynamically-typed languages.

  When we use the `prop-types` library with React we are specifying preconditions:
  so long at the prop (effectively the caller) passed to the component
  (effectively the routine) is of type X, the component will render invariantly as
  R. If the prop is of type Y, an exception will be raised highlighting a breach
  in the contract.

  Another example would be more advanced type checking with Javascript written
  using Typescript.

  ## The Law of Demeter

  Demeter's Law has applicability chiefly when programming with classes.

  It's a fancy name for a simple principle summarised by 'don't talk to
  strangers'. Demeter's law is violated when code has more than one step between
  classes. You should avoid invoking methods of an object returned by another
  method. You should only use your own methods when dealing with it.

  ### Formal

  A method _m_ of object _O_ may only invoke the methods of the following kinds of
  objects:

  - _O_ itself
  - _m_'s parameters
  - any objects created or instantiated within _m_
  - _O_'s direct component objects (in other words nested objects)
  - a global variable (over and above _O_) accessible by _O_, within the scope of
    _m_

  ## Model, View, Controller design pattern

  The key concept behind the MVC idiom is separating the model from both the GUI
  that represents it and the controls that manage the view.

  - **Model**
    - The abstract data model representing the target object
    - The model has no direct knowledge of any views or controllers
  - **View**
    - A way to interpret the model. It subscribes to changes in the model and
      logical events from the controller
  - **Controller**
    - A way to control the view and provide the model with new data. It publishes
      events to both the model and the view

  For comparison, distinguish React from MVC. In React data is unidirectional: the
  JSX component as controller cannot change the state. The state is passed down to
  the controller. Also MVC lends itself to separation of technologies: code used
  to create the View is different from Code that manages Controller and data
  Model. In React it's all one integrated system.

  ## Refactoring

  > Rewriting, reworking, and re-architecting code is collectively known as
  > refactoring

  ### When to refactor

  - **Duplication**: you've discovered a violation of the DRY principle
  - **Non-orthogonal design**: you've discovered some code or design that could be
    made more orthogonal
  - **Outdated knowledge**: your knowledge about the problem and you skills at
    implementing a solution have changed since the code was first written. Update
    and improve the code to reflect these changes
  - **Performance: y**ou need to move functionality from one area of the system to
    another to improve performance

  ### Tips when refactoring

  - Don't try to refactor and add new functionality at the same time!
  - Make sure you have good tests before you begin refactoring. Run the tests as
    you refactor. That way you will know quickly if your changes have broken
    anything
  - Take short, deliberative steps. Refactoring often involves making many
    localised changes that result in a larger-scale change.

  ## Testing

  > Most developers hate testing. They tend to test-gently, subconsciously knowing
  > where the code will break and avoiding the weak spots. Pragmatic Programmers
  > are different. We are _driven_ to find our bugs _now_, so we don't have to
  > endure the shame of others finding our bugs later.

  ### Unit testing

  A unit test is code that exercises a module. It consists in testing each module
  in isolation to verify its behaviour. Unit testing is the foundation of all
  other forms of testing. If the parts don't work by themselves, they probably
  won't work well together. All the modules you are using must pass their own unit
  tests before you can proceed.

  We can think of unit testing as **testing against contract** (detailed above).
  We want to test that the module delivers the functionality it promises over a
  wide range of test cases and boundary conditions.

  Scope for unit testing should cover:

  - Obviously, returning the expected value/outcome
  - Ensuring that faulty arguments/ types are rejected and initiate error handling
    (deliberately breaking your code to ensure it is handled appropriately)
  - Pass in the boundary and maximum value
  - Pass in values between the zero and the maximum expressible argument to cover
    a range of cases

  Benefits of unit testing include:

  - It creates an example to other developers how to use all of the functionality
    of a given module
  - It is a means to build **regression tests** which can be used to validate any
    future changes to the code. In other words, the future changes should pass the
    older tests to prove they are consistent with the code base

  ### Integration testing

  Integration testing shows that the major subsystems that make up the project
  work and play well with each other.

  Integration testing is really just an extension of the unit testing described,
  only know you're testing how entire subsystems honour their contracts.

  ## Commenting your code

  In general, comments should detail **why** something is done, its purpose and
  its goal. The code already shows _how_ it's done, so commenting on this is
  redundant, and violates the DRY principle.

  > We like to see a simple module-level comment, comments for significant data
  > and type declarations, and a brief class and per-method header describing how
  > the function is used and anything it does that is not obvious

  ```js
  /*
  Find the highest value within a specified data range of samples

  Parameter: aRange = range of dates to search for data
  Parameter: aThreshold = minimum value to consider

  Return: the value, or null if no value found that is greater than or equal to the threshold
  */
  ```


path: The_Shadow_Text.md
sortable_path: The_Shadow_Text.md
title: 4dec6fe0_shadow_text
checksum: 4285ac86bb54e3db9fde04613ea1b8ebaa3870262e8a7fd582492a1e59f86832
created: 2025-10-01 13:31:02.069975728+00:00
modified: 2025-08-13 15:57:13.846645595+00:00
metadata: {"created":"Monday, January 20, 2025","tags":["ecopolsoc","internet","surveillance-capitalism"]}
lead:
  Surveillance capitalism creates two "electronic texts". The first is the
  public-facing text which consists in the benefits conferred by the surveillance
  capitalists' services, expressed in their self-regarding mission statements. The
  "universe of information and connection [they] bring to our fingertips" (posts,
  blogs, likes, photos, spreadsheets, music, tweets etc).

body:
  Surveillance capitalism creates two "electronic texts". The first is the
  public-facing text which consists in the benefits conferred by the surveillance
  capitalists' services, expressed in their self-regarding mission statements. The
  "universe of information and connection [they] bring to our fingertips" (posts,
  blogs, likes, photos, spreadsheets, music, tweets etc).

  The first text is a sort of Trojan Horse, serving as the "supply operation" for
  the second, _shadow_ text. Everything we contribute to the first text becomes a
  target for surplus extraction via the shadow text. This is the ultimate purpose
  of the services and is hidden from view and only for the purview of the
  surveillance capitalists.

  _In this text, our experience is dragooned as raw material to be accumulated and
  analyzed as means to others' market ends. The shadow text is a burgeoning
  accumulation of behavioural surplus and its analyses, and it says more about us
  than we can know about ourselves. Worse still, it becomes increasingly
  difficult, and perhaps impossible, to refrain from contributing to the shadow
  text. It automatically feeds on our experience as we engage in the normal and
  necessary routines of social participation._

  _The Age of Surveillance Capitalism_ (2019): 185

raw_content:
  ---
  tags: [ecopolsoc, internet, surveillance-capitalism]
  created: Monday, January 20, 2025
  ---

  # 4dec6fe0_shadow_text

  Surveillance capitalism creates two "electronic texts". The first is the
  public-facing text which consists in the benefits conferred by the surveillance
  capitalists' services, expressed in their self-regarding mission statements. The
  "universe of information and connection [they] bring to our fingertips" (posts,
  blogs, likes, photos, spreadsheets, music, tweets etc).

  The first text is a sort of Trojan Horse, serving as the "supply operation" for
  the second, _shadow_ text. Everything we contribute to the first text becomes a
  target for surplus extraction via the shadow text. This is the ultimate purpose
  of the services and is hidden from view and only for the purview of the
  surveillance capitalists.

  _In this text, our experience is dragooned as raw material to be accumulated and
  analyzed as means to others' market ends. The shadow text is a burgeoning
  accumulation of behavioural surplus and its analyses, and it says more about us
  than we can know about ourselves. Worse still, it becomes increasingly
  difficult, and perhaps impossible, to refrain from contributing to the shadow
  text. It automatically feeds on our experience as we engage in the normal and
  necessary routines of social participation._

  _The Age of Surveillance Capitalism_ (2019): 185


path: The_Web_versus_modem_BBSs.md
sortable_path: The_Web_versus_modem_BBSs.md
title: 241fe1a3_the_Web_versus_modem_BBSs
checksum: 84911f03e729d16f43038f10b328821c150b1b362115c555864dea1e500d54e4
created: 2025-10-01 13:31:02.077740842+00:00
modified: 2025-08-13 15:57:13.90984958+00:00
lead:
  As access to the Web and the demand for public access grew, services like AOL,
  which provided dial-up access to a subset of the broader Internet (along with
  email and chat-rooms) became increasingly redundant.

body:
  As access to the Web and the demand for public access grew, services like AOL,
  which provided dial-up access to a subset of the broader Internet (along with
  email and chat-rooms) became increasingly redundant.

  In response to this, they pivoted in their marketing to presenting their service
  as a safe, curated means of accessing the information: the Web was confusing, it
  was hard to know what was out there and how to find useful information.

  By using AOL, you would know which sites to visit for different tasks and which
  of these were reputable. They adopted a kind of friendly gatekeeper persona in
  the era of unhampered and universal access to information that the Web heralded
  and embodied.

  (This isn't so strange if we consider that outside of work (and the communities
  of computer enthusiasts), personal computers were still relatively new and
  computers themselves still rather forbidding entities.)

  This function of a gateway to the overwhelming content of the web would be taken
  up first by browsers
  ([Mosaic's](c301a0b3-1d8_Mosaic_Netscape_and_Browser_Wars.md) landing page would
  list websites for specific sources of information and user tasks and links to
  other directories) and then of course search engines which would allow users to
  search for what they needed against an index (usually a subset) of the Web's
  total content. (Interestingly in this regard, AOL would eventually buy
  Netscape.)

  AOL et al made sense when users wanted access to the internet and the web didn't
  yet exist. Different networks had different access criteria and were often
  poorly signposted, accessible only to those in the know. Private commercial
  networks were set up to service typical consumer needs (news, banking, weather
  reports etc) but the whole point of the Web as conceived by TBL was to make
  everything available to anyone who wanted it through a streamlined and universal
  set of protocols.

raw_content:
  ---
  tags: [world-wide-web, computer-history, internet, bulletin-boards]
  created: Monday, November 04, 2024
  ---

  # 241fe1a3_the_Web_versus_modem_BBSs

  As access to the Web and the demand for public access grew, services like AOL,
  which provided dial-up access to a subset of the broader Internet (along with
  email and chat-rooms) became increasingly redundant.

  In response to this, they pivoted in their marketing to presenting their service
  as a safe, curated means of accessing the information: the Web was confusing, it
  was hard to know what was out there and how to find useful information.

  By using AOL, you would know which sites to visit for different tasks and which
  of these were reputable. They adopted a kind of friendly gatekeeper persona in
  the era of unhampered and universal access to information that the Web heralded
  and embodied.

  (This isn't so strange if we consider that outside of work (and the communities
  of computer enthusiasts), personal computers were still relatively new and
  computers themselves still rather forbidding entities.)

  This function of a gateway to the overwhelming content of the web would be taken
  up first by browsers
  ([Mosaic's](c301a0b3-1d8_Mosaic_Netscape_and_Browser_Wars.md) landing page would
  list websites for specific sources of information and user tasks and links to
  other directories) and then of course search engines which would allow users to
  search for what they needed against an index (usually a subset) of the Web's
  total content. (Interestingly in this regard, AOL would eventually buy
  Netscape.)

  AOL et al made sense when users wanted access to the internet and the web didn't
  yet exist. Different networks had different access criteria and were often
  poorly signposted, accessible only to those in the know. Private commercial
  networks were set up to service typical consumer needs (news, banking, weather
  reports etc) but the whole point of the Web as conceived by TBL was to make
  everything available to anyone who wanted it through a streamlined and universal
  set of protocols.

metadata:
  {"created":"Monday, November 04, 2024","tags":["world-wide-web","computer-history","internet","bulletin-boards"]}


path: The_kernel.md
sortable_path: The_kernel.md
title: The kernel
checksum: 96c9ce4e7d5964c10b9af81b81e350210f24146bc8a16245169ff057cdb3dd4f
created: 2025-10-01 13:31:02.09850122+00:00
modified: 2025-08-13 15:57:13.786179087+00:00
metadata: {"tags":["systems-programming","memory","operating-systems","computer-architecture"]}
lead:
  Within the [hierarchy of the OS](./Basic_model_of_the_operating_system.md), the
  kernel acts as the primary mediator between the hardware (CPU, memory) and
  [user](./User_Space.md) [processes](Processes.md). Let's look at each of its
  responsibilities in greater depth:

body:
  Within the [hierarchy of the OS](./Basic_model_of_the_operating_system.md), the
  kernel acts as the primary mediator between the hardware (CPU, memory) and
  [user](./User_Space.md) [processes](Processes.md). Let's look at each of its
  responsibilities in greater depth:

  - process management
  - memory management
  - device drivers
  - system calls

  ## Process management

  > A process is just another name for a running program. Process management is
  > the starting, pausing, resuming, scheduling and terminating of processes.

  On modern computers it appears that multiple processes can run simultaneously at
  once. This is only because the processor is so fast that we do not detect
  changes. In fact access to the CPU is always sequential. The sequence in which
  multiple programs are allowed to access the CPU is managed by the kernel.

  > Consider a system with a one-core CPU. Many processes may be _able_ to use the
  > CPU, but only one process can actually use the CPU at any given time...Each
  > process uses the CPU for a fraction of a second, then pauses, then another
  > process uses it for a fraction of a second and so on... (_How Linux Works:
  > Third Edition_, Brian Ward 2021)

  This process of the CPU shuffling between multiple processes is called _context
  switching_.

  The role of the kernel in facilitating this, is as follows:

  1. CPU runs process for a time slice based on its internal time. Then hands
     control back to the kernel (kernel mode)
  2. Kernel records current state of CPU and memory. This is necessary in order to
     resume the progress that was just interupted.
  3. The kernel executes any tasks that arose in the last timeslice executed by
     the CPU (e.g. collecting data from I/0)
  4. Kernel then analyses the list of processes that are ready to run next and
     chooses one.
  5. Kernel prepares memory for this new process and prepares the CPU.
  6. Kernel tells CPU how long the time slice for the new process will last.
  7. Kernel switches the CPU into user mode and hands control of CPU to the
     process.

  ## Memory management

  During the context switch from CPU to user space, the kernel allocates memory.
  It has the following jobs to manage:

  - Keeping its own private area in memory for itself that user processes cannot
    access
  - Assigning each user process its own section of memory
  - Managing shared memory between processes and ensuring the private memory of
    processes is not accessed by others
  - Managing read-only memory
  - Allowing for the use of disk space as auxiliary memory

  > Modern CPUs include a
  > [memory management unit](Virtual_memory_and_the_MMU_in_Linux.md#the-memory-management-unit-mmu)
  > which provides the kernel with **virtual** memory. In this scenario, memory
  > isn't directly accessed by the process instead it works on the assumption that
  > is has access to the entire memory of the machine and this is then translated
  > into a map that is applied to the real memory and managed by the kernel.

  ## Device drivers

  Devices are managed by the kernal and are not accessible directly via user
  space, since improper usage could crash the whole machine. There is little
  uniformity between devices and as a result drivers are needed. Thes are kernl
  code that enable different OS kernels to access and control the devices.

  ## System calls

  Syscalls are what enable programs to start and are required for the acts of
  opening, reading and writing files. System calls in Linux are typically managed
  via C.

  In Linux there are two particularly important system calls:

  - `fork()`
    - When a process calls fork, the kernel creates a nearly identical copy of
      this running process
  - `exec()`
    - When a process calls exec it passes a program name as a parameter. Then the
      kernel loads and starts this program, replacing the current process.

  Example with a terminal program like `ls`:

  > When you enter `ls` into the terminal window, the shell that's running inside
  > the terminal window calls `fork()` to create a copy of the shell, and then the
  > new copy of the shell calls `exec(ls)` to run `ls`. (_Ibid._)

  ## Controlling processes

  In Linux we can view, kill, pause and resume processes using [ps](Processes.md).

raw_content:
  ---
  tags:
    - systems-programming
    - memory
    - operating-systems
    - computer-architecture
  ---

  # The kernel

  Within the [hierarchy of the OS](./Basic_model_of_the_operating_system.md), the
  kernel acts as the primary mediator between the hardware (CPU, memory) and
  [user](./User_Space.md) [processes](Processes.md). Let's look at each of its
  responsibilities in greater depth:

  - process management
  - memory management
  - device drivers
  - system calls

  ## Process management

  > A process is just another name for a running program. Process management is
  > the starting, pausing, resuming, scheduling and terminating of processes.

  On modern computers it appears that multiple processes can run simultaneously at
  once. This is only because the processor is so fast that we do not detect
  changes. In fact access to the CPU is always sequential. The sequence in which
  multiple programs are allowed to access the CPU is managed by the kernel.

  > Consider a system with a one-core CPU. Many processes may be _able_ to use the
  > CPU, but only one process can actually use the CPU at any given time...Each
  > process uses the CPU for a fraction of a second, then pauses, then another
  > process uses it for a fraction of a second and so on... (_How Linux Works:
  > Third Edition_, Brian Ward 2021)

  This process of the CPU shuffling between multiple processes is called _context
  switching_.

  The role of the kernel in facilitating this, is as follows:

  1. CPU runs process for a time slice based on its internal time. Then hands
     control back to the kernel (kernel mode)
  2. Kernel records current state of CPU and memory. This is necessary in order to
     resume the progress that was just interupted.
  3. The kernel executes any tasks that arose in the last timeslice executed by
     the CPU (e.g. collecting data from I/0)
  4. Kernel then analyses the list of processes that are ready to run next and
     chooses one.
  5. Kernel prepares memory for this new process and prepares the CPU.
  6. Kernel tells CPU how long the time slice for the new process will last.
  7. Kernel switches the CPU into user mode and hands control of CPU to the
     process.

  ## Memory management

  During the context switch from CPU to user space, the kernel allocates memory.
  It has the following jobs to manage:

  - Keeping its own private area in memory for itself that user processes cannot
    access
  - Assigning each user process its own section of memory
  - Managing shared memory between processes and ensuring the private memory of
    processes is not accessed by others
  - Managing read-only memory
  - Allowing for the use of disk space as auxiliary memory

  > Modern CPUs include a
  > [memory management unit](Virtual_memory_and_the_MMU_in_Linux.md#the-memory-management-unit-mmu)
  > which provides the kernel with **virtual** memory. In this scenario, memory
  > isn't directly accessed by the process instead it works on the assumption that
  > is has access to the entire memory of the machine and this is then translated
  > into a map that is applied to the real memory and managed by the kernel.

  ## Device drivers

  Devices are managed by the kernal and are not accessible directly via user
  space, since improper usage could crash the whole machine. There is little
  uniformity between devices and as a result drivers are needed. Thes are kernl
  code that enable different OS kernels to access and control the devices.

  ## System calls

  Syscalls are what enable programs to start and are required for the acts of
  opening, reading and writing files. System calls in Linux are typically managed
  via C.

  In Linux there are two particularly important system calls:

  - `fork()`
    - When a process calls fork, the kernel creates a nearly identical copy of
      this running process
  - `exec()`
    - When a process calls exec it passes a program name as a parameter. Then the
      kernel loads and starts this program, replacing the current process.

  Example with a terminal program like `ls`:

  > When you enter `ls` into the terminal window, the shell that's running inside
  > the terminal window calls `fork()` to create a copy of the shell, and then the
  > new copy of the shell calls `exec(ls)` to run `ls`. (_Ibid._)

  ## Controlling processes

  In Linux we can view, kill, pause and resume processes using [ps](Processes.md).


path: The_memory_hierarchy.md
sortable_path: The_memory_hierarchy.md
title: The memory hierarchy
checksum: 520f038e59b4ff5a9e388265ab28df99ee8da22437dbdbe7be84333c974b1aac
created: 2025-10-01 13:31:02.144707801+00:00
modified: 2025-08-13 15:57:13.757049347+00:00
metadata: {"created":"Friday, July 12, 2024","tags":["memory"]}
lead:
  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

body:
  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

  ![Memory hierarchy diagram](/img/Memory-Hierarchy.jpg)

raw_content:
  ---
  tags: [memory]
  created: Friday, July 12, 2024
  ---

  # The memory hierarchy

  The diagram below compares the different forms of memory within a computing
  device in terms of speed, monetary cost and capacity:

  ![Memory hierarchy diagram](/img/Memory-Hierarchy.jpg)


path: Theorems_and_empty_sets.md
sortable_path: Theorems_and_empty_sets.md
title: Theorems and empty sets
checksum: 52cac37f8c279c1d947878b3bd503eac5dc8f7c50468f4bdaec0cf7e7e2f04fe
created: 2025-10-01 13:31:02.15096233+00:00
modified: 2025-08-13 15:57:14.071692162+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  We know that when we construct a
  [derivation](Formal_proofs_in_propositional_logic.md#derivation-rules) we start
  from a set of assumptions and then attempt to reach a proposition that is a
  consequence of the starting assumptions. However it does not always have to be
  the case that the starting set contains members. The set can in fact be empty.

body:
  We know that when we construct a
  [derivation](Formal_proofs_in_propositional_logic.md#derivation-rules) we start
  from a set of assumptions and then attempt to reach a proposition that is a
  consequence of the starting assumptions. However it does not always have to be
  the case that the starting set contains members. The set can in fact be empty.

  _Demonstration_

  ![](/img/proofs-drawio-Page-5.drawio_2.png)

  We see in this example that there is no starting set and thus no primary
  assumptions. Instead we start with nothing other than the proposition we wish to
  derive. The proposition is effectively derived from itself. In these scenarios
  we say that we are constructing a derivation from an **empty set**.

  Propositions which possess this property are called theorems:

  > A proposition $P$ or a system of propositions in propositional logic is a
  > theorem in a system of derivation for that logic if $P$ is derivable from the
  > empty set.

  We represent a theorem as:

  $$
  \vdash P
  $$

  (There is no preceding $\Gamma$ as the set is empty. )

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Theorems and empty sets

  We know that when we construct a
  [derivation](Formal_proofs_in_propositional_logic.md#derivation-rules) we start
  from a set of assumptions and then attempt to reach a proposition that is a
  consequence of the starting assumptions. However it does not always have to be
  the case that the starting set contains members. The set can in fact be empty.

  _Demonstration_

  ![](/img/proofs-drawio-Page-5.drawio_2.png)

  We see in this example that there is no starting set and thus no primary
  assumptions. Instead we start with nothing other than the proposition we wish to
  derive. The proposition is effectively derived from itself. In these scenarios
  we say that we are constructing a derivation from an **empty set**.

  Propositions which possess this property are called theorems:

  > A proposition $P$ or a system of propositions in propositional logic is a
  > theorem in a system of derivation for that logic if $P$ is derivable from the
  > empty set.

  We represent a theorem as:

  $$
  \vdash P
  $$

  (There is no preceding $\Gamma$ as the set is empty. )


path: Threads.md
sortable_path: Threads.md
title: Threads
checksum: 59e40ca378bfe6cdffdc24908333c2184b4fc172e8428052b85a6b322df2d351
created: 2025-10-01 13:31:02.167193605+00:00
modified: 2025-08-13 15:57:14.085436535+00:00
metadata: {"created":"Wednesday, June 26, 2024","id":"zadl","tags":["operating-systems"]}
lead:
  A ![process](Processes.md) is a running instance of a given program. A program
  runs sequentially handling one task at a time, however we may need to run
  certain tasks in parallel.

body:
  A ![process](Processes.md) is a running instance of a given program. A program
  runs sequentially handling one task at a time, however we may need to run
  certain tasks in parallel.

  Think of a program that is downloading a resource from the internet and wants to
  update the UI at the same time to show the download process.

  If the program is strictly sequential, once the program starts the download, the
  UI is neglected since the CPU time dedicated to the process must be focused
  elsewhere.

  We need the UI update and the download to execute in parallel. This is achieved
  via operating system **threads of execution**.

  > A thread is a schedulable unit of execution within a process. It runs within a
  > process, shares its memory address and can execute any program code loaded
  > within it.

  Each thread has an identifier called a _thread ID_ (TID).

  Both procesess in [user space](User_Space.md) and the [kernel](The_kernel.md)
  utilise threads.

  ## Hardware considerations

  Each [processor core](CPU_architecture.md) can only run one thread at a time so
  the number of cores that a device's CPU has deterimines how many threads can run
  at once. In practice, then, the CPU shuffles between multiple threads in
  [clock segments](Clock_signals.md). This is known as scheduling and is managed
  by the process that the thread belongs to.

  ## Related notes

  ![Processes](Processes.md)

raw_content:
  ---
  id: zadl
  tags: [operating-systems]
  created: Wednesday, June 26, 2024
  ---

  # Threads

  A ![process](Processes.md) is a running instance of a given program. A program
  runs sequentially handling one task at a time, however we may need to run
  certain tasks in parallel.

  Think of a program that is downloading a resource from the internet and wants to
  update the UI at the same time to show the download process.

  If the program is strictly sequential, once the program starts the download, the
  UI is neglected since the CPU time dedicated to the process must be focused
  elsewhere.

  We need the UI update and the download to execute in parallel. This is achieved
  via operating system **threads of execution**.

  > A thread is a schedulable unit of execution within a process. It runs within a
  > process, shares its memory address and can execute any program code loaded
  > within it.

  Each thread has an identifier called a _thread ID_ (TID).

  Both procesess in [user space](User_Space.md) and the [kernel](The_kernel.md)
  utilise threads.

  ## Hardware considerations

  Each [processor core](CPU_architecture.md) can only run one thread at a time so
  the number of cores that a device's CPU has deterimines how many threads can run
  at once. In practice, then, the CPU shuffles between multiple threads in
  [clock segments](Clock_signals.md). This is known as scheduling and is managed
  by the process that the thread belongs to.

  ## Related notes

  ![Processes](Processes.md)


path: Three_bit_counter.md
sortable_path: Three_bit_counter.md
title: 3-bit Counter
checksum: 3ce02ec5bc1d8a10a459d711a57cd60465a70e680b6b4f7b2b66a443232fa403
created: 2025-10-01 13:31:02.20120712+00:00
modified: 2025-08-13 15:57:14.078543838+00:00
metadata: {"tags":["logic-gates","binary","memory"]}
lead:
  To demonstrate the use of
  [Flip-Flops](Flip_flops.md) we will
  create the circuit for a 3-bit counter. This simply counts up from 0 to 7
  because 7 is the maximum decimal number we can create with three bits ($2^3$):

body:
  To demonstrate the use of
  [Flip-Flops](Flip_flops.md) we will
  create the circuit for a 3-bit counter. This simply counts up from 0 to 7
  because 7 is the maximum decimal number we can create with three bits ($2^3$):

  | Binary | Decimal |
  | ------ | ------- |
  | 000    | 0       |
  | 001    | 1       |
  | 010    | 2       |
  | 011    | 3       |
  | 100    | 4       |
  | 101    | 5       |
  | 110    | 6       |
  | 111    | 7       |

  The circuit will have three memory components, each representing one bit of the
  3-bit number. When the
  [clock pulses](Clock_signals.md), the
  3-bit number increments by one. We need to synchronise the operation with a
  clock because each bit by itself is meaningless, it only gains meaning by the
  relation it sustains to the other two bits hence it must be kept in sync with
  them.

  We can derive the circuit arrangement by analysing the outputs of a flipflop at
  each pulse:

  | Combined bits | Q2  | Q1  | Q0  | Decimal |
  | ------------- | --- | --- | --- | ------- |
  | 000           | 0   | 0   | 0   | 0       |
  | 001           | 0   | 0   | 1   | 1       |
  | 010           | 0   | 1   | 0   | 2       |
  | 011           | 0   | 1   | 1   | 3       |
  | 100           | 1   | 0   | 0   | 4       |
  | 101           | 1   | 0   | 1   | 5       |
  | 110           | 1   | 1   | 0   | 6       |
  | 111           | 1   | 1   | 1   | 7       |

  If we look at the pattern of each flip-flops' output we notice the following:

  - _Q0_ toggles on every pulse
  - _Q1_ toggles every time _Q0_ was previously 1
  - _Q2_ toggles when both _Q1_ and _Q0_ were previously 1

  This means that to construct a circuit that displays this behaviour we just have
  to use a
  [T flip-flop](Flip_flops.md#t-flip-flops)
  since the only state change we need is a single bit toggle three times that
  retains its value.

  Using these pulse patterns we can construct a circuit as follows:
  ![](/img/3-bit-adder-diagram.png)

  <iframe src="https://circuitverse.org/simulator/embed/3-bit-counter-d33846e3-7538-427d-b4cc-dc64fdaf0af3?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="500" width="600" allowFullScreen></iframe>

  <br />

  > Remember from the table: Q2 is the most significant bit and Q0 is the least
  > significant bit so we should read the output in the interactive circuit
  > backwards, reversing the order. Then we will see that the cicuit is cyling
  > through 0-7

  This is how the circuit works:

  - All three flip-flops are connected to the same clock signal so that they are
    synchnonised
  - Because _T0_ is connected to the voltage source _Q0_ toggles every time the
    clock pulses
  - _T1_ is connected to _Q0_ so a clock pulse causes _Q1_ to toggle only when
    _Q0_ is high in line with the table
  - _T2_ is connected to _Q0 AND Q1_ so _Q2_ only toggles on a clock pulse when
    _Q0_ and _Q1_ are both high

raw_content:
  ---
  tags:
    - logic-gates
    - binary
    - memory
  ---

  # 3-bit Counter

  To demonstrate the use of
  [Flip-Flops](Flip_flops.md) we will
  create the circuit for a 3-bit counter. This simply counts up from 0 to 7
  because 7 is the maximum decimal number we can create with three bits ($2^3$):

  | Binary | Decimal |
  | ------ | ------- |
  | 000    | 0       |
  | 001    | 1       |
  | 010    | 2       |
  | 011    | 3       |
  | 100    | 4       |
  | 101    | 5       |
  | 110    | 6       |
  | 111    | 7       |

  The circuit will have three memory components, each representing one bit of the
  3-bit number. When the
  [clock pulses](Clock_signals.md), the
  3-bit number increments by one. We need to synchronise the operation with a
  clock because each bit by itself is meaningless, it only gains meaning by the
  relation it sustains to the other two bits hence it must be kept in sync with
  them.

  We can derive the circuit arrangement by analysing the outputs of a flipflop at
  each pulse:

  | Combined bits | Q2  | Q1  | Q0  | Decimal |
  | ------------- | --- | --- | --- | ------- |
  | 000           | 0   | 0   | 0   | 0       |
  | 001           | 0   | 0   | 1   | 1       |
  | 010           | 0   | 1   | 0   | 2       |
  | 011           | 0   | 1   | 1   | 3       |
  | 100           | 1   | 0   | 0   | 4       |
  | 101           | 1   | 0   | 1   | 5       |
  | 110           | 1   | 1   | 0   | 6       |
  | 111           | 1   | 1   | 1   | 7       |

  If we look at the pattern of each flip-flops' output we notice the following:

  - _Q0_ toggles on every pulse
  - _Q1_ toggles every time _Q0_ was previously 1
  - _Q2_ toggles when both _Q1_ and _Q0_ were previously 1

  This means that to construct a circuit that displays this behaviour we just have
  to use a
  [T flip-flop](Flip_flops.md#t-flip-flops)
  since the only state change we need is a single bit toggle three times that
  retains its value.

  Using these pulse patterns we can construct a circuit as follows:
  ![](/img/3-bit-adder-diagram.png)

  <iframe src="https://circuitverse.org/simulator/embed/3-bit-counter-d33846e3-7538-427d-b4cc-dc64fdaf0af3?theme=default&display_title=false&clock_time=true&fullscreen=true&zoom_in_out=true" style="border-width:; border-style: solid; border-color:;" name="myiframe" id="projectPreview" scrolling="no" frameborder="1" marginheight="0px" marginwidth="0px" height="500" width="600" allowFullScreen></iframe>

  <br />

  > Remember from the table: Q2 is the most significant bit and Q0 is the least
  > significant bit so we should read the output in the interactive circuit
  > backwards, reversing the order. Then we will see that the cicuit is cyling
  > through 0-7

  This is how the circuit works:

  - All three flip-flops are connected to the same clock signal so that they are
    synchnonised
  - Because _T0_ is connected to the voltage source _Q0_ toggles every time the
    clock pulses
  - _T1_ is connected to _Q0_ so a clock pulse causes _Q1_ to toggle only when
    _Q0_ is high in line with the table
  - _T2_ is connected to _Q0 AND Q1_ so _Q2_ only toggles on a clock pulse when
    _Q0_ and _Q1_ are both high


path: Time_and_computers.md
sortable_path: Time_and_computers.md
title: Time and computers
lead: ## Epochs and Unix Time
checksum: 27ee0c19f35eb4bd4eaff528bf3d7f8c2bdaa3723eaddf607a8ef49ef4d21a69
created: 2025-10-01 13:31:02.221655258+00:00
modified: 2025-08-13 15:57:13.992609399+00:00
metadata: {"created":"Sunday, April 28, 2024","id":"l29u","tags":["unix","time"]}
body:
  ## Epochs and Unix Time

  An epoch is a period of time identified by a starting point.

  The standard epoch for measuring against is known colloqially as "Unix Time":
  midnight on 1st January 1970. Unix time is the number of seconds that have
  elapsed since this point. This is the standard for Windows and most unix
  systems.

  For example midnight on 2nd January 1970 would be 86400 seconds since the epoch.
  The deduction is as follows:

  ```
  60 * 60 * 24 = 86400
  ```

  We are multiplpying the number of seconds in a minute by the number of minutes
  in an hour by the number of hours in a day.

  To represent times and dates before 1970, we use negative integers.

  ## UTC and time zones

  "Coordinated Universal Time" (UTC) is the time standard against which the
  world's timekeeping is synchronized. It is not itself a timezone, it is a
  transcendent standard that defines what time zones are.

  Time zones are defined relative to UTC, by their offset to this value.

  For example North America is behind UTC by five or six hours hence uses the
  notation UTC-5:00 or UTC-6:00.

  UTC is the successor to GMT of which there were variances necessitating a
  universal value. Despite this GMT is equal to UTC+00:00 so apart from when
  daylight saving is in effect in the UK, GMT = UTC.

  Depending on your locale, relative to UTC, you will get different outputs when
  you attempt to log Unix Time.

  Also daylight savings can impact on the outputs. For instance for half of the
  year, when DST applies, the UK is UTC+01:00.

  ## Examples in Python

  The following gives us the unix seconds in my current time zone (GMT DST):

  ```py
  import time
  print(time.time())
  # 1714322393.2929392
  ```

  If I want UTC, I can do:

  ```py
  import datetime
  print(datetime.datetime.utcnow().strftime('%s'))
  # '1714318952'
  ```

  We see clearly that they are not identical.

raw_content:
  ---
  id: l29u
  tags:
    - unix
    - time
  created: Sunday, April 28, 2024
  ---

  # Time and computers

  ## Epochs and Unix Time

  An epoch is a period of time identified by a starting point.

  The standard epoch for measuring against is known colloqially as "Unix Time":
  midnight on 1st January 1970. Unix time is the number of seconds that have
  elapsed since this point. This is the standard for Windows and most unix
  systems.

  For example midnight on 2nd January 1970 would be 86400 seconds since the epoch.
  The deduction is as follows:

  ```
  60 * 60 * 24 = 86400
  ```

  We are multiplpying the number of seconds in a minute by the number of minutes
  in an hour by the number of hours in a day.

  To represent times and dates before 1970, we use negative integers.

  ## UTC and time zones

  "Coordinated Universal Time" (UTC) is the time standard against which the
  world's timekeeping is synchronized. It is not itself a timezone, it is a
  transcendent standard that defines what time zones are.

  Time zones are defined relative to UTC, by their offset to this value.

  For example North America is behind UTC by five or six hours hence uses the
  notation UTC-5:00 or UTC-6:00.

  UTC is the successor to GMT of which there were variances necessitating a
  universal value. Despite this GMT is equal to UTC+00:00 so apart from when
  daylight saving is in effect in the UK, GMT = UTC.

  Depending on your locale, relative to UTC, you will get different outputs when
  you attempt to log Unix Time.

  Also daylight savings can impact on the outputs. For instance for half of the
  year, when DST applies, the UK is UTC+01:00.

  ## Examples in Python

  The following gives us the unix seconds in my current time zone (GMT DST):

  ```py
  import time
  print(time.time())
  # 1714322393.2929392
  ```

  If I want UTC, I can do:

  ```py
  import datetime
  print(datetime.datetime.utcnow().strftime('%s'))
  # '1714318952'
  ```

  We see clearly that they are not identical.


path: Tor_prioritising_uptake.md
sortable_path: Tor_prioritising_uptake.md
title: c8820974_Tor_prioritising_uptake
lead:
  When formulating the initial architecture, the Tor engineers were faced with the
  following dilemma.

checksum: 489e3cb853474db12b6e0c16dd1d6d5a0557de4d8dcbc1640e23a4a7afd44cfd
created: 2025-10-01 13:31:02.228675505+00:00
modified: 2025-08-13 15:57:13.744921787+00:00
metadata: {"created":"Wednesday, March 26, 2025","tags":["Tor"]}
body:
  When formulating the initial architecture, the Tor engineers were faced with the
  following dilemma.

  - Greater security meant greater complexity for the user and therefore reduced
    adoption.

  - Less adoption means less security because there are less users on the network
    and thus individual nodes become easier to track and identify.

  Factors that would result in greater security include more cryptographic rigour
  or more hops. Also additional methods of obfuscation such as mixing and padding.

  Both could involve more overhead for the user, which introduces friction and the
  likelihood of continued use. More hops increases latency which makes everyday
  use less viable.

  Broad uptake and a rich ecosystem including users who were not the main or
  target beneficiaries (spies, totalitarian subjects) was necessary in order to
  obscure the activities of the target users.

  ## Source

  Ben Collier, _Tor: From the Dark Web to the Future of Privacy_ (2024)

raw_content:
  ---
  tags: [Tor]
  created: Wednesday, March 26, 2025
  ---

  # c8820974_Tor_prioritising_uptake

  When formulating the initial architecture, the Tor engineers were faced with the
  following dilemma.

  - Greater security meant greater complexity for the user and therefore reduced
    adoption.

  - Less adoption means less security because there are less users on the network
    and thus individual nodes become easier to track and identify.

  Factors that would result in greater security include more cryptographic rigour
  or more hops. Also additional methods of obfuscation such as mixing and padding.

  Both could involve more overhead for the user, which introduces friction and the
  likelihood of continued use. More hops increases latency which makes everyday
  use less viable.

  Broad uptake and a rich ecosystem including users who were not the main or
  target beneficiaries (spies, totalitarian subjects) was necessary in order to
  obscure the activities of the target users.

  ## Source

  Ben Collier, _Tor: From the Dark Web to the Future of Privacy_ (2024)


path: Torrenting.md
sortable_path: Torrenting.md
title: Torrenting
checksum: 5fbc5690a97e5fb717e54d69288e597c1f5003ed492b254c9f864e4b38bd948b
created: 2025-10-01 13:31:02.234546981+00:00
modified: 2025-08-13 15:57:13.803125791+00:00
metadata: {"created":"Saturday, August 03, 2024","tags":["question","networks"]}
lead:
  **What is torrenting? Is this the same thing as a peer-to-peer network? How do
  these technologies fit within the client-server paradigm?**

body:
  **What is torrenting? Is this the same thing as a peer-to-peer network? How do
  these technologies fit within the client-server paradigm?**

  Torrenting is a specific application of
  [peer_to_peer_networking](Peer_to_peer_network.md) applied to file-sharing.

  Each peer on the network both consumes resources and makes resources available
  to others.

  ## Procedure

  Large files are divided into small equal sized pieces and each piece is assigned
  a unique hash identifier. The `.torrent` file details the number of pieces,
  their sizes and hash values.

  Clients request different pieces from different peers simultaneously. As pieces
  are received, they are verified against their hash. Corrupted or incorrect
  pieces are discarded and re-downloaded.

  The client software reassembles the pieces in the correct order and the final
  file will be identical to the original which can be verified by comparing
  hashes.

  ## Key terms

  - _tracker_
    - a server that coordinates communication between peers (so there is a server
      but its role is more lightweight than client-server, coordinating the peers
      rather than hosting or distributing file content)
  - _swarm_
    - a group of peers sharing the same file
  - _seeding_
    - uploading file pieces to other peers
  - _leeching_
    - downloading file pieces from other peers

  > An alternative to a tracker server is to use trackerless methods such as
  > distributed hash tables

  ## Terminological clarification

  - _BitTorrent_ is the communications [protocols](Network_protocols.md) for P2P
    file sharing. It was also a client software developed by the company
    BitTorrent Inc.

  - _torrenting_ is the general terms for that act of sharing files using the
    BitTorrent protocol

  ## Napster

  Napster pre-dated the emergence of the BitTorrent protocol. It did not segment
  files and files were downloaded directly from peers. It was a centralised P2P
  network because there was a central server that indexed all available files
  which kept a database of all connected users and their files.

  When you searched Napster you would be querying this DB on the server. The
  server would facilitate a direct connection between the consumer and provider
  peer for the data exchange.

  When users share with BitTorrent the `.torrent` file is the source of truth and
  a swarm is created around the users who have possess the file.

raw_content:
  ---
  tags: [question, networks]
  created: Saturday, August 03, 2024
  ---

  # Torrenting

  **What is torrenting? Is this the same thing as a peer-to-peer network? How do
  these technologies fit within the client-server paradigm?**

  Torrenting is a specific application of
  [peer_to_peer_networking](Peer_to_peer_network.md) applied to file-sharing.

  Each peer on the network both consumes resources and makes resources available
  to others.

  ## Procedure

  Large files are divided into small equal sized pieces and each piece is assigned
  a unique hash identifier. The `.torrent` file details the number of pieces,
  their sizes and hash values.

  Clients request different pieces from different peers simultaneously. As pieces
  are received, they are verified against their hash. Corrupted or incorrect
  pieces are discarded and re-downloaded.

  The client software reassembles the pieces in the correct order and the final
  file will be identical to the original which can be verified by comparing
  hashes.

  ## Key terms

  - _tracker_
    - a server that coordinates communication between peers (so there is a server
      but its role is more lightweight than client-server, coordinating the peers
      rather than hosting or distributing file content)
  - _swarm_
    - a group of peers sharing the same file
  - _seeding_
    - uploading file pieces to other peers
  - _leeching_
    - downloading file pieces from other peers

  > An alternative to a tracker server is to use trackerless methods such as
  > distributed hash tables

  ## Terminological clarification

  - _BitTorrent_ is the communications [protocols](Network_protocols.md) for P2P
    file sharing. It was also a client software developed by the company
    BitTorrent Inc.

  - _torrenting_ is the general terms for that act of sharing files using the
    BitTorrent protocol

  ## Napster

  Napster pre-dated the emergence of the BitTorrent protocol. It did not segment
  files and files were downloaded directly from peers. It was a centralised P2P
  network because there was a central server that indexed all available files
  which kept a database of all connected users and their files.

  When you searched Napster you would be querying this DB on the server. The
  server would facilitate a direct connection between the consumer and provider
  peer for the data exchange.

  When users share with BitTorrent the `.torrent` file is the source of truth and
  a swarm is created around the users who have possess the file.


path: Transistors.md
sortable_path: Transistors.md
title: Transistors
checksum: 0614b5cebf4f7eb2dc413e31ea7e64a83a356d6362d4970ffc14917642857bb7
created: 2025-10-01 13:31:02.252864106+00:00
modified: 2025-08-13 15:57:13.945484026+00:00
metadata: {"tags":["logic-gates","binary","electronics"]}
lead:
  A digital circuit requires that electrical phenomena be treated as discrete
  rather than continuous values.

body:
  A digital circuit requires that electrical phenomena be treated as discrete
  rather than continuous values.

  Although a given voltage at a point in the circuit can vary widely, in order to
  represent the binary states of 'on' and 'off' we need it to remain fixed within
  certain narrow parameters. This is achieved with transistors.

  Transistors are an electrical component that is capable of controlling the flow
  of current in the manner of a switch where the 'off' and 'on' states are
  represented by [voltage](Voltage.md) values within set parameters.

  There are different types of transistors but the simplest for the purposes of
  explanation are **bipolar junction transistors**.

  ![BJT transistor terminals](../img/bjt-terminals.jpg)

  The pins:

  - C: collector
  - B: base
  - E: emitter

  Applying a small amount of current at the base allows a larger current to flow
  from the collector to the emitter. Applying current to the base is like turning
  the switch on. Removing this current is like turning the switch off.

  This happens because of how current and voltage interact in a transistor. The
  small base current controls the larger collector-emitter current through a
  process called current amplification.

  When a small voltage is applied to the base, it allows a small current to flow
  from the base to the emitter. This base current triggers a much larger current
  to flow from the collector to the emitter. Think of it like a water valve: a
  small turn of the handle (base current) can control a large flow of water
  (collector-emitter current). The transistor amplifies this effect, allowing a
  tiny input signal to control a much larger output.

  ## Transistors and logic gates

  We can combine transistors to create logic gates. A logic gate is a combination
  of transistors arranged such that the logical function is embodied by the
  characteristic input and output voltages to the transistor.

  For example to create an [AND](Logic_gates.md#and-gate) gate we would have two
  voltage inputs going into two transistors that are connected in sequence. The
  two transistors create a continuous line going from the collector of one to the
  emitter of the other. If either voltage input is low then the voltage of the
  combined line is low (equivalent to the circuit being broken) and there is no
  current flowing.

  ![](/img/and-transistor.png)

  // Add example of OR gate created with transistors

raw_content:
  ---
  tags:
    - logic-gates
    - binary
    - electronics
  ---

  # Transistors

  A digital circuit requires that electrical phenomena be treated as discrete
  rather than continuous values.

  Although a given voltage at a point in the circuit can vary widely, in order to
  represent the binary states of 'on' and 'off' we need it to remain fixed within
  certain narrow parameters. This is achieved with transistors.

  Transistors are an electrical component that is capable of controlling the flow
  of current in the manner of a switch where the 'off' and 'on' states are
  represented by [voltage](Voltage.md) values within set parameters.

  There are different types of transistors but the simplest for the purposes of
  explanation are **bipolar junction transistors**.

  ![BJT transistor terminals](../img/bjt-terminals.jpg)

  The pins:

  - C: collector
  - B: base
  - E: emitter

  Applying a small amount of current at the base allows a larger current to flow
  from the collector to the emitter. Applying current to the base is like turning
  the switch on. Removing this current is like turning the switch off.

  This happens because of how current and voltage interact in a transistor. The
  small base current controls the larger collector-emitter current through a
  process called current amplification.

  When a small voltage is applied to the base, it allows a small current to flow
  from the base to the emitter. This base current triggers a much larger current
  to flow from the collector to the emitter. Think of it like a water valve: a
  small turn of the handle (base current) can control a large flow of water
  (collector-emitter current). The transistor amplifies this effect, allowing a
  tiny input signal to control a much larger output.

  ## Transistors and logic gates

  We can combine transistors to create logic gates. A logic gate is a combination
  of transistors arranged such that the logical function is embodied by the
  characteristic input and output voltages to the transistor.

  For example to create an [AND](Logic_gates.md#and-gate) gate we would have two
  voltage inputs going into two transistors that are connected in sequence. The
  two transistors create a continuous line going from the collector of one to the
  emitter of the other. If either voltage input is low then the voltage of the
  combined line is low (equivalent to the circuit being broken) and there is no
  current flowing.

  ![](/img/and-transistor.png)

  // Add example of OR gate created with transistors


path: Transport_Layer_clarification.md
sortable_path: Transport_Layer_clarification.md
title: b01fd836_Transport_Layer_clarification
checksum: 7892a77009c267ad84909fb00fc355bbd66657c0b8573f6ee7ff633487c66751
created: 2025-10-01 13:31:02.274703738+00:00
modified: 2025-08-13 15:57:13.882924388+00:00
metadata: {"created":"Sunday, March 02, 2025","tags":["networks","internet"]}
lead:
  The Transport Layer is a _logical_ representation of the connection between two
  hosts on the Internet. It does not include the entire path (through various
  routers) from source to destination. This detail is assumed and is the concern
  of the Internet Layer - the next layer down.

body:
  The Transport Layer is a _logical_ representation of the connection between two
  hosts on the Internet. It does not include the entire path (through various
  routers) from source to destination. This detail is assumed and is the concern
  of the Internet Layer - the next layer down.

  When applications communicate accross via TCP they "perceive" a simple direct
  connection however this abstracts the fact that the data may:

  - be split into dozens of packets
  - travel through different physical pathways and different routers
  - face congestion, packet loss and network failure.

raw_content:
  ---
  tags: [networks, internet]
  created: Sunday, March 02, 2025
  ---

  # b01fd836_Transport_Layer_clarification

  The Transport Layer is a _logical_ representation of the connection between two
  hosts on the Internet. It does not include the entire path (through various
  routers) from source to destination. This detail is assumed and is the concern
  of the Internet Layer - the next layer down.

  When applications communicate accross via TCP they "perceive" a simple direct
  connection however this abstracts the fact that the data may:

  - be split into dozens of packets
  - travel through different physical pathways and different routers
  - face congestion, packet loss and network failure.


path: Transport_Layer_of_Internet_Protocol.md
sortable_path: Transport_Layer_of_Internet_Protocol.md
title: Transport Layer of the Internet Protocol
checksum: aa99fc3e230cb3bb27d01dcfb0b48ee63a4c9e950e1a2e15f9fd1dd5c65cb09b
created: 2025-10-01 13:31:02.280067424+00:00
modified: 2025-08-13 15:57:13.783626957+00:00
metadata: {"created":"Tuesday, September 03, 2024","tags":["internet","networks","ports"]}
lead:
  Transport Layer protocols provide a communications channel for data to be sent
  between hosts. The Transport Layer is dependent upon the [Internet
  Layer](./Internet_Layer_of_Internet_Protocol.md routing the data and the
  [Link_Layer_of_Internet_Protocol](./Link_Layer_of_Internet_Protocol) for
  transferring the data to a specifichost once it has reached the local network.

body:
  Transport Layer protocols provide a communications channel for data to be sent
  between hosts. The Transport Layer is dependent upon the [Internet
  Layer](./Internet_Layer_of_Internet_Protocol.md routing the data and the
  [Link_Layer_of_Internet_Protocol](./Link_Layer_of_Internet_Protocol) for
  transferring the data to a specifichost once it has reached the local network.

  The protocol governing the Transport Layer is the **Transmission Control
  Protocol** (TCP).

  A TCP **fragment** fits within an IP packet's data section, giving us the
  following iteration of the network stack:

  ![Transport layer fragment](../img/transport-layer-segment.png)

  The segment header contains a destination network port number. The port number
  identifies the specific service or process on the host device which will receive
  the data.

  A useful analogy:

  _The IP address is like the street address of an office building. The port
  number is like the office number of a specific worker within the building. Once
  a letter arrives at the building, the mail processing office (like the OS) is
  not the intended recipient. Instead the mail worker passes on the letter to the
  intended recipient. Likewise, the OS examines the port number and delivers the
  inbound data to the process listening on the specified port._

  ## Ports

  A host with a single IP address can have multiple active ports, each used for
  performing a different type of activity on the network.

  Both servers and clients possess ports. A server will have a dedicated port
  which it listens for requests on (typically port 80 or port 443). A client will
  have an "ephemeral" port where it listens for responses from a server. They are
  ephemeral because they are open only temporarily to receive a response to a
  certain request and are assigned dynamically by the networking components of the
  OS.

  An IP address plus a port (e.g: 192.168.2.1:3000) constitutes an **endpoint**.
  An instance of an endpoint is a **socket**.

  If multiple clients connect to the same endpoint each has its own socket (for
  instance on a host that is a server). A socket can listen for new connections or
  it can represent an established, ongoing connection.

raw_content:
  ---
  tags: [internet, networks, ports]
  created: Tuesday, September 03, 2024
  ---

  # Transport Layer of the Internet Protocol

  Transport Layer protocols provide a communications channel for data to be sent
  between hosts. The Transport Layer is dependent upon the [Internet
  Layer](./Internet_Layer_of_Internet_Protocol.md routing the data and the
  [Link_Layer_of_Internet_Protocol](./Link_Layer_of_Internet_Protocol) for
  transferring the data to a specifichost once it has reached the local network.

  The protocol governing the Transport Layer is the **Transmission Control
  Protocol** (TCP).

  A TCP **fragment** fits within an IP packet's data section, giving us the
  following iteration of the network stack:

  ![Transport layer fragment](../img/transport-layer-segment.png)

  The segment header contains a destination network port number. The port number
  identifies the specific service or process on the host device which will receive
  the data.

  A useful analogy:

  _The IP address is like the street address of an office building. The port
  number is like the office number of a specific worker within the building. Once
  a letter arrives at the building, the mail processing office (like the OS) is
  not the intended recipient. Instead the mail worker passes on the letter to the
  intended recipient. Likewise, the OS examines the port number and delivers the
  inbound data to the process listening on the specified port._

  ## Ports

  A host with a single IP address can have multiple active ports, each used for
  performing a different type of activity on the network.

  Both servers and clients possess ports. A server will have a dedicated port
  which it listens for requests on (typically port 80 or port 443). A client will
  have an "ephemeral" port where it listens for responses from a server. They are
  ephemeral because they are open only temporarily to receive a response to a
  certain request and are assigned dynamically by the networking components of the
  OS.

  An IP address plus a port (e.g: 192.168.2.1:3000) constitutes an **endpoint**.
  An instance of an endpoint is a **socket**.

  If multiple clients connect to the same endpoint each has its own socket (for
  instance on a host that is a server). A socket can listen for new connections or
  it can represent an established, ongoing connection.


path: Trapdoor_functions.md
sortable_path: Trapdoor_functions.md
title: a4601796_trapdoor_functions
checksum: 663700b03beee5d3d47e4d43afabc07b52f25db9a0844c1b033528bed50eced8
created: 2025-10-01 13:31:02.291737224+00:00
modified: 2025-08-13 15:57:14.038198196+00:00
metadata: {"created":"Friday, December 20, 2024","tags":["cryptography","encryption"]}
lead:
  How does [public key cryptography](./bbdcb54f_public_key_cryptography.md)
  achieve the feat of making the encyrpted data only decipherable in one
  direction. In other words how come the same data can be encrypted with one key
  but only decryptable by another?

body:
  How does [public key cryptography](./bbdcb54f_public_key_cryptography.md)
  achieve the feat of making the encyrpted data only decipherable in one
  direction. In other words how come the same data can be encrypted with one key
  but only decryptable by another?

  It is acheived by the use of **trapdoor functions**. These are functions that
  are easy to compute in one direction but extremely difficult to reverse. But
  they also contain trapdoors which, if known, make the functions trivially easy
  to compute in both directions.

  The public key holder can compute the encryption but only the private key holder
  has the trapdoor that makes reversing the encryption trivial.

  As an example we can look at a common method for generating trapdoor functions
  using prime numbers.

  First we need to convert the message into numerical form. One way would be to
  convert the characters to their ASCII numbers. These numbers then become input
  into the mathematical encryption function.

  Next we would pick two very large prime numbers. Using RSA-like encryption they
  would be need to be 2048+ bits in length. For the example we will use 17 and 23.

  For simplicity let's say that our message is one character equivalent to `5` in
  ASCII. We multiply our two primes to get 391 (17 x 23 = 391) and then find the
  remainder of: our message cubed and divided by the prime sum. This is `125`
  (`5^3 / 391`) which is now our encrypted message which we send.

  Unless you know the two primes which were used, it is extremely difficult and
  time consuming to reverse the calculation and find the factors that would get
  you back to the starting point (remember the numbers will be much larger in
  reality). The owner of the private key knows these primes however, so can do
  this.

raw_content:
  ---
  tags: [cryptography, encryption]
  created: Friday, December 20, 2024
  ---

  # a4601796_trapdoor_functions

  How does [public key cryptography](./bbdcb54f_public_key_cryptography.md)
  achieve the feat of making the encyrpted data only decipherable in one
  direction. In other words how come the same data can be encrypted with one key
  but only decryptable by another?

  It is acheived by the use of **trapdoor functions**. These are functions that
  are easy to compute in one direction but extremely difficult to reverse. But
  they also contain trapdoors which, if known, make the functions trivially easy
  to compute in both directions.

  The public key holder can compute the encryption but only the private key holder
  has the trapdoor that makes reversing the encryption trivial.

  As an example we can look at a common method for generating trapdoor functions
  using prime numbers.

  First we need to convert the message into numerical form. One way would be to
  convert the characters to their ASCII numbers. These numbers then become input
  into the mathematical encryption function.

  Next we would pick two very large prime numbers. Using RSA-like encryption they
  would be need to be 2048+ bits in length. For the example we will use 17 and 23.

  For simplicity let's say that our message is one character equivalent to `5` in
  ASCII. We multiply our two primes to get 391 (17 x 23 = 391) and then find the
  remainder of: our message cubed and divided by the prime sum. This is `125`
  (`5^3 / 391`) which is now our encrypted message which we send.

  Unless you know the two primes which were used, it is extremely difficult and
  time consuming to reverse the calculation and find the factors that would get
  you back to the starting point (remember the numbers will be much larger in
  reality). The owner of the private key knows these primes however, so can do
  this.


path: Troubleshooting_MongoDB.md
sortable_path: Troubleshooting_MongoDB.md
title: Troubleshooting MongoDB connection issues
lead: ## Arch Linux troubleshooting
checksum: 764b19f1d36a6c0d05f28bf1f30d52c829fe3030cef31f8e3c8efabf28f1f4c3
created: 2025-10-01 13:31:02.313516942+00:00
modified: 2025-08-13 15:57:13.840911467+00:00
metadata: {"tags":["mongo-db"]}
body:
  ## Arch Linux troubleshooting

  Most times any problems will be a result of a Mongo process that is already
  running. Resolve with:

  ```bash
  # Kill running Mongo process...
  # Identify PID
  sudo lsof -iTCP -sTCP:LISTEN -n -P
  sudo kill [pid]
  ```

  Otherwise try the below.

  ```bash
  # Check that the systemd service is runnign for Mongo
  sudo systemctl status mongodb

  # If not, enable and start service (only need to do this once for machine)
  sudo systemctl enable --now mongodb
  sudo systemctl start --now mongodb

  # If issues with Mongo daemon...
  # Remove the current socket
  rm /tmp/mongodb-27017.sock
  ```

  For additional command line utilities you will also need to install
  `mongodb-tools` from the AUR.

raw_content:
  ---
  tags: [mongo-db]
  ---

  # Troubleshooting MongoDB connection issues

  ## Arch Linux troubleshooting

  Most times any problems will be a result of a Mongo process that is already
  running. Resolve with:

  ```bash
  # Kill running Mongo process...
  # Identify PID
  sudo lsof -iTCP -sTCP:LISTEN -n -P
  sudo kill [pid]
  ```

  Otherwise try the below.

  ```bash
  # Check that the systemd service is runnign for Mongo
  sudo systemctl status mongodb

  # If not, enable and start service (only need to do this once for machine)
  sudo systemctl enable --now mongodb
  sudo systemctl start --now mongodb

  # If issues with Mongo daemon...
  # Remove the current socket
  rm /tmp/mongodb-27017.sock
  ```

  For additional command line utilities you will also need to install
  `mongodb-tools` from the AUR.


path: Truth_functional_connectives.md
sortable_path: Truth_functional_connectives.md
title: Truth-functional connectives
checksum: 3c886282a2241af43af36a2ea37b6afc0518d29737ccc87107855f072226efeb
created: 2025-10-01 13:31:02.321664222+00:00
modified: 2025-08-13 15:57:13.77276657+00:00
metadata: {"tags":["propositional-logic","logic"]}
lead:
  Propositions generated from other (simple) propositions by means of
  propositional connectives are
  [compound propositions](Atomic_and_molecular_propositions.md).

body:
  Propositions generated from other (simple) propositions by means of
  propositional connectives are
  [compound propositions](Atomic_and_molecular_propositions.md).

  We know that
  [logically determinant](Logical_indeterminacy.md)
  propositions express a truth value. When simple propositions are joined with a
  connective to make a compound proposition they also have a truth value. This is
  determined by the nature of the connective and the truth value of the
  constituent propositions. We therefore call connectives of this nature truth
  _functional_ connectives since the **truth value of the compound is a function
  of the truth values of its components**.

  > A propositional connective is used truth-functionally if and only if it is
  > used to generate a compound proposition from one or more propositions in such
  > a way that the truth value of the generated compound is wholly determined by
  > the truth-values of those one or more propositions from which the compound is
  > generated, no matter what the truth values may be.

  Each truth-functional connective has a characteristic **truth table**. This
  discloses the conditions under which the constituent propositions have a given
  truth value when combined with one or more connectives.

  We shall now review each of the truth-functional connectives in detail.

  ### Conjunction

  Conjunction is equivalent to the word AND in natural language. We use $\land$ as
  the symbol for this connective.

  A molecular proposition joining two conjuncts P and Q is true iff both conjuncts
  are true and false otherwise:

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ### Disjunction

  Conjunction is equivalent to the word OR in natural language. We use `v` as the
  symbol of this connective.

  A molecular proposition joining two disjuncts P and Q is true if either disjunct
  is true or if both disjuncts are true and false otherwise. This corresponds to
  the inclusive sense of OR in natural language.

  ```
  P	Q				P	∨	Q
  T	T					T
  T	F					T
  F	T					T
  F	F					F
  ```

  ### Negation

  In contrast to the two previous connectives, negation is a unary connective not
  a binary connective. We use `~` to symbolise negation. It does not join two or
  more propositions, it applies to one proposition as a whole. This can be a
  simple proposition or a complex proposition. It simply negates the truth-value
  of whichever proposition it is applied to. Hence applied to P, it is true if P
  is false. And if P is false, it is true when P is true. !

  ```
  P			~	P
  T			F
  F			T
  ```

  ### Material conditional (a.k.a implication)

  The material conditional approximates the meaning expressed in natural language
  when we say _if_ such-and-such is the case _then_ such-and-such will the case.
  Another way of expressing the sense of the material conditional is to say that
  **P** implies **Q.**

  ```
  If it rains today the pavement will be wet.
  ```

  We call the proposition that expresses the 'if' proposition the **antecedent**
  and the proposition that expresses the 'then' statement the **consequent**. The
  symbol we use to represent the material conditional is `⊃` although you may see
  `→` used as well.

  The truth table is as follows:

  ```
  P	Q				P	⊃	Q
  T	T					T
  T	F					F
  F	T					T
  F	F					T
  ```

  The material conditional is perhaps the least intuitive of the logical
  connectives. The first case (TT) closely matches what we expect the connective
  to mean: it has rained so the pavement is wet. The antecedent is true and
  therefore the consequent is true. This chimes with what we tend to mean by 'if'
  in natural language. In the second case (TF) it also makes sense: the complex
  proposition is false because it rained and the pavement wasn't wet: this negates
  the truth of the expression. The final case (FF) is also straight forward. It
  didn't rain therefore the pavement wasn't wet, thus the overall assertion that
  rain implies wet pavements is retained.

  FT is less intuitive:

  ```
  It did not rain today. The pavement was wet.
  ```

  To some degree one just has to take these statements as axioms, whether or not
  they have intuitive sense is a secondary, more philosophical question. The
  semantic issues arise because we tacitly assume the material conditional to be a
  causal connective: there is something about the nature of **P** that _engenders_
  or _brings about_ **Q** but causality is not a logical concern.

  If we instead just focus on the simple propositions that comprise the truth
  value it is more plausible. In the case of FT we can say it didn't rain yet the
  pavement was wet does not stop the pavement being wet when it rains. The fact
  that I can pour a beer on the pavement thereby making it wet doesn't stop or
  render false the idea that the rain can also make the pavement wet. The same
  explanation covers the FF case: it hasn't rained and so the pavement is not wet
  does not contradict the assertion that when it rains the pavement will be wet.

  Things are elucidated when we look at an equivalent expression of P ⊃ Q, ~P v Q:

  ```
  P	Q				~	P	∨	Q
  T	T						T
  T	F						F
  F	T						T
  F	F						T
  ```

  A disjunction is true whenever either disjunct is true so when both are false
  the overall expression is false, the same as with FT and FF with the material
  conditional.

  ### Material biconditional (a.k.a equivalence)

  The material biconditional equates to the English expression 'if and only if',
  as a conditional connective it therefore avoids some of the perplexity aroused
  by its material cousin. In this scenario both antecedent and consequent have to
  be true for the overall expression to be true. If either is false the complex
  proposition is false. Other ways of expressing the semantics of this connective
  is to say that one proposition implies the other or that **P** and **Q** are
  equivalent.

  ```
  If and only if James studies every day he will pass the exam.
  ```

  There is no possibility in which James passes the exam and has not studied every
  day. If he studies for three out of the seven days leading up to the exam he
  will not pass. Alternatively, there is no possibility that James studied every
  day yet failed the exam. The antecedent and consequent are locked, as indicated
  by the truth-table:

  ```
  P	Q				P	≡	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					T
  ```

  The last condition (FF) maybe requires some explanation: if he has not studied
  every day then he cannot have passed the exam. Therefore, to say that he will
  pass iff he studies every day is rendered true.

  ## Combinations of truth-functional connectives

  ---

  So far we have applied connectives to simple propositions. In so doing we
  generate complex propositions. However propositions and connectives are
  inherently generative: we can build more complex expressions from less complex
  parts, using more than one type of connective or several different connectives
  to make larger complex propositions and express more detailed logical conditions
  ans statements about the world.

  For example the proposition:

  ```
  Socrates was either a philosopher or a drinker but he wasn't a politician.
  ```

  Can be expressed with greater logical clarity as:

  ```
  Socrates was a philosopher or Socrates was a drinker and Socrates was not a politician.
  ```

  Using P for 'Socrates was a philosopher', Q for 'Socrates was a drinker' and R
  for 'Socrates was a politician' we can express this symbolically as:

  ```
  (P v Q) & ~R
  ```

  Which has the truth table:

  ```
  P	Q	R				(	P	∨	Q	)	&	~	R
  T	T	T									F
  T	T	F									T
  T	F	T									F
  T	F	F									T
  F	T	T									F
  F	T	F									T
  F	F	T									F
  F	F	F									F
  ```

  Let's walk through each case where S stands for the overall proposition.

  1. S is false if Socrates was a philosopher, a drinker and a politician.
  1. **S is true if Socrates was a philosopher, a drinker but not a politician.**
  1. S is false if Socrates was a philosopher, a politician but not a drinker.
  1. **S is true if Socrates was a philosopher but not a drinker or politician.**
  1. S is false if Socrates was not a philosopher but was a drinker and politician
  1. **S is true if Socrates was not a philosopher or politician but was a
     drinker.**
  1. S is false if Socrates was neither a philosopher or drinker but was a
     politician.
  1. S is false if Socrates was neither a philosopher, drinker, or politician.

  If we look just at the true cases for simplicity, it becomes obvious that the
  truth value of the whole is a function of the truth-values of the parts.

  At the highest level of generality the proposition is a conjunction with two
  disjuncts: `P v Q` and `~R` . Therefore, for the proposition to be true both
  conjuncts must be true. The first conjunct is true just if one of the
  subordinate disjuncts is true (Socrates is either a philosopher, a drinker, or
  both). The second conjunct is true just if Socrates is not a politician. Thus
  there is only one variation for the second conjunct (not being a politician) and
  two variations for the first conjunct (being a drinker/being a philosopher)
  hence there are three cases where the overall proposition is true.

  ### Logical equivalence

  Once we start working with complex propositions with more than one
  truth-functional connective it becomes clear that the same proposition expressed
  in natural language can be expressed formally more than one way and thus that in
  logical terms, both formal expressions are equivalent. We can prove this
  equivalence by comparing truth tables.

  For example the proposition:

  ```
  I am going to the shops and the gym.
  ```

  Can obviously be expressed formally as:

  ```
  P & Q
  ```

  But also as:

  ```
  ~ (~P v ~Q)
  ```

  And we know this because the truth-tables are identical:consistency

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ```
  P	Q			~	(	~	P	∨	~	Q	)
  T	T			T
  T	F			F
  F	T			F
  F	F			F
  ```

  Another example of equivalent expressions:

  ```
  Neither Watson or Sherlock Holmes is fond of criminals
  ```

  The first formalisation:

  ```
  ~P & ~Q
  ```

  Equivalent to:

  ```
  ~(P v Q)
  ```

  Again the truth-tables for verification:

  ```
  P	Q				~	P	&	~	Q
  T	T						F
  T	F						F
  F	T						F
  F	F						T
  ```

  `~P & ~Q`

  ```
  P	Q			~	(	P	∨	Q	)
  T	T			F
  T	F			F
  F	T			F
  F	F			T
  ```

  ### Important equivalences

  The example above is a key equivalence that you will encounter a lot especially
  when deriving formal proofs. It goes together with another one. We have noted
  them both below for future reference:

  ```
  ~P & ~Q = ~P v ~Q
  ```

  ```
  ~P v ~Q = ~(P & Q)
  ```

  ## Enforcing binary connectives through bracketing

  If we had a proposition of the form

  ```
  Socrates is man, is mortal and a philosopher.
  ```

  We could not write this as:

  ```
  P & Q & R
  ```

  This would not be a well-formed proposition because at most truth functional
  connectives can only connect two simple propositions. It would not be possible
  to generate truth conditions for this proposition in its current form. Instead
  we introduce brackets to enforce a binary grouping of simple propositions. In
  this instance, the placement of the brackets does not affect the accurate
  interpretation of the truth conditions of the compound, so the following two
  formalisations are equivalent:

  ```
  (P & Q) & R

  P & (Q & R)
  ```

raw_content:
  ---
  tags:
    - propositional-logic
    - logic
  ---

  # Truth-functional connectives

  Propositions generated from other (simple) propositions by means of
  propositional connectives are
  [compound propositions](Atomic_and_molecular_propositions.md).

  We know that
  [logically determinant](Logical_indeterminacy.md)
  propositions express a truth value. When simple propositions are joined with a
  connective to make a compound proposition they also have a truth value. This is
  determined by the nature of the connective and the truth value of the
  constituent propositions. We therefore call connectives of this nature truth
  _functional_ connectives since the **truth value of the compound is a function
  of the truth values of its components**.

  > A propositional connective is used truth-functionally if and only if it is
  > used to generate a compound proposition from one or more propositions in such
  > a way that the truth value of the generated compound is wholly determined by
  > the truth-values of those one or more propositions from which the compound is
  > generated, no matter what the truth values may be.

  Each truth-functional connective has a characteristic **truth table**. This
  discloses the conditions under which the constituent propositions have a given
  truth value when combined with one or more connectives.

  We shall now review each of the truth-functional connectives in detail.

  ### Conjunction

  Conjunction is equivalent to the word AND in natural language. We use $\land$ as
  the symbol for this connective.

  A molecular proposition joining two conjuncts P and Q is true iff both conjuncts
  are true and false otherwise:

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ### Disjunction

  Conjunction is equivalent to the word OR in natural language. We use `v` as the
  symbol of this connective.

  A molecular proposition joining two disjuncts P and Q is true if either disjunct
  is true or if both disjuncts are true and false otherwise. This corresponds to
  the inclusive sense of OR in natural language.

  ```
  P	Q				P	∨	Q
  T	T					T
  T	F					T
  F	T					T
  F	F					F
  ```

  ### Negation

  In contrast to the two previous connectives, negation is a unary connective not
  a binary connective. We use `~` to symbolise negation. It does not join two or
  more propositions, it applies to one proposition as a whole. This can be a
  simple proposition or a complex proposition. It simply negates the truth-value
  of whichever proposition it is applied to. Hence applied to P, it is true if P
  is false. And if P is false, it is true when P is true. !

  ```
  P			~	P
  T			F
  F			T
  ```

  ### Material conditional (a.k.a implication)

  The material conditional approximates the meaning expressed in natural language
  when we say _if_ such-and-such is the case _then_ such-and-such will the case.
  Another way of expressing the sense of the material conditional is to say that
  **P** implies **Q.**

  ```
  If it rains today the pavement will be wet.
  ```

  We call the proposition that expresses the 'if' proposition the **antecedent**
  and the proposition that expresses the 'then' statement the **consequent**. The
  symbol we use to represent the material conditional is `⊃` although you may see
  `→` used as well.

  The truth table is as follows:

  ```
  P	Q				P	⊃	Q
  T	T					T
  T	F					F
  F	T					T
  F	F					T
  ```

  The material conditional is perhaps the least intuitive of the logical
  connectives. The first case (TT) closely matches what we expect the connective
  to mean: it has rained so the pavement is wet. The antecedent is true and
  therefore the consequent is true. This chimes with what we tend to mean by 'if'
  in natural language. In the second case (TF) it also makes sense: the complex
  proposition is false because it rained and the pavement wasn't wet: this negates
  the truth of the expression. The final case (FF) is also straight forward. It
  didn't rain therefore the pavement wasn't wet, thus the overall assertion that
  rain implies wet pavements is retained.

  FT is less intuitive:

  ```
  It did not rain today. The pavement was wet.
  ```

  To some degree one just has to take these statements as axioms, whether or not
  they have intuitive sense is a secondary, more philosophical question. The
  semantic issues arise because we tacitly assume the material conditional to be a
  causal connective: there is something about the nature of **P** that _engenders_
  or _brings about_ **Q** but causality is not a logical concern.

  If we instead just focus on the simple propositions that comprise the truth
  value it is more plausible. In the case of FT we can say it didn't rain yet the
  pavement was wet does not stop the pavement being wet when it rains. The fact
  that I can pour a beer on the pavement thereby making it wet doesn't stop or
  render false the idea that the rain can also make the pavement wet. The same
  explanation covers the FF case: it hasn't rained and so the pavement is not wet
  does not contradict the assertion that when it rains the pavement will be wet.

  Things are elucidated when we look at an equivalent expression of P ⊃ Q, ~P v Q:

  ```
  P	Q				~	P	∨	Q
  T	T						T
  T	F						F
  F	T						T
  F	F						T
  ```

  A disjunction is true whenever either disjunct is true so when both are false
  the overall expression is false, the same as with FT and FF with the material
  conditional.

  ### Material biconditional (a.k.a equivalence)

  The material biconditional equates to the English expression 'if and only if',
  as a conditional connective it therefore avoids some of the perplexity aroused
  by its material cousin. In this scenario both antecedent and consequent have to
  be true for the overall expression to be true. If either is false the complex
  proposition is false. Other ways of expressing the semantics of this connective
  is to say that one proposition implies the other or that **P** and **Q** are
  equivalent.

  ```
  If and only if James studies every day he will pass the exam.
  ```

  There is no possibility in which James passes the exam and has not studied every
  day. If he studies for three out of the seven days leading up to the exam he
  will not pass. Alternatively, there is no possibility that James studied every
  day yet failed the exam. The antecedent and consequent are locked, as indicated
  by the truth-table:

  ```
  P	Q				P	≡	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					T
  ```

  The last condition (FF) maybe requires some explanation: if he has not studied
  every day then he cannot have passed the exam. Therefore, to say that he will
  pass iff he studies every day is rendered true.

  ## Combinations of truth-functional connectives

  ---

  So far we have applied connectives to simple propositions. In so doing we
  generate complex propositions. However propositions and connectives are
  inherently generative: we can build more complex expressions from less complex
  parts, using more than one type of connective or several different connectives
  to make larger complex propositions and express more detailed logical conditions
  ans statements about the world.

  For example the proposition:

  ```
  Socrates was either a philosopher or a drinker but he wasn't a politician.
  ```

  Can be expressed with greater logical clarity as:

  ```
  Socrates was a philosopher or Socrates was a drinker and Socrates was not a politician.
  ```

  Using P for 'Socrates was a philosopher', Q for 'Socrates was a drinker' and R
  for 'Socrates was a politician' we can express this symbolically as:

  ```
  (P v Q) & ~R
  ```

  Which has the truth table:

  ```
  P	Q	R				(	P	∨	Q	)	&	~	R
  T	T	T									F
  T	T	F									T
  T	F	T									F
  T	F	F									T
  F	T	T									F
  F	T	F									T
  F	F	T									F
  F	F	F									F
  ```

  Let's walk through each case where S stands for the overall proposition.

  1. S is false if Socrates was a philosopher, a drinker and a politician.
  1. **S is true if Socrates was a philosopher, a drinker but not a politician.**
  1. S is false if Socrates was a philosopher, a politician but not a drinker.
  1. **S is true if Socrates was a philosopher but not a drinker or politician.**
  1. S is false if Socrates was not a philosopher but was a drinker and politician
  1. **S is true if Socrates was not a philosopher or politician but was a
     drinker.**
  1. S is false if Socrates was neither a philosopher or drinker but was a
     politician.
  1. S is false if Socrates was neither a philosopher, drinker, or politician.

  If we look just at the true cases for simplicity, it becomes obvious that the
  truth value of the whole is a function of the truth-values of the parts.

  At the highest level of generality the proposition is a conjunction with two
  disjuncts: `P v Q` and `~R` . Therefore, for the proposition to be true both
  conjuncts must be true. The first conjunct is true just if one of the
  subordinate disjuncts is true (Socrates is either a philosopher, a drinker, or
  both). The second conjunct is true just if Socrates is not a politician. Thus
  there is only one variation for the second conjunct (not being a politician) and
  two variations for the first conjunct (being a drinker/being a philosopher)
  hence there are three cases where the overall proposition is true.

  ### Logical equivalence

  Once we start working with complex propositions with more than one
  truth-functional connective it becomes clear that the same proposition expressed
  in natural language can be expressed formally more than one way and thus that in
  logical terms, both formal expressions are equivalent. We can prove this
  equivalence by comparing truth tables.

  For example the proposition:

  ```
  I am going to the shops and the gym.
  ```

  Can obviously be expressed formally as:

  ```
  P & Q
  ```

  But also as:

  ```
  ~ (~P v ~Q)
  ```

  And we know this because the truth-tables are identical:consistency

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  ```
  P	Q			~	(	~	P	∨	~	Q	)
  T	T			T
  T	F			F
  F	T			F
  F	F			F
  ```

  Another example of equivalent expressions:

  ```
  Neither Watson or Sherlock Holmes is fond of criminals
  ```

  The first formalisation:

  ```
  ~P & ~Q
  ```

  Equivalent to:

  ```
  ~(P v Q)
  ```

  Again the truth-tables for verification:

  ```
  P	Q				~	P	&	~	Q
  T	T						F
  T	F						F
  F	T						F
  F	F						T
  ```

  `~P & ~Q`

  ```
  P	Q			~	(	P	∨	Q	)
  T	T			F
  T	F			F
  F	T			F
  F	F			T
  ```

  ### Important equivalences

  The example above is a key equivalence that you will encounter a lot especially
  when deriving formal proofs. It goes together with another one. We have noted
  them both below for future reference:

  ```
  ~P & ~Q = ~P v ~Q
  ```

  ```
  ~P v ~Q = ~(P & Q)
  ```

  ## Enforcing binary connectives through bracketing

  If we had a proposition of the form

  ```
  Socrates is man, is mortal and a philosopher.
  ```

  We could not write this as:

  ```
  P & Q & R
  ```

  This would not be a well-formed proposition because at most truth functional
  connectives can only connect two simple propositions. It would not be possible
  to generate truth conditions for this proposition in its current form. Instead
  we introduce brackets to enforce a binary grouping of simple propositions. In
  this instance, the placement of the brackets does not affect the accurate
  interpretation of the truth conditions of the compound, so the following two
  formalisations are equivalent:

  ```
  (P & Q) & R

  P & (Q & R)
  ```


path: Truth_tables.md
sortable_path: Truth_tables.md
title: Truth-tables
checksum: b6e6d1948359ad35aa044e4981d8103136f4294c4447567608a2b2a15e38db4d
created: 2025-10-01 13:31:02.342869021+00:00
modified: 2025-08-13 15:57:13.769140855+00:00
metadata: {"tags":["logic","propositional-logic"]}
lead:
  We are already familiar with truth-tables from the previous entry on the
  _truth-functional connectives_ and the relationship between sentences,
  connectives and the overall truth-value of a sentence. Here we will look in
  further depth at how to build truth-tables and on their mathematical relation to
  binary truth-values. We will also look at examples of complex truth-tables for
  large compound expressions and the systematic steps we follow to derive the
  truth conditions of compound sentences from their simple constituents.

body:
  We are already familiar with truth-tables from the previous entry on the
  _truth-functional connectives_ and the relationship between sentences,
  connectives and the overall truth-value of a sentence. Here we will look in
  further depth at how to build truth-tables and on their mathematical relation to
  binary truth-values. We will also look at examples of complex truth-tables for
  large compound expressions and the systematic steps we follow to derive the
  truth conditions of compound sentences from their simple constituents.

  ## Formulae for constructing truth-tables

  For any truth-table, the number of rows it will contain is equal to $2n$ where:

  - $n$ stands for the number of sentences
  - $2$ is the total number of possible truth values that the sentence may have:
    true or false.

  When we count the number of sentences, we mean atomic sentences. And we only
  count each sentence once. Hence for a compound sentence of the form
  $(\sim B \supset C) & (A \equiv B)$, $B$ occurs twice but there are only three
  sentences: $A$, $B$, and $C$.

  Thus for the sentence $P & Q$ ,we have two sentences so $n$ is 2 which equals 4
  rows (2 x 2):

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  For the sentence $(P \lor Q) & R$ we have three sentences so $n$ is 3 which
  equals 8 rows (2 x 2 x 2):

  ```
  P	Q	R				(	P	∨	Q	)	&	R
  T	T	T									T
  T	T	F									F
  T	F	T									T
  T	F	F									F
  F	T	T									T
  F	T	F									F
  F	F	T									F
  F	F	F									F
  ```

  For the single sentence $P$ we have one sentence so $n$ is 1 which equals 2 rows
  (2 x 1):

  ```
  P			P
  T			T
  F			F
  ```

  This tells us how many rows the truth-table should have but it doesn't tell us
  what each row should consist in. In other words: how many Ts and Fs it should
  contain. This is fine with simple truth-tables since we can just alternate each
  value but for truth-tables with three sentences and more it is easy to make
  mistakes.

  To simplify this and ensure that we are including the right number of possible
  truth-values we can extend the formula to $2n^-i$. This formula tells us how
  many groups of T and F we should have in each column.

  We can already see that there is a pattern at work by looking at the columns of
  the truth tables above. If we take the sentence $(P \lor Q) & R$ we can see that
  for each sentence:

  - $P$ consists in two sets of ${\textsf{T,T,T,T}}$ and ${\textsf{F,F,F,F}}$ with
    **four** elements per set
  - $Q$ consists in four sets of ${\textsf{T,T}}$ , ${\textsf{F,F}}$,
    ${\textsf{T,T}}$ , ${\textsf{F,F}}$ with **two** elements per set
  - $R$ consists in eight sets of ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$,
    ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$
    with **one** element per set.

  If we work through the formula we see that it returns 4, 2, 1:

  $$\begin{equation} \begin{split} 2n^-1 = 3 -1 \\ = 2 \\ = 2 \cdot 2 \\ = 4 \end{split} \end{equation}$$

  $$
  \\begin{equation} \begin{split} 2n^-2 = 3 - 2 \\ = 1 \\ = 2 \cdot 1 \\ = 2 \end{split} \end{equation}
  $$

  $$
  \\begin{equation} \begin{split} 2n^-3 = 3 - 3 \\ = 0 \\ = 2 \cdot 0 \\ = 1 \end{split} \end{equation}
  $$

  ## Truth-table concepts

  ### Recursion

  When we move to complex truth-tables with more than one connective we realise
  that truth-tables are recursive. The truth-tables for the truth-functional
  connectives provide all that we need to determine the truth-values of complex
  sentences:

  > The core truth-tables tell us how to determine the truth-value of a molecular
  > sentence given the truth-values of its
  > [immediate sentential components](Syntax%20of%20sentential%20logic.md). And if
  > the immediate sentential components of a molecular sentence are also
  > molecular, we can use the information in the characteristic truth-tables to
  > determine how the truth-value of each immediate component depends n the
  > truth-values of _its_ components and so on.

  ### Truth-value assignment

  > A truth-value assignment is an assignment of truth-values (either T or F) to
  > the atomic sentences of SL.

  When working on complex truth tables, we use the truth-assignment of atomic
  sentences to count as the values that we feed into the larger expressions at a
  higher level of the sentential abstraction.

  ### Partial assignment

  We talk about partial assignments of truth-values when we look at one specific
  row of the truth-table, independently of the others. The total set of partial
  assignments comprise all possible truth assignments for the given sentence.

  ## Working through complex truth-tables

  The truth-table below shows all truth-value assignments for the sentence
  $(\sim B \supset C) & (A \equiv B)$ :

  ```
  A	B	C				(	~	B	⊃	C	)	&	(	A	≡	B	)
  T	T	T					F	T	T	T		T		T	T	T
  T	T	F					F	T	T	F		T		T	T	T
  T	F	T					T	F	T	T		F		T	F	F
  T	F	F					T	F	F	F		F		T	F	F
  F	T	T					F	T	T	T		F		F	F	T
  F	T	F					F	T	T	F		F		F	F	T
  F	F	T					T	F	T	T		T		F	T	F
  F	F	F					T	F	F	F		F		F	T	F
  ```

  As with algebra we work outwards from each set of brackets. The sequence for
  manually arriving at the above table would be roughly as follows:

  1. For each sentence letter, copy the truth value for it in each row.
  1. Identify the connectives in the atomic sentences and the main overall
     sentence.
  1. Work out the truth-values for the smallest connectives and sub-compound
     sentences. The first should always be negation and then the other atomic
     connectives.
  1. Feed-in the truth-values of the atomic sentences as values into the main
     connective, through a process of elimination you then reach the core
     truth-assignments:

raw_content:
  ---
  tags:
    - logic
    - propositional-logic
  ---

  # Truth-tables

  We are already familiar with truth-tables from the previous entry on the
  _truth-functional connectives_ and the relationship between sentences,
  connectives and the overall truth-value of a sentence. Here we will look in
  further depth at how to build truth-tables and on their mathematical relation to
  binary truth-values. We will also look at examples of complex truth-tables for
  large compound expressions and the systematic steps we follow to derive the
  truth conditions of compound sentences from their simple constituents.

  ## Formulae for constructing truth-tables

  For any truth-table, the number of rows it will contain is equal to $2n$ where:

  - $n$ stands for the number of sentences
  - $2$ is the total number of possible truth values that the sentence may have:
    true or false.

  When we count the number of sentences, we mean atomic sentences. And we only
  count each sentence once. Hence for a compound sentence of the form
  $(\sim B \supset C) & (A \equiv B)$, $B$ occurs twice but there are only three
  sentences: $A$, $B$, and $C$.

  Thus for the sentence $P & Q$ ,we have two sentences so $n$ is 2 which equals 4
  rows (2 x 2):

  ```
  P	Q				P	&	Q
  T	T					T
  T	F					F
  F	T					F
  F	F					F
  ```

  For the sentence $(P \lor Q) & R$ we have three sentences so $n$ is 3 which
  equals 8 rows (2 x 2 x 2):

  ```
  P	Q	R				(	P	∨	Q	)	&	R
  T	T	T									T
  T	T	F									F
  T	F	T									T
  T	F	F									F
  F	T	T									T
  F	T	F									F
  F	F	T									F
  F	F	F									F
  ```

  For the single sentence $P$ we have one sentence so $n$ is 1 which equals 2 rows
  (2 x 1):

  ```
  P			P
  T			T
  F			F
  ```

  This tells us how many rows the truth-table should have but it doesn't tell us
  what each row should consist in. In other words: how many Ts and Fs it should
  contain. This is fine with simple truth-tables since we can just alternate each
  value but for truth-tables with three sentences and more it is easy to make
  mistakes.

  To simplify this and ensure that we are including the right number of possible
  truth-values we can extend the formula to $2n^-i$. This formula tells us how
  many groups of T and F we should have in each column.

  We can already see that there is a pattern at work by looking at the columns of
  the truth tables above. If we take the sentence $(P \lor Q) & R$ we can see that
  for each sentence:

  - $P$ consists in two sets of ${\textsf{T,T,T,T}}$ and ${\textsf{F,F,F,F}}$ with
    **four** elements per set
  - $Q$ consists in four sets of ${\textsf{T,T}}$ , ${\textsf{F,F}}$,
    ${\textsf{T,T}}$ , ${\textsf{F,F}}$ with **two** elements per set
  - $R$ consists in eight sets of ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$,
    ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$, ${\textsf{T}}$, ${\textsf{F}}$
    with **one** element per set.

  If we work through the formula we see that it returns 4, 2, 1:

  $$\begin{equation} \begin{split} 2n^-1 = 3 -1 \\ = 2 \\ = 2 \cdot 2 \\ = 4 \end{split} \end{equation}$$

  $$
  \\begin{equation} \begin{split} 2n^-2 = 3 - 2 \\ = 1 \\ = 2 \cdot 1 \\ = 2 \end{split} \end{equation}
  $$

  $$
  \\begin{equation} \begin{split} 2n^-3 = 3 - 3 \\ = 0 \\ = 2 \cdot 0 \\ = 1 \end{split} \end{equation}
  $$

  ## Truth-table concepts

  ### Recursion

  When we move to complex truth-tables with more than one connective we realise
  that truth-tables are recursive. The truth-tables for the truth-functional
  connectives provide all that we need to determine the truth-values of complex
  sentences:

  > The core truth-tables tell us how to determine the truth-value of a molecular
  > sentence given the truth-values of its
  > [immediate sentential components](Syntax%20of%20sentential%20logic.md). And if
  > the immediate sentential components of a molecular sentence are also
  > molecular, we can use the information in the characteristic truth-tables to
  > determine how the truth-value of each immediate component depends n the
  > truth-values of _its_ components and so on.

  ### Truth-value assignment

  > A truth-value assignment is an assignment of truth-values (either T or F) to
  > the atomic sentences of SL.

  When working on complex truth tables, we use the truth-assignment of atomic
  sentences to count as the values that we feed into the larger expressions at a
  higher level of the sentential abstraction.

  ### Partial assignment

  We talk about partial assignments of truth-values when we look at one specific
  row of the truth-table, independently of the others. The total set of partial
  assignments comprise all possible truth assignments for the given sentence.

  ## Working through complex truth-tables

  The truth-table below shows all truth-value assignments for the sentence
  $(\sim B \supset C) & (A \equiv B)$ :

  ```
  A	B	C				(	~	B	⊃	C	)	&	(	A	≡	B	)
  T	T	T					F	T	T	T		T		T	T	T
  T	T	F					F	T	T	F		T		T	T	T
  T	F	T					T	F	T	T		F		T	F	F
  T	F	F					T	F	F	F		F		T	F	F
  F	T	T					F	T	T	T		F		F	F	T
  F	T	F					F	T	T	F		F		F	F	T
  F	F	T					T	F	T	T		T		F	T	F
  F	F	F					T	F	F	F		F		F	T	F
  ```

  As with algebra we work outwards from each set of brackets. The sequence for
  manually arriving at the above table would be roughly as follows:

  1. For each sentence letter, copy the truth value for it in each row.
  1. Identify the connectives in the atomic sentences and the main overall
     sentence.
  1. Work out the truth-values for the smallest connectives and sub-compound
     sentences. The first should always be negation and then the other atomic
     connectives.
  1. Feed-in the truth-values of the atomic sentences as values into the main
     connective, through a process of elimination you then reach the core
     truth-assignments:


path: Truth_trees.md
sortable_path: Truth_trees.md
title: Truth trees
lead: ## Rationale
checksum: 345d5759680369e156c87dc4229f912c1cd9ae3319d8d3e509a792ac372c1366
created: 2025-10-01 13:31:02.363836813+00:00
modified: 2025-08-13 15:57:14.021640056+00:00
metadata: {"tags":["logic","propositional-logic"]}
body:
  ## Rationale

  Like [truth-tables](Truth-tables.md), truth-trees are a means of graphically
  representing the logical relationships that may obtain between propositions.
  Truth-trees and truth-tables complement each other and which method you choose
  depends on which logical property you are seeking to derive.

  Whilst truth-tables have the benefit of being exhaustive - every possible truth
  assignment is factored into the representation - their complexity grows
  exponentially with each additional proposition they contain. This can make
  manually constructing truth tables long-winded and prone to mistakes.

  Truth-trees are less onerous but they lack the exhaustive scope of a
  truth-table. They are more targeted and are best used for demonstrating _that
  something is the case_ rather than _all the possible states that could be the
  case_. For example, a truth tree will tell us that a set _S is logically
  consistent_ whereas a truth-table will tell us that _S is consistent on the
  following three assignments._

  ## Logical consistency

  Recall that a set of propositions is logically or truth-functionally
  [consistent](Consistency.md) just if there is at least one assignment of truth
  conditions which results in all members of the set being true. To identify
  consistency for a set of three propositions via the truth table approach we
  would need to construct a truth table with $2^3$ (8) rows. Assume that this set
  is consistent on one partial assignment only. This means that 87.5% of our rows
  are redundant, they are not required to prove the consistency of the set.
  However we can only know this and we can only be sure of consistency once we
  have gone through the process of generating an assignment for each row.

  Truth trees allow us to reduce the amount of work required and go straight to
  the assignment that proves consistency, disregarding the rest which are
  irrelevant.

  ## Truth tree structure and key terms

  **When using a truth tree to derive logical consistency, the goal is to
  determine whether there is a truth-value assignment on which all of the
  sentences of a set are true. If the set is consistent we should be able to
  derive a partial assignment from the tree that demonstrates consistency.**

  Each truth tree begins with a series of sentences one on top of the other in a
  column. We call the sentences that comprise the initial column **set members**.
  In constructing the tree, we work downwards from the initial column decomposing
  set members into their atomic constituents. We a call an atomic sentence that
  has been decomposed a **literal.** A literal will either be an atomic sentence
  or the negation of an atomic sentence. If one of the set members is already a
  literal, there is no need to decompose it; it can remain as it is.

  Once every set member has been decomposed the truth tree is complete. It can
  then be interpreted in order to derive logical consistency or inconsistency. If
  the set is consistent, we are able to derive the partial assignment(s) that
  demonstrate consistency.

  The rules for decomposing compound sentences match the truth conditions of the
  logical connectives. There are rules for every possible connective and the
  negation of every possible connective however in terms of their tree shape they
  all correspond to either a conjunction or a disjunction. Disjunctive
  decomposition results in new branches being formed off the main column (or
  trunk). Conjunctive decomposition is non-branching which means the decomposed
  constituents are placed within the trunk of whichever tree or branch they are
  decomposed within.

  As we construct the tree we list each line in the left-hand margin and the
  decomposition rule in the right-hand margin. When we apply a decomposition rule
  we must cite the lines to which it applies.

  ### Closed and open branches

  Any branch on which an atomic sentence ($P$) and the negation of that sentence
  ($\sim P$) both occur is a **closed branch**. A branch that is not closed is an
  **open branch**. No partial assignment is recoverable from a closed branch. An
  open branch allows truth to ‘flow up’ to the original set members whereas a
  closed branch blocks this passage.

  ### Completed open branch

  A completed open branch occurs when we have an open branch that has been fully
  decomposed: the branch is open and all molecular sentences have been ticked off
  such that it contains only literals.

  ### Completed tree

  A tree where all its branches are either completed open branches or closed
  branches.

  ### Closed tree

  A tree where all the branches are closed

  ### Open tree

  A tree with at least one completed open branch

  ## Deriving consistency

  Using the definitions above, we can now define truth-functional consistency and
  inconsistency in terms of truth trees:

  > A finite set ($\Gamma$ ) of sentences is truth-functionally inconsistent if
  > $\Gamma$ is a closed tree

  > A finite set ($\Gamma$ ) of sentences is truth-functionally consistent if
  > $\Gamma$ is an open tree

  ## Examples

  ### First example

  The following is a truth tree for the set ${P \lor Q, \sim P }$:

  ![basic-open-tree 1.svg](basic-open-tree%201.svg)

  ### Interpretation

  - We decompose the disjunction at line 1 on line 3. We tick off the compound
    sentence to indicate that it is now decomposed and no longer under
    consideration.
  - Both P and its negation exist on a single branch (at line 2 and line 3). This
    makes it a closed branch. We indicate this by the X beneath the branch that is
    closed, citing the source of the closure by line number.
  - The rightward branch is a completed open branch given the decomposition at 3
    and the lack of negation of Q. Overall this makes the tree an open tree.

  As the set gives us an open tree, it must be truth-functionally consistent. If
  this is the case we should be able to determine the partial assignment in which
  each set member is true. Given that Q is not negated the assignment of
  consistency will contain Q but we have both P and ~P. This means there are two
  possible assignments where the set is consistent: $P, Q$ and $\sim P, Q$. This
  is confirmed by the truth-table:

  ```
  P	Q				P	∨	~	P				Q
  T	T					T						T     *
  T	F					T						F
  F	T					T						T     *
  F	F					T						F
  ```

  **Any time there is an open tree with a closed branch it will be the case that
  the negated sentences of the closed branch will appear both as** $S$ and
  $\sim S$ i**n the resultant assignment.**

  Invoking the truth-table highlights the differences between the two techniques.
  The values that are derived when we interpret a truth tree are not the
  truth-functions of the set members but the truth-values for when they are
  simultaneously true. With truth-tables in contrast, we are deriving the truth
  functions for every possible truth-value assignment. In other words the values
  derived from a truth tree correspond to the left hand side of the truth table
  not the right hand side.

  ### Second example

  The following is a truth tree for the set
  ${A & \sim B, C, \sim A \lor \sim B }$.

  ![basic-closed-tree 1.svg](basic-closed-tree%201.svg)

  ### Interpretation

  - The two molecular set members are decomposed. The disjunction (line 3) results
    in a branching tree. The conjunction (line 1) results in the continuation of
    the trunk.
  - Both branches are completed making it a completed tree. As each branch is
    closed this is a closed tree.

  As this is a closed tree, the set is not truth-functionally consistent. This is
  confirmed by the truth table where there is no partial assignment where all set
  members are true.

  ```
  A	B	C				A	&	~	B				C				~	A	∨	~	C
  T	T	T					F						T						F
  T	T	F					F						F						T
  T	F	T					T						T						F
  T	F	F					T						F						T
  F	T	T					F						T						T
  F	T	F					F						F						T
  F	F	T					F						T						T
  F	F	F					F						F						T
  ```

  ## Truth tree decomposition rules

  ---

  So far we have encountered the decomposition rules for conjunction (`&D`) and
  disjunction (`vD`). We will now list all the rules. We will see that for each
  rule, the decomposition either branches or does not branch which is to say that
  each rule either has the shape of a conjunction or a disjunction (however the
  permitted values of the specific disjuncts/conjuncts obviously differ in each
  case). Moreover there is a parallel rule for the decomposition of the negation
  of each of the main connectives and these rules rely on logical equivalences

  ### Negated negation decomposition: `~~D`

  ![negated-negation-decomposition-rule 2.svg](negated-negation-decomposition-rule%202.svg)

  Truth passes only if $P$ is true

  ### Conjunction decomposition: `&D`

  ![conjunction-decomposition-rule.svg](conjunction-decomposition-rule.svg)

  Truth passes only $P$ and $Q$ are both true.

  ### Negated Conjunction decomposition: `~&D`

  ![negated-conjunction-decomposition-rule.svg](negated-conjunction-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $\sim Q$ is true. This rule is a consequence
  of the equivalence between $\sim (P & Q)$ and $\sim P \lor \sim Q$ , the first
  of DeMorgan’s Laws.

  ### Disjunction decomposition: `vD`

  ![disjunction-decomposition-rule.svg](disjunction-decomposition-rule.svg)

  Truth passes if either $P$or $Q$ are true.

  ### Negated Disjunction decomposition: `~vD`

  ![negated-disjunction-decomposition-rule.svg](negated-disjunction-decomposition-rule.svg)

  Truth passes if both $P$ and $Q$ are false. This rule is a consequence of the
  equivalence between $\sim (P \lor Q)$ and $\sim P & \sim Q$, the second of
  DeMorgan’s Laws.

  ### Conditional decomposition: `⊃D`

  ![conditional-decomposition-rule.svg](conditional-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $Q$ are true. This rule is a consequence of
  the equivalence between $P \supset Q$ and $\sim P \lor Q$ therefore this branch
  has the shape of a disjunction with $\sim P$ , $Q$ as its disjuncts.

  ### Negated Conditional decomposition: `~⊃D`

  Truth passes if both $P$ and $\sim Q$ are true. This is a consequence of the
  equivalence between $\sim (P \supset Q)$ and $P & \sim Q$.

  ![negated-conditional-decomposition-rule.svg](negated-conditional-decomposition-rule.svg)

  ### Biconditional decomposition: `≡D`

  ![biconditional-decomposition-rule.drawio(1).svg](biconditional-decomposition-rule.drawio%281%29.svg)

  Truth passes if either $P$ and $Q$ are true or $\sim P & \sim Q$ are true. This
  is an interesting rule because it combines the disjunction and conjunction tree
  shapes.

  ### Negated biconditional decomposition: `~≡D`

  ![negated-biconditional-decomposition-rule.drawio.svg](negated-biconditional-decomposition-rule.drawio.svg)

  Truth passes if either $P$ and $\sim Q$ is true or if $\sim P$ and $Q$ is true.

  ## Further examples and heuristics for complex truth trees

  With truth-trees regardless of which order you decompose the set members, the
  conclusion should always be the same. This said, there more are more efficient
  ways than others to construct the trees. You want to find the route that will
  demonstrate consistency or non-consistency with the shortest amount of work. The
  following heuristic techniques followed in order, facilitate this:

  1. Decompose those molecular sentences the decomposition of which does not
     produce new branches. In other words that are decompositions of double
     negations or pure conjunctions.
  1. Perform those decompositions that will rapidly generate closed branches.
  1. If neither (1) or (2) is applicable, decompose **the most complex** sentence
     first.

  Here are some examples of these rules applied:

  ![complex-tree.svg](complex-tree.svg)

  Observe that here we don’t bother to decompose the sentence on line 1. This is
  because, having decomposed the sentences on lines 2 and 3 we have arrived at a
  closed tree. It is therefore unnecessary to go any further for if two sentences
  in the set are inconsistent with each other, adding another sentence is not
  going to change the overall assignment of inconsistency.

  ## Deriving properties other than logical consistency from truth trees

  So far truth trees have been discussed purely in terms of logical consistency
  however they can be used to derive all the other key truth-functional properties
  of propositional logic. Given the foundational role of consistency to logic,
  these properties are expressible in terms of consistency which is what makes
  them amenable to formulation in terms of truth trees.

  ### Logical falsity

  For a given finite set $\Gamma$, $\Gamma$ is logically consistent just if all of
  its members can be true at once. Expressed in terms of truth trees, this is
  equivalent to an open tree. Contrariwise, $\Gamma$ is inconsistent if it is not
  possible for every member of the set to be true at once. This is the same as a
  tree where all of the branches are closed (i.e. a closed tree).

  When we wish to assess
  [logical falsity](Logical%20truth%20and%20falsity.md#logical-falsity) we are not
  focused on sets however, we are interested in a property of a sentence. However
  we can easily construe single sentences as unit sets: sets with a single member.
  With this in mind and the above accounts of consistency and logical falsity we
  are equipped to express logical falsity in terms of truth-trees with the
  following rule:

  > A sentence $P$ is logically false if and only if the unit set ${ P }$ has a
  > closed tree

  A logically false sentence cannot be true on any assignment. This is the same
  thing as an inconsistent set. Thus it will be represented in a truth tree as
  inconsistency which is disclosed via a closed tree.

  ![logical-falsity-tree.svg](logical-falsity-tree.svg)

  ### Logical truth

  For a sentence $P$ to be
  [logically true](Logical%20truth%20and%20falsity.md#logical-truth), there must
  be no possible assignment in which $P$ is false. We express this informally by
  saying _it is not possible to consistently deny $P$._ We know that in terms of
  truth trees an inconsistent set is a closed tree therefore a unit set of ${ P }$
  is logically true if ${ \sim P }$ is a closed tree. This is to say: if the
  negation of $P$ is inconsistent.

  > A sentence $P$ is logically true if and only if the set ${ \sim P }$ has a
  > closed tree

  ### Logical indeterminacy

  [Indeterminacy](Indeterminacy.md) follows from the two definitions above; we do
  not require any additional apparatus. We recall that a sentence $P$ is logically
  indeterminate just if it is neither logically true or logically false. Thus the
  truth tree for an indeterminate sentence is straightforward:

  > A sentence $P$ is logically indeterminate if and only if neither the set
  >  ${ P }$ nor the set ${ \sim P }$ has a closed tree

  This follows because a closed tree for  ${ P }$ means it is not logically false
  and an open tree for ${ \sim P }$ means it is not logically true. So if it is
  neither of these things, $P$ must be indeterminate.

  ### Logical equivalence

  Recall that $P$ and $Q$ are [logically equivalent](Logical%20equivalence.md)
  just if there is no truth assignment on which one is true and the other is
  false. We know from the
  [material biconditional shorthand](Corresponding%20material%20and%20biconditional.md#corresponding-material-biconditional)
  that this state of affairs can be expressed as $P \equiv Q$ and that if this
  compound sentence is true on every assignment then both simple sentences are
  equivalent. But ‘true on every assignment’ is another way of saying _logically
  true_ since there is no possibility of a false assignment. We already know what
  logical truth looks like as a truth tree: it is a closed tree for the negation
  of the sentence being tested. Therefore, to test the logical equivalence of two
  sentences it is necessary to construct a truth tree for the negation of the
  sentences conjoined by the biconditional (i.e. $\sim (P \equiv Q)$ )and see if
  this results in a closed tree. If it does, the two sentences are logically
  equivalent.

  > Sentences $P$ and $Q$ are truth-functionally equivalent if and only if the set
  > $\sim (P \equiv Q)$ has a closed tree

  ![logical-equivalence-tree.svg](logical-equivalence-tree.svg)

  ### Logical entailment and validity

  Let’s remind ourselves of the meaning of truth-functional
  [entailment](Validity%20and%20entailment.md#entailment) and
  [validity](Validity%20and%20entailment.md#validity) and the relation between the
  two. $\Gamma$ $\vdash$ $P$ is true if and only if there is no truth-assignment
  in which every member of $\Gamma$ is true and $P$ is false. Entailment is
  closely related to validity; it is really just a matter of emphasis: we say that
  $\Gamma$ are the premises and $P$ is the conclusion and that this is a valid
  argument if there is no assignment in which every member of $\Gamma$ is true and
  $P$ is false.

  As with the previous properties, to express validity and entailment in terms of
  truth trees we need to express these concepts in the language of logical
  consistency. $\Gamma$ entails $P$ just if one cannot consistently assert
  $\Gamma$ whilst denying $P$. This is to say that the set $\Gamma \cup {\sim P}$
  is inconsistent. So we just need a closed truth tree for $\Gamma \cup {\sim P}$
  to demonstrate the validity of this set.

  > A finite set of sentences $\Gamma$ truth-functionally entails a sentence $P$
  > if and only if the set $\Gamma \cup {\sim P}$ has a closed truth tree.

  > An argument is truth functionally valid if and only if the set consisting of
  > the premises and the negation of the conclusion has a closed truth tree.

raw_content:
  ---
  tags:
    - logic
    - propositional-logic
  ---

  # Truth trees

  ## Rationale

  Like [truth-tables](Truth-tables.md), truth-trees are a means of graphically
  representing the logical relationships that may obtain between propositions.
  Truth-trees and truth-tables complement each other and which method you choose
  depends on which logical property you are seeking to derive.

  Whilst truth-tables have the benefit of being exhaustive - every possible truth
  assignment is factored into the representation - their complexity grows
  exponentially with each additional proposition they contain. This can make
  manually constructing truth tables long-winded and prone to mistakes.

  Truth-trees are less onerous but they lack the exhaustive scope of a
  truth-table. They are more targeted and are best used for demonstrating _that
  something is the case_ rather than _all the possible states that could be the
  case_. For example, a truth tree will tell us that a set _S is logically
  consistent_ whereas a truth-table will tell us that _S is consistent on the
  following three assignments._

  ## Logical consistency

  Recall that a set of propositions is logically or truth-functionally
  [consistent](Consistency.md) just if there is at least one assignment of truth
  conditions which results in all members of the set being true. To identify
  consistency for a set of three propositions via the truth table approach we
  would need to construct a truth table with $2^3$ (8) rows. Assume that this set
  is consistent on one partial assignment only. This means that 87.5% of our rows
  are redundant, they are not required to prove the consistency of the set.
  However we can only know this and we can only be sure of consistency once we
  have gone through the process of generating an assignment for each row.

  Truth trees allow us to reduce the amount of work required and go straight to
  the assignment that proves consistency, disregarding the rest which are
  irrelevant.

  ## Truth tree structure and key terms

  **When using a truth tree to derive logical consistency, the goal is to
  determine whether there is a truth-value assignment on which all of the
  sentences of a set are true. If the set is consistent we should be able to
  derive a partial assignment from the tree that demonstrates consistency.**

  Each truth tree begins with a series of sentences one on top of the other in a
  column. We call the sentences that comprise the initial column **set members**.
  In constructing the tree, we work downwards from the initial column decomposing
  set members into their atomic constituents. We a call an atomic sentence that
  has been decomposed a **literal.** A literal will either be an atomic sentence
  or the negation of an atomic sentence. If one of the set members is already a
  literal, there is no need to decompose it; it can remain as it is.

  Once every set member has been decomposed the truth tree is complete. It can
  then be interpreted in order to derive logical consistency or inconsistency. If
  the set is consistent, we are able to derive the partial assignment(s) that
  demonstrate consistency.

  The rules for decomposing compound sentences match the truth conditions of the
  logical connectives. There are rules for every possible connective and the
  negation of every possible connective however in terms of their tree shape they
  all correspond to either a conjunction or a disjunction. Disjunctive
  decomposition results in new branches being formed off the main column (or
  trunk). Conjunctive decomposition is non-branching which means the decomposed
  constituents are placed within the trunk of whichever tree or branch they are
  decomposed within.

  As we construct the tree we list each line in the left-hand margin and the
  decomposition rule in the right-hand margin. When we apply a decomposition rule
  we must cite the lines to which it applies.

  ### Closed and open branches

  Any branch on which an atomic sentence ($P$) and the negation of that sentence
  ($\sim P$) both occur is a **closed branch**. A branch that is not closed is an
  **open branch**. No partial assignment is recoverable from a closed branch. An
  open branch allows truth to ‘flow up’ to the original set members whereas a
  closed branch blocks this passage.

  ### Completed open branch

  A completed open branch occurs when we have an open branch that has been fully
  decomposed: the branch is open and all molecular sentences have been ticked off
  such that it contains only literals.

  ### Completed tree

  A tree where all its branches are either completed open branches or closed
  branches.

  ### Closed tree

  A tree where all the branches are closed

  ### Open tree

  A tree with at least one completed open branch

  ## Deriving consistency

  Using the definitions above, we can now define truth-functional consistency and
  inconsistency in terms of truth trees:

  > A finite set ($\Gamma$ ) of sentences is truth-functionally inconsistent if
  > $\Gamma$ is a closed tree

  > A finite set ($\Gamma$ ) of sentences is truth-functionally consistent if
  > $\Gamma$ is an open tree

  ## Examples

  ### First example

  The following is a truth tree for the set ${P \lor Q, \sim P }$:

  ![basic-open-tree 1.svg](basic-open-tree%201.svg)

  ### Interpretation

  - We decompose the disjunction at line 1 on line 3. We tick off the compound
    sentence to indicate that it is now decomposed and no longer under
    consideration.
  - Both P and its negation exist on a single branch (at line 2 and line 3). This
    makes it a closed branch. We indicate this by the X beneath the branch that is
    closed, citing the source of the closure by line number.
  - The rightward branch is a completed open branch given the decomposition at 3
    and the lack of negation of Q. Overall this makes the tree an open tree.

  As the set gives us an open tree, it must be truth-functionally consistent. If
  this is the case we should be able to determine the partial assignment in which
  each set member is true. Given that Q is not negated the assignment of
  consistency will contain Q but we have both P and ~P. This means there are two
  possible assignments where the set is consistent: $P, Q$ and $\sim P, Q$. This
  is confirmed by the truth-table:

  ```
  P	Q				P	∨	~	P				Q
  T	T					T						T     *
  T	F					T						F
  F	T					T						T     *
  F	F					T						F
  ```

  **Any time there is an open tree with a closed branch it will be the case that
  the negated sentences of the closed branch will appear both as** $S$ and
  $\sim S$ i**n the resultant assignment.**

  Invoking the truth-table highlights the differences between the two techniques.
  The values that are derived when we interpret a truth tree are not the
  truth-functions of the set members but the truth-values for when they are
  simultaneously true. With truth-tables in contrast, we are deriving the truth
  functions for every possible truth-value assignment. In other words the values
  derived from a truth tree correspond to the left hand side of the truth table
  not the right hand side.

  ### Second example

  The following is a truth tree for the set
  ${A & \sim B, C, \sim A \lor \sim B }$.

  ![basic-closed-tree 1.svg](basic-closed-tree%201.svg)

  ### Interpretation

  - The two molecular set members are decomposed. The disjunction (line 3) results
    in a branching tree. The conjunction (line 1) results in the continuation of
    the trunk.
  - Both branches are completed making it a completed tree. As each branch is
    closed this is a closed tree.

  As this is a closed tree, the set is not truth-functionally consistent. This is
  confirmed by the truth table where there is no partial assignment where all set
  members are true.

  ```
  A	B	C				A	&	~	B				C				~	A	∨	~	C
  T	T	T					F						T						F
  T	T	F					F						F						T
  T	F	T					T						T						F
  T	F	F					T						F						T
  F	T	T					F						T						T
  F	T	F					F						F						T
  F	F	T					F						T						T
  F	F	F					F						F						T
  ```

  ## Truth tree decomposition rules

  ---

  So far we have encountered the decomposition rules for conjunction (`&D`) and
  disjunction (`vD`). We will now list all the rules. We will see that for each
  rule, the decomposition either branches or does not branch which is to say that
  each rule either has the shape of a conjunction or a disjunction (however the
  permitted values of the specific disjuncts/conjuncts obviously differ in each
  case). Moreover there is a parallel rule for the decomposition of the negation
  of each of the main connectives and these rules rely on logical equivalences

  ### Negated negation decomposition: `~~D`

  ![negated-negation-decomposition-rule 2.svg](negated-negation-decomposition-rule%202.svg)

  Truth passes only if $P$ is true

  ### Conjunction decomposition: `&D`

  ![conjunction-decomposition-rule.svg](conjunction-decomposition-rule.svg)

  Truth passes only $P$ and $Q$ are both true.

  ### Negated Conjunction decomposition: `~&D`

  ![negated-conjunction-decomposition-rule.svg](negated-conjunction-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $\sim Q$ is true. This rule is a consequence
  of the equivalence between $\sim (P & Q)$ and $\sim P \lor \sim Q$ , the first
  of DeMorgan’s Laws.

  ### Disjunction decomposition: `vD`

  ![disjunction-decomposition-rule.svg](disjunction-decomposition-rule.svg)

  Truth passes if either $P$or $Q$ are true.

  ### Negated Disjunction decomposition: `~vD`

  ![negated-disjunction-decomposition-rule.svg](negated-disjunction-decomposition-rule.svg)

  Truth passes if both $P$ and $Q$ are false. This rule is a consequence of the
  equivalence between $\sim (P \lor Q)$ and $\sim P & \sim Q$, the second of
  DeMorgan’s Laws.

  ### Conditional decomposition: `⊃D`

  ![conditional-decomposition-rule.svg](conditional-decomposition-rule.svg)

  Truth passes if either $\sim P$ or $Q$ are true. This rule is a consequence of
  the equivalence between $P \supset Q$ and $\sim P \lor Q$ therefore this branch
  has the shape of a disjunction with $\sim P$ , $Q$ as its disjuncts.

  ### Negated Conditional decomposition: `~⊃D`

  Truth passes if both $P$ and $\sim Q$ are true. This is a consequence of the
  equivalence between $\sim (P \supset Q)$ and $P & \sim Q$.

  ![negated-conditional-decomposition-rule.svg](negated-conditional-decomposition-rule.svg)

  ### Biconditional decomposition: `≡D`

  ![biconditional-decomposition-rule.drawio(1).svg](biconditional-decomposition-rule.drawio%281%29.svg)

  Truth passes if either $P$ and $Q$ are true or $\sim P & \sim Q$ are true. This
  is an interesting rule because it combines the disjunction and conjunction tree
  shapes.

  ### Negated biconditional decomposition: `~≡D`

  ![negated-biconditional-decomposition-rule.drawio.svg](negated-biconditional-decomposition-rule.drawio.svg)

  Truth passes if either $P$ and $\sim Q$ is true or if $\sim P$ and $Q$ is true.

  ## Further examples and heuristics for complex truth trees

  With truth-trees regardless of which order you decompose the set members, the
  conclusion should always be the same. This said, there more are more efficient
  ways than others to construct the trees. You want to find the route that will
  demonstrate consistency or non-consistency with the shortest amount of work. The
  following heuristic techniques followed in order, facilitate this:

  1. Decompose those molecular sentences the decomposition of which does not
     produce new branches. In other words that are decompositions of double
     negations or pure conjunctions.
  1. Perform those decompositions that will rapidly generate closed branches.
  1. If neither (1) or (2) is applicable, decompose **the most complex** sentence
     first.

  Here are some examples of these rules applied:

  ![complex-tree.svg](complex-tree.svg)

  Observe that here we don’t bother to decompose the sentence on line 1. This is
  because, having decomposed the sentences on lines 2 and 3 we have arrived at a
  closed tree. It is therefore unnecessary to go any further for if two sentences
  in the set are inconsistent with each other, adding another sentence is not
  going to change the overall assignment of inconsistency.

  ## Deriving properties other than logical consistency from truth trees

  So far truth trees have been discussed purely in terms of logical consistency
  however they can be used to derive all the other key truth-functional properties
  of propositional logic. Given the foundational role of consistency to logic,
  these properties are expressible in terms of consistency which is what makes
  them amenable to formulation in terms of truth trees.

  ### Logical falsity

  For a given finite set $\Gamma$, $\Gamma$ is logically consistent just if all of
  its members can be true at once. Expressed in terms of truth trees, this is
  equivalent to an open tree. Contrariwise, $\Gamma$ is inconsistent if it is not
  possible for every member of the set to be true at once. This is the same as a
  tree where all of the branches are closed (i.e. a closed tree).

  When we wish to assess
  [logical falsity](Logical%20truth%20and%20falsity.md#logical-falsity) we are not
  focused on sets however, we are interested in a property of a sentence. However
  we can easily construe single sentences as unit sets: sets with a single member.
  With this in mind and the above accounts of consistency and logical falsity we
  are equipped to express logical falsity in terms of truth-trees with the
  following rule:

  > A sentence $P$ is logically false if and only if the unit set ${ P }$ has a
  > closed tree

  A logically false sentence cannot be true on any assignment. This is the same
  thing as an inconsistent set. Thus it will be represented in a truth tree as
  inconsistency which is disclosed via a closed tree.

  ![logical-falsity-tree.svg](logical-falsity-tree.svg)

  ### Logical truth

  For a sentence $P$ to be
  [logically true](Logical%20truth%20and%20falsity.md#logical-truth), there must
  be no possible assignment in which $P$ is false. We express this informally by
  saying _it is not possible to consistently deny $P$._ We know that in terms of
  truth trees an inconsistent set is a closed tree therefore a unit set of ${ P }$
  is logically true if ${ \sim P }$ is a closed tree. This is to say: if the
  negation of $P$ is inconsistent.

  > A sentence $P$ is logically true if and only if the set ${ \sim P }$ has a
  > closed tree

  ### Logical indeterminacy

  [Indeterminacy](Indeterminacy.md) follows from the two definitions above; we do
  not require any additional apparatus. We recall that a sentence $P$ is logically
  indeterminate just if it is neither logically true or logically false. Thus the
  truth tree for an indeterminate sentence is straightforward:

  > A sentence $P$ is logically indeterminate if and only if neither the set
  >  ${ P }$ nor the set ${ \sim P }$ has a closed tree

  This follows because a closed tree for  ${ P }$ means it is not logically false
  and an open tree for ${ \sim P }$ means it is not logically true. So if it is
  neither of these things, $P$ must be indeterminate.

  ### Logical equivalence

  Recall that $P$ and $Q$ are [logically equivalent](Logical%20equivalence.md)
  just if there is no truth assignment on which one is true and the other is
  false. We know from the
  [material biconditional shorthand](Corresponding%20material%20and%20biconditional.md#corresponding-material-biconditional)
  that this state of affairs can be expressed as $P \equiv Q$ and that if this
  compound sentence is true on every assignment then both simple sentences are
  equivalent. But ‘true on every assignment’ is another way of saying _logically
  true_ since there is no possibility of a false assignment. We already know what
  logical truth looks like as a truth tree: it is a closed tree for the negation
  of the sentence being tested. Therefore, to test the logical equivalence of two
  sentences it is necessary to construct a truth tree for the negation of the
  sentences conjoined by the biconditional (i.e. $\sim (P \equiv Q)$ )and see if
  this results in a closed tree. If it does, the two sentences are logically
  equivalent.

  > Sentences $P$ and $Q$ are truth-functionally equivalent if and only if the set
  > $\sim (P \equiv Q)$ has a closed tree

  ![logical-equivalence-tree.svg](logical-equivalence-tree.svg)

  ### Logical entailment and validity

  Let’s remind ourselves of the meaning of truth-functional
  [entailment](Validity%20and%20entailment.md#entailment) and
  [validity](Validity%20and%20entailment.md#validity) and the relation between the
  two. $\Gamma$ $\vdash$ $P$ is true if and only if there is no truth-assignment
  in which every member of $\Gamma$ is true and $P$ is false. Entailment is
  closely related to validity; it is really just a matter of emphasis: we say that
  $\Gamma$ are the premises and $P$ is the conclusion and that this is a valid
  argument if there is no assignment in which every member of $\Gamma$ is true and
  $P$ is false.

  As with the previous properties, to express validity and entailment in terms of
  truth trees we need to express these concepts in the language of logical
  consistency. $\Gamma$ entails $P$ just if one cannot consistently assert
  $\Gamma$ whilst denying $P$. This is to say that the set $\Gamma \cup {\sim P}$
  is inconsistent. So we just need a closed truth tree for $\Gamma \cup {\sim P}$
  to demonstrate the validity of this set.

  > A finite set of sentences $\Gamma$ truth-functionally entails a sentence $P$
  > if and only if the set $\Gamma \cup {\sim P}$ has a closed truth tree.

  > An argument is truth functionally valid if and only if the set consisting of
  > the premises and the negation of the conclusion has a closed truth tree.


