Пример #1
0
 def __init__(self, bb=None):
     from yanntricks.src.BoundingBox import BoundingBox
     if bb is None:
         bb = BoundingBox()
     ObjectGraph.__init__(self, self)
     self.BB = bb
     self.separator_name = "GRID"
     # Default values, have to be integer.
     self.add_option({"Dx": 1, "Dy": 1})
     self.Dx = self.options.DicoOptions["Dx"]
     self.Dy = self.options.DicoOptions["Dy"]
     self.num_subX = 2
     self.num_subY = 2
     self.draw_border = False
     self.draw_horizontal_grid = True
     self.draw_vertical_grid = True
     self.main_horizontal = Segment(Point(0, 1), Point(1, 1))
     self.main_horizontal.parameters.color = "gray"
     self.main_horizontal.parameters.style = "solid"
     self.main_vertical = Segment(Point(0, 1), Point(1, 1))
     self.main_vertical.parameters.color = "gray"
     self.main_vertical.parameters.style = "solid"
     self.sub_vertical = Segment(Point(0, 1), Point(1, 1))
     self.sub_vertical.parameters.color = "gray"
     self.sub_vertical.parameters.style = "dotted"
     self.sub_horizontal = Segment(Point(0, 1), Point(1, 1))
     self.sub_horizontal.parameters.color = "gray"
     self.sub_horizontal.parameters.style = "dotted"
     self.border = Segment(Point(0, 1), Point(1, 1))
     self.border.parameters.color = "gray"
     self.border.parameters.style = "dotted"
Пример #2
0
    def __init__(self, curve1, curve2):
        """
        Give the graph of the surface between the two lines.

        The lines are needed to have a starting and ending point
        that will be joined by straight lines.
        """
        from yanntricks.src.segment import Segment
        # By convention, the first line goes from left to right and the second one to right to left.

        ObjectGraph.__init__(self, self)

        if curve1.I.x > curve1.F.x:
            curve1 = curve1.reverse()
        if curve2.I.x > curve2.F.x:
            curve2 = curve2.reverse()

        self.curve1 = curve1
        self.curve2 = curve2

        self.I1 = curve1.I
        self.I2 = curve2.I

        self.F1 = curve1.F
        self.F2 = curve2.F

        self.Isegment = Segment(self.I1, self.I2)
        self.Fsegment = Segment(self.F1, self.F2)
Пример #3
0
 def __init__(self, O, A, B):
     ObjectGraph.__init__(self, self)
     self.O = O
     self.A = A
     self.B = B
     self.angleI = 0
     self.angleF = 2 * pi
Пример #4
0
    def __init__(self, op, P, a, b, c):
        from yanntricks.src.segment import Segment
        from yanntricks.src.point import Point
        ObjectGraph.__init__(self, self)
        self.op = op
        self.P = P
        self.Px = P[0]
        self.Py = P[1]
        self.a = a
        self.b = b
        self.c = c
        self.transparent = True

        self.A = [
            Point(self.Px, self.Py + b),
            Point(self.Px + a, self.Py + b),
            Point(self.Px + a, self.Py),
            Point(self.Px, self.Py)
        ]

        # The points on the first and second rectangle
        self.c1 = [self.op.point(P.x, P.y, 0) for P in self.A]
        self.c2 = [self.op.point(P.x, P.y, self.c) for P in self.A]

        self.A = self.c1[0]
        self.B = self.c1[1]
        self.C = self.c1[2]
        self.D = self.c1[3]
        self.E = self.c2[0]
        self.F = self.c2[1]
        self.G = self.c2[2]
        self.H = self.c2[3]

        for P in self.c1:
            P.parameters.symbol = ""
        for P in self.c2:
            P.parameters.symbol = ""

        # The edges.
        self.segP = [
            Segment(self.c1[i], self.c2[i]) for i in range(0, len(self.c1))
        ]
        self.segc1 = [
            Segment(self.c1[i], self.c1[(i + 1) % len(self.c1)])
            for i in range(0, len(self.c1))
        ]
        self.segc2 = [
            Segment(self.c2[i], self.c2[(i + 1) % len(self.c2)])
            for i in range(0, len(self.c2))
        ]

        if op.alpha < 90:
            self.segP[3].parameters.style = "dashed"
            self.segc2[2].parameters.style = "dashed"
            self.segc2[3].parameters.style = "dashed"
        else:
            self.segP[2].parameters.style = "dashed"
            self.segc2[2].parameters.style = "dashed"
            self.segc2[1].parameters.style = "dashed"
Пример #5
0
 def __init__(self, A, B=None, vector=None):
     if vector:
         B = A.translate(vector)
     self.I = A
     self.F = B
     self._advised_mark_angle = None
     ObjectGraph.__init__(self, self)
     self.measure = None
     self.coefs = None
Пример #6
0
 def __init__(self, minimum, Q1, M, Q3, maximum, h, delta_y=0):
     ObjectGraph.__init__(self, self)
     self.Q1 = Q1
     self.Q3 = Q3
     self.M = M
     self.h = h
     self.delta_y = delta_y
     self.minimum = minimum
     self.maximum = maximum
Пример #7
0
 def __init__(self, implicit_curve, xrange, yrange, plot_points=300):
     ObjectGraph.__init__(self, implicit_curve)
     GeometricImplicitCurve.__init__(self, implicit_curve.f)
     self.implicit_curve = implicit_curve
     self.implicit_plot = implicit_plot(self.f, xrange, yrange)
     self.xrange = xrange
     self.yrange = yrange
     self.plot_points = plot_points
     self.paths = get_paths_from_implicit_plot(self.implicit_plot)
     self.parameters.color = "blue"
    def __init__(self, points_list, context_object=None, mode=None):
        ObjectGraph.__init__(self, self)
        self.parameters.color = "brown"

        self.points_list = points_list

        self.I = self.points_list[0]
        self.F = self.points_list[-1]
        self.context_object = context_object
        if self.context_object is None:
            self.contex_object = self
        self.mode = mode
        self._minmax_data = None
Пример #9
0
 def __init__(self, a, b, n, histo):
     """
     It is given by the initial value, the final value and the "surrounding" histogram
     """
     ObjectGraph.__init__(self, self)
     self.d_xmin = a
     self.d_xmax = b
     self.n = n
     self.histo = histo
     self.size = self.d_xmax - self.d_xmin
     self.th_height = self.n / self.size
     self.length = None
     self.height = None
Пример #10
0
    def __init__(self, P, text, hide=True):
        from yanntricks.src.Constructors import Mark
        from yanntricks.src.Constructors import Rectangle
        ObjectGraph.__init__(self, self)
        self.P = P
        self.text = text
        self.mark = Mark(self, 0, 0, self.text)
        self.hide = hide

        # This is fake; just to have an object to act on.
        self.rectangle = Rectangle(Point(0, 0), Point(1, 1))
        self.rectangle.parameters.filled()
        self.rectangle.parameters.fill.color = "white"
        self.rectangle.parameters.style = "none"
Пример #11
0
 def __init__(self, fun, mx=None, Mx=None):
     ObjectGraph.__init__(self, fun)
     self.mx = mx
     self.Mx = Mx
     self.fun = fun
     self.parameters.plotpoints = 100
     # Will be used in order to simulate a lazy_attribute in self.get_minmax_data
     self.old_mx = None
     self.old_Mx = None
     self.minmax_result = None
     if self.mx is not None and self.Mx is not None:
         self.drawpoints = linspace(
             self.mx, self.Mx, self.parameters.plotpoints, endpoint=True)
     self.parameters.color = "blue"
Пример #12
0
    def __init__(self, f1, f2, mx, Mx):
        ObjectGraph.__init__(self, self)
        self.f1 = f1
        self.f2 = f2
        self.mx = mx
        self.Mx = Mx
        self.I = self.get_point(mx)
        self.F = self.get_point(Mx)

        self.parameters.plotpoints = 100

        from numpy import linspace
        if self.mx is not None and self.Mx is not None:
            self.drawpoints = linspace(
                self.mx, self.Mx, self.parameters.plotpoints, endpoint=True)
Пример #13
0
    def __init__(self, values, h, delta_y=0):
        ObjectGraph.__init__(self, self)

        import numpy
        from scipy.stats.mstats import mquantiles

        ms = mquantiles(values)
        self.average = numpy.mean(values)
        self.q1 = ms[0]
        self.median = ms[1]
        self.q3 = ms[2]
        self.minimum = min(values)
        self.maximum = max(values)
        self.h = h
        self.delta_y = delta_y
Пример #14
0
    def __init__(self, f, mx, Mx):
        ObjectGraph.__init__(self, self)
        self.f = f
        self.mx = mx
        self.Mx = Mx
        self.I = self.get_point(mx)
        self.F = self.get_point(Mx)

        self.parameters.plotpoints = 100

        from numpy import linspace
        if self.mx is not None and self.Mx is not None:
            self.drawpoints = linspace(numerical_approx(self.mx), numerical_approx(
                self.Mx), self.parameters.plotpoints, endpoint=True)
        self._curve = None
        self.mode = None
Пример #15
0
    def __init__(self,X,Y):
        ObjectGraph.__init__(self,self)
        self.X=X
        self.Y=Y
        self.linewidth=1    # width of the lines (in centimetrs)
        self.numbering=True
        self.numbering_decimals=2

        # Definition of the default bars to be drawn.
        self.lines_list=[]
        for i,x in enumerate(self.X):
            y=self.Y[i]
            l=Segment(Point(x,0),Point(x,y)  )
            l.parameters.color="blue"
            l.parameters.add_option("linewidth","{}cm".format(self.linewidth))
            self.lines_list.append(l)
Пример #16
0
    def __init__(self, center, radius, a, b):
        """
        The pie diagram for the fraction 'a/b' inside the circle of given center and radius.

        2/4 and 1/2 are not treated in the same way because 2/4 divides the pie into 4 parts (and fills 2) while 1/2 divides into 2 parts (and fills 1).
        """
        from yanntricks.src.Constructors import Circle
        ObjectGraph.__init__(self, self)
        self.center = center
        self.radius = radius
        self.numerator = a
        self.denominator = b
        if a > b:
            raise ValueError("Numerator is larger than denominator")
        self.circle = Circle(self.center, self.radius)
        self._circular_sector = None
Пример #17
0
    def __init__(self, C, bb, pspict=None):
        # if a pspicture is passed, these axes will be considered as the
        # default axes system of `pspict`. This has an influence in the
        # computation of the bounding box.
        from yanntricks.src.Constructors import Vector
        ObjectGraph.__init__(self, self)
        self.take_math_BB = False
        self.C = C

        self.BB = bb.copy()

        self.pspict = pspict
        self.options = Options()
        self.Dx = 1
        self.Dy = 1
        self.arrows = "->"
        self.separator_name = "AXES"
        self.graduation = True
        self.numbering = True
        # Since the size of the axe is given in multiple of self.base,
        # one cannot give mx=-1000 as "minimal value".
        self.single_axeX = SingleAxe(self.C,
                                     Vector(1, 0),
                                     0,
                                     0,
                                     pspict=self.pspict)
        self.single_axeX.mark_origin = False
        self.single_axeX.axes_unit = AxesUnit(1, "")
        self.draw_single_axeX = True
        self.single_axeY = SingleAxe(self.C,
                                     Vector(0, 1),
                                     0,
                                     0,
                                     pspict=self.pspict)
        self.single_axeY.mark_origin = False
        self.single_axeY.axes_unit = AxesUnit(1, "")
        self.single_axeY.mark_angle = 180
        self.draw_single_axeY = True
        self.single_axeX.Dx = self.Dx
        self.single_axeY.Dx = self.Dy
        self.already_enlarged = False
        self.enlarge_size = 0.5
        self.do_enlarge = True
        self.do_mx_enlarge = True
        self.do_my_enlarge = True
        self.do_Mx_enlarge = True
        self.do_My_enlarge = True
Пример #18
0
    def __init__(self, a, b):
        self.x = SR(a)
        self.y = SR(b)
        ObjectGraph.__init__(self, self)
        self.point = self.obj
        self.add_option("PointSymbol=*")
        self._advised_mark_angle = None

        try:
            ax = abs(numerical_approx(self.x))
            if ax < 0.00001 and ax > 0:
                self.x = 0
            ay = abs(numerical_approx(self.y))
            if ay < 0.00001 and ay > 0:
                self.y = 0
        except TypeError:
            pass
Пример #19
0
    def __init__(self, f1, f2, llamI, llamF):
        """
        Use the constructor :func:`ParametricCurve`.

        INPUT:

        - ``f1,f2`` - two functions.

        - ``llamI,llamF`` - initial and final values of the parameter.

        ATTRIBUTES:

        - ``plotpoints`` - (default=50)  number of points to be computed.
                           If the function seems wrong, increase that number.
                           It can happen with functions like sin(1/x) close to zero:
                            such a function have too fast oscillations.

        """
        if isinstance(f1, ParametricCurveGraph):
            print(
                "You cannot creare a parametric curve by giving a parametric curve"
            )
            raise TypeError
        ObjectGraph.__init__(self, self)
        GenericCurve.__init__(self, llamI, llamF)
        self._derivative_dict = {0: self}
        self.f1 = f1
        self.f2 = f2
        self.curve = self.obj
        self.llamI = llamI
        self.llamF = llamF
        self.mx = llamI
        self.Mx = llamF
        self.parameters.color = "blue"
        self.plotstyle = "curve"
        self.record_arrows = []
        # TODO: if I remove the protection "if self.llamI", sometimes it
        # tries to make self.get_point(self.llamI) with self.llamI==None
        # In that case the crash is interesting since it is a segfault instead of an exception.
        if self.llamI != None:
            self.I = self.get_point(self.llamI, advised=False)
            self.F = self.get_point(self.llamF, advised=False)
    def __init__(self, fun, mx, Mx):
        ObjectGraph.__init__(self, fun)
        GenericCurve.__init__(self, mx, Mx)
        self.sage = fun
        x, y = var('x,y')
        self.sage = fun
        try:
            self.sageFast = self.sage._fast_float_(x)
        except (NotImplementedError, TypeError, ValueError, AttributeError):
            # Happens when the derivative of the function is
            # not implemented in Sage
            # Also happens when there is a free variable,
            # as an example
            # F=VectorFieldGraph(x,y)
            # Also when something non analytic is given
            # like a distribution.
            self.sageFast = self.sage
        self.string = repr(self.sage)
        self.fx = self.string.replace("x |--> ", "")
        # self.pstricks = SubstitutionMathPsTricks(self.fx)
        # self.tikz is suppressed on October 16, 2019
        # self.tikz = SubstitutionMathTikz(self.fx)
        self.ListeSurface = []
        self.listeTests = []
        self.TesteDX = 0
        self.listeExtrema = []
        self.listeExtrema_analytique = []
        self.equation = y == self.sage

        self.f = self.obj
        self.mx = mx
        self.Mx = Mx
        self.do_cut_y = False
        self.cut_ymin = None
        self.cut_ymax = None
        self.pieces = []
        # Modification with respect to the attribute in ObjectGraph
        self.parameters.color = "blue"
        self.nul_function = None

        self._derivative = None
        self._parametric_curve = None
Пример #21
0
    def __init__(self,
                 curve1,
                 curve2,
                 interval1=None,
                 interval2=None,
                 reverse1=False,
                 reverse2=True):
        from yanntricks.src.segment import Segment
        # TODO: I think that the parameters reverse1 and reverse2 are no more useful
        #   since I enforce the condition curve1 : left -> right by hand.
        ObjectGraph.__init__(self, self)

        self.curve1 = curve1
        self.curve2 = curve2

        #self.f1=self.curve1       # TODO: Soon or later, one will have to fusion these two
        #self.f2=self.curve2

        self.mx1 = interval1[0]
        self.mx2 = interval1[1]
        self.Mx1 = interval2[0]
        self.Mx2 = interval2[1]

        for attr in [self.mx1, self.mx2, self.Mx1, self.Mx2]:
            if attr == None:
                raise TypeError(
                    "At this point, initial and final values have to be already chosen"
                )
        self.curve1.llamI = self.mx1
        self.curve1.llamF = self.Mx1
        self.curve2.llamI = self.mx2
        self.curve2.llamF = self.Mx2

        self.draw_Isegment = True
        self.draw_Fsegment = True
        self.Isegment = Segment(self.curve2.get_point(self.mx2, advised=False),
                                self.curve1.get_point(self.mx1, advised=False))
        self.Fsegment = Segment(self.curve1.get_point(self.Mx1, advised=False),
                                self.curve2.get_point(self.Mx2, advised=False))

        self.add_option("fillstyle=vlines")
        self.parameters.color = None
Пример #22
0
    def __init__(self, A, O, B, r=None):
        from yanntricks.src.affine_vector import AffineVector
        self.A = A
        self.O = O
        self.B = B
        if r == None:
            # Does not depend on the radius because we are giving a 'visual' length.
            r = 0.5
        self.r = r
        self.angleA = AffineVector(O, A).angle()
        self.angleB = AffineVector(O, B).angle()

        # I think that one does not have to check and fix what angle is first here
        # because the angles are re-computed in self.circle.

        self.angleI = self.angleA
        self.angleF = self.angleB

        ObjectGraph.__init__(self, self)
        self._mark_angle = None
Пример #23
0
    def __init__(self, tuple_box_list, legende=None):
        ObjectGraph.__init__(self, self)
        self.tuple_box_list = tuple_box_list
        self.box_list = []
        for t in self.tuple_box_list:
            self.box_list.append(HistogramBox(t[0], t[1], t[2], self))

        #self.n=sum( [b.n for b in self.box_list] )
        # New iterator trick http://python3porting.com/improving.html
        self.n = sum(b.n for b in self.box_list)

        self.length = 12  # Visual length (in centimeter) of the histogram
        self.height = 6  # Visual height (in centimeter) of the histogram
        self.d_xmin = min([b.d_xmin
                           for b in self.box_list])  # min and max of the data
        self.d_xmax = max([b.d_xmax for b in self.box_list])
        # max of the data ordinate.
        self.d_ymax = max([b.n for b in self.box_list])
        self.xsize = self.d_xmax - self.d_xmin
        self.ysize = self.d_ymax  # d_ymin is zero (implicitly)

        self.legende = legende

        # TODO : For sure one can sort it easier.
        # The problem is that if several differences
        # x.th_height-y.th_height are small,
        # int(...) always returns 1 (or -1), so that the sorting
        # gets wrong.
        self.xscale = self.length / self.xsize
        classement = self.box_list[:]
        facteur = 10
        for x in classement:
            for y in classement:
                try:
                    facteur = max(facteur, 10 / (x.th_height - y.th_height))
                except ZeroDivisionError:
                    pass
        classement.sort(key=lambda x: int(x.th_height * facteur))
        self.yscale = self.height / classement[-1].th_height
        self.height / self.ysize
Пример #24
0
    def __init__(self, op, O, A, B, angleI=0, angleF=0):
        """
        The circle passing trough A and B with center O.

        `A`, `B` and `O` are tuples of numbers
        """
        from yanntricks.src.Constructors import Vector3D
        ObjectGraph.__init__(self, self)
        self.op = op
        self.O = O
        self.A = A
        self.B = B
        self.center = Vector3D(O[0], O[1], O[2])
        self.u = Vector3D(A[0] - O[0], A[1] - O[1], A[2] - O[2])
        self.v = Vector3D(B[0] - O[0], B[1] - O[1], B[2] - O[2])
        self.radius_u = sqrt(sum(k**2 for k in self.u))
        self.radius_v = sqrt(sum(k**2 for k in self.v))
        self.parameters.plotpoints = 10 * max(self.radius_u, self.radius_v)
        self.angleI = angleI
        self.angleF = angleF
        self.divide = False
        self.linear_plotpoints = CIRCLE3D_LINEAR_PLOTPOINTS
Пример #25
0
    def __init__(self, d1, d2, r, n1, n2):
        """
        two lines and a distance.

        n1 and n2 are 0 or 1 and indicating which sector has to be marked.
        'n1' if for the intersection with d1. If 'n1=0' then we choose the intersection nearest to d1.I
        Similarly for n2
        """
        ObjectGraph.__init__(self, self)
        self.d1 = d1
        self.d2 = d2

        # If the intersection point is one of the initial or final point of d1 or d2, then the sorting
        # in 'action_on_pspict' does not work.
        # This happens in RightAngle(  Segment(D,E),Segment(D,F),l=0.2, n1=1,n2=1 ) because the same point 'D' is given
        # for both d1 and d2.
        # We need d1.I, d1.F, d2.I and d2.F to be four distinct points.
        if self.d1.I.is_almost_equal(self.d2.I) or self.d1.I.is_almost_equal(
                self.d2.F) or self.d1.F.is_almost_equal(
                    self.d2.I) or self.d1.F.is_almost_equal(self.d2.F):
            self.d1 = d1.dilatation(1.5)
            self.d2 = d2.dilatation(1.5)

        self.r = r
        self.n1 = n1
        self.n2 = n2
        self.intersection = Intersection(d1, d2)[0]

        # If the intersection point is one of the given points,
        # there will be troubles.
        # For then angle between AB and CD at point I, we need A,B,C,D
        # and I to be five different points.
        if self.intersection.is_almost_equal(
                self.d1.I) or self.intersection.is_almost_equal(self.d1.F):
            self.d1 = d1.dilatation(1.5)
        if self.intersection.is_almost_equal(
                self.d2.I) or self.intersection.is_almost_equal(self.d2.F):
            self.d2 = d2.dilatation(1.5)
Пример #26
0
    def __init__(self, nlines, ncolumns):
        ObjectGraph.__init__(self, self)
        self.nlines = nlines
        self.ncolumns = ncolumns
        self._computed_central_points = False
        self.elements = {}
        self._lines = {}
        self._columns = {}
        self.matrix_environment = "pmatrix"
        for i in range(1, nlines + 1):
            for j in range(1, ncolumns + 1):
                self.elements[i, j] = MatrixElement(line=i, column=j)

        # Line constructions
        for i in range(1, nlines + 1):
            self._lines[i] = MatrixLineColumn(i, self)
            for j in range(1, ncolumns + 1):
                self.getLine(i).elements[j] = self.getElement(i, j)
        # Column constructions
        for j in range(1, ncolumns + 1):
            self._columns[j] = MatrixLineColumn(j, self)
            for i in range(1, nlines + 1):
                self.getColumn(j).elements[i] = self.getElement(i, j)
Пример #27
0
    def __init__(self, C, base, mx, Mx, pspict=None):
        ObjectGraph.__init__(self, self)
        self.C = C
        self.base = base
        self.mx = mx
        self.Mx = Mx
        self.pspict = pspict
        self.options = Options()
        self.IsLabel = False
        self.axes_unit = AxesUnit(self.base.length, "")
        self.Dx = 1
        self.arrows = "->"
        self.graduation = True
        self.numbering = True
        self.imposed_graduation = []
        self.mark_origin = True
        self.mark = None
        self.mark_angle = degree(base.angle().radian-pi/2)
        self.enlarge_size = 0.5

        # The `conclude` method performs the last computations before
        # to be drawn. The graduation bars are added there.
        self._already_concluded = False
Пример #28
0
 def __init__(self,
              center,
              radius,
              angleI=0,
              angleF=360,
              visual=False,
              pspict=None):
     from yanntricks.src.AngleMeasure import AngleMeasure
     from yanntricks.src.Defaults import CIRCLE_LINEAR_PLOTPOINTS
     GenericCurve.__init__(self, pI=angleI, pF=angleF)
     self.linear_plotpoints = CIRCLE_LINEAR_PLOTPOINTS
     self.center = center
     self.radius = radius
     ObjectGraph.__init__(self, self)
     self.diameter = 2 * self.radius
     self._parametric_curve = None
     self.angleI = AngleMeasure(value_degree=angleI, keep_negative=True)
     self.angleF = AngleMeasure(value_degree=angleF, keep_negative=True)
     a = numerical_approx(self.angleI.degree)
     b = numerical_approx(self.angleF.degree)
     self.visual = visual
     self.pspict = pspict
     self._equation = None
     self._numerical_equation = None
Пример #29
0
    def __init__(self, points_list):
        ObjectGraph.__init__(self, self)
        self.edges = []
        self.vertices = points_list
        self.points_list = self.vertices

        for i in range(0, len(self.points_list)):
            segment = Segment(
                self.points_list[i],
                self.points_list[(i + 1) % len(self.points_list)])
            self.edges.append(segment)
        self.draw_edges = True
        self.independent_edge = False
        self.parameters = None

        from yanntricks.src.parameters.Parameters import Parameters
        from yanntricks.src.parameters.HatchParameters import HatchParameters
        from yanntricks.src.parameters.FillParameters import FillParameters

        self.edges_parameters = Parameters(self)
        self.hatch_parameters = HatchParameters()
        self.fill_parameters = FillParameters()
        self._hatched = False
        self._filled = False
Пример #30
0
 def __init__(self, question, length=1):
     ObjectGraph.__init__(self, self)
     self.question = sudoku_substitution(question)
     self.length = length  # length of a cell