Lecture 7¶
September 23, 2024
While loops, functions returning functions, vectors, classes.
While loops¶
While loops execute as long as a statement remains true. Truth values are checked before running the code block and between runs of the code block.
a = 1
b = 17
while a < b:
a += 3
a
19
a = 1
b = 17
while a+3 < b:
a += 3
a
16
a = 1
b = 17
while True:
aa = a + 3
if aa >= b:
break
a = aa
a
16
Example:¶
A triangular number has the form $1+2+\ldots+n$ for some $n \geq 1$.
Write a function that returns the list of all the triangular numbers less than $k$.
def triangular_numbers(k):
tri = 1
n = 1
tris = []
while tri < k:
tris.append(tri)
n += 1
tri += n
return tris
triangular_numbers(1)
[]
triangular_numbers(11)
[1, 3, 6, 10]
triangular_numbers(10)
[1, 3, 6]
triangular_numbers(1000)
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, 666, 703, 741, 780, 820, 861, 903, 946, 990]
While with break¶
Sometimes it is convienient to exit the loop from somewhere in the middle of a loop statement. You can break out at any point with the break
statement:
Example a drunk gambler¶
Write a function that prints out the behavior of a drunk gambler who starts with n
dollars. Each round, he
- Buys a $\$1$ drink with 25% probability.
- He makes a bet. If he wins, he wins $\$1$. If he loses, he loses $\$1$. His chances of winning are 50%.
The loop should stop if he has no money left or as soon has he has 8 drinks. Return the amount of money he has left.
We will use random()
which produces a uniformly random float in $[0, 1]$.
random()
0.4337163864472886
type(random())
<class 'float'>
def drunk_gambler(n):
drinks = 0
while True:
# bet
if random() < 1/4:
n -= 1
drinks += 1
print(f'Gambler drinks: This is drink #{drinks}$, he has ${n}.')
if drinks >= 8:
print(f'Gambler has had too many drinks: Thrown out with ${n}!')
break
if n <= 0:
print(f'Gambler drank away his last dollar!')
break
if random() > 1/2:
# wins
n += 1
print(f'Gambler wins: He has ${n}')
else:
# loses
n -= 1
print(f'Gambler loses: He has ${n}')
if n <= 0:
print(f'Gambler is now broke and goes home!')
break
drunk_gambler(3)
Gambler wins: He has $4 Gambler drinks: This is drink #1$, he has $3. Gambler loses: He has $2 Gambler wins: He has $3 Gambler wins: He has $4 Gambler wins: He has $5 Gambler drinks: This is drink #2$, he has $4. Gambler loses: He has $3 Gambler wins: He has $4 Gambler drinks: This is drink #3$, he has $3. Gambler wins: He has $4 Gambler wins: He has $5 Gambler wins: He has $6 Gambler loses: He has $5 Gambler loses: He has $4 Gambler drinks: This is drink #4$, he has $3. Gambler wins: He has $4 Gambler wins: He has $5 Gambler wins: He has $6 Gambler drinks: This is drink #5$, he has $5. Gambler wins: He has $6 Gambler loses: He has $5 Gambler wins: He has $6 Gambler wins: He has $7 Gambler drinks: This is drink #6$, he has $6. Gambler loses: He has $5 Gambler wins: He has $6 Gambler wins: He has $7 Gambler wins: He has $8 Gambler loses: He has $7 Gambler wins: He has $8 Gambler loses: He has $7 Gambler wins: He has $8 Gambler loses: He has $7 Gambler loses: He has $6 Gambler wins: He has $7 Gambler loses: He has $6 Gambler loses: He has $5 Gambler wins: He has $6 Gambler wins: He has $7 Gambler wins: He has $8 Gambler drinks: This is drink #7$, he has $7. Gambler wins: He has $8 Gambler loses: He has $7 Gambler wins: He has $8 Gambler loses: He has $7 Gambler loses: He has $6 Gambler drinks: This is drink #8$, he has $5. Gambler has had too many drinks: Thrown out with $5!
drunk_gambler(10)
Gambler drinks: This is drink #1$, he has $9. Gambler wins: He has $10 Gambler wins: He has $11 Gambler wins: He has $12 Gambler wins: He has $13 Gambler drinks: This is drink #2$, he has $12. Gambler wins: He has $13 Gambler wins: He has $14 Gambler wins: He has $15 Gambler drinks: This is drink #3$, he has $14. Gambler loses: He has $13 Gambler wins: He has $14 Gambler loses: He has $13 Gambler wins: He has $14 Gambler loses: He has $13 Gambler wins: He has $14 Gambler loses: He has $13 Gambler drinks: This is drink #4$, he has $12. Gambler loses: He has $11 Gambler loses: He has $10 Gambler wins: He has $11 Gambler drinks: This is drink #5$, he has $10. Gambler wins: He has $11 Gambler wins: He has $12 Gambler loses: He has $11 Gambler loses: He has $10 Gambler wins: He has $11 Gambler wins: He has $12 Gambler loses: He has $11 Gambler wins: He has $12 Gambler loses: He has $11 Gambler loses: He has $10 Gambler wins: He has $11 Gambler loses: He has $10 Gambler wins: He has $11 Gambler drinks: This is drink #6$, he has $10. Gambler wins: He has $11 Gambler wins: He has $12 Gambler wins: He has $13 Gambler drinks: This is drink #7$, he has $12. Gambler loses: He has $11 Gambler drinks: This is drink #8$, he has $10. Gambler has had too many drinks: Thrown out with $10!
drunk_gambler(10)
Gambler drinks: This is drink #1$, he has $9. Gambler wins: He has $10 Gambler wins: He has $11 Gambler drinks: This is drink #2$, he has $10. Gambler wins: He has $11 Gambler loses: He has $10 Gambler loses: He has $9 Gambler drinks: This is drink #3$, he has $8. Gambler loses: He has $7 Gambler loses: He has $6 Gambler loses: He has $5 Gambler drinks: This is drink #4$, he has $4. Gambler loses: He has $3 Gambler wins: He has $4 Gambler wins: He has $5 Gambler wins: He has $6 Gambler wins: He has $7 Gambler wins: He has $8 Gambler wins: He has $9 Gambler wins: He has $10 Gambler wins: He has $11 Gambler loses: He has $10 Gambler drinks: This is drink #5$, he has $9. Gambler wins: He has $10 Gambler drinks: This is drink #6$, he has $9. Gambler loses: He has $8 Gambler drinks: This is drink #7$, he has $7. Gambler wins: He has $8 Gambler drinks: This is drink #8$, he has $7. Gambler has had too many drinks: Thrown out with $7!
drunk_gambler(10)
Gambler wins: He has $11 Gambler drinks: This is drink #1$, he has $10. Gambler wins: He has $11 Gambler drinks: This is drink #2$, he has $10. Gambler loses: He has $9 Gambler loses: He has $8 Gambler loses: He has $7 Gambler wins: He has $8 Gambler loses: He has $7 Gambler loses: He has $6 Gambler loses: He has $5 Gambler loses: He has $4 Gambler wins: He has $5 Gambler wins: He has $6 Gambler drinks: This is drink #3$, he has $5. Gambler wins: He has $6 Gambler wins: He has $7 Gambler drinks: This is drink #4$, he has $6. Gambler wins: He has $7 Gambler loses: He has $6 Gambler wins: He has $7 Gambler drinks: This is drink #5$, he has $6. Gambler wins: He has $7 Gambler drinks: This is drink #6$, he has $6. Gambler loses: He has $5 Gambler wins: He has $6 Gambler wins: He has $7 Gambler wins: He has $8 Gambler drinks: This is drink #7$, he has $7. Gambler loses: He has $6 Gambler drinks: This is drink #8$, he has $5. Gambler has had too many drinks: Thrown out with $5!
Scopes¶
References:
Python associates variable names with objects. The scope of a variable is a code block in which the variable is available.
a = [1, 2]
b = a
a.append(3)
b
[1, 2, 3]
b = copy(a)
a.append(4)
b
[1, 2, 3]
a
[1, 2, 3, 4]
copy?
Signature: copy(x) Docstring: Shallow copy operation on arbitrary Python objects. See the module's __doc__ string for more info. Init docstring: Initialize self. See help(type(self)) for accurate signature. File: /usr/lib/python3.11/copy.py Type: function
a = [1, [2, 3]]
b = copy(a)
a[1].append(4)
b
[1, [2, 3, 4]]
Local Scope:¶
A function defined within a funtion is only available within that function.
def shift(x):
k = 3
return x+k
shift(5)
8
k
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[35], line 1 ----> 1 k NameError: name 'k' is not defined
kk = 4
def shift_square1(x):
kk = 3
def square():
kk = 2
return kk^2
return x + square()
shift_square1(0)
4
Enclosing (or nonlocal) scope¶
For nested functions, the variables in the outer function are available to the inner function.
def shift_function(n):
def f(x):
return x+n
return f
f = shift_function(5)
f
<function shift_function.<locals>.f at 0x7f992da1a480>
f(7)
12
kk = 4
def shift_square1(x):
kk = 3
def square():
return kk^2
return x + square()
shift_square1(0)
9
Global scope¶
This is the scope of variables created in a Jupyter notebook. (If you do more porgramming, it will be the variables defined in your program, script, or module).
kk = 4
def shift_square2(x):
k = 3
def square():
return kk^2
return x + square()
shift_square2(0)
16
Built-in scope¶
These are the objects made available by Python/Sage. For example, the function var
is in global scope.
var
<built-in function var>
Where does a variable come from?¶
When a variable is accessed, Python and Sage use the LEGB
rule:
- First it checks if the variable is in the local scope.
- If it finds nothing, it checks the enclosing (nonlocal) scope.
- If it finds nothing, it checks the global scope.
- If it finds nothing, it checks the built-in scope.
- If it finds nothing, it produces a
NameError
.
What about when you assign a variable?¶
Assignments are made to the local scope, unless you specify otherwise. Example:
j = 3
def change_j():
j = 4
change_j()
j
3
j = 3
def change_j():
global j
j = 4
change_j()
j
4
An UnboundLocalError
occurs if you read from a variable outside the local scope, and then later write to it (without declaring the variable global
or nonlocal
). So, this is okay:
j = 3
def change_j():
j = j+1
return j
change_j()
j
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) Cell In[49], line 5 3 j = j+Integer(1) 4 return j ----> 5 change_j() 6 j Cell In[49], line 3, in change_j() 2 def change_j(): ----> 3 j = j+Integer(1) 4 return j UnboundLocalError: cannot access local variable 'j' where it is not associated with a value
j = 3
def change_j():
j+1
change_j()
j
3
j = 3
def change_j():
j = 17
j = j+1
change_j()
j
3
Remarks on functions returning functions¶
It is common to ask for me to ask you to write a function that returns a function. Most commonly, you want to think of the outer function as defining constants for the inner function to use; the inner function should typically not modify those values. For example:
def shift(n):
def shift_by_n(x):
return x+n
return shift_by_n
f = shift(3)
f(7)
10
For a case that you might want to modify values, suppose we want to keep track of the number of shifts applied.
def shift_with_counter(n):
count = 0
def shift_by_n(x):
nonlocal count
count += 1
return x+n
def counter():
return count
return shift_by_n, counter
f,c = shift_with_counter(3)
c()
0
f(1)
4
c()
1
[ f(i) for i in range(10)]
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
c()
11
Vectors and VectorSpaces¶
Sage math has built in support for vectors. For example:
v = vector([1,2,3,4])
v
(1, 2, 3, 4)
Elements can be accessed just like lists and tuples:
v[0]
1
v[3]
4
v[-1]
4
v[1] = 10
v
(1, 10, 3, 4)
v[0].parent()
Integer Ring
ZZ
Integer Ring
Sage finds a common parent for all enties. Conversions are handled automatically. This parent can be accessed with the base_ring()
method:
v=vector([1, 1/2, 7, 8])
v
(1, 1/2, 7, 8)
v[0]
1
v[0].parent()
Rational Field
QQ
Rational Field
You can explicitly choose a base ring by passing it to the vector constructor:
w = vector([sqrt(2), 3, 9])
w
(sqrt(2), 3, 9)
w[2].parent()
Symbolic Ring
w2 = vector(AA, [sqrt(2), 3, 9])
w2
(1.414213562373095?, 3, 9)
w2[2].parent()
Algebraic Real Field
AA
Algebraic Real Field
w3 = vector(QQ, [sqrt(2), 3, 9])
w3
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) File ~/Git/sagemath/sage/src/sage/symbolic/expression.pyx:1572, in sage.symbolic.expression.Expression._rational_() 1571 try: -> 1572 n = self.pyobject() 1573 except TypeError: File ~/Git/sagemath/sage/src/sage/symbolic/expression.pyx:764, in sage.symbolic.expression.Expression.pyobject() 763 if not is_a_numeric(self._gobj): --> 764 raise TypeError("self must be a numeric expression") 765 return py_object_from_numeric(self._gobj) TypeError: self must be a numeric expression During handling of the above exception, another exception occurred: TypeError Traceback (most recent call last) File ~/Git/sagemath/sage/src/sage/structure/sequence.py:451, in Sequence_generic.__init__(self, x, universe, check, immutable, cr, cr_str, use_sage_types) 450 try: --> 451 x[i] = universe(x[i]) 452 except TypeError: File ~/Git/sagemath/sage/src/sage/structure/parent.pyx:908, in sage.structure.parent.Parent.__call__() 907 if no_extra_args: --> 908 return mor._call_(x) 909 else: File ~/Git/sagemath/sage/src/sage/structure/coerce_maps.pyx:164, in sage.structure.coerce_maps.DefaultConvertMap_unique._call_() 163 print(type(C._element_constructor), C._element_constructor) --> 164 raise 165 File ~/Git/sagemath/sage/src/sage/structure/coerce_maps.pyx:159, in sage.structure.coerce_maps.DefaultConvertMap_unique._call_() 158 try: --> 159 return C._element_constructor(x) 160 except Exception: File ~/Git/sagemath/sage/src/sage/rings/rational.pyx:555, in sage.rings.rational.Rational.__init__() 554 if x is not None: --> 555 self.__set_value(x, base) 556 File ~/Git/sagemath/sage/src/sage/rings/rational.pyx:639, in sage.rings.rational.Rational._Rational__set_value() 638 elif hasattr(x, "_rational_"): --> 639 set_from_Rational(self, x._rational_()) 640 File ~/Git/sagemath/sage/src/sage/symbolic/expression.pyx:1574, in sage.symbolic.expression.Expression._rational_() 1573 except TypeError: -> 1574 raise TypeError("unable to convert %s to a rational" % self) 1575 if isinstance(n, sage.rings.rational.Rational): TypeError: unable to convert sqrt(2) to a rational During handling of the above exception, another exception occurred: TypeError Traceback (most recent call last) Cell In[87], line 1 ----> 1 w3 = vector(QQ, [sqrt(Integer(2)), Integer(3), Integer(9)]) 2 w3 File ~/Git/sagemath/sage/src/sage/modules/free_module_element.pyx:595, in sage.modules.free_module_element.vector() 593 sparse = False 594 --> 595 v, R = prepare(v, R, degree) 596 597 M = FreeModule(R, len(v), bool(sparse)) File ~/Git/sagemath/sage/src/sage/modules/free_module_element.pyx:696, in sage.modules.free_module_element.prepare() 694 except TypeError: 695 pass --> 696 v = Sequence(v, universe=R, use_sage_types=True) 697 ring = v.universe() 698 if ring not in Rings(): File ~/Git/sagemath/sage/src/sage/structure/sequence.py:267, in Sequence(x, universe, check, immutable, cr, cr_str, use_sage_types) 264 if is_MPolynomialRing(universe) or isinstance(universe, BooleanMonomialMonoid) or (is_QuotientRing(universe) and is_MPolynomialRing(universe.cover_ring())): 265 return PolynomialSequence(x, universe, immutable=immutable, cr=cr, cr_str=cr_str) --> 267 return Sequence_generic(x, universe, check, immutable, cr, cr_str, use_sage_types) File ~/Git/sagemath/sage/src/sage/structure/sequence.py:453, in Sequence_generic.__init__(self, x, universe, check, immutable, cr, cr_str, use_sage_types) 451 x[i] = universe(x[i]) 452 except TypeError: --> 453 raise TypeError("unable to convert {} to an element of {}" 454 .format(x[i], universe)) 455 list.__init__(self, x) 456 self._is_immutable = immutable TypeError: unable to convert sqrt(2) to an element of Rational Field
Changing entries¶
Like a list, you can (by default) change the entries in a vector.
w
(sqrt(2), 3, 9)
w[2] = pi + 4
w
(sqrt(2), 3, pi + 4)
d = {}
d[w] = 'red'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[91], line 2 1 d = {} ----> 2 d[w] = 'red' File ~/Git/sagemath/sage/src/sage/modules/free_module_element.pyx:1214, in sage.modules.free_module_element.FreeModuleElement.__hash__() 1212 """ 1213 if not self._is_immutable: -> 1214 raise TypeError("mutable vectors are unhashable") 1215 return hash(tuple(self)) 1216 TypeError: mutable vectors are unhashable
But, you can set a vector to be immutable. Then it can no longer be changed.
w.set_immutable()
w[0]=0
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[93], line 1 ----> 1 w[Integer(0)]=Integer(0) File ~/Git/sagemath/sage/src/sage/modules/free_module_element.pyx:1939, in sage.modules.free_module_element.FreeModuleElement.__setitem__() 1937 """ 1938 if self._is_immutable: -> 1939 raise ValueError("vector is immutable; please change a copy instead (use copy())") 1940 cdef Py_ssize_t d = self._degree 1941 cdef Py_ssize_t start, stop, step, slicelength ValueError: vector is immutable; please change a copy instead (use copy())
d[w] = 'red'
w2 = copy(w)
w2
(sqrt(2), 3, pi + 4)
w2[0]=0
w2
(0, 3, pi + 4)
v = vector([1,2,3], immutable=True)
v
(1, 2, 3)
v[0]=0
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[103], line 1 ----> 1 v[Integer(0)]=Integer(0) File ~/Git/sagemath/sage/src/sage/modules/free_module_element.pyx:1939, in sage.modules.free_module_element.FreeModuleElement.__setitem__() 1937 """ 1938 if self._is_immutable: -> 1939 raise ValueError("vector is immutable; please change a copy instead (use copy())") 1940 cdef Py_ssize_t d = self._degree 1941 cdef Py_ssize_t start, stop, step, slicelength ValueError: vector is immutable; please change a copy instead (use copy())
vector?
Docstring: Return a vector or free module element with specified entries. CALL FORMATS: This constructor can be called in several different ways. In each case, "sparse=True" or "sparse=False" as well as "immutable=True" or "immutable=False" can be supplied as an option. "free_module_element()" is an alias for "vector()". 1. vector(object) 2. vector(ring, object) 3. vector(object, ring) 4. vector(ring, degree, object) 5. vector(ring, degree) INPUT: * "object" -- a list, dictionary, or other iterable containing the entries of the vector, including any object that is palatable to the "Sequence" constructor * "ring" -- a base ring (or field) for the vector space or free module, which contains all of the elements * "degree" -- an integer specifying the number of entries in the vector or free module element * "sparse" -- boolean, whether the result should be a sparse vector * "immutable" -- boolean (default: "False"); whether the result should be an immutable vector In call format 4, an error is raised if the "degree" does not match the length of "object" so this call can provide some safeguards. Note however that using this format when "object" is a dictionary is unlikely to work properly. OUTPUT: An element of the ambient vector space or free module with the given base ring and implied or specified dimension or rank, containing the specified entries and with correct degree. In call format 5, no entries are specified, so the element is populated with all zeros. If the "sparse" option is not supplied, the output will generally have a dense representation. The exception is if "object" is a dictionary, then the representation will be sparse. EXAMPLES: sage: v = vector([1,2,3]); v (1, 2, 3) sage: v.parent() Ambient free module of rank 3 over the principal ideal domain Integer Ring sage: v = vector([1,2,3/5]); v (1, 2, 3/5) sage: v.parent() Vector space of dimension 3 over Rational Field All entries must *canonically* coerce to some common ring: sage: v = vector([17, GF(11)(5), 19/3]); v Traceback (most recent call last): ... TypeError: unable to find a common ring for all elements sage: v = vector([17, GF(11)(5), 19]); v (6, 5, 8) sage: v.parent() Vector space of dimension 3 over Finite Field of size 11 sage: v = vector([17, GF(11)(5), 19], QQ); v (17, 5, 19) sage: v.parent() Vector space of dimension 3 over Rational Field sage: v = vector((1,2,3), QQ); v (1, 2, 3) sage: v.parent() Vector space of dimension 3 over Rational Field sage: v = vector(QQ, (1,2,3)); v (1, 2, 3) sage: v.parent() Vector space of dimension 3 over Rational Field sage: v = vector(vector([1,2,3])); v (1, 2, 3) sage: v.parent() Ambient free module of rank 3 over the principal ideal domain Integer Ring You can also use "free_module_element", which is the same as "vector". sage: free_module_element([1/3, -4/5]) (1/3, -4/5) We make a vector mod 3 out of a vector over \ZZ. sage: vector(vector([1,2,3]), GF(3)) (1, 2, 0) The degree of a vector may be specified: sage: vector(QQ, 4, [1,1/2,1/3,1/4]) (1, 1/2, 1/3, 1/4) But it is an error if the degree and size of the list of entries are mismatched: sage: vector(QQ, 5, [1,1/2,1/3,1/4]) Traceback (most recent call last): ... ValueError: incompatible degrees in vector constructor Providing no entries populates the vector with zeros, but of course, you must specify the degree since it is not implied. Here we use a finite field as the base ring. sage: w = vector(FiniteField(7), 4); w (0, 0, 0, 0) sage: w.parent() Vector space of dimension 4 over Finite Field of size 7 The fastest method to construct a zero vector is to call the "zero_vector()" method directly on a free module or vector space, since vector(...) must do a small amount of type checking. Almost as fast as the "zero_vector()" method is the "zero_vector()" constructor, which defaults to the integers. sage: vector(ZZ, 5) # works fine (0, 0, 0, 0, 0) sage: (ZZ^5).zero_vector() # very tiny bit faster (0, 0, 0, 0, 0) sage: zero_vector(ZZ, 5) # similar speed to vector(...) (0, 0, 0, 0, 0) sage: z = zero_vector(5); z (0, 0, 0, 0, 0) sage: z.parent() Ambient free module of rank 5 over the principal ideal domain Integer Ring Here we illustrate the creation of sparse vectors by using a dictionary: sage: vector({1:1.1, 3:3.14}) (0.000000000000000, 1.10000000000000, 0.000000000000000, 3.14000000000000) With no degree given, a dictionary of entries implicitly declares a degree by the largest index (key) present. So you can provide a terminal element (perhaps a zero?) to set the degree. But it is probably safer to just include a degree in your construction. sage: v = vector(QQ, {0:1/2, 4:-6, 7:0}); v (1/2, 0, 0, 0, -6, 0, 0, 0) sage: v.degree() 8 sage: v.is_sparse() True sage: w = vector(QQ, 8, {0:1/2, 4:-6}) sage: w == v True It is an error to specify a negative degree. sage: vector(RR, -4, [1.0, 2.0, 3.0, 4.0]) Traceback (most recent call last): ... ValueError: cannot specify the degree of a vector as a negative integer (-4) It is an error to create a zero vector but not provide a ring as the first argument. sage: vector('junk', 20) Traceback (most recent call last): ... TypeError: first argument must be base ring of zero vector, not junk And it is an error to specify an index in a dictionary that is greater than or equal to a requested degree. sage: vector(ZZ, 10, {3:4, 7:-2, 10:637}) Traceback (most recent call last): ... ValueError: dictionary of entries has a key (index) exceeding the requested degree A 1-dimensional numpy array of type float or complex may be passed to vector. Unless an explicit ring is given, the result will be a vector in the appropriate dimensional vector space over the real double field or the complex double field. The data in the array must be contiguous, so column-wise slices of numpy matrices will raise an exception. sage: import numpy sage: x = numpy.random.randn(10) sage: y = vector(x) sage: parent(y) Vector space of dimension 10 over Real Double Field sage: parent(vector(RDF, x)) Vector space of dimension 10 over Real Double Field sage: parent(vector(CDF, x)) Vector space of dimension 10 over Complex Double Field sage: parent(vector(RR, x)) Vector space of dimension 10 over Real Field with 53 bits of precision sage: v = numpy.random.randn(10) * complex(0,1) sage: w = vector(v) sage: parent(w) Vector space of dimension 10 over Complex Double Field Multi-dimensional arrays are not supported: sage: import numpy as np sage: a = np.array([[1, 2, 3], [4, 5, 6]], np.float64) sage: vector(a) Traceback (most recent call last): ... TypeError: cannot convert 2-dimensional array to a vector If any of the arguments to vector have Python type int, real, or complex, they will first be coerced to the appropriate Sage objects. This fixes https://github.com/sagemath/sage/issues/3847. sage: v = vector([int(0)]); v (0) sage: v[0].parent() Integer Ring sage: v = vector(range(10)); v (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) sage: v[3].parent() Integer Ring sage: v = vector([float(23.4), int(2), complex(2+7*I), 1]); v (23.4, 2.0, 2.0 + 7.0*I, 1.0) sage: v[1].parent() Complex Double Field If the argument is a vector, it doesn't change the base ring. This fixes https://github.com/sagemath/sage/issues/6643: sage: K.<sqrt3> = QuadraticField(3) sage: u = vector(K, (1/2, sqrt3/2)) sage: vector(u).base_ring() Number Field in sqrt3 with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878? sage: v = vector(K, (0, 1)) sage: vector(v).base_ring() Number Field in sqrt3 with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878? Constructing a vector from a numpy array behaves as expected: sage: import numpy sage: a = numpy.array([1,2,3]) sage: v = vector(a); v (1, 2, 3) sage: parent(v) Ambient free module of rank 3 over the principal ideal domain Integer Ring Complex numbers can be converted naturally to a sequence of length 2. And then to a vector. sage: c = CDF(2 + 3*I) sage: v = vector(c); v (2.0, 3.0) A generator, or other iterable, may also be supplied as input. Anything that can be converted to a "Sequence" is a possible input. sage: type(i^2 for i in range(3)) <... 'generator'> sage: v = vector(i^2 for i in range(3)); v (0, 1, 4) An empty list, without a ring given, will default to the integers. sage: x = vector([]); x () sage: x.parent() Ambient free module of rank 0 over the principal ideal domain Integer Ring The "immutable" switch allows to create an immutable vector. sage: v = vector(QQ, {0:1/2, 4:-6, 7:0}, immutable=True); v (1/2, 0, 0, 0, -6, 0, 0, 0) sage: v.is_immutable() True The "immutable" switch works regardless of the type of valid input to the constructor. sage: v = vector(ZZ, 4, immutable=True) sage: v.is_immutable() True sage: w = vector(ZZ, [1,2,3]) sage: v = vector(w, ZZ, immutable=True) sage: v.is_immutable() True sage: v = vector(QQ, w, immutable=True) sage: v.is_immutable() True sage: import numpy as np sage: w = np.array([1, 2, pi], float) sage: v = vector(w, immutable=True) sage: v.is_immutable() True sage: w = np.array([i, 2, 3], complex) sage: v = vector(w, immutable=True) sage: v.is_immutable() True Init docstring: Initialize self. See help(type(self)) for accurate signature. File: ~/Git/sagemath/sage/src/sage/modules/free_module_element.pyx Type: builtin_function_or_method
Immutable vectors are useful: You can use them as keys in dictionaries.
Vector spaces¶
Assuming that a vector is defined over a field, the parent of a vector is a vector space.
v =vector([1, 1/2, 1/3])
v.parent()
Vector space of dimension 3 over Rational Field
VS = VectorSpace(QQ, 3)
VS
Vector space of dimension 3 over Rational Field
VS.base_ring()
Rational Field
VS.zero()
(0, 0, 0)
VS([4,5,6])
(4, 5, 6)
You can also define a vector space explicitly:
Then you can define objects in the vector space by passing parameters, which are automatically converted to the Field.
The parent of the entries should always be at least a ring. If a ring is used, you get a FreeModule
instead.
v = vector([1,2,3])
v.parent()
Ambient free module of rank 3 over the principal ideal domain Integer Ring
FM = FreeModule(ZZ, 3)
FM.base_ring()
Integer Ring
Free modules can be used in much the same way as vector spaces.
List operations¶
vl = [1, 2, 8]
wl = [sqrt(2), 3, sqrt(7)]
vl + wl
[1, 2, 8, sqrt(2), 3, sqrt(7)]
Vector operations:¶
VS
Vector space of dimension 3 over Rational Field
v = vector([1, 2, 8])
w = vector(AA, [sqrt(2), 3, sqrt(7)])
w
(1.414213562373095?, 3, 2.645751311064591?)
v+w
(2.414213562373095?, 5, 10.64575131106459?)
Dot product:
v*w
28.58022405088982?
Cross product:
v.cross_product(w)
(-18.70849737787082?, 8.66795718792017?, 0.1715728752538099?)
Scalar multiplication:
7*v
(7, 14, 56)
Classes¶
I'll follow the Official Python Tutorial on classes
Classes are a way of bundling data with functionality.
Class attributes¶
Here is a simple class whose name is SomeClassName
. It has one class attribute, counter
.
class SomeClassName:
counter = 0
SomeClassName.counter
0
You can change the value if you wish:
SomeClassName.counter = 3
SomeClassName.counter
3
An attribute can also be a function. But I'll postpone discussing this.
Instantiation¶
The main idea of a class is to be able to create many instances of similar objects, that would typically be associated with different data. You can then interact with thes objects in a similar way. For example:
class ProjectivePoint:
V = VectorSpace(QQ, 3)
def __init__(self, v):
v = ProjectivePoint.V(v)
assert v != ProjectivePoint.V.zero()
self.v = v
p = ProjectivePoint([2, 3, 1/3])
p
<__main__.ProjectivePoint object at 0x7f9927ceffd0>
type(p)
<class '__main__.ProjectivePoint'>
p.v
(2, 3, 1/3)
Instance Methods¶
class ProjectivePoint:
V = VectorSpace(QQ, 3)
def __init__(self, v):
v = ProjectivePoint.V(v)
assert v != ProjectivePoint.V.zero()
self.v = v
def x(self):
return self.v[0] / self.v[2]
p = ProjectivePoint([2, 3, 1/3])
p
<__main__.ProjectivePoint object at 0x7f99274d0e90>
p.x()
6
p = ProjectivePoint([2, 3, 0])
p.x()
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[141], line 2 1 p = ProjectivePoint([Integer(2), Integer(3), Integer(0)]) ----> 2 p.x() Cell In[138], line 10, in ProjectivePoint.x(self) 9 def x(self): ---> 10 return self.v[Integer(0)] / self.v[Integer(2)] File ~/Git/sagemath/sage/src/sage/rings/rational.pyx:2494, in sage.rings.rational.Rational.__truediv__() 2492 if type(left) is type(right): 2493 if mpq_cmp_si((<Rational> right).value, 0, 1) == 0: -> 2494 raise ZeroDivisionError('rational division by zero') 2495 x = <Rational> Rational.__new__(Rational) 2496 mpq_div(x.value, (<Rational>left).value, (<Rational>right).value) ZeroDivisionError: rational division by zero
class ProjectivePoint:
V = VectorSpace(QQ, 3)
def __init__(self, v):
v = ProjectivePoint.V(v)
assert v != ProjectivePoint.V.zero()
self.v = v
def x(self):
if self.v[2]!=0:
return self.v[0] / self.v[2]
return Infinity
def y(self):
if self.v[2]!=0:
return self.v[1] / self.v[2]
return Infinity
p = ProjectivePoint([2, 3, 0])
p.x(), p.y()
(+Infinity, +Infinity)
p = ProjectivePoint([2, 3, 3/2])
p.x(), p.y()
(4/3, 2)