时间:2024-05-11 来源:合肥网hfw.cc 作者:hfw.cc 我要纠错
Assignment (INFO1110)
Introduction
The assignment is an individual assessment. It contributes 30% of your final marks.
The due date for the assignment is on 9th May 2024, 11:59 pm (Sydney time).
This is an assignment, and staff are not permitted to give guidance on your code or how to solve a
specific problem. That is the purpose of the assessment that you are required to perform to achieve
the grade.
You may ask clarification questions about the assignment description. This is often necessary to implement
functionality that may need further understanding to complete. If you have a question to ask on Ed, please
search before asking. With a cohort of almost 2000 students, chances are that someone has already asked a
question you have planned to ask.
Do not wait too long before starting. This assignment needs time and sustained effort.
Remember that you should not be posting any assignment code publicly (including Ed), as this would
constitute academic dishonesty.
Submissions
Late submissions are not accepted unless an approved special consideration or special arrangement in the
form of a Simple Extension or Extension of time has been granted. Please inform staff if you have been
granted this.
All submissions must be made via Ed, including any supporting documentation that is produced
during the planning of the program design such as flowcharts, pseudocodes, and UML class
diagrams.
You may submit as many times before the due date, there is 0 penalty for submitting multiple times.
We will use the latest submission received before the due date for marking. Request to grade files
and derive marks from a combination of different submissions will not be accepted.
It is your responsibility to check that your submission is complete and it meets the following rules:
The Python programs must be able to compile and run within the Ed environment provided.
The Python version that is currently being used on Ed is Python 3.11.8.
Only the files given in the scaffold code will be started by the auto-marker. You are free to write
additional python files, but you must implement all functions and classes provided in the
scaffold. Ensure that you have submitted these files with the correct file name as given in the
questions' scaffold:
board_displayer.py
emitter.py
input_parser.py
laser_circuit.py
mirror.py
photon.py
receiver.py
run.py
sorter.py
Your submission must also include a circuit_for_testing.py and test.py file which
will be used for the manual grading process.
All files specified above must include your name, SID, and unikey in the following format (order
matters!). A placeholder has been provided at the top of the file in the docstring. Providing
incorrect details will cause your submission to fail all test cases.
Name: Xxx Yyy
SID: XXXXXXXXX
Unikey: xxxxXXXX
If you attempt to deceive the auto-grader, obfuscate your code, or do not answer the question by hard coding,
0 marks will be awarded for the question.
Marks
Marks are allocated by the automatic test cases passed for each section, as well as manual grading by
your tutor.
Automatic Test Cases (20/30)
Your marks for this component will be based purely on the automatic test cases you pass. There are 3
types of automatic test cases, all contributing to your mark:
Public: The name of these test cases describes what it is testing, and additionally gives you
feedback on what you got wrong e.g. incorrect output, incorrect return values from functions,
etc. Students can easily see what they got right and wrong.
Hidden: The test case is only named Hidden testcase and does not provide detailed
feedback. You will only be able to see if you passed it or not. The idea behind this is to
encourage students to carefully read through the assignment description and ensure its
reflected in their program.
Private: These tests will only be visible after the deadline. You will not know how many private
test cases there are until the assignment is graded and returned.
There are several features for this assignment, with each one having their own marking proportion.
As example, the first feature SET-MY-CIRCUIT weighs 25% of the automatic test cases. This means
passing all public, hidden and private test cases for this feature gets you 5 out of the 20 marks.
Manual Grading (10/30)
Manual grading will assess the style, layout, and comments, correctness of test case implementation
in test.py and your responses in the test_plan.md document. The test.py file will be executed
during the marking process. Style marking is only applied for reasonable attempts, those which have
code beyond the initial scaffold and are passing at least some test cases.
The style guide for this assessment can be found on the official Python website
https://peps.python.org/pep-0008/.
In addition to the official style guide, you can also refer to these resources:
Code style guide - Part 1
Code style guide - Part 2
If there's an issue running the program, the tutor will not be responsible to debug your program, so please
ensure it runs as expected before making it your final submission.
Restrictions
The following must be read, as it says what you can and can't use. A 20% penalty will be levied on all
testcases passed if one or more of the restricted codes are used in your submission.
Keywords
The following is not allowed.
for
in (this includes the use of __contains__() as in simply invokes this method)
global
lambda
nonlocal
Built-in Functions
The following is not allowed.
all()/any()
map()/filter()
min()/max()/sum()
eval()/compile()/exec()
enumerate()
globals()/locals()
Modules
The following is allowed.
sys
typing
unittest
any module you have written, e.g. emitter .
Every other module is not allowed.
Size Restrictions
A submission containing a more than 5000 lines of code, or larger than 500kB will not be accepted.
To check the file sizes of your files, use the bash commands wc -l *.py and du -skc *.py .
Individual files, such as run.py can be checked with wc -l run.py and du -skc run.py
Help and Feedback
You are encouraged to ask questions about the assignment during the Helpdesk and on the Ed
discussion board. However, remember that you should not be posting any assignment code
publicly, as this would constitute academic dishonesty. Also, you should not disclose your code
or talk about your solutions in any of the PASS sessions.
Friendly Reminder
On occasion, typos or other errors may appear in the assignment description. Sometimes the
description could be clearer. Students and tutors often make great suggestions for improving the
description. Therefore, this assignment description may be clarified up to Week 8, Monday 15,
April. No changes will be made. Revised versions will be clearly marked in the Log of Changes slide
and the most recent version will be updated in the assignment description.
Academic Declaration
By submitting this assignment, you declare the following:
I declare that I have read and understood the University of Sydney Student Plagiarism: Coursework Policy
and Procedure, and except where specifically acknowledged, the work contained in this assignment is my
own work and has not been copied from other sources or been previously submitted for award or
assessment. I also did not use any generative AI tools (including ChatGPT) to assist in writing this
assignment.
I understand that failure to comply with the Student Plagiarism: Coursework Policy and Procedure can
lead to severe penalties as outlined under Chapter 8 of the University of Sydney By-Law 1999 (as
amended). These penalties may be imposed in cases where any significant portion of my submitted work
has been copied without proper acknowledgment from other sources, including published works, the
Internet, existing programs, the work of other students, or work previously submitted for other awards or
assessments.
I realize that I may be asked to identify those portions of the work contributed by me and required to
demonstrate my knowledge of the relevant material by answering oral questions or by undertaking
supplementary work, either written or in the laboratory, in order to arrive at the final assessment mark.
I acknowledge that the School of Computer Science, in assessing this assignment, may reproduce it
entirely, may provide a copy to another member of faculty, and/or communicate a copy of this
assignment to a plagiarism checking service or in-house computer program, and that a copy of the
assignment may be maintained by the service or the School of Computer Science for the purpose of future
plagiarism checking.
Any attempts to trick, break or circumstancing the automate marking system may leads to a mark of 0 and will
be reported to academic honesty committee.
Assignment Breakdown
You will be implementing and emulating a (very rough approximation of) a photonic circuit. In a highlevel overview, photonic circuits use lasers to emit photons (particles of light) which perform
computation through their interactions with other photonic components. There are many parts we
are leaving out, however our main goal is to build a simple emulation of the system which will give
you first-hand experience on implementing a coding project from the ground-up, and hopefully be
interesting enough for you to enjoy.
You do not need any understanding of physics for this assignment, and indeed we will be doing it a degree of
discourtesy by violating rules of quantum mechanics and in turn invent some elements whole-cloth (purefabrication).
The assignment is broken down into separate features. These features are:
1. SET-MY-CIRCUIT
2. GET-MY-INPUTS
3. RUN-MY-CIRCUIT
4. ADD-MY-MIRRORS
The features above are marked purely on automatic test cases.
There is then a separate part for testing Testing (Manually Graded) . This is instead manually
marked, in which the marker will mark your tests (both documentation and implementation) and
code style.
Marks
As discussed in Introduction , there is a marking proportion for each feature. These are marked
purely on the automatic test cases. These are the marking proportions below.
This sums up to a total of 20 marks.
The remaining 10 marks go towards the testing component which is manually graded. You can find
the section here Testing (Manually Graded) .
Summary of Features
This assignment will involve implementing each feature one-by-one. Each feature involves
implementing a set number of functions and classes which will be used in our program. Each feature
is separate, meaning adding a new feature does not require any changes to your existing features to
incorporate. In addition, when running the program, the user can specify which features they want
ran in their program, meaning not necessarily every feature needs to be used. As example, we could
create a circuit from inputs with mirrors added into it (Feature 1, 2 and 4), but not want to run it
(Feature 3). This creates a very flexible and modular program that is easy to extend.
1. SET-MY-CIRCUIT
SET-MY-CIRCUIT will focus on implementing the necessities to setup a circuit. A circuit comprises of
a board in which we can place components on it. Components include emitters (lasers that emits
photons) labelled from A to J and receivers (photodetectors that absorb photons) labelled from
R0 to R9 . We do not yet implement the circuitry functionalities.
By the end of this feature, you will be able to setup a circuit and display it to the user. An example is
we could setup a circuit with some fixed values; a board of size 18x6 characters, with two emitters A
and B and two receivers R0 and R1 (receivers are displayed by their number). We should then be
able to display it such as below.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
2. GET-MY-INPUTS
GET-MY-INPUTS allows the user to specify what circuit they want to setup by providing inputs to the
program. From this, the user can setup a circuit to their own specifications.
Below is an example of how a user will setup their circuit.
We prepended the inputs with a # symbol so you can clearly see what the inputs to the program are.
$ python3 run.py
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
It will first ask to get the size of the board. The size is given in the format <width> <height> . The
example above creates a circuit board of size 18x6 .
It will then ask to add emitters on the board. The emitters are given in the format <symbol> <x> <y> .
Users can add up to 10 emitters, where each emitter is uniquely labelled from A to J . The example
above adds emitter A at position (2, 2) , followed by emitter B at position (8, 1) . Once users are
done adding emitters, they enter END EMITTERS to move on with the program.
Similarly, it will then ask to add receivers on the board. Users can add up to 10 receivers, where each
receiver is uniquely labelled from R0 to R9 . The example above adds receiver R0 at position (15,
2) then R1 at position (8, 4) . The user will be asked to keep adding receivers until 10 are added.
Once users are done adding receivers, they enter END RECEIVERS to move on with the program. At
this point, the only part left to do is to display the board.
By the end of this feature, you will have a run program that will be able to setup a circuit based on
the user's inputs and display the board.
3. RUN-MY-CIRCUIT
RUN-MY-CIRCUIT is responsible for running the circuit. The first step is that it reads from a file the
frequency and direction of each photon emitted. Each line of the file is in the format of <symbol>
<frequency> <direction> where <symbol> is the symbol of the emitter which will emit the photon,
<frequency> is the frequency of the photon and <direction> is the direction in which the photon
will travel.
This is called the pulse sequence. An example of a pulse_sequence.in file is shown below which is
located in /home/input/ .
A 100 E
B 256 S
The pulse sequence above defines that A will emit a photon at 100THz (terahertz) east and B will
emit a photon at 256THz south. After loading the pulse sequence into the circuit, the circuit performs
the following steps:
1. Each emitter emits a photon.
2. In each tick (a tick is a nanosecond), each photon moves. If a photon reaches a receiver, the
receiver will absorb the photon.
3. After each tick, we increment our clock (a simple counter) which keeps track of how long our
circuit has run for.
4. Repeat Steps 2-3 until all photons have been absorbed.
Once a receiver absorbs a photon, the receiver becomes activated in which it's charged with the
photon's energy. An activated receiver can continue absorbing more photons.
Let's look at a small example of running a circuit.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
With the given circuit, we load the pulse sequence defined above into it. Then, we provide some steps
below to show what happens from here.
At 0ns (nanoseconds), the circuit emits photons. A emits a 100THz photon which will move to the
east and B emits a 256THz photon which will move to the south. When emitting the photons, the
photons initially start inside the emitter (hence the diagram looks no different).
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
At 1ns, each photon moves one unit on the board at their given directions.
+------------------+
| |
| B |
| A. . 0 |
| |
| 1 |
| |
+------------------+
Similarly for 2ns, each photon moves one unit on the board. You can see that we draw the path each
photon takes. You can picture it as the board being made of sand, and each time the photon moves, it
leaves a footstep on the ground.
+------------------+
| |
| B |
| A.. . 0 |
| . |
| 1 |
| |
+------------------+
At 3ns, the photon emitted from B has reached R1 , hence has been absorbed by this receiver. R1 is
now activated.
+------------------+
| |
| B |
| A... . 0 |
| . |
| 1 |
| |
+------------------+
At 13ns, R0 is activated as it absorbs the photon emitted from A .
+------------------+
| |
| B |
| A............0 |
| . |
| 1 |
| |
+------------------+
Running the circuit is now completed as all photons have been absorbed.
By the end of this feature, you will have a run program that can optionally run a circuit given that a
-RUN-MY-CIRCUIT flag is included as an argument. If it is not there, it will not run the circuit. Below is
an example run through of the program.
We prepended the inputs with a # symbol so you can clearly see what the inputs to the program are.
$ python3 run.py -RUN-MY-CIRCUIT
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
<RUN-MY-CIRCUIT FLAG DETECTED!>
Setting pulse sequence...
-- (A, B)
Line 1: #A 100 E
-- (B)
Line 2: #B 256 S
Pulse sequence set.
========================
RUNNING CIRCUIT...
========================
0ns: Emitting photons.
A: 100THz, East
B: 256THz, South
5ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A...... 0 |
| . |
| 1 |
| |
+------------------+
10ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A.......... 0 |
| . |
| 1 |
| |
+------------------+
13ns: 2/2 receiver(s) activated.
+------------------+
| |
| B |
| A............0 |
| . |
| 1 |
| |
+------------------+
Activation times:
R1: 3ns
R0: 13ns
Total energy absorbed:
R1: 1.06eV (1)
R0: 0.41eV (1)
========================
CIRCUIT FINISHED!
========================
Here are a few notes about the output above:
When setting the pulse sequence, you may have noticed the lines -- (A, B) and -- (B) .
These are showing the remaining emitters to set the pulse sequence for after each input read
from the pulse_sequence.in file.
When running the circuit, the state of the board is printed every 5ns, along with how many
receivers have been activated. We print the circuit one last time when all photons have been
absorbed, which in this case is at 13ns.
The activation times are printed in ascending order. You can see R1 is first which was activated
at time 3ns. Next was R0 which was activated at 13ns.
Next, the total energy absorbed is printed in descending order. The energy absorbed is
displayed in electronvolts (eV) and the number following it is the number of photons absorbed
by the receiver. In this case, R1 stores 1.06eV and absorbed 1 photon. Similarly, R2 stores
0.41eV and absorbed 1 photon
You will find out how to convert THz to electronvolts (eV) in RUN-MY-CIRCUIT . For now, just know that
a higher frequency (THz) means a higher energy (eV).
4. ADD-MY-MIRRORS
ADD-MY-MIRRORS allows the user to add mirrors into the circuit. Mirrors are able to reflect photons
off its surface which changes the direction in which the photons travel. Mirrors are a component just
like emitters and receivers.
Mirrors are given in the format <symbol> <x> <y> , similar to emitters and receivers. There are 4
different types of mirrors which include / , \ , > and ^ . We won't go in the details for each mirror,
just know that it will reflect photons off it depending on both the type of mirror and the direction of
the photon. Below is an example valid input of a mirror.
\ 2 5
This would create a mirror / and place it at position (2, 5) on the circuit board. Since mirrors
aren't uniquely labelled (you can have multiple mirrors with the same symbol), users can enter as
many mirrors as they want, as long as there is space for it. Once they are finished, they can stop
adding mirrors by entering END MIRRORS .
By the end of this feature, you will have a run program that can optionally include mirrors given that
the -ADD-MY-MIRRORS flag is included as an argument. If it is not there, it will not involve mirrors in
the program. Below is an example run through of the program.
We prepended the inputs with a # symbol so you can clearly see what the inputs to the program are.
$ python3 run.py -RUN-MY-CIRCUIT -ADD-MY-MIRRORS
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
<ADD-MY-MIRRORS FLAG DETECTED!>
Adding mirror(s)...
> #\ 2 5
> #^ 5 5
> #/ 5 4
> #\ 11 1
> #> 11 4
> #/ 15 4
> #END MIRRORS
6 mirror(s) added.
+------------------+
| |
| B \ |
| A 0 |
| |
| / 1 > / |
| \ ^ |
+------------------+
<RUN-MY-CIRCUIT FLAG DETECTED!>
Setting pulse sequence...
-- (A, B)
Line 1: #A 100 S
-- (B)
Line 2: #B 256 E
Pulse sequence set.
========================
RUNNING CIRCUIT...
========================
0ns: Emitting photons.
A: 100THz, South
B: 256THz, East
5ns: 0/2 receiver(s) activated.
+------------------+
| |
| B..\ |
| A . 0 |
| . . |
| . / 1 > / |
| \..^ |
+------------------+
10ns: 1/2 receiver(s) activated.
+------------------+
| |
| B..\ |
| A . 0 |
| . . |
| . /..1 >.../ |
| \..^ |
+------------------+
12ns: 2/2 receiver(s) activated.
+------------------+
| |
| B..\ |
| A . 0 |
| . . . |
| . /..1 >.../ |
| \..^ |
+------------------+
Activation times:
R1: 10ns
R0: 12ns
Total energy absorbed:
R0: 1.06eV (1)
R1: 0.41eV (1)
========================
CIRCUIT FINISHED!
========================
SET-MY-CIRCUIT (5 marks)
Introduction
A circuit board is defined on a two dimensional plane.
+------------------+
| |
| |
| |
| |
| |
| |
+------------------+
Each circuit can have up to 10 emitters and 10 receivers. Photons are emitted from emitters A to J
which may be received by a receiver R0 to R9 . The receivers on the board are displayed by their
number.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
We can see that the circuit board has size 18x6 . The size does not include the border around the
board.
These are the positions of the emitters and receivers. The top left corner has position (0, 0) .
A : (2, 2)
B : (8, 1)
R0 : (15, 2)
R1 : (8, 4)
This should be enough information to cover the context of this feature, but you can scout for
additional information in Assignment Breakdown under Section 1. SET-MY-CIRCUIT if needed.
Your Task
The SET-MY-CIRCUIT feature will allow you to setup a circuit board just like the example in the above
section. In an implementation perspective, you'll be able to make a LaserCircuit instance and add
your own Emitter and Receiver instances into it. You will also be able to output the board on the
screen.
In this feature, you will be adding implementation to the following:
Emitter class
Receiver class
BoardDisplayer class
LaserCircuit class
There may be some functionalities in these classes that will be skipped. It will be specified what you need to
implement for this feature.
1. Emitter
An Emitter instance represents a laser which emits a photon with a frequency (THz) and direction
(either right or downwards).
These are the instance attributes of an Emitter instance.
When initialising an Emitter instance, it is given a symbol from A to J which is how this emitter
will be shown on the board. The emitter is given an x and y position which is its position on the
board. At the start, frequency is 0 and direction is None . This is later set by the pulse sequence
once we get up to running the circuit, hence the reason why pulse_sequence_set also starts false.
component_type is a constant with value 'emitter' which acts as an identity string for all emitters.
These are the instance methods of an Emitter instance that you'll need to implement for this
feature.
2. Receiver
A Receiver instance represents a photodetector that charges up by absorbing photons. When a
receiver absorbs a photon, it becomes activated in which it is charged with the photon's energy. An
activated receiver can keep absorbing more photons to store more energy. A receiver stores
information about the number of photons it has absorbed, the total energy it is charged with and
when it was activated. The amount of energy a receiver is charged with directly corresponds to the
frequency of all photons it absorbs summed up.
These are the instance attributes of a Receiver instance.
When initialising a Receiver instance, it is given a symbol from R0 to R9 . The number ( 0 to 9 ) is
what will be used to show the receiver on the board. The receiver is given an x and y position which
is its position on the board. At the start, total_energy is 0.0, photons_absorbed is 0 as it starts with
no photons absorbed. Similarly, activated is false and activation_time is 0 as it must absorb a
photon to become activated. component_type is a constant with value 'receiver' which acts as an
identity string for all receivers.
These are the instance methods of a Receiver instance that you'll need to implement for this
feature.
3. BoardDisplayer
A BoardDisplayer instance is responsible for displaying the circuit board. It's considered a helper
class as it does not assist with the functionality of the circuit. Its only purpose is to store a list of list of
strings (2 dimensional list of strings) representing the board. Each time a component is added to the
circuit, the BoardDisplayer instance is updated to store the component's symbol in its assigned
position in the 2D list.
These are the instance attributes of a BoardDisplayer instance.
When initialising a BoardDisplayer instance, it is given a width and height which will then be
used to initialise an empty board of that size.
These are the instance methods of a BoardDisplayer instance that you'll need to implement for this
feature.
Below we will provide clarifications for some of the instance methods to implement.
3.1 create_board
The create_board method takes in a width and height and will create a list of list of strings
representing an empty board of size width x height . As example, let's say width is 6 and height
is 3. Then the function needs to return the following:
[
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ']
]
You can see it's one list containing 3 inner-lists, representing our height. These are the rows.
Each row contains 6 elements, representing the width. These are the columns. Each element is a
single space, representing an empty cell on the board.
In the BoardDisplayer constructor, the board instance attribute should be given the return value of the
create_board method.
3.2 add_component_to_board
The add_component_to_board method takes in a component and adds its symbol on the board at
its assigned position.
It shouldn't scare you that it can accept different types of components. They all have common properties, that
being their symbol , x and y value. Since every component has these attributes, it doesn't actually matter
what component is passed in, you can treat them all the same.
Let's take the empty board we just made, and say we call the add_component_to_board method
twice. In the first call, we passed an emitter with symbol A and position (3, 1) and in the second
call, we passed a receiver with symbol R9 and position (0, 2) . Then, the board attribute will now
look like this.
[
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', 'A', ' ', ' '],
['9', ' ', ' ', ' ', ' ', ' ']
]
A is at board[1][3] and R9 is at board[2][0] .
To access position (x, y) on the board, it is board[y][x] as the first index represents the row (height) and
the second index represents the column (width).
3.3 print_board
For print_board , it will translate the board attribute into output. Let's take the example above
where we just put in an emitter and a receiver. After calling the method, it would print the following:
+------+
| |
| A |
|9 |
+------+
You can see the border is not included in the size (it wraps around the board).
4. Laser Circuit
A LaserCircuit instance is responsible for storing all components of the circuit and handling the
computation of running the circuit. It's responsible for delegating tasks to the specific components
e.g. making each emitter emit a photon, getting each photon to move and interact with components,
etc. In general, this class is responsible for handling any task related to the circuit.
These are the instance attributes of a LaserCircuit instance.
When initialising a LaserCircuit instance, it is given a width and height used to create the
board_displayer . The laser circuit initially starts with 0 components, so the list of emitters ,
receivers and mirrors start empty. Similarly, the list of photons start empty as no photons have
been emitted. clock starts at 0, this will increment once we begin running the circuit.
These are the instance methods of a LaserCircuit instance that you'll need to implement for this
feature.
4.1 print_board
The print_board method is straightforward. We simply call the print_board method of
board_displayer . This is so when we have a LaserCircuit instance, we can easily print the board.
4.2 get_collided_emitter
The get_collided_emitter checks if entity has the same x and y value as any emitter in the
circuit and returns that emitter if there exists a collision, else it returns None . Remember that all
components and photons have an x and y value, so we shouldn't need to worry about what specific
entity was passed in to check the collision. This also applies for get_collided_receiver in the
context that we are checking receivers instead of emitters.
Below are more methods that need to be implemented for this feature.
4.3 add_emitter
For add_emitter , here are the error messages for the checks you must perform.
Note from the method description that if at any point an error occurs, you return False . As example, if the
emitter is out-of-bounds, after printing the error message you return False , meaning the remaining 2 checks
are skipped.
Below are more methods that need to be implemented for this feature.
4.4 add_receiver
Similarly, here are the error messages for the checks you must perform. They mostly borrow from
add_emitters .
Below are the remaining methods that need to be implemented for this feature.
Let's run through of an example of initialising a LaserCircuit instance, printing the board, then
adding some components and printing the board again. We show an example of each error message
that can occur when adding a component into the circuit.
The following is run in a Python interactive session.
>>> from emitter import Emitter
>>> from receiver import Receiver
>>> from laser_circuit import LaserCircuit
# create the circuit and print board
>>> circuit = LaserCircuit(18, 6)
>>> circuit.print_board() # should initially be empty board
+------------------+
| |
| |
| |
| |
| |
| |
+------------------+
# create our emitters
>>> e1 = Emitter('A', 2, 2)
>>> e2 = Emitter('B', 8, 1)
# make an extra emitter to show error
>>> e3 = Emitter('C', 8, 1)
# add the emitters and print the board
>>> circuit.add_emitter(e1)
>>> circuit.add_emitter(e2)
>>> circuit.add_emitter(e3)
Error: position (8, 1) is already taken by emitter 'B'
>>> circuit.print_board()
+------------------+
| |
| B |
| A |
| |
| |
| |
+------------------+
# create out receivers
>>> r1 = Receiver('R0', 15, 2)
>>> r2 = Receiver('R1', 8, 4)
# make extra receivers to show error
>>> r3 = Receiver('R1', 9, 0)
>>> r4 = Receiver('R1', 0, 13)
# add the receivers and print the board
>>> circuit.add_receiver(r1)
>>> circuit.add_receiver(r2)
>>> circuit.add_receiver(r3)
Error: symbol 'R1' is already taken
>>> circuit.add_receiver(r4)
Error: position (0, 13) out-of-bounds on 18x6 circuit board
>>> circuit.print_board()
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
GET-MY-INPUTS (5 marks)
Introduction
In SET-MY-CIRCUIT , we implemented some classes to provide the base for building our circuit. Now
we want to allow the user to provide inputs to the program in which they will be able to create a
circuit to their own specifications.
On top of what's covered on Assignment Breakdown under Section 2. GET-MY-INPUTS , we will also
be covering input parsing. By this, we mean when users enter any input, whether it be for the board
size, emitters, or receivers, you will need to parse the input to validate that the input is correct.
An example of input parsing is shown below, where the user enters an incorrect size for the circuit
board multiple times.
Inputs are prepended with a # in the output snippets for your own clarity.
Creating circuit board...
> #18
Error: <width> <height>
> #18 six
Error: height must be an integer
> #18 6
18x6 board created.
Until the user enters a valid size, the program will keep asking for input. You can see entering only the
width displayed an error message Error: <width> <height> . The program then asked for input
again, in which the user incorrectly entered the height, displaying an error message Error: height
must be an integer . On the third input, the user entered a valid size, creating the board where the
program can proceed.
This is just a small look at the input parsing, there will be more to cover.
Your Task
The GET-MY-INPUTS feature will allow the user to set up a circuit to their specifications using inputs.
From an implementation perspective, you'll be making a LaserCircuit instance and adding
Emitter and Receiver instances into it based on the user's inputs. Then, you'll display the circuit
board to the user.
In this feature, you will be adding implementation to the following:
input_parser module
run module
There may be some functionalities in these modules that will be skipped. It will be specified what you need to
implement for this feature.
1. Input Parser
The input_parser module is responsible for parsing the inputs of the program. We define parsing as
checking the validity of what has been entered to determine if it's valid. If it's not valid, an appropriate
error message should be printed to indicate what was wrong with the input. Whenever we retrieve
input in the program, we should be using functions from this module to validate it.
These are the functions in the input_parser module that you'll need to implement for this feature.
1.1 parse_size
For parse_size , here are the error messages for the checks you must perform.
Note from the method description that if at any point an error occurs, you return None . As example, if the first
check passes but the second check fails (where width is not an integer), we return None , meaning the
remaining 3 checks are skipped.
Here are some examples of running parse_size in a Python interactive session.
Output Snippet 1 - Calling parse_size in a Python interactive session.
>>> from input_parser import parse_size
# Error 1
>>> size = parse_size('6')
Error: <width> <height>
>>> size
None
# Error 2
>>> size = parse_size('six 3')
Error: width is not an integer
>>> size
None
# Error 3
# Note: width is not positive (Error 4), however Error 3 is checked first
>>> size = parse_size('-6 three')
Error: height is not an integer
>>> size
None
# Error: 4
# Note: height is not positive (Error 5), however Error 4 is checked first
>>> size = parse_size('-6 -3')
Error: width must be greater than 0
>>> size
None
# Error 5
>>> size = parse_size('6 0')
Error: height must be greater than 0
>>> size
None
# No errors
>>> size = parse_size('6 3')
>>> size
(6, 3)
Below are more methods that need to be implemented for this feature.
1.2 parse_emitter
For parse_emitter , here are the error messages for the checks you must perform.
The checks and error messages are identical for parse_receiver , with the only exception being when
checking the symbol. Instead of A to J , we need to check from R0 to R9 ,. and the error message would be
Error: symbol is not between R0-R9 .
Here are some examples of running parse_emitter in a Python interactive session.
Output Snippet 2 - Calling parse_emitters in a Python interactive session.
>>> from input_parser import parse_emitter
# Error 1
>>> emitter = parse_emitter('A 0')
Error: <symbol> <x> <y>
>>> emitter
None
# Error 2
>>> emitter = parse_emitter('K 0 0')
Error: symbol is not between 'A'-'J'
>>> emitter
None
# Error 3
# Note: y is not an integer (Error 4), however Error 3 is checked first
>>> emitter = parse_emitter('A B C')
Error: x is not an integer
>>> emitter
None
# Error 4
>>> emitter = parse_emitter('A 0 zero')
Error: y is not an integer
>>> emitter
None
# Error 5
>>> emitter = parse_emitter('A -1 0')
Error: x cannot be negative
>>> emitter
None
# Error 6
>>> emitter = parse_emitter('A 0 -3')
Error: y cannot be negative
>>> emitter
None
# No errors
>>> emitter = parse_emitter('A 0 0')
>>> emitter
<Object: Emitter> # an emitter instance, just shown as this for readability
2. run
The run module is responsible for running the entire program. In summary, it needs to take in the
inputs, process them into creating the circuit, and then print the board. A large part of the
implementation will revolve around calling functions and creating instances of classes we have
implemented from previous works.
These are the functions of the run module that you'll need to implement for this feature.
2.1 initialise_circuit
This function is responsible for getting the inputs of the board size, emitters, and receivers and
creating the circuit. These are the general steps for this function.
1. Gets input from the user for the board size to create a circuit.
2. Until the maximum amount of emitters are added (10) or END EMITTERS is inputted by the
user, for each input:
1. Create a new emitter with the specified values.
2. Add the new emitter into the circuit.
3. Until the maximum amount of receivers is added (10), or END RECEIVERS is inputted by the
user, for each input:
1. Create a new Receiver instance with the specified values.
2. Add the new emitter into the circuit.
The steps above don't include the instance when an invalid input is given. If an invalid input is given,
it will simply print an error message stating the cause of the error and ask for input again.
1. Size
Inputs are prepended with a # in the output snippets for your own clarity.
This is an example of entering the board size to create the circuit board.
Creating circuit board...
> #18 6
18x6 board created.
Now let's look at an example with some errors:
Creating circuit board...
> #18 0
Error: height must be greater than zero
> #18
Error: <width> <height>
> #18 6
18x6 board created.
You can see that it will continuously take in input until the user enters a valid size. Notice the above is
what we have just implemented in parse_size , how convenient!
2. Emitters
This is an example of entering the values to add new emitters to the circuit board.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
You can see that the user explicitly ended up adding the emitters with END EMITTERS . Without it, it
would keep prompting for input until 10 emitters have been added (even if there are 100 errors, it
will keep prompting until 10 emitters are added).
Here's another example where they add 10 emitters to the circuit board.
Adding emitter(s)...
> #A 0 1
> #B 1 2
> #D 2 1
> #E 3 3
> #C 4 1
> #H 5 5
> #G 6 1
> #F 7 4
> #I 8 1
> #J 9 6
10 emitter(s) added.
Now let's look at an example with some errors:
Adding emitter(s)...
> #A 2 2
> #A 3 4
Error: symbol 'A' is already taken
> #B 2 2
Error: position (2, 2) is already taken by emitter 'A'
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
You can see that the program will continuously take in input until either 10 emitters are added, or
they end it explicitly with END EMITTERS . Notice the above is what we have just implemented in
parse_emitter and add_emitter , how convenient!
3. Receivers
This is an example of entering the values to add new emitters to the circuit board.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
You can see it's essentially the same input format as adding emitters.
Adding receiver(s)...
> #R0 15 2
> #R0 8 4
Error: symbol 'R0' is already taken
> #R1 15 2
Error: position (15, 2) is already taken by receiver 'R0'
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
4. Putting it Together
This is an example output snippet of what this function does from start to end.
Output Snippet 4 - Calling initialise_circuit in a Python interactive session.
Inputs are prepended with a # in the output snippets for your own clarity.
>>> from run import initialise_circuit
>>> circuit = initialise_circuit()
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #B 8 1
> #A 2 2
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R1 8 4
> #R0 15 2
> #END RECEIVERS
2 receiver(s) added.
>>> circuit
>>> <Object: LaserCircuit>
The function will return a LaserCircuit instance with the following values:
A width of 18 and height of 6 .
Emitters A with position (2, 2) and B with position (8, 1) in the circuit's emitters .
Receivers R1 with position (8, 4) and R0 with position (15, 2) in the circuit's receivers .
The BoardDisplayer instance should inheritably have a fully up-to-date board to display.
Here is a detailed look at the LaserCircuit returned from the example above (we exclude the self
keyword to avoid clutter).
laser_circuit:
emitters: [<Object: Emitter A>, <Object: Emitter B>]
receivers: [<Object: Receiver R0>, <Object: Receiver R1>]
photons: []
mirrors: []
width: 18
height: 6
board_displayer: <Object: BoardDisplayer>
width: 18
height: 6
board: [
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'B', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
[' ', ' ', 'A', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', ' '
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
]
clock: 0
You can see that the emitters and receivers are sorted, regardless of what order it is added in from
the inputs.
2.2 main
The main function takes in one argument args which is the command line arguments of the
program. For now, you can ignore this. This function for now will simply just call
initialise_circuit , get the LaserCircuit instance and use its methods to print the board. It
should be the shortest function in this module!
Output Snippet 6 - Output from calling main function
Inputs are prepended with a # in the output snippets for your own clarity.
>>> from run import main
>>> main([])
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
5. Output Examples
When running the run program, it simply should be just calling the main function.
Output Snippet 7 - Output from run program
Inputs are prepended with a # in the output snippets for your own clarity.
$ python3 run.py
Creating circuit board...
> #12 0
Error: height must be greater than zero
> #12 4
12x4 board created.
Adding emitter(s)...
> #END RECEIVERS
Error: <symbol> <x> <y>
> #D 2 2
> #A 4 1
> #J 9 0
> #I 11 2
> #E 7 1
> #B 20 20
Error: position (20, 20) is out-of-bounds of 12x4 circuit board
> #B 0 0
> #C 8 1
> #F 5 2
> #G 1 0
> #H 10 1
10 emitter(s) added.
Adding receiver(s)...
> #R0 9 0
Error: position (9, 0) is already taken by emitter 'J'
> #R0 0 3
> #R1 1 3
> #R2 2 3
> #R3 4 3
> #R4 5 3
> #R5 7 3
> #R6 8 3
> #R7 9 3
> #R8 10 3
> #R9 11 3
10 receiver(s) added.
+------------+
|BG J |
| A EC H |
| D F I|
|012 34 56789|
+------------+
RUN-MY-CIRCUIT (7 marks)
Introduction
In GET-MY-INPUTS , we have allowed users to set up a circuit based on the their inputs. Now we want
to be able to run the circuit, which comprise of photons travelling across the circuit board and
interacting with components.
Please have a read of the Assignment Breakdown under Section 3. RUN-MY-CIRCUIT as it covers the
basis for this entire feature. There are only some very small parts on top of what's covered on that
page that need to be implemented which will be explained once we get to it.
This feature requires the most implementation out of the rest, so we have broke it down into two
parts for simplicity. Part 1 (15%) focuses on implementing individual functionalities such as moving
photons, displaying photon paths, energy conversions, etc. Part 2 (20%) will then cover putting
everything together so we can run the circuit from start-to-end when running the program.
Your Task
The RUN-MY-CIRCUIT feature will allow the user to run their circuit. They can optionally choose to do
so by adding the -RUN-MY-CIRCUIT flag as an argument. You can view it as an extension of the
existing program.
In Part 1 (Sections 1-5), you will be adding implementation to the following:
Photon class
Emitter class
Receiver class
BoardDisplayer class
LaserCircuit class
This will cover implementing the individual functionalities for running the circuit.
In Part 2 (Sections 6-8), you will be adding implementation to the following:
LaserCircuit class
input_parser module
run module
This will cover putting everything together so we can run the circuit from start-to-end when running
the program.
Part 1 will cover implementing the logic for the photons and components.
1. Photon
A Photon instance is a particle of light that is emitted by an Emitter instance and travels along the
circuit board. Photons have a frequency (THz) and direction in which they move in. They can interact
with components in the circuit such as a Receiver instance in which it can be absorbed.
These are the instance attributes of a Photon instance.
When initialising a Photon instance, it takes in a x and y position, as well as a frequency and
direction . These values will come from the emitter it is emitted from. Initially, absorbed is false. All
photons have a dot as their symbol.
These are the instance methods of a Photon instance that you'll need to implement for this feature.
1.1 move
When a photon moves, it moves one unit depending on its direction.
direction : 'N' , y is decremented (moving up).
direction : 'E' , x is incremented (moving right).
direction : 'S' , y is incremented (moving down).
direction : 'W' , x is decremented (moving left).
Here are some examples of running move in a Python interactive session.
Output Snippet 1 - Calling instance method move in Python interactive session.
>>> from photon import Photon
>>> photon = Photon(0, 0, 100, 'S')
# initial position
>>> photon.x, photon.y
(0, 0)
# move up
# 2, 2 is the board with and height respectively
>>> photon.move(2, 2)
>>> photon.x, photon.y
(0, 1)
# move right
>>> photon.set_direction('E')
>>> photon.move(2, 2)
>>> photon.x, photon.y
(1, 1)
# going out of bounds
>>> photon.move(2, 2)
>>> photon.x, photon.y
(1, 1)
# notice the position is still (1, 1), we do not allow it to move out of the circuit
# now the photon is absorbed
>>> photon.absorbed
True
1.2 interact_with_component
You may find it easier to implement this method once you have at least read the implementation for
Receiver in section 3.
Below are the remaining methods that need to be implemented for this feature.
2. Emitter
You will be finishing the implementation of the Emitter instance. These methods relate to the
running of the circuit.
Below we will provide clarifications for some of the instance methods to implement.
2.1 emit_photon
For further clarification on emit_photon() , this method needs to create and return a Photon
instance. This photon should inherit the position, frequency, and direction of the emitter.
Output Snippet 2 - Calling instance method emit_photon of Emitter instance in Python interactive
session.
>>> from emitter import Emitter
>>> emitter = Emitter('A', 0, 0)
>>> emitter.set_pulse_sequence(100, 'N')
# get the photon emitted from the emitter
>>> photon = emitter.emit_photon()
>>> photon.x, photon.y
(0, 0)
>>> photon.frequency
100
>>> photon.direction
'N'
2.2 __str__
For the __str__ method, printing the emitter instance itself should output the custom format string.
As an example, if we had an emitter A that has frequency set to 100 and the direction set to 'N' ,
this would be the output.
Output Snippet 3 - Printing an Emitter instance in Python interactive session.
>>> from emitter import Emitter
>>> emitter = Emitter('A', 0, 0)
>>> emitter.set_pulse_sequence(100, 'N')
>>> emitter.frequency
100
>>> emitter.direction
'N'
>>> print(emitter)
A: 100THz, North
3. Receiver
You will be finishing the implementation of the Receiver instance. These functions and methods
relate to the running of the circuit.
Below we will provide clarifications for some of the instance methods to implement.
3.1 convert_frequency_to_energy
We promised we would not involve any physics in this assignment, so we have implemented this function for
you. However, it cannot hurt to get some context behind its workings. 😄
To calculate how much energy a photon carries, we use a constant that relates a photon's frequency
to its energy in joules known as the Planck Constant h.
h = 6.62607015 × 10 J ⋅ −34 Hz−1
The unit represents joules per hertz. Frequency (Hz) is defined as a cycle per second, hence
can be substituted as or more intuitively .
J ⋅ Hz−1 Hz−1
s−1 1/s
The Planck Constant is a very significant physical constant in quantum mechanics as it describes the
behaviour of particles on the atomic scale. In summary, it defines the quantum nature of energy and
relates the energy of a photon to its frequency. It is this theory that electromagnetic energy cannot
flow continuously, and must transfer energy through discrete values, allowing us to mathematically
compute how the universe operates on an atomic scale such as stellar evolution (the lifetime of a
star).
Let's say that we have a photon with 256THz. To calculate its energy we have the given equation
, where given Planck's constant and the frequency of the photon in hertz (Hz), we can
calculate its energy in joules (J). Since 1THz is equal to Hz, we simply just need to multiply it
by a factor of 12 to get our units correct, giving us the following equation.
E = h × f h f
E 1012
E = h × 256 × 1012
The final part is that is in joules. We want it to be in electronvolts (eV). By definition, one
electronvolt is the energy acquired by an electron when it is accelerated through a potential difference
of 1 volt. We have it that 1eV is equal to . So we simply need to divide by
this amount.
E
1.60217662 × 10 J −19 E
eV = 1.60217662 × 10−19J
E
Hence, let us say we want to convert 256THz to electronvolts. The equation is given as follows.
eV = = 1.60217662 × 10−19J
6.62607015 × 10 × 256 × 10 J ⋅ Hz −34 12 −1
1.06 2 ( ) dp
Knowing any of the above is likely not helpful for the assignment, but you will need to use this
function to convert frequencies to energy.
3.2 absorb_photon
The absorb_photon is responsible for handling the logic for the receiver absorbing a photon. You will
need to mathematically convert the photon's frequency (THz) to energy (eV) so we can correctly add
to our total energy.
Output Snippet 4 - Calling instance method absorb_photon of Receiver instance in Python interactive
session
>>> from receiver import Receiver
>>> from photon import Photon
# create our receiver
>>> receiver = Receiver('R0', 0, 0)
# absorb first photon
>>> p1 = Photon(0, 0, 256, 'R')
>>> receiver.absorb_photon(p1, 15)
>>> receiver.total_energy
1.058730939663818
>>> receiver.photons_absorbed
1
>>> receiver.activation_time
15
# absorb second photon
>>> p2 = Photon(0, 0, 300, 'D')
>>> receiver.absorb_photon(p2, 25)
# it should be adding to the existing total energy
>>> receiver.total_energy
2.299431259582355
>>> receiver.photons_absorbed
2
# notice that the activation time does not change once it has been set
>>> receiver.activation_time
15
3.3 __str__
Printing the receiver instance itself should result in the formatted string returned by __str__ being
printed. The example below shows a receiver R0 that has absorbed 2 photons, the first having a
frequency of 69THz absorbed at 60ns and the second being 420THz at 120ns.
>>> from receiver import Receiver
>>> from photon import Photon
>>> receiver = Receiver('R0', 0, 0)
>>> p1 = Photon(0, 0, 69, 'R')
>>> p2 = Photon(0, 0, 420, 'R')
>>> receiver.absorb_photon(p1, 60)
>>> receiver.absorb_photon(p2, 120)
>>> print(receiver)
R0: 2.02eV (2)
4. BoardDisplayer
You will be finishing the implementation of the instance methods in the BoardDisplayer class.
This method is very similar to the add_component_to_board method but it is dedicated for Photon
instances. The only key difference in terms of placing the symbol on the board is that it should only
be placed if the cell is empty. The logic is that components have a higher priority being shown in
comparison to a photon, and replacing it with another photon makes no difference.
Let's take this simple example of a board .
[
[' ', ' ', ' ', ' ', ' '],
['A', ' ', ' ', ' ', '0'],
[' ', ' ', ' ', ' ', ' ']
}
This would be the output from the print_board method.
+-----+
| |
|A 0|
| |
+-----+
Let's take the board above and say we call the add_photon_to_board 5 times. In the first call, the
photon passed in has position (0, 1) , the same position as the emitter A . In the second call, the
photon passed in has position (1, 1) , and so on, all the way up to position (4, 1) , which has the
same position as the receiver R0 .. The board attribute will now look like this.
[
[' ', ' ', ' ', ' ', ' '],
['A', '.', '.', '.', '0'],
[' ', ' ', ' ', ' ', ' ']
}
Notice that it does not replace either the emitter or receiver on the board. This would now be the
output from the print_board method.
+-----+
| |
|A...0|
| |
+-----+
When we get to running the circuit, the general idea is that each time a photon moves, we call the
add_photon_to_board method to update our board and show the photon in its new position, but we'll worry
about that later.
5. Laser Circuit
You will need to implement an adder and getter method for the photons.
If you've implemented everything above then congratulations, you have completed Part 1! You have
implemented the basic building blocks for running a circuit, now it's just a matter of putting
everything together.
Part 2 will focus on implementing the LaserCircuit methods to run the circuit. This is possible as it
has knowledge of all components, photons, and the board state. We will then integrate it with our
run program such that from start to end, we can create the user's circuit, and then run it. How fun!
6. Laser Circuit
You will be finishing the implementation of the LaserCircuit class. Implementing these methods
will allow you to run a circuit.
6.1 print_emit_photons
The print_emit_photons method is responsible for producing the output of the photon emission
sorted by their symbol. An example is below.
Output Snippet 5 - Output from calling print_emit_photons method.
0ns: Emitting photons.
A: 100THz, Down
B: 256THz, Right
Note that we already sorted the circuit's emitters by their symbol in GET-MY-INPUTS so no extra
sorting is required.
The method needs to also write the output to a file named emit_photons.out inside
/home/output/ . You can assume /home/output/ exists. It should only include the times, not the
header, so the example below shows what the file will contain.
Output Snippet 6 - File contents of /home/output/emit_photons.out from calling
print_activation_times method.
$ cat /home/output/emit_photons.out
A: 100THz, Down
B: 256THz, Right
6.2 print_activation_times
The print_activation_times is responsible for producing the output of the activation times for
each receiver, sorted in ascending order. It should only include receivers that have been
activated. An example is below.
Output Snippet 7 - Output from calling print_activation_times method.
Activation times:
R1: 3ns
R0: 13ns
You have been provided a function in the sorter module named
sort_receivers_by_activation_time that will take in a list of Receiver instances and return a new
list of the same instances sorted by their activation time in ascending order. However note that it
does not filter out receivers that are not activated, so you will need to do this yourself when printing
the activation times.
The method needs to also write the output to a file named activiation_times.out inside
/home/output/ . You can assume /home/output/ exists. Below is an example that shows what the
file will contain given the above output.
Output Snippet 8 - File contents of /home/output/emit_photons.out from calling
print_activation_times method.
$ cat /home/output/emit_photons.out
A: 100THz, Down
B: 256THz, Right
6.3 print_total_energy
The print_total_energy is responsible for producing the output of the total energy absorbed for
each receiver, sorted in descending order. It should only include receivers that have been
activated. An example is below.
Output Snippet 9 - Output from calling print_total_energy_absorbed method of LaserCircuit
instance.
Total energy absorbed:
R0: 1.06eV (1)
R1: 0.41eV (1)
You have been provided a function in the sorter module named
sort_receivers_by_total_energy that will take in a list of Receiver instances and return a new list
of the same instances sorted by their total energy in descending order. However note that it does not
filter out receivers that are not activated, so you will need to do this yourself when printing the total
energy.
The method needs to also write the output to a file named total_energy_absorbed.out inside
/home/output/ . You can assume /home/output/ exists. Below is an example that shows what the
file will contain given the above output.
Output Snippet 10 - File contents of /home/output/total_energy.out from calling
print_activation_times method.
$ cat /home/output/total_energy.out
R0: 1.06eV (1)
R1: 0.41eV (1)
Below are more methods that need to be implemented for this feature.
6.4 get_collided_component
The get_collided_component method takes in one argument photon which is a Photon instance
and checks if it has collided with any component in the circuit. For now, you will only need to check
for collision with all Emitter and Receiver instances in the circuit.
Hint: We have already implemented the individual checks in get_collided_component and
get_collided_receiver .
6.5 tick
To provide a more general understanding, tick runs the circuit for one nanosecond, you can
imagine it being a single frame of a video game where all calculations are executed and the graphics
are updated to reflect this. Similarly, in this one nanosecond, for each photon that has not been
absorbed yet, we:
1. Move the photon.
2. Update board_displayer to show its new position.
3. If the photon collides with a component, interact with it (if applicable).
After that, we then increment the clock .
Just note that if the circuit is finished running we do not execute any of the above.
6.6 run_circuit
The run_circuit method runs the circuit from start to end. You can consider this the start button for
the circuit. Everything regarding running the circuit should occur in this one method. Let us go
through each step of running a circuit.
Firstly, we begin with the following message.
========================
RUNNING CIRCUIT...
========================
Secondly, each emitter emits a photon. After that, we print each emitter to show what photons are
being emitted, sorted by the symbol of the emitter. We also need to write this to the
/home/output/emitting photons.out output file.
0ns: Emitting photons.
A: 100THz, East
B: 256THz, South
Thirdly, we need to keep calling the tick method in a loop until the circuit is finished. Every 5 ticks,
we need to print how many receivers are activated. Once all photons have been absorbed, we
explicitly show how many receivers are activated and show the final board.
5ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A...... 0 |
| . |
| 1 |
| |
+------------------+
10ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A.......... 0 |
| . |
| 1 |
| |
+------------------+
13ns: 2/2 receiver(s) activated.
+------------------+
| |
| B |
| A............0 |
| . |
| 1 |
| |
+------------------+
Here's a small code snippet to help you with the third step, you are free to modify it.
# this loop keeps running until the circuit is finished running
while not self.is_finished():
# TODO: call tick
# ...
# TODO: handle displaying receivers activated and board (if required)
# HINT: you have a clock keep tracking of how many nanoseconds has passed
# ...
pass
# if we reach here (outside the loop), that means the circuit is finished running
Fourthly, we'll need to print the activation times of the receivers in ascending order and additionally
write the output to the /home/output/absorption_times.out output file.
Activation times:
3ns: R1
13ns: R0
Fifthly, we'll need to print the total energy absorbed of the receivers in descending order and
additionally write the output to the /home/output/absorption_times.out output file.
Total energy absorbed:
R1: 1.06eV (1)
R0: 0.41eV (1)
Lastly, we end with the following message.
========================
CIRCUIT FINISHED!
========================
Putting these steps together, an example output would look like the following snippet below.
Output Snippet 11 - Output from calling run_circuit method of LaserCircuit instance.
========================
RUNNING CIRCUIT...
========================
0ns: Emitting photons.
A: 100Hz, East
B: 256Hz, South
5ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A...... 0 |
| . |
| 1 |
| |
+------------------+
10ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A.......... 0 |
| . |
| 1 |
| |
+------------------+
13ns: 2/2 receiver(s) activated.
+------------------+
| |
| B |
| A............0 |
| . |
| 1 |
| |
+------------------+
Activation times:
3ns: R1
13ns: R0
Total energy absorbed:
R1: 1.06eV (1)
R0: 0.41eV (1)
========================
CIRCUIT FINISHED!
========================
7. Input Parser
You will need to implement one function in this module which is parse_pulse_sequence . This
function is responsible for parsing an input for the pulse sequence. If you don't know what pulse
sequence is, please refer back to Assignment Breakdown under Section 3. RUN-MY-CIRCUIT as it
covers it thoroughly.
Here are the error messages for the checks you must perform.
8. run
In our current implementation, the run program accepts user inputs, creates a circuit board, and
displays it. However, with this feature, users can optionally include a -RUN-MY-CIRCUIT flag in the
command line arguments which will additionally perform the following steps:
1. Read from a pulse_sequnce.in file located in /home/input/ to set the pulse sequence for the
circuit
2. Run the circuit, inclusive of printing outputs and writing them to .out files in the
/home/output/ folder.
These are the functions of the run module that you'll need to implement for this feature.
8.1 is_run_my_circuit_enabled
The is_run_my_circuit_enabled function takes in one argument args which are the command line
arguments of the program and returns whether or not the string -RUN-MY-CIRCUIT is in args . This
function can be used to check if the user wants their circuit to be run.
8.2 set_pulse_sequence
The set_pulse_sequence function takes in two arguments, circuit which is a LaserCircuit
instance and pulse_file which is a file object containing the lines for the pulse sequence. The
function will open the file and process each line one by one to set the pulse sequence. This is an
example contents of the file.
A 100 E
B 256 S
Now let us see setting the pulse sequence with the above pulse_sequence.in file.
Output Snippet 12 - Output from calling set_pulse_sequence with the above pulse file.
Setting pulse sequence...
-- (A, B)
Line 1: A 100 E
-- (B)
Line 2: B 256 S
Pulse sequence set.
After the initial Setting pulse sequence... message, we have -- (A, B) . The lines with the
double dash -- shows emitters that have not had their pulse sequence set. Since we haven't set the
pulse sequence for any of the emitters yet, it shows all emitters. We then read our first line from the
pulse_sequence.in file, in which we have A 100 E , which will set the pulse sequence for the
emitter A , setting its frequency to 100 and direction to 'S' .
Then, it shows -- (B) . Since we've set the pulse sequence for the emitter A , it's showing now that
the emitter B is remaining to set the pulse sequence for. We then read the next line from the
pulse_sequence.in file which is B 256 S , in which it sets the pulse sequence for the emitter B .
Once all emitters have been set, it ends with a concluding message that the pulse sequence has been
set.
Now let's look at an example with some errors. Here are the contents of another pulse file.
A 100
A 100 E
A 100 S
C 256 W
B 256 D
Output Snippet 13 - Output from calling set_pulse_sequence with the above pulse file.
Setting pulse sequence...
-- (A, B)
Line 1: A 100
Error: <symbol> <frequency> <direction>
-- (A, B)
Line 2: A 100 E
-- (B)
Line 3: A 100 S
Error: emitter 'A' already has its pulse sequence set
-- (B)
Line 4: C 256 W
Error: emitter 'C' does not exist
Line 5: B 256 D
Pulse sequence set.
There are 2 new errors that can occur beyond the scope of parsing the line in parse_pulse_sequence
as this requires knowledge of the current state of the circuit. For Line 3, even though it is considered
a valid line from parse_pulse_sequence , since the emitter A has already had its pulse sequence set,
it shows the error message Error: emitter 'A' already has its pulse sequence set .
For Line 4, even though it is considered a valid line when passed into parse_pulse_sequence ,
emitter C does not exist in the circuit, hence showing the error message Error: emitter 'C' does
not exist .
In both Output Snippet 10 and Output Snippet 11, emitter A should have a frequency of 100
and a direction of 'E' and emitter B should have a frequency of 256 and a direction of 'S' .
8.3 main
After setting up the circuit, you need to make it so if the -RUN-MY-CIRCUIT flag is given in the
command line arguments, then display <RUN-MY-CIRCUIT FLAG DETECTED!> and check if the
/home/input/pulse_sequence.in exists. If it does not, your program should print Error: -RUN-MYCIRCUIT flag detected but /home/input/pulse_sequence.in does not exist and finish
execution. Otherwise, your program should set the pulse sequence of the circuit and run it (these
parts you have just implemented).
9. Output Examples
Output Snippet 14 - Run of run.py program.
We have accompanied the output with a display of what the pulse_sequence.in file contains.
Inputs are prepended with a # in the output snippets for your own clarity.
$ cat /home/input/pulse_sequence.in
A 100 E
B 256 S
$ python3 run.py -RUN-MY-CIRCUIT
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
<RUN-MY-CIRCUIT FLAG DETECTED!>
Setting pulse sequence...
-- (A, B)
Line 1: A 100 E
-- (B)
Line 2: B 256 S
Pulse sequence set.
========================
RUNNING CIRCUIT...
========================
0ns: Emitting photons.
A: 100THz, East
B: 256THz, South
5ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A...... 0 |
| . |
| 1 |
| |
+------------------+
10ns: 1/2 receiver(s) activated.
+------------------+
| |
| B |
| A.......... 0 |
| . |
| 1 |
| |
+------------------+
13ns: 2/2 receiver(s) activated.
+------------------+
| |
| B |
| A............0 |
| . |
| 1 |
| |
+------------------+
Activation times:
R1: 3ns
R0: 13ns
Total energy absorbed:
R1: 1.06eV (1)
R0: 0.41eV (1)
========================
CIRCUIT FINISHED!
========================
Here is another example.
Output Snippet 15 - Run of run.py program.
We have accompanied the output with a display of what the pulse_sequence.in file contains.
Inputs are prepended with a # in the output snippets for your own clarity.\
$ cat /home/input/pulse_sequence.in
A 90 S
C 70 S
F 30 S
B 70 E
D 900 E
$ python3 run.py -RUN-MY-CIRCUIT
Creating circuit board...
> #14 3
14x3 board created.
Adding emitters...
> #A 0 0
> #C 13 0
> #D 1 1
> #B 6 2
> END EMITTERS
4 emitter(s) added.
Adding receivers...
> #R0 0 2
> #R2 11 1
> #R1 13 2
> #END RECEIVERS
3 receiver(s) added.
+--------------+
|A C|
| D 2 |
|0 B 1|
+--------------+
<RUN-MY-CIRCUIT FLAG DETECTED!>
Setting pulse sequence...
-- (A, B, C, D)
Line 1: A 90 S
-- (B, C, D)
Line 2: C 70 S
-- (B, D)
Line 3: E 30 S
Error: symbol is not between 'A'-'J'
-- (B, D)
Line 4: B 70 E
-- (D)
Line 5: D 900 E
Pulse sequence set.
========================
RUNNING CIRCUIT...
========================
0ns: Emitting photons.
A: 90THz, South
B: 70THz, East
C: 70THz, South
D: 900THz, East
5ns: 2/3 receiver(s) activated.
+--------------+
|A C|
|.D..... 2 .|
|0 B..... 1|
+--------------+
10ns: 3/3 receiver(s) activated.
+--------------+
|A C|
|.D.........2 .|
|0 B......1|
+--------------+
Activation times:
R0: 2ns
R1: 2ns
R2: 10ns
Total energy absorbed:
R2: 3.72eV (1)
R1: 0.58eV (2)
R0: 0.37eV (1)
========================
CIRCUIT FINISHED!
========================
This is what happens if the file does not exist.
Output Snippet 16 - Run of run.py program.
$ python3 run.py -RUN-MY-CIRCUIT
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
+------------------+
| |
| B |
| A 0 |
| |
| 1 |
| |
+------------------+
<RUN-MY-CIRCUIT FLAG DETECTED!>
Error: -RUN-MY-CIRCUIT flag detected but /home/input/pulse_sequence.in does not exist
ADD-MY-MIRRORS (3 marks)
Introduction
ADD-MY-MIRRORS will allow users to add mirrors into the circuit, an entirely new component that can
reflect photons.
Please have a read of the Assignment Breakdown under Section 4. ADD-MY-MIRRORS as it covers the
basis for this entire feature.
We will now look on how these mirrors work.
Slanted Mirror
The slanted mirrors / and \ will reflect off photons in a 90° angle in the direction that the mirror is
slanting towards.
Here are some simplified examples, where the underlined photon is the starting point.
(1) (2) (3)
.....\ . .
. . .
. . .
. ...../ ...../
Side-Facing Mirror
The side-facing mirrors > and < will reflect off photons travelling vertically into it to travel to where
its pointing. Here are some simplified examples, where the underlined photon is the starting point.
(1) (2) (3) (4)
. >... ..< .
. . . .
>... . . ..<
If a photon is travelling horizontally into it, the photon is absorbed by the mirror and will no longer
move.
(1) (2)
.....> >.....
Vertical-Facing Mirror
The vertical-facing mirrors ^ and v will reflect off photons travelling horizontally into it to travel to
where its pointing. Here are some simplified examples, where the underlined photon is the starting
point.
(1) (2) (3) (4)
. ...v ...B .
. . . .
...^ . . ...B
If a photon is travelling vertically into it, the photon is absorbed by the mirror and will no longer
move.
(1) (2)
. ^
. .
. .
^ .
Sample Mirror Circuits
Example 1
Let's look at one example circuit that uses each mirror.
+------------------+
| A \ |
| / 0 |
| |
| > ^ |
+------------------+
Using a pulse sequence of A 100 E , the paths of the photons is given as follows:
+------------------+
| A...\ |
| . /......0 |
| . . |
| >...^ |
+------------------+
Example 2
Let's now look at another circuit. Receiver R0 will receive two photons at the same time travelling in
the same direction.
+------------------+
| A \ |
| |
| \ \ |
| |
| > 0 |
| |
| / / |
| |
| B / |
+------------------+
We give it the following pulse sequence.
A 100 R
B 256 R
The paths of the photons is given as follows.
+------------------+
| A...\ |
| . |
| \..\ |
| . |
| >.......0 |
| . |
| /../ |
| . |
| B.../ |
+------------------+
This would be the activation times and total energy absorbed.
Activation times:
R0: 19ns
Total energy absorbed:
R0: 1.47ev (2)
Example 3
We can also develop snake-like circuits, where mirrors are directly next to each other. It does not
introduce anything new, but adds visual complexity.
The photon emitted from A would reach R0 at 11ns.
+------------------+
| /\ |
| A.../\...0 |
+------------------+
The photon emitted from C would reach R3 at 16ns.
+------------------+
| /........3 |
| \\ |
| C...^ |
+------------------+
Your Task
The ADD-MY-MIRRORS feature will allow the user to add mirrors into their circuit in which photons can
reflect off its surface when running the circuit. The user can optionally choose to include mirrors by
adding the -ADD-MY-MIRRORS flag as an argument. It is an extension of the existing program.
You will be adding implementation to the following:
Mirror class
Photon class
LaserCircuit class
input_parser module
run module
1. Mirror
Due to the Mirror class having the most simple implementation out of all the components, we will skip the
tables showing the instance attributes and methods as most are straightforward.
A Mirror instance is a component that are able to reflect photons off its surface, changing the
direction in which photons travel.
When initialising a Mirror instance, it is given one of the six symbols / , \ , > , < , ^ or v . The
symbol will determine the type of mirror, impacting how it will reflect photons. The mirror is given an
x and y position which is its position on the board. component_type is given the value 'mirror' .
There is only one instance method that excludes the constructor and getter methods named
reflect_photon .
As example, if the mirror was the slanted mirror / and the photon passed in had a direction of 'E' ,
the photon's new direction should be 'N' . Another example, if the mirror was a side-angled mirror
> and the photon passed in had a direction of 'E' , the direction will remain 'E' but the photon will
now have absorbed set to True .
2. Photon
You will need to extend the functionality to the existing interact_with_component instance method
to handle interacting with Mirror instances. You just implemented this in the previous section!
3. LaserCircuit
You will need to implement the adder and getter method for the circuit's list of mirrors .
For add_mirror , here are the error messages for the checks you must perform.
Since mirrors don't have unique symbols, there are no conflicts with having the same symbol as
other mirrors. This is contrary to emitters and receivers, as these must be uniquely identifiable by
their symbol.
4. Input Parser
You will need to implement the parse_mirror function in input_parser.py to handle validating the
input for a mirror.
Here are the error messages for the checks you must perform.
5. run
If users include a -ADD-MY-MIRRORS flag, the user will be able to add mirrors into the circuit. Believe it
or not, this is all we will need to do as we have already extended our implementation to handle
mirrors if the circuit is run.
You will need to implement the following functions.
Below we will provide clarifications for the functions to implement.
5.1 is_add_my_mirrors_enabled
The is_add_my_mirrors_enabled function takes in one argument args which are the command line
arguments of the program and returns whether or not the string -ADD_MY_MIRRORS is in args . This
function can be used to check if the user wants to add mirrors in their circuit.
5.2 add_mirrors
The add_mirrors function takes in one argument circuit and handles adding the mirrors into the
circuit. This is an example of adding mirrors into the circuit.
Output Snippet 1 - Output from calling add_mirrors in run module.
Inputs are prepended with a # in the output snippets for your own clarity.
Adding mirror(s)...
> #\ 2 5
> #^ 5 5
> #/ 5 4
> #\ 11 1
> #> 11 4
> #/ 15 4
> #END MIRRORS
6 mirror(s) added.
There are no limit on the mirrors users can enter, hence it will keep prompting for inputs until the
user explicitly ends it with END MIRRORS .
Here is another example with some errors.
Output Snippet 2 - Output from calling add_mirrors in run module
Inputs are prepended with a # in the output snippets for your own clarity.
Adding mirror(s)...
> #\ 2 5
> #s 5 5
Error: symbol must be '/', '\', '>', '<', '^' or 'v'
> #^ 5 5
> #/ 5 4
> #\ 11
Error: <symbol> <x> <y>
> #\ 11 1
> #> 11 4
> #< 11 4
Error: position (11, 4) is already taken by mirror '>'
> #/ 15 4
> #END MIRRORS
6 mirror(s) added.
Notice the above is what we have just implemented in parse_mirror and add_mirror , how
convenient!
5.3 main
After adding the receivers in the circuit, you need to make it so if the -ADD-MY-MURRORS flag is given
in the command line arguments, then display <ADD-MY-MIRRORS FLAG DETECTED!> , followed by
adding the mirrors.
6. Output Examples
In the end, users should be able to add mirrors in the circuit if a -ADD-MY-MIRRORS flag is included in
the command line arguments.
Output Snippet 3 - Run of run.py program.
Both inputs and lines from the file are prepended with a # in the output snippets for your own clarity.
$ python3 run.py -RUN-MY-CIRCUIT -ADD-MY-MIRRORS
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
<ADD-MY-MIRRORS FLAG DETECTED!>
Adding mirror(s)...
> #\ 2 5
> #^ 5 5
> #/ 5 4
> #\ 11 1
> #> 11 4
> #/ 15 4
> #END MIRRORS
6 mirror(s) added.
+------------------+
| |
| B \ |
| A 0 |
| |
| / 1 > / |
| \ ^ |
+------------------+
<RUN-MY-CIRCUIT FLAG DETECTED!>
Setting pulse sequence...
-- (A, B)
Line 1: #A 100 S
-- (B)
Line 2: #B 256 E
Pulse sequence set.
========================
RUNNING CIRCUIT...
========================
0ns: Emitting photons.
A: 100THz, South
B: 256THz, East
5ns: 0/2 receiver(s) activated.
+------------------+
| |
| B..\ |
| A . 0 |
| . . |
| . / 1 > / |
| \..^ |
+------------------+
10ns: 1/2 receiver(s) activated.
+------------------+
| |
| B..\ |
| A . 0 |
| . . |
| . /..1 >.../ |
| \..^ |
+------------------+
12ns: 2/2 receiver(s) activated.
+------------------+
| |
| B..\ |
| A . 0 |
| . . . |
| . /..1 >.../ |
| \..^ |
+------------------+
Activation times:
R1: 10ns
R0: 12ns
Total energy absorbed:
R0: 1.06eV (1)
R1: 0.41eV (1)
========================
CIRCUIT FINISHED!
========================
Here is an example where the -ADD-MY-MIRRORS flag is added but not the -RUN-MY-CIRCUIT flag.
Output Snippet 4 - Run of run.py program.
Both inputs and lines from the file are prepended with a # in the output snippets for your own clarity.
$ python3 run.py -ADD-MY-MIRRORS
Creating circuit board...
> #18 6
18x6 board created.
Adding emitter(s)...
> #A 2 2
> #B 8 1
> #END EMITTERS
2 emitter(s) added.
Adding receiver(s)...
> #R0 15 2
> #R1 8 4
> #END RECEIVERS
2 receiver(s) added.
<ADD-MY-MIRRORS FLAG DETECTED!>
Adding mirror(s)...
> #\ 2 5
> #^ 5 5
> #/ 5 4
> #\ 11 1
> #> 11 4
> #/ 15 4
> #END MIRRORS
6 mirror(s) added.
+------------------+
| |
| B \ |
| A 0 |
| |
| / 1 > / |
| \ ^ |
+------------------+
Testing (10 marks)
Design test cases to verify if the set_pulse_sequence is implemented correctly. You should be using
the circuit retrieved by get_my_lasercircuit() in circuit_for_testing.py for your tests.
You will need to create input files to be parsed in the set_pulse_sequence function. There should be
sufficient input files to create at least 6 test cases for the set_pulse_sequence function: 2 positive
test cases, 2 negative test cases and 2 edge cases. Each test case must have an associated input file
(in other words, you need to submit at least 6 input files in total).
Then, implement the test program within the test.py file that uses the input files you have written
and the given circuit to test the set_pulse_sequence function. Provide summarized explanations for
each test case in the accompanying markdown file test_plan.md .
If your testing involves output that will be printed to standard output (such as error messages displayed in the
terminal) and is therefore challenging to test directly with code, please specify what the output would be
under the Expected Error Message(s) (if any) column in the test_plan.md file and this will be
sufficient. However, we welcome any other methodologies.
Code Style
Remember that your code in test.py will have marks allocated based on its code style. The style
guide for this assessment can be found on the official Python website https://peps.python.org/pep0008/.
In addition to the official style guide, you can also refer to these resources:
Code style guide - Part 1
Code style guide - Part 2
Log of Changes
For any changes to the code, you will need to submit your code, then click the ... on the top right and Reset
to Scaffold to bring back the old code. It will override your existing code, so that's why we need to submit to
copy over the code back.
However, the changes as of now is simply miscellaneous and very small. It would be faster to just copy and
paste it over.
No further changes will be made within 7 days of submission due date.
11/04
1:45PM
Python interactive shell at bottom of SET-MY-CIRCUIT page has been corrected to have r2 at
position (8, 4) instead of (6, 4) .
1:23PM
For parse_emitter in output snippet 2 in GET-MY-INPUTS , it was showing that the emitter returned
was a True value.
This has been updated to show an Emitter object.
10/04
1:18PM
The docstring of the receiver.py file on line 9 has a typo, the first word is "abosrb" which has been
changed to "absorb".
2:11AM
The docstring of the get_collided_receiver mentioned to return the collided emitter. This has
been changed to the collided receiver.
08/04
10:05PM
The docstring for the Receiver constructor had the following line:
symbol: str - the symbol of this receiver ('A' to 'J')
This is incorrect as these are the symbol ranges for emitters. This has been updated to:
symbol: str - the symbol of this receiver ('R0' to 'R9')
Submission
Submission Checklist
Don't forget to press the Mark button to submit your work!
The latest submission before the due date will be used for marking. Marks will only be reported based on this
single submission. Request to grade files and derive marks from a combination of different submissions will
not be accepted. It is your responsibility to check that your latest submission is complete.
If you applied for special consideration, you must continue making a submission each day. We will use
the last submission before the approved deadline for marking.
Head to the Submissions section under Introduction to ensure you have met the following criteria. It
specifies what version of Python is ran, the files to be submitted and the author's details to be filled out.
No test case will pass if you are missing files provided in the scaffold, or you provided incorrect author details.
Understanding Test Cases Output
There will be 3 main types of test cases:
[UNIT] - Tests individual functions to check actions such as return values or modification of
attributes of an instance
[IO] - Tests a function to check if the values printed to stdout (terminal) is what was expected
[IO-UNIT] - Mix of Unit and IO where both return values, modification of attributes and stdout
will be tested altogether
Have a look at the examples below for a better understanding!
Example 1 [UNIT]
On the header you can find the information on what is being tested:
SET : The section the test case is part of.
(LaserCircuit) : File being tested.
get_collided_receiver : Function/method being tested.
empty circuit : Brief description of the test case.
You will find the relevant output under Test Case Output where:
A brief description will tell you how your function/method was called. e.g with what
arguments.
Expected is what the the test case was expecting your function to do.
Got is what your function did.
############## <END> ############# is a marker showing the end of the
expected/actual block (not to be printed or returned by your function).
Additional comments will give you additional information on the error. In this case, the
function did not return the correct value.
Example 2 [IO]
The header is similar to the previous example
Highlighted in Green (+): Your program did not produce/output that line
Highlighted in Red (-): Your program produced an incorrect output and should be removed.
Example 3 [IO-UNIT]
The header is similar to the previous example
(stdout)->: This shows what your program should produce after each test case action.
If the IO-testing part of this test case fails, then you will see an additional output similar to [IO]
test cases shown in Example 2.
If the unit-testing part of this test case fails, then you will see an additional output similar to
[UNIT] test cases shown in Example 1.
Style Guide
The style guide for this assessment can be found on the official Python website
https://peps.python.org/pep-0008/.
In addition to the official style guide, you can also refer to these resources:
Code style guide - Part 1
Code style guide - Part 2
Slide Shortcuts
You can Ctrl + Left Click the slides on the left side of the page to easily open on a new tab.
Special consideration
请加QQ:99515681 邮箱:99515681@qq.com WX:codinghelp