Skip to content
copperhead.sh
Get started

Examples / JEE Advanced

Problem 2: JEE (Advanced) 2015, Paper 2

JEE (Advanced) 2015, Paper 2, question 8, written as a fang program: the ten resistors and one 6.5 V battery below, with the one current the paper asks for, I through R, claimed and checked. I = 1 A.

the question as the paper prints it
The question as the paper prints it

The second of the two circuit-analysis examples, and it is here for the thing problem_1/ does not show: a claim worth checking that the paper never asks for. Two of the ten resistors carry nothing at all, and that is the whole trick of the question, so the program claims that too, and the checker decides it beside the answer.

A square with a resistor on each side, three spokes into a centre node, two legs down to the bottom rail, and R in series with the battery feeding it:

R2 Ωin series with the battery; the one the question is about
top, left, right, bottom1 Ω, 6 Ω, 2 Ω, 10 Ωthe four sides of the square
top-left, top-right, bottom-right spokes2 Ω, 8 Ω, 4 Ωinto the centre
left leg, right leg12 Ω, 4 Ωthe lower corners down to the rail

GND1 is the bottom rail: a one-terminal part that marks the node the potentials are measured against, and adds nothing to it.

problem_2.py claims six numbers and nothing else:

v_ground = Parameter("V", default=0 * V, description="the bottom rail")
v_top_left = Parameter("V", default=4.5 * V, description="R's far end")
v_top_right = Parameter("V", default=4 * V, description="the top right corner")
v_centre = Parameter("V", default=4 * V, description="where the spokes meet")
v_bottom_left = Parameter("V", default=3 * V, description="the bottom left corner")
v_bottom_right = Parameter("V", default=3 * V, description="the bottom right corner")

Every current is derived from those by Ohm’s law, so there is one claim to check and not ten. Kirchhoff’s current law is written once per node, and then the answer, and the two facts that explain it:

require(equals(into_top_left, 1 * A)) # I = 1 A through R
require(equals(bottom_left_to_bottom_right, no_current)) # the 10 ohm
require(equals(top_right_to_centre, no_current)) # the 8 ohm

Read the two potentials beside each of them and the trick is in the open: v_bottom_left and v_bottom_right are both 3 V, and v_top_right and v_centre are both 4 V. A resistor bridging equal potentials carries nothing, which takes the 10 Ω and the 8 Ω out of the circuit entirely and leaves the answer a round number.

Six node equations and three claims: nine constraints, and the checker decides all nine and fails none. Move any potential and the node equations fail first, which is what makes the three claims worth anything.

They were not solved in the program. The kernel checks claims; it does not solve linear systems. solve.py elaborates the same graph, has fang.simulation compile a plan and lower it to SPICE, and runs ngspice on the deck fang wrote. GND1 carries no simulation model, so it is named as abstracted, which is what lets the plan compile and what puts the abstraction in the plan’s own assumptions.

The operating point gives every branch: R1 1 A, which is I, with R2 and R10, the 10 Ω and the 8 Ω, at exactly 0, and the rest 0.25 A to 0.75 A. The potentials go into the program, where the constraints judge them. The two paths are independent: ngspice solves, the kernel decides.

The question and the answer are in the graph too

Section titled “The question and the answer are in the graph too”

A netlist says what the circuit is. It does not say what was asked of it, or what came back. Both are entities here. The question is Cites, the answer is Requires, the numbers are Calculates, and a Verifies closes the requirement, so out/rationale.md carries them out of the program with nothing retyped:

system.answer: The current I through R (= 2 Ω) is 1 A MUST, state KNOWN, validation by analysis. Verified by system.answered: PASS by analysis

and every resistor with its value and its current beside it:

r (2 Ω) 1 A, r_top (1 Ω) 0.5 A, r_left (6 Ω) 0.25 A, r_right (2 Ω) 0.5 A, r_bottom (10 Ω) 0 A, r_top_left_spoke (2 Ω) 0.25 A, r_top_right_spoke (8 Ω) 0 A, r_bottom_right_spoke (4 Ω) 0.25 A, r_left_leg (12 Ω) 0.25 A, r_right_leg (4 Ω) 0.75 A

This paper’s question is the integer-answer kind and offers nothing to choose between, which is why the requirement states a number rather than a set of letters. problem_1 beside it does offer four options, and states them.

12 parts, 7 nets, 108 entities, 9 checks, none failed and none undecided.

the schematic, drawn by copperhead from the circuit's netlist
The schematic, drawn by copperhead from the circuit's netlist

The schematic is drawn by copperhead’s drafting engine from this circuit’s netlist, with KiCad’s own library symbols, and it opens in KiCad as figure/problem_2.kicad_sch. KiCad reads back from the sheet exactly the connections the circuit has; draw_figures.py refuses to write one that does not. The engine is built for amplifier stages, and it draws this square of resistors as pieces joined by net labels rather than as the figure the paper draws. Designators are assigned in the order the program names its parts, so R1 is r, the 2 Ω resistor the question asks about, and the rest run R2 to R10 alphabetically by the name they have in the program.

the interconnect view, fang's own projection
The interconnect view, fang's own projection

The interconnect view answers the other question: it is fang’s own projection, drawn by fang view, and it names the parts the way the program does, so r_top_right_spoke is the 8 Ω that turns out to carry nothing.

Terminal window
fang check examples/jee_advanced/problem_2/problem_2.py
fang netlist examples/jee_advanced/problem_2/problem_2.py
fang view examples/jee_advanced/problem_2/problem_2.py interconnect -o interconnect.svg
python examples/draw_figures.py problem_2 # needs copperhead and kicad-cli
python examples/jee_advanced/problem_2/solve.py # needs ngspice on PATH
examples/jee_advanced/problem_2/problem_2.py
"""Ten resistors, one 6.5 V battery, and the single number the paper asks for.
Show 28 more lines
JEE (Advanced) 2015, Paper 2, question 8: "In the following circuit, the
current through the resistor R (= 2 ohm) is I Amperes. The value of I is". The
figure is a square, three spokes meeting at a centre node, two legs down to the
bottom rail, and R in series with the battery feeding the whole thing:
the square r_top (1) and r_bottom (10) across, r_left (6) and
r_right (2) down the sides
the spokes r_top_left_spoke (2), r_top_right_spoke (8) and
r_bottom_right_spoke (4), all meeting at the centre
the legs r_left_leg (12) and r_right_leg (4), from the two lower
corners to the bottom rail
in series r (2), between the battery and the top left corner
Like `jee_advanced` beside it, this is not a board. It is here because the
question is the one a board asks all day -- "is this claim about my circuit
true?" -- and the kernel answers it the same way: the potentials are a value,
Kirchhoff's current law is a constraint, and the paper's claim is a constraint
the checker decides rather than a comment nobody re-reads.
Two of the ten resistors turn out to carry nothing at all, and that is the
whole trick of the question. Those two are claimed here as well, because a fact
that explains the answer is worth checking beside it.
The potentials below were not solved here. `solve.py` beside this file lowers
the graph to SPICE through `fang.simulation` and runs ngspice on it; the
numbers it returns are written in, and the constraints are what judge them.
"""
from fang.constraints import Arithmetic, Comparison, Literal, Node
from fang.interfaces import Pin, PinMap
from fang.lang import (
A,
Electrical,
Ohm,
Parameter,
ParameterRef,
Part,
System,
V,
require,
)
from fang.parts import Resistor, TwoPin
from fang.rationale import Calculates, Cites, Requires, Verifies
# --------------------------------------------------------------------------
# Writing the expression tree out
# --------------------------------------------------------------------------
#
# A parameter reference builds a node from one operator: `a - b` is an
# expression, and so is `a / b`. Kirchhoff needs them nested, and a node is not
# itself an operand of Python's operators, so the tree is written out. That is
# not a workaround. Every node checks its own dimensions as it is constructed,
# so a term that divides a voltage by the wrong parameter is rejected where it
# is written, not where it is evaluated.
def _node(value) -> Node:
if isinstance(value, Node):
return value
if isinstance(value, ParameterRef):
return value._node()
return Literal.of(value)
def total(*terms) -> Arithmetic:
"""The sum of the currents named. Dimensions must agree."""
return Arithmetic("add", tuple(_node(term) for term in terms))
def across(here, there) -> Arithmetic:
"""The potential difference from one node to another."""
return Arithmetic("sub", (_node(here), _node(there)))
def through(difference, resistance) -> Arithmetic:
"""Ohm's law: the current a difference drives through a resistance."""
return Arithmetic("div", (_node(difference), _node(resistance)))
def equals(left, right) -> Comparison:
return Comparison("eq", (_node(left), _node(right)))
# --------------------------------------------------------------------------
# The parts
# --------------------------------------------------------------------------
class Battery(TwoPin):
"""An ideal EMF, no internal resistance. Pin 1 is the positive terminal."""
designator_prefix = "V"
voltage = Parameter("V")
class GroundReference(Part):
"""The node every potential below is measured against.
Show 4 more lines
One terminal and no value: it marks a node rather than adding anything to
it, and a one-terminal part emits no device into a simulation deck.
"""
designator_prefix = "GND"
node = Electrical()
PIN1 = Pin("1", role="ground", number="1")
pinmap = PinMap({"node.line": "1"})
class Ladder(System):
"""The figure, then the claim, then the law that judges it."""
# -- the question, and the answer it asks for --------------------------
#
# A netlist says what the circuit is; it does not say what was asked of it
# or what came back. Both are entities here, so `out/rationale.md` carries
# them out of the program and nothing has to be retyped to say what this
# example concluded.
question = Cites(
"In the following circuit, the current through the resistor "
"R (= 2 ohm) is I Amperes. The value of I is",
document="JEE (Advanced) 2015, Paper 2",
locator="question 8, an integer answer, no options offered",
)
answer = Requires(
"The current I through R (= 2 ohm) is 1 A",
priority="MUST",
validation="analysis",
)
# -- the numbers behind it ----------------------------------------------
node_potentials = Calculates(
"the operating point, taken against the bottom rail",
inputs=("battery", "reference"),
result=(
"top left 4.5 V, top right 4 V, centre 4 V, "
"bottom left 3 V, bottom right 3 V, bottom rail 0 V"
),
requirements=("answer",),
)
branch_currents = Calculates(
"I = (V_here - V_there) / R, once per branch",
inputs=(
"r",
"r_top",
"r_left",
"r_right",
"r_bottom",
"r_top_left_spoke",
"r_top_right_spoke",
"r_bottom_right_spoke",
"r_left_leg",
"r_right_leg",
),
result=(
"r (2 ohm) 1 A, r_top (1 ohm) 0.5 A, r_left (6 ohm) 0.25 A, "
"r_right (2 ohm) 0.5 A, r_bottom (10 ohm) 0 A, "
"r_top_left_spoke (2 ohm) 0.25 A, r_top_right_spoke (8 ohm) 0 A, "
"r_bottom_right_spoke (4 ohm) 0.25 A, "
"r_left_leg (12 ohm) 0.25 A, r_right_leg (4 ohm) 0.75 A"
),
requirements=("answer",),
)
balanced_branches = Calculates(
"a branch between two nodes at equal potential carries no current",
inputs=("r_bottom", "r_top_right_spoke"),
result=(
"r_bottom (10 ohm) bridges 3 V to 3 V and r_top_right_spoke "
"(8 ohm) bridges 4 V to 4 V, so both carry 0 A and what is left "
"is series-parallel, which is why I is a whole ampere"
),
requirements=("answer",),
)
operating_point = Cites(
"ngspice reports 1.000000 A through R1, the 2 ohm the question names",
document="examples/jee_advanced/problem_2/solve.py",
locator="the operating point fang.simulation lowered and ran",
)
answered = Verifies(
"answer",
method="analysis",
evidence=("question", "operating_point"),
result="PASS",
)
# The candidate answer, and the whole of it: every current below is derived
# from these six numbers by Ohm's law, so there is one claim to check and
# not ten.
v_ground = Parameter("V", default=0 * V, description="the bottom rail")
v_top_left = Parameter("V", default=4.5 * V, description="R's far end")
v_top_right = Parameter("V", default=4 * V, description="the top right corner")
v_centre = Parameter("V", default=4 * V, description="where the spokes meet")
v_bottom_left = Parameter("V", default=3 * V, description="the bottom left corner")
v_bottom_right = Parameter("V", default=3 * V, description="the bottom right corner")
battery = Battery(voltage=6.5 * V, package="Battery")
# The resistor the paper asks about, in series with the battery.
r = Resistor(resistance=2 * Ohm, package="R_0805")
# The four sides of the square.
r_top = Resistor(resistance=1 * Ohm, package="R_0805")
r_left = Resistor(resistance=6 * Ohm, package="R_0805")
r_right = Resistor(resistance=2 * Ohm, package="R_0805")
r_bottom = Resistor(resistance=10 * Ohm, package="R_0805")
# The three spokes into the centre node.
r_top_left_spoke = Resistor(resistance=2 * Ohm, package="R_0805")
r_top_right_spoke = Resistor(resistance=8 * Ohm, package="R_0805")
r_bottom_right_spoke = Resistor(resistance=4 * Ohm, package="R_0805")
# The two legs from the lower corners down to the bottom rail.
r_left_leg = Resistor(resistance=12 * Ohm, package="R_0805")
r_right_leg = Resistor(resistance=4 * Ohm, package="R_0805")
reference = GroundReference(package="GND")
def architecture(self):
# The battery's positive terminal, and R in series with it.
self.battery.p1 >> self.r.p1
# Top left corner: R's far end, two sides of the square, one spoke.
self.r.p2 >> self.r_top.p1
self.r_top.p1 >> self.r_left.p1
self.r_left.p1 >> self.r_top_left_spoke.p1
# Top right corner.
self.r_top.p2 >> self.r_right.p1
self.r_right.p1 >> self.r_top_right_spoke.p1
# The centre, where the three spokes meet and nothing else does.
self.r_top_left_spoke.p2 >> self.r_top_right_spoke.p2
self.r_top_right_spoke.p2 >> self.r_bottom_right_spoke.p1
# Bottom left corner.
self.r_left.p2 >> self.r_bottom.p1
self.r_bottom.p1 >> self.r_left_leg.p1
# Bottom right corner.
self.r_right.p2 >> self.r_bottom.p2
self.r_bottom.p2 >> self.r_bottom_right_spoke.p2
self.r_bottom_right_spoke.p2 >> self.r_right_leg.p1
# The bottom rail, and the reference the potentials are taken against.
self.r_left_leg.p2 >> self.r_right_leg.p2
self.r_right_leg.p2 >> self.battery.p2
self.battery.p2 >> self.reference.node
# -- the branch currents, each read as leaving the first node named -----
def _through_r(self):
"""Bottom rail -> battery -> R -> top left corner. This is I."""
return through(
across(total(self.v_ground, self.battery.voltage), self.v_top_left),
self.r.resistance,
)
def constraints(self):
no_current = 0 * A
into_top_left = self._through_r()
top_left_to_top_right = through(
across(self.v_top_left, self.v_top_right), self.r_top.resistance
)
top_left_to_bottom_left = through(
across(self.v_top_left, self.v_bottom_left), self.r_left.resistance
)
top_left_to_centre = through(
across(self.v_top_left, self.v_centre),
self.r_top_left_spoke.resistance,
)
top_right_to_centre = through(
across(self.v_top_right, self.v_centre),
self.r_top_right_spoke.resistance,
)
top_right_to_bottom_right = through(
across(self.v_top_right, self.v_bottom_right), self.r_right.resistance
)
centre_to_bottom_right = through(
across(self.v_centre, self.v_bottom_right),
self.r_bottom_right_spoke.resistance,
)
bottom_left_to_bottom_right = through(
across(self.v_bottom_left, self.v_bottom_right), self.r_bottom.resistance
)
bottom_left_to_ground = through(
across(self.v_bottom_left, self.v_ground), self.r_left_leg.resistance
)
bottom_right_to_ground = through(
across(self.v_bottom_right, self.v_ground), self.r_right_leg.resistance
)
# Kirchhoff's current law, once per node. The bottom rail is implied by
# the other five and is written anyway: a redundant check that agrees is
# worth more than one that was left out.
require(
equals(
into_top_left,
total(
top_left_to_top_right,
top_left_to_bottom_left,
top_left_to_centre,
),
)
)
require(
equals(
top_left_to_top_right,
total(top_right_to_centre, top_right_to_bottom_right),
)
)
require(
equals(
total(top_left_to_centre, top_right_to_centre),
centre_to_bottom_right,
)
)
require(
equals(
top_left_to_bottom_left,
total(bottom_left_to_bottom_right, bottom_left_to_ground),
)
)
require(
equals(
total(
top_right_to_bottom_right,
centre_to_bottom_right,
bottom_left_to_bottom_right,
),
bottom_right_to_ground,
)
)
require(
equals(
total(bottom_left_to_ground, bottom_right_to_ground), into_top_left
)
)
# The answer the paper asks for, written in the direction the current
# actually flows, which is what makes the magnitude it wants the value
# on the left.
require(equals(into_top_left, 1 * A)) # I = 1 A through R
# Not asked, and the reason the answer is a round number: the two
# resistors that bridge equal potentials carry nothing, which takes the
# 10 ohm and the 8 ohm out of the circuit entirely.
require(equals(bottom_left_to_bottom_right, no_current)) # the 10 ohm
require(equals(top_right_to_centre, no_current)) # the 8 ohm

The parts, then the nets and the pads on them.

out/netlist.txt
GND1 GroundReference Package:GND
R1 2 Ohm Package:R_0805
R10 8 Ohm Package:R_0805
R2 10 Ohm Package:R_0805
R3 4 Ohm Package:R_0805
R4 6 Ohm Package:R_0805
R5 12 Ohm Package:R_0805
R6 2 Ohm Package:R_0805
R7 4 Ohm Package:R_0805
R8 1 Ohm Package:R_0805
R9 2 Ohm Package:R_0805
V1 6.5 V Package:Battery
Net-(GND1-Pad1) GND1.1 R5.2 R7.2 V1.2
Net-(R1-Pad1) R1.1 V1.1
Net-(R1-Pad2) R1.2 R4.1 R8.1 R9.1
Net-(R10-Pad1) R10.1 R6.1 R8.2
Net-(R10-Pad2) R10.2 R3.1 R9.2
Net-(R2-Pad1) R2.1 R4.2 R5.1
Net-(R2-Pad2) R2.2 R3.2 R6.2 R7.1

Every check that ran, and every one left undecided.

out/checks.txt
9 checks, 0 failed, 0 undecided

What the elaborated graph contains, by entity kind.

out/graph.txt
1 block
3 calculation
12 component
32 connection
9 constraint
2 evidence
1 interface
23 pin
23 port
1 requirement
1 verification
108 total
snapshot sha256:bda827d7dd3d3ea608be39507cdcb7911a9cad66fa8c96672a565e905ea3d4b7

All of it, including the KiCad netlist, is in examples/jee_advanced/problem_2/out/. Rebuild it with:

Terminal window
fang build examples/jee_advanced/problem_2/problem_2.py