<jupyter_start><jupyter_text>Enigma Machine Assignment IntroductionPlease ensure you have read the instructions on Engage for a full introduction to this assignment.This notebook contains the full instructions for each part of the assignment, but it assumes you already have an understanding of the assignment and the basics of how an Enigma machine works.This notebook also includes instructions for how to format your code and some test cells like the ones you have seen in the exercise sheets. These are not exhaustive and outside of matching the names in the specified tests you are welcome to choose how you structure your code. We have tried to offer a good balance between being able to test your code, offering structure for the assignment, and also offering flexibility for you to be able to show off your own design ability.The earlier stages of this assignment contain more guidance than the later ones. You are encouraged to research and use Python features that you have not been taught in the unit, and this can add to the quality of your code (as defined in the instructions on Engage). Remember also that advanced work which goes beyond the specification and is well documented will be rewarded (see the final section below). Part One – SimulationIn this part you will be producing a simulation of an Enigma machine using Python. Your code must be object-oriented, i.e. it must use classes. Some names of classes and methods will be specified in test cells, and you must match these where this is the case. Otherwise it is up to you how you implement the functionality and how you design your class structure.**For all parts, you must write all of your Enigma machine code in separate files, i.e. not in the notebook cells.** In the cell below we have included an import statement `from enigma import *`. This will import the definitions (functions and classes) in a file called `enigma.py` which is located in the same directory, and a file is provided to get you started. If you wish to use a different file name, you can change this line and our tests will still run. Your code can be split across multiple files if you like, and you can include multiple import statements.<jupyter_code>from enigma import *<jupyter_output><empty_output><jupyter_text>Plug LeadsLet's start simulating the Enigma machine with the plugboard, specifically the leads. Each lead in the enigma machine connects two plugs in the plugboard. If we think of the functionality of the plugboard itself in terms of how it encodes a single character, the plugboard *aggregates* the leads, so it makes sense to make a class to represent the lead objects.Write a class called `PlugLead`. The constructor should take a string of length two, which represents the two characters this lead should connect. So the following code:```python3lead = PlugLead("AG")```creates a lead which connects A to G. Remember leads are reversible so this lead also connects G to A. If you are still new to object-oriented design, when you are first conceptualising a class, you should take some time to think about what attributes it has and what methods it has. Some will be required by this specification, but you are always free to add more of your own.As part of the specification, you must implement a method called `encode(c)` on your lead, which takes a single character `c`, and returns the result of this lead on this character. So, with the `lead` object we created above, `lead.encode("G")` should return `"A"`.`lead.encode("D")` should return `"D"` – this lead had no impact on the letter D, it only connects A and G. Of course even though it would have no effect, it should not be possible to connect a letter to itself – there is only one plug for each letter on the plugboard. For this assignment you should write your code to be robust, obeying the physical limitations of the Engima machine (at least for part one; you may decide to lift these in your extension material). In general “robustness” is left up to your interpretation and discretion, but in this instance we will help out by suggesting your code should `raise` an error if someone tries to construct an invalid lead. You are welcome to include your own errors as custom classes, but in this case a `ValueError` would also be appropriate.***Note:*** at this point we'll note that, of course, whenever the Enigma machine *encodes*, it also *decodes* for the exact same settings. We'll still use the method name `encode(…)` throughout our tests, and take it as assumed that the same method is used for encoding and decoding.<jupyter_code>lead = PlugLead("AG")
assert(lead.encode("A") == "G")
assert(lead.encode("D") == "D")

lead = PlugLead("DA")
assert(lead.encode("A") == "D")
assert(lead.encode("D") == "A")<jupyter_output><empty_output><jupyter_text>PlugboardNaturally we now need the plugboard itself to house our leads. An interesting part of object-oriented design is the idea that the *interface* of the object is really all an outsider needs to know. In the previous part we asked you to ensure a lead object supports `.encode("A")`, but how you achieve that is up to you.Of course in this assignment we will be looking at your code, not just running automated tests. So two different implementations that both work could get different grades. But this is a programming unit and so it is part of the learning outcomes to learn how to write good code. In later units, you might be asked to show you are able to select and implement AI algorithms – not to test your ability to code, but to show you understand the techniques. Code quality is still important, it might even be worth marks, but it is not the primary goal.With all that in mind, I am now going to ask you to write a class called `Plugboard`. The plugboard should accept leads which connect plugs via the method `.add(…)` as demonstrated below.Like the leads, the plugboard should have an `.encode(…)` method as well, which should return the result of passing the character through the entire plugboard. These are the only interface requirements, but you are encouraged to elaborate on these with additional methods and/or constructor keywords. Remember that the Enigma machine only came with 10 leads to connect plugs. From now onwards, we will not always specifically point out the physical limitations that you need to model for robustness – you are expected to think of these and include them yourself. You do not need to know arcane details about how Enigma machines work, but from the information presented you can think of obvious incompatibilities and handle them.<jupyter_code>plugboard = Plugboard()

plugboard.add(PlugLead("SZ"))
plugboard.add(PlugLead("GT"))
plugboard.add(PlugLead("DV"))
plugboard.add(PlugLead("KU"))

assert(plugboard.encode("K") == "U")
assert(plugboard.encode("A") == "A")<jupyter_output><empty_output><jupyter_text>RotorsThe number of possible combinations due to the plugboard is staggering, making brute force attempts to break a code extremely difficult. But using it alone would result in a simple substitution cipher – easily cracked with techniques like frequency analysis. The next step in the process, the rotors, allow the letter substitution to change mechanically every time a key is pressed, which prevents simple frequency analysis.Over time, rotors with many different wiring patterns were developed, and different Enigma machines supported different types and numbers of rotors. We are not necessarily looking for exact historical accuracy in this assignment – for your extension material you might choose to be much more accurate or much less accurate! For this part, you should work on the following specification.Your Enigma machine must support *three or four* rotors and one reflector – notice that a reflector is really just a type of rotor where the characters line up in 13 perfect pairs. The rotors will be numbered *from right to left*, which is the “path” the current takes when first entering the rotors. The signal goes through each rotor in turn, hits the reflector, then goes through the rotors again in reverse order (left to right). When it goes through the rotors in reverse order, it uses the *reverse* wiring. So if A is mapped to L when going from right to left, then L is mapped to A on the reverse journey (of course it is not possible to actually hit the same wire on the way back – a letter cannot encode into itself in the machine as a whole).Ignoring reflectors, which never rotate, there are two types of rotating rotor, based on whether or not the rotor contains a *notch*. If the rotor contains a notch, then when it is on a certain position and is rotated, it will cause the rotor in the next slot to rotate as well. In addition, in a four slot Engima machine, the leftmost (fourth) rotor never rotates. Rotation will be explained in more detail shortly.Each rotor can be chosen from a box containing seven possible wiring patterns. There are two rotors labelled `Beta` and `Gamma`. Then there are five rotors labelled with Roman numerals which do rotate: `I, II, III, IV, V`. You must also support three possible reflector wiring patterns, labelled `A, B, C`.Note: the wiring patterns are all real, taken from [this page](https://en.wikipedia.org/wiki/Enigma_rotor_details). The page contains more rotors if you wish to consider them, or you can make up your own, but you must support the ones here. The following patterns all assume the rotors are in their default position, with their default ring setting, and going from right to left (the initial path).Mapping from letterLabelABCDEFGHIJKLMNOPQRSTUVWXYZBetaLEYJVCNIXWPBQMDRTAKZGFUHOSGammaFSOKANUERHMBTIYCWLQPZXVGJDIEKMFLGDQVZNTOWYHXUSPAIBRCJIIAJDKSIRUXBLHWTMCQGZNPYFVOEIIIBDFHJLCPRTXVZNYEIWGAKMUSQOIVESOVPZJAYQUIRHXLNFTGKDCMWBVVZBRGITYUPSDNHLXAWMJQOFECKAEJMZALYXVBWFCRQUONTSPIKHGDBYRUHQSLDPXNGOKMIEBFZCWVJATCFVPJIAOYEDRZXWGCTKUQSBNMHL  Single Rotor DemonstrationFrom now on, the specification does not require you to follow specific class or method names, it is totally up to you.Rotors still get bit more complicated when we introduce their settings and put multiple in the same machine. But you might want to take this opportunity to see if you can write some code to represent what we've seen so far.In the cell below, you should demonstrate some basic rotor functionality using your classes. I have left some sample code in here in case you want to use it, either directly or for inspiration – I am not implying this is the only or best way to achieve it, and either way you will likely want to add to it. The idea is to show us that your rotors work with the concepts introduced so far.This code cell is especially important if you end up struggling with the next part, to allow for partial credit.<jupyter_code>rotor = rotor_from_name("I")
assert(rotor.encode_right_to_left("A") == "E")
assert(rotor.encode_left_to_right("A") == "U")<jupyter_output><empty_output><jupyter_text>The Enigma MachineTo fully understand rotors, we need to imagine multiple of them in the Enigma machine itself. For this next part, you will need to model many more details of how the rotors work, and in addition work out how to incorporate them into a single machine that is capable of performing the full encoding path. Multiple RotorsFor now, let's ignore the plugboard, and introduce the remaining details for the rotors.It is common to see the selection of rotors specified in a single sequence from left to right, as the operator would see when looking down, such as on a [German code book](https://en.wikipedia.org/wiki/Enigma_machine/media/File:Enigma_keylist_3_rotor.jpg). For example, on the top row of that linked image, you can see the rotors should be `I V III`. This means the first (rightmost) rotor is `III`, and so on. How you conceptualise the order inside your code is up to you providing it is consistent with the terminology here.The code book also specifies each rotor's “ring setting” – in that image you can see the rotors on the top line should be set to `14 09 24` correspondingly. The rotor settings will be explained later, but let's first ensure you understand the way the wiring works with multiple rotors. We will show a worked example using three rotors, with all the settings in their default positions – this means you can read the character mappings directly from the table above.Imagine a `III` rotor sat upright in the machine in the right hand position. The right hand side of the rotor has 26 *pins* and the left hand side has 26 *contacts*, one for each character. The pins on the right are connected to the contacts of the rotor housing which is wired into the plugboard. So if the plugboard sends a signal on the `A` contact of the housing, then this hits the `A` *pin* of the rotor, then this passes through the rotor's internal wiring (check the table for the `III` rotor) and we receive an output signal on the `B` *contact* of the rotor. This will now go into the next rotor: remember there are three or four rotors, followed by a reflector.Suppose there is a `II` rotor in the middle position, a `I` rotor in the left position, and then a `B` reflector. Here is the full path when we send an `A` signal from the plugboard:* `A` signal comes in* `III` rotor receives signal on `A` pin, which connects to `B` contact* `II` rotor receives signal on `B` pin, which connects to `J` contact* `I` rotor receives signal on `J` pin, which connects to `Z` contact* `B` reflector receives signal on `Z` connecting to `T`   (now the signal goes backwards, hitting the *contacts* and coming out the *pins*)* `I` rotor receives signal on `T` contact, which connects to `L` pin* `II` rotor receives signal on `L` contact, which connects to `K` pin* `III` rotor receives signal on `K` contact, which connects to `U` pin* `U` signal is outputThe final output for this `A` signal is a `U`. Make sure you can follow this using the table above, as things are about to get more complicated. RotationAs mentioned, the rightmost rotor (first in the order of the electrical circuit) rotates at *the start* of each keypress, i.e. *before* the character signal is passed through the circuit. This is what makes the Enigma machine more powerful than a fixed substitution cipher, one rotation causes a completely different circuit and substitution.The rotation of the rotor advances a setting called the ***position*** of each rotor, which is visible through a window on the machine. This setting is marked on the rotor and is a character between `A` and `Z`, but it is helpful to think of this number as an *offset* rather than a *letter*. In the example above we assumed all of the rotors were set to position `A`. Rotating moves the *pins and the contacts* up by one position (`A` becomes `B`, etc).We mentioned before that if we input `A` into the `III` rotor in its default position (which is labelled `A`) then it produces an output of `B`. In our full example we assumed that the rotors were set to `AAA`, but if we set them this way on the machine, then as soon as we press the `A` key the rightmost rotor would rotate giving `AAB`, and this is the circuit that would be made (rotation always happens first). Let's look at what happens in this setting.Now if we input an `A` signal from the plugboard, it will come out of the `A` contact of the housing, but it will hit the `B` pin of the rotor due to its rotation by one position. The `B` *pin* is wired to the `D` *contact* (reading from the table).***But*** the `D` contact has also been rotated one position inside the machine, so it actually lines up with the `C` *pin* of the next rotor in its default position. The other two rotors and the reflector work as normal, and on the way back rotor `II` sends a signal on its `E` pin. Since `III` is rotated, this hits the `F` contact which is wired to the `C` pin. But again since it is rotated, this his the `B` contact of the rotor housing, and is what is sent back to the plugboard.Here is the full example again, now assuming the rotors are set to `AAB` instead of `AAA`:* `A` signal comes in* `III (B)` rotor receives signal on `B` pin, which connects to `D` contact * `II (A)` rotor receives signal on `C` pin, which connects to `D` contact* `I (A)` rotor receives signal on `D` pin, which connects to `F` contact* `B` reflector receives signal on `F` connecting to `S`* `I (A)` rotor receives signal on `S` contact, which connects to `S` pin* `II (A)` rotor receives signal on `S` contact, which connects to `E` pin* `III (B)` rotor receives signal on `F` contact, which connects to `C` pin* `B` signal is outputYou can try using the Enigma machine emulator [on this page](https://www.101computing.net/enigma-machine-emulator/). It defaults to the same settings: rotors `I II III` all initially set to position `A`, so when you press the `A` key on the keyboard you should get `B`.Make sure you can follow this path using the table of wirings to help you understand how you will implement the behaviour. If you simply do not follow, there are lots of explanations and videos online for Enigma machines.  Ring SettingsIn addition each rotor could be configured by changing the *ring setting*, which is a fixed offset that would apply between the internal wiring and the external markings. The ring settings were either given from `A-Z` or `01-26` – you saw the latter in the code book image linked above, and we'll use these too to avoid confusion with the *position* setting.If a rotor's ring setting is set to `01` then nothing is changed, the wiring is exactly as written in the table above.*Increasing* the ring setting has the exact same effect as *decreasing* the position setting. It shifts the internal wiring in the opposite direction.Earlier we said `A` becomes `U` with the given rotors actually set to `AAA` – we'd have to start on `AAZ` to get this result on the machine, since the rotation happens first (try it on the emulator). Alternatively, we could set the ring position to `02` on the rightmost rotor and set the initial positions to `AAA`, and we'll get the same result for a single press (again you can try this on the emulator; it uses letters for ring settings, if you click the rotor you can set the ring setting to `B`).If we keep pressing `A`, the two configurations will produce many of the same characters, but not always. We have detailed how the rightmost rotor rotates, but not the others, and this detail will eventually produce a difference between the two sets of settings above. TurnoverThe rotors labelled `I` to `V` have *notches*. If a rotor has a notch and is currently set to its notch position, then it will turn the next rotor on the next keypress (this is called *turnover*). Here are the notch positions:RotorNotchIQIIEIIIVIVJVZSo if a `II` rotor in the first, rightmost, slot of the machine is *currently set to* position `E` then when you press a key the first rotor will turn to position `F` *and* the rotor in the second position will turn as well, and then the electrical signal will be sent through the circuit.If the `II` rotor had been in the second position, then it will obviously turn much more slowly. But if its position is set to `E` and a key is pressed then it will rotate and turn the rotor in the third position also. There is an important detail here called the *double step*. Normally the second rotor will only turn once every 26 turns of the rightmost rotor. But if the second rotor is *on* its notch setting, then it will turn again *as it turns the third rotor*. Obviously the second rotor must have just rotated to land on its notch, so it actually rotates for two keypresses in a row.Suppose we have the rotors `I II III`, on positions `A C U`. Pressing a key turns the `III` rotor and we get `A C V`. Now the `III` rotor is on its notch, so pressing a key also turns `II` and we get `A D W`. If we continue pressing keys we'll get `A D X`, `A D Y`, `A D Z`, `A D A`, and so on. Several keypresses later we wrap round again and approach the notch on `III` again on setting `A D U`. When we press once we get `A D V`. Now `III` is on its notch so pressing again turns `II` and we get `A E W`. But now `II` is on its notch, so when we press again *all three* rotors rotate and we get `B F X` – `III` turns because it always turns, `II` turns because it is on its notch, and `I` turns because `II` turned on its notch (turnover).Notice the *ring setting* is inconsequential in this turnover process – it only requires the current position to line up with the notch setting.The four-rotor machines did not have a additional lever, and so whether the third rotor had a notch or not the fourth rotor would not turn. In addition if a notchless rotor (e.g. `Beta`) was in the first position, then it will never cause the second rotor to rotate. The rightmost rotor will still always rotate exactly once on every keypress. Notchless rotors can still be set to different position settings as part of the setup process.If you are curious about the mechanism, you can see a video of a mock-up version of an Enigma machine in action [here](https://www.youtube.com/watch?v=hcVhQeZ5gI4), showing how the the ratchets, levers, and notches contribute towards the rotation on each keypress, and also demonstrating the double step (around 26 seconds into the video).  Multiple Rotor DemonstrationYou have free reign to model the rotors however you wish, and you are encouraged to think about how object-oriented design principles and features might apply.In the cell below, demonstrate that your rotors work. The code is left entirely up to you, though you must ensure to demonstrate the following:* With rotors `I II III`, reflector `B`, ring settings `01 01 01`, and initial positions `A A Z`, encoding an `A` produces a `U`.* With rotors `I II III`, reflector `B`, ring settings `01 01 01`, and initial positions `A A A`, encoding an `A` produces a `B`.* With rotors `I II III`, reflector `B`, ring settings `01 01 01`, and initial positions `Q E V`, encoding an `A` produces an `L`.* With rotors `IV V Beta`, reflector `B`, ring settings `14 09 24`, and initial positions `A A A`, encoding an `H` produces a `Y`.* With rotors `I II III IV`, reflector `C`, ring settings `07 11 15 19`, and initial positions `Q E V Z`, encoding a `Z` produces a `V`.<jupyter_code># YOUR CODE HERE
raise NotImplementedError()<jupyter_output><empty_output><jupyter_text>Enigma Machine DemonstrationNow in the cell below, demonstrate that you can put all of the elements together. Your full Engima machine should support a plugboard with up to 10 leads, three or four rotors, and a reflector. In addition it should be able to encode an entire string of characters made from the letters `A`-`Z`, correctly advancing the rotors.Again the code is up to you, and you may want to include additional points to demonstrate your Enigma machine, but please ensure you include the following two examples at minimum: Example 1Set up your enigma machine with rotors `I II III`, reflector `B`, ring settings `01 01 01`, and initial positions `A A Z`.The plugboard should map the following pairs: `HL MO AJ CX BZ SR NI YW DG PK`.*The result of encoding the string* `HELLOWORLD` *should be* `RFKTMBXVVW`. Example 2Set up your enigma machine with rotors `IV V Beta I`, reflector `A`, ring settings `18 24 03 05`, and initial positions `E Z G P`. The plugboard should map the following pairs: `PC XZ FM QA ST NB HY OR EV IU`.*Find the result of decoding the following string:* `BUPXWJCDPFASXBDHLBBIBSRNWCSZXQOLBNXYAXVHOGCUUIBCVMPUZYUUKHI`.(You should run this string through your Enigma machine in the cell below; do not just include the result.)<jupyter_code># YOUR CODE HERE
raise NotImplementedError()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.054857480182155506, 'fraction_numerical': 0.0034997470062405125, 'mean_word_length': 4.672566371681416, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '269856', 'license': 'FTL', 'n_tokens_mistral': 6194, 'n_tokens_neox': 5833, 'n_words': 3931, 'path': 'resources/jupyter/enigma.ipynb', 'repo_name': 'earlution/enigma-machine', 'size': '23716'}