Révision 247 pobysoPythonSage/src/sageSLZ/sageRunSLZ.sage

sageRunSLZ.sage (revision 247)
3460 3460
    ## Output counters
3461 3461
# End srs_runSLZ-v05_gram
3462 3462
#
3463
def srs_run_SLZ_v05_proj(inputFunction,
3464
                         inputLowerBound,
3465
                         inputUpperBound,
3466
                         alpha,
3467
                         degree,
3468
                         precision,
3469
                         emin,
3470
                         emax,
3471
                         targetHardnessToRound,
3472
                         debug = False):
3473
    """
3474
    changes from plain V5:
3475
       LLL reduction is not performed on the matrix itself but rather on the
3476
       product of the matrix with a uniform random matrix.
3477
       The reduced matrix obtained is discarded but the transformation matrix 
3478
       obtained is used to multiply the original matrix in order to reduced it.
3479
       If a sufficient level of reduction is obtained, we stop here. If not
3480
       the product matrix obtained above is LLL reduced. But as it has been
3481
       pre-reduced at the above step, reduction is supposed to be much fastet.
3482
       Both reductions combined should hopefully be faster than a straight
3483
       single reduction.
3484
    Changes from V4:
3485
        Approximation polynomial has coefficients rounded.
3486
    Changes from V3:
3487
        Root search is changed again:
3488
            - only resultants in i are computed;
3489
            - roots in i are searched for;
3490
            - if any, they are tested for hardness-to-round.
3491
    Changes from V2:
3492
        Root search is changed:
3493
            - we compute the resultants in i and in t;
3494
            - we compute the roots set of each of these resultants;
3495
            - we combine all the possible pairs between the two sets;
3496
            - we check these pairs in polynomials for correctness.
3497
    Changes from V1: 
3498
        1- check for roots as soon as a resultant is computed;
3499
        2- once a non null resultant is found, check for roots;
3500
        3- constant resultant == no root.
3501
    """
3502

  
3503
    if debug:
3504
        print "Function                :", inputFunction
3505
        print "Lower bound             :", inputLowerBound
3506
        print "Upper bounds            :", inputUpperBound
3507
        print "Alpha                   :", alpha
3508
        print "Degree                  :", degree
3509
        print "Precision               :", precision
3510
        print "Emin                    :", emin
3511
        print "Emax                    :", emax
3512
        print "Target hardness-to-round:", targetHardnessToRound
3513
        print
3514
    ## Important constants.
3515
    ### Stretch the interval if no error happens.
3516
    noErrorIntervalStretch = 1 + 2^(-5)
3517
    ### If no vector validates the Coppersmith condition, shrink the interval
3518
    #   by the following factor.
3519
    noCoppersmithIntervalShrink = 1/2
3520
    ### If only (or at least) one vector validates the Coppersmith condition, 
3521
    #   shrink the interval by the following factor.
3522
    oneCoppersmithIntervalShrink = 3/4
3523
    #### If only null resultants are found, shrink the interval by the 
3524
    #    following factor.
3525
    onlyNullResultantsShrink     = 3/4
3526
    ## Structures.
3527
    RRR         = RealField(precision)
3528
    RRIF        = RealIntervalField(precision)
3529
    ## Converting input bound into the "right" field.
3530
    lowerBound = RRR(inputLowerBound)
3531
    upperBound = RRR(inputUpperBound)
3532
    ## Before going any further, check domain and image binade conditions.
3533
    print inputFunction(1).n()
3534
    output = slz_fix_bounds_for_binades(lowerBound, upperBound, inputFunction)
3535
    if output is None:
3536
        print "Invalid domain/image binades. Domain:",\
3537
        lowerBound, upperBound, "Images:", \
3538
        inputFunction(lowerBound), inputFunction(upperBound)
3539
        raise Exception("Invalid domain/image binades.")
3540
    lb = output[0] ; ub = output[1]
3541
    if lb != lowerBound or ub != upperBound:
3542
        print "lb:", lb, " - ub:", ub
3543
        print "Invalid domain/image binades. Domain:",\
3544
        lowerBound, upperBound, "Images:", \
3545
        inputFunction(lowerBound), inputFunction(upperBound)
3546
        raise Exception("Invalid domain/image binades.")
3547
    #
3548
    ## Progam initialization
3549
    ### Approximation polynomial accuracy and hardness to round.
3550
    polyApproxAccur           = 2^(-(targetHardnessToRound + 1))
3551
    polyTargetHardnessToRound = targetHardnessToRound + 1
3552
    ### Significand to integer conversion ratio.
3553
    toIntegerFactor           = 2^(precision-1)
3554
    print "Polynomial approximation required accuracy:", polyApproxAccur.n()
3555
    ### Variables and rings for polynomials and root searching.
3556
    i=var('i')
3557
    t=var('t')
3558
    inputFunctionVariable = inputFunction.variables()[0]
3559
    function = inputFunction.subs({inputFunctionVariable:i})
3560
    #  Polynomial Rings over the integers, for root finding.
3561
    Zi = ZZ[i]
3562
    Zt = ZZ[t]
3563
    Zit = ZZ[i,t]
3564
    ## Number of iterations limit.
3565
    maxIter = 100000
3566
    #
3567
    ## Set the variable name in Sollya.
3568
    pobyso_name_free_variable_sa_so(str(function.variables()[0]))
3569
    ## Compute the scaled function and the degree, in their Sollya version 
3570
    #  once for all.
3571
    (scaledf, sdlb, sdub, silb, siub) = \
3572
        slz_compute_scaled_function(function, lowerBound, upperBound, precision)
3573
    print "Scaled function:", scaledf._assume_str().replace('_SAGE_VAR_', '')
3574
    #print "Scaled bounds:", sdlb, sdub
3575
    scaledfSo = sollya_lib_parse_string(scaledf._assume_str().replace('_SAGE_VAR_', ''))
3576
    degreeSo  = pobyso_constant_from_int_sa_so(degree)
3577
    #
3578
    ## Compute the scaling. boundsIntervalRifSa defined out of the loops.
3579
    domainBoundsInterval   = RRIF(lowerBound, upperBound)
3580
    (unscalingFunction, scalingFunction) = \
3581
        slz_interval_scaling_expression(domainBoundsInterval, i)
3582
    #print scalingFunction, unscalingFunction
3583
    ## Set the Sollya internal precision (with an arbitrary minimum of 192).
3584
    internalSollyaPrec = ceil((RR('1.5') * targetHardnessToRound) / 64) * 64
3585
    if internalSollyaPrec < 192:
3586
        internalSollyaPrec = 192
3587
    pobyso_set_prec_sa_so(internalSollyaPrec)
3588
    print "Sollya internal precision:", internalSollyaPrec
3589
    ## Some variables.
3590
    ### General variables
3591
    lb                  = sdlb
3592
    ub                  = sdub
3593
    nbw                 = 0
3594
    intervalUlp         = ub.ulp()
3595
    #### Will be set by slz_interval_and_polynomila_to_sage.
3596
    ic                  = 0 
3597
    icAsInt             = 0    # Set from ic.
3598
    solutionsSet        = set()
3599
    tsErrorWidth        = []
3600
    csErrorVectors      = []
3601
    csVectorsResultants = []
3602
    floatP              = 0  # Taylor polynomial.
3603
    floatPcv            = 0  # Ditto with variable change.
3604
    intvl               = "" # Taylor interval
3605
    terr                = 0  # Taylor error.
3606
    iterCount = 0
3607
    htrnSet = set()
3608
    ### Timers and counters.
3609
    wallTimeStart                   = 0
3610
    cpuTimeStart                    = 0
3611
    taylCondFailedCount             = 0
3612
    coppCondFailedCount             = 0
3613
    resultCondFailedCount           = 0
3614
    coppCondFailed                  = False
3615
    resultCondFailed                = False
3616
    globalResultsList               = []
3617
    basisConstructionsCount         = 0
3618
    basisConstructionsFullTime      = 0
3619
    basisConstructionTime           = 0
3620
    reductionsCount                 = 0
3621
    reductionsFullTime              = 0
3622
    reductionTime                   = 0
3623
    resultantsComputationsCount     = 0
3624
    resultantsComputationsFullTime  = 0
3625
    resultantsComputationTime       = 0
3626
    rootsComputationsCount          = 0
3627
    rootsComputationsFullTime       = 0
3628
    rootsComputationTime            = 0
3629
    print
3630
    ## Global times are started here.
3631
    wallTimeStart                   = walltime()
3632
    cpuTimeStart                    = cputime()
3633
    ## Main loop.
3634
    while True:
3635
        if lb >= sdub:
3636
            print "Lower bound reached upper bound."
3637
            break
3638
        if iterCount == maxIter:
3639
            print "Reached maxIter. Aborting"
3640
            break
3641
        iterCount += 1
3642
        print "[", lb, ",", ub, "]", ((ub - lb) / intervalUlp).log2().n(), \
3643
            "log2(numbers)." 
3644
        ### Compute a Sollya polynomial that will honor the Taylor condition.
3645
        prceSo = slz_compute_polynomial_and_interval_01(scaledfSo,
3646
                                                        degreeSo,
3647
                                                        lb,
3648
                                                        ub,
3649
                                                        polyApproxAccur)
3650
        if debug:
3651
            print "Approximation polynomial computed."
3652
        if prceSo is None:
3653
            raise Exception("Could not compute an approximation polynomial.")
3654
        ### Convert back the data into Sage space.                         
3655
        (floatP, floatPcv, intvl, ic, terr) = \
3656
            slz_interval_and_polynomial_to_sage((prceSo[0], prceSo[0],
3657
                                                 prceSo[1], prceSo[2], 
3658
                                                 prceSo[3]))
3659
        intvl = RRIF(intvl)
3660
        ## Clean-up Sollya stuff.
3661
        for elem in prceSo:
3662
            sollya_lib_clear_obj(elem)
3663
        #print  floatP, floatPcv, intvl, ic, terr
3664
        #print floatP
3665
        #print intvl.endpoints()[0].n(), \
3666
        #      ic.n(),
3667
        #intvl.endpoints()[1].n()
3668
        ### Check returned data.
3669
        #### Is approximation error OK?
3670
        if terr > polyApproxAccur:
3671
            exceptionErrorMess  = \
3672
                "Approximation failed - computed error:" + \
3673
                str(terr) + " - target error: "
3674
            exceptionErrorMess += \
3675
                str(polyApproxAccur) + ". Aborting!"
3676
            raise Exception(exceptionErrorMess)
3677
        #### Is lower bound OK?
3678
        if lb != intvl.endpoints()[0]:
3679
            exceptionErrorMess =  "Wrong lower bound:" + \
3680
                               str(lb) + ". Aborting!"
3681
            raise Exception(exceptionErrorMess)
3682
        #### Set upper bound.
3683
        if ub > intvl.endpoints()[1]:
3684
            ub = intvl.endpoints()[1]
3685
            print "[", lb, ",", ub, "]", ((ub - lb) / intervalUlp).log2().n(), \
3686
            "log2(numbers)." 
3687
            taylCondFailedCount += 1
3688
        #### Is interval not degenerate?
3689
        if lb >= ub:
3690
            exceptionErrorMess = "Degenerate interval: " + \
3691
                                "lowerBound(" + str(lb) +\
3692
                                ")>= upperBound(" + str(ub) + \
3693
                                "). Aborting!"
3694
            raise Exception(exceptionErrorMess)
3695
        #### Is interval center ok?
3696
        if ic <= lb or ic >= ub:
3697
            exceptionErrorMess =  "Invalid interval center for " + \
3698
                                str(lb) + ',' + str(ic) + ',' +  \
3699
                                str(ub) + ". Aborting!"
3700
            raise Exception(exceptionErrorMess)
3701
        ##### Current interval width and reset future interval width.
3702
        bw = ub - lb
3703
        nbw = 0
3704
        icAsInt = int(ic * toIntegerFactor)
3705
        #### The following ratio is always >= 1. In case we may want to
3706
        #    enlarge the interval
3707
        curTaylErrRat = polyApproxAccur / terr
3708
        ### Make the  integral transformations.
3709
        #### Bounds and interval center.
3710
        intIc = int(ic * toIntegerFactor)
3711
        intLb = int(lb * toIntegerFactor) - intIc
3712
        intUb = int(ub * toIntegerFactor) - intIc
3713
        #
3714
        #### Polynomials
3715
        basisConstructionTime         = cputime()
3716
        ##### To a polynomial with rational coefficients with rational arguments
3717
        ratRatP = slz_float_poly_of_float_to_rat_poly_of_rat_pow_two(floatP)
3718
        ##### To a polynomial with rational coefficients with integer arguments
3719
        ratIntP = \
3720
            slz_rat_poly_of_rat_to_rat_poly_of_int(ratRatP, precision)
3721
        #####  Ultimately a multivariate polynomial with integer coefficients  
3722
        #      with integer arguments.
3723
        coppersmithTuple = \
3724
            slz_rat_poly_of_int_to_poly_for_coppersmith(ratIntP, 
3725
                                                        precision, 
3726
                                                        targetHardnessToRound, 
3727
                                                        i, t)
3728
        #### Recover Coppersmith information.
3729
        intIntP = coppersmithTuple[0]
3730
        N = coppersmithTuple[1]
3731
        nAtAlpha = N^alpha
3732
        tBound = coppersmithTuple[2]
3733
        leastCommonMultiple = coppersmithTuple[3]
3734
        iBound = max(abs(intLb),abs(intUb))
3735
        basisConstructionsFullTime        += cputime(basisConstructionTime)
3736
        basisConstructionsCount           += 1
3737
        #### Compute the matrix to reduce for debug purpose. Otherwise
3738
        #    slz_compute_coppersmith_reduced_polynomials does the job
3739
        #    invisibly.
3740
        if debug:
3741
            matrixToReduce = slz_compute_initial_lattice_matrix(intIntP,
3742
                                                                alpha,
3743
                                                                N,
3744
                                                                iBound,
3745
                                                                tBound)
3746
            maxNorm     = 0
3747
            latticeSize = 0
3748
            matrixFile = file('/tmp/matrixToReduce.txt', 'w')
3749
            for row in matrixToReduce.rows():
3750
                currentNorm = row.norm()
3751
                if currentNorm > maxNorm:
3752
                    maxNorm = currentNorm
3753
                latticeSize += 1
3754
                for elem in row:
3755
                    matrixFile.write(elem.str(base=2) + ",")
3756
                matrixFile.write("\n")
3757
            #matrixFile.write(matrixToReduce.str(radix="2") + "\n")
3758
            matrixFile.close()
3759
            #### We use here binary length as defined in LLL princepts.
3760
            binaryLength = latticeSize * log(maxNorm)
3761
            print "Binary length:", binaryLength.n()
3762
            raise Exception("Deliberate stop here.")
3763
        # End if debug
3764
        reductionTime                     = cputime()
3765
        #### Compute the reduced polynomials.
3766
        print "Starting reduction..."
3767
        ccReducedPolynomialsList =  \
3768
                slz_compute_coppersmith_reduced_polynomials_proj(intIntP, 
3769
                                                                 alpha, 
3770
                                                                 N, 
3771
                                                                 iBound, 
3772
                                                                 tBound)
3773
        print "...reduction accomplished in", cputime(reductionTime), "s."
3774
        if ccReducedPolynomialsList is None:
3775
            raise Exception("Reduction failed.")
3776
        reductionsFullTime    += cputime(reductionTime)
3777
        reductionsCount       += 1
3778
        if len(ccReducedPolynomialsList) < 2:
3779
            print "Nothing to form resultants with."
3780
            print
3781
            coppCondFailedCount += 1
3782
            coppCondFailed = True
3783
            ##### Apply a different shrink factor according to 
3784
            #  the number of compliant polynomials.
3785
            if len(ccReducedPolynomialsList) == 0:
3786
                ub = lb + bw * noCoppersmithIntervalShrink
3787
            else: # At least one compliant polynomial.
3788
                ub = lb + bw * oneCoppersmithIntervalShrink
3789
            if ub > sdub:
3790
                ub = sdub
3791
            if lb == ub:
3792
                raise Exception("Cant shrink interval \
3793
                anymore to get Coppersmith condition.")
3794
            nbw = 0
3795
            continue
3796
        #### We have at least two polynomials.
3797
        #    Let us try to compute resultants.
3798
        #    For each resultant computed, go for the solutions.
3799
        ##### Build the pairs list.
3800
        polyPairsList           = []
3801
        for polyOuterIndex in xrange(0, len(ccReducedPolynomialsList) - 1):
3802
            for polyInnerIndex in xrange(polyOuterIndex+1, 
3803
                                         len(ccReducedPolynomialsList)):
3804
                polyPairsList.append((ccReducedPolynomialsList[polyOuterIndex],
3805
                                      ccReducedPolynomialsList[polyInnerIndex]))
3806
        #### Actual root search.
3807
        iRootsSet           = set()
3808
        hasNonNullResultant = False
3809
        for polyPair in polyPairsList:
3810
            resultantsComputationTime   = cputime()
3811
            currentResultantI = \
3812
                slz_resultant(polyPair[0],
3813
                              polyPair[1],
3814
                              t)
3815
            resultantsComputationsCount    += 1
3816
            resultantsComputationsFullTime +=  \
3817
                cputime(resultantsComputationTime)
3818
            #### Function slz_resultant returns None both for None and O
3819
            #    resultants.
3820
            if currentResultantI is None:
3821
                print "Nul resultant"
3822
                continue # Next polyPair.
3823
            ## We deleted the currentResultantI computation.
3824
            #### We have a non null resultant. From now on, whatever this
3825
            #    root search yields, no extra root search is necessary.
3826
            hasNonNullResultant = True
3827
            #### A constant resultant leads to no root. Root search is done.
3828
            if currentResultantI.degree() < 1:
3829
                print "Resultant is constant:", currentResultantI
3830
                break # There is no root.
3831
            #### Actual iroots computation.
3832
            rootsComputationTime        = cputime()
3833
            iRootsList = Zi(currentResultantI).roots()
3834
            rootsComputationsCount      +=  1
3835
            rootsComputationsFullTime   =   cputime(rootsComputationTime)
3836
            if len(iRootsList) == 0:
3837
                print "No roots in \"i\"."
3838
                break # No roots in i.
3839
            else:
3840
                for iRoot in iRootsList:
3841
                    # A root is given as a (value, multiplicity) tuple.
3842
                    iRootsSet.add(iRoot[0])
3843
        # End loop for polyPair in polyParsList. We only loop again if a 
3844
        # None or zero resultant is found.
3845
        #### Prepare for results for the current interval..
3846
        intervalResultsList = []
3847
        intervalResultsList.append((lb, ub))
3848
        #### Check roots.
3849
        rootsResultsList = []
3850
        for iRoot in iRootsSet:
3851
            specificRootResultsList = []
3852
            failingBounds           = []
3853
            # Root qualifies for modular equation, test it for hardness to round.
3854
            hardToRoundCaseAsFloat = RRR((icAsInt + iRoot) / toIntegerFactor)
3855
            #print "Before unscaling:", hardToRoundCaseAsFloat.n(prec=precision)
3856
            #print scalingFunction
3857
            scaledHardToRoundCaseAsFloat = \
3858
                scalingFunction(hardToRoundCaseAsFloat) 
3859
            print "Candidate HTRNc at x =", \
3860
                scaledHardToRoundCaseAsFloat.n().str(base=2),
3861
            if slz_is_htrn(scaledHardToRoundCaseAsFloat,
3862
                           function,
3863
                           2^-(targetHardnessToRound),
3864
                           RRR):
3865
                print hardToRoundCaseAsFloat, "is HTRN case."
3866
                specificRootResultsList.append(hardToRoundCaseAsFloat.n().str(base=2))
3867
                if lb <= hardToRoundCaseAsFloat and hardToRoundCaseAsFloat <= ub:
3868
                    print "Found in interval."
3869
                else:
3870
                    print "Found out of interval."
3871
                # Check the i root is within the i bound.
3872
                if abs(iRoot) > iBound:
3873
                    print "IRoot", iRoot, "is out of bounds for modular equation."
3874
                    print "i bound:", iBound
3875
                    failingBounds.append('i')
3876
                    failingBounds.append(iRoot)
3877
                    failingBounds.append(iBound)
3878
                if len(failingBounds) > 0:
3879
                    specificRootResultsList.append(failingBounds)
3880
            else: # From slz_is_htrn...
3881
                print "is not an HTRN case."
3882
            if len(specificRootResultsList) > 0:
3883
                rootsResultsList.append(specificRootResultsList)
3884
        if len(rootsResultsList) > 0:
3885
            intervalResultsList.append(rootsResultsList)
3886
        ### Check if a non null resultant was found. If not shrink the interval.
3887
        if not hasNonNullResultant:
3888
            print "Only null resultants for this reduction, shrinking interval."
3889
            resultCondFailed      =  True
3890
            resultCondFailedCount += 1
3891
            ### Shrink interval for next iteration.
3892
            ub = lb + bw * onlyNullResultantsShrink
3893
            if ub > sdub:
3894
                ub = sdub
3895
            nbw = 0
3896
            continue
3897
        #### An intervalResultsList has at least the bounds.
3898
        globalResultsList.append(intervalResultsList)   
3899
        #### Compute an incremented width for next upper bound, only
3900
        #    if not Coppersmith condition nor resultant condition
3901
        #    failed at the previous run. 
3902
        if not coppCondFailed and not resultCondFailed:
3903
            nbw = noErrorIntervalStretch * bw
3904
        else:
3905
            nbw = bw
3906
        ##### Reset the failure flags. They will be raised
3907
        #     again if needed.
3908
        coppCondFailed   = False
3909
        resultCondFailed = False
3910
        #### For next iteration (at end of loop)
3911
        #print "nbw:", nbw
3912
        lb  = ub
3913
        ub += nbw     
3914
        if ub > sdub:
3915
            ub = sdub
3916
        print
3917
    # End while True
3918
    ## Main loop just ended.
3919
    globalWallTime = walltime(wallTimeStart)
3920
    globalCpuTime  = cputime(cpuTimeStart)
3921
    ## Output results
3922
    print ; print "Intervals and HTRNs" ; print
3923
    for intervalResultsList in globalResultsList:
3924
        intervalResultString = "[" + str(intervalResultsList[0][0]) +\
3925
                               "," + str(intervalResultsList[0][1])  + "]"
3926
        print intervalResultString,
3927
        if len(intervalResultsList) > 1:
3928
            rootsResultsList = intervalResultsList[1]
3929
            specificRootResultIndex = 0
3930
            for specificRootResultsList in rootsResultsList:
3931
                if specificRootResultIndex == 0:
3932
                    print "\t", specificRootResultsList[0],
3933
                else:
3934
                    print " " * len(intervalResultString), "\t", \
3935
                          specificRootResultsList[0],
3936
                if len(specificRootResultsList) > 1:
3937
                    print specificRootResultsList[1]
3938
                specificRootResultIndex += 1
3939
        print ; print
3940
    #print globalResultsList
3941
    #
3942
    print "Timers and counters"
3943
    print
3944
    print "Number of iterations:", iterCount
3945
    print "Taylor condition failures:", taylCondFailedCount
3946
    print "Coppersmith condition failures:", coppCondFailedCount
3947
    print "Resultant condition failures:", resultCondFailedCount
3948
    print "Iterations count: ", iterCount
3949
    print "Number of intervals:", len(globalResultsList)
3950
    print "Number of basis constructions:", basisConstructionsCount 
3951
    print "Total CPU time spent in basis constructions:", \
3952
        basisConstructionsFullTime
3953
    if basisConstructionsCount != 0:
3954
        print "Average basis construction CPU time:", \
3955
            basisConstructionsFullTime/basisConstructionsCount
3956
    print "Number of reductions:", reductionsCount
3957
    print "Total CPU time spent in reductions:", reductionsFullTime
3958
    if reductionsCount != 0:
3959
        print "Average reduction CPU time:", \
3960
            reductionsFullTime/reductionsCount
3961
    print "Number of resultants computation rounds:", \
3962
        resultantsComputationsCount
3963
    print "Total CPU time spent in resultants computation rounds:", \
3964
        resultantsComputationsFullTime
3965
    if resultantsComputationsCount != 0:
3966
        print "Average resultants computation round CPU time:", \
3967
            resultantsComputationsFullTime/resultantsComputationsCount
3968
    print "Number of root finding rounds:", rootsComputationsCount
3969
    print "Total CPU time spent in roots finding rounds:", \
3970
        rootsComputationsFullTime
3971
    if rootsComputationsCount != 0:
3972
        print "Average roots finding round CPU time:", \
3973
            rootsComputationsFullTime/rootsComputationsCount
3974
    print "Global Wall time:", globalWallTime
3975
    print "Global CPU time:", globalCpuTime
3976
    ## Output counters
3977
# End srs_runSLZ-v05_proj
3978
#
3463 3979
def srs_run_SLZ_v06(inputFunction,
3464 3980
                    inputLowerBound,
3465 3981
                    inputUpperBound,

Formats disponibles : Unified diff