Journal Un article de "Pour la science" m'ayant amené à coder pour une petite vérification perso...

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
37
20
juil.
2021

Sommaire

Bonjour,

Je lis « régulièrement » (autant que je le peux en résidant en Chine où les journaux n’arrivent pas toujours…) Pour la science.
Ce n’est pas libre, et tout ça, mais il y a un travail de sélection d’articles, de mise en contexte, d’infographie… qui représente un vrai travail éditorial. Je n’ai aucune idée de la relation qu’ils entretiennent avec les auteurs, mais du point de vue du lecteur, je suis satisfait de leur travail. Donc, même si ce n’est pas libre je vais en parler tout de même.

D’autant que la licence de l’article n’est pas le sujet de ce journal. L’article a été publié dans le numéro de janvier 2020, et s’appelle : Aux sources mathématiques des inégalités de richesse (par Bruce M Boghosian). Comme son titre l’indique, il parle de finance, de maths, et aussi un peu de physique statistique.

Le propos

L’article détaille un phénomène contre-intuitif, mais qui me semble fondamental si on veut parler des écarts de richesse. En se plaçant dans un système économique fermé, on peut simuler tous les échanges entre acteurs. Le parti pris de l’étude est que pour chaque échange, l’acteur le moins riche aura une probabilité de gain positive, alors que le plus riche aura une probabilité de gain négative. Il prend pour exemple le cas suivant :
Si le plus riche paye, il donne 20 % d’une base au moins riche. Si c’est au contraire le moins riche qui paye, il ne donnera que 17 % de cette même base au plus riche. Donc à chaque transaction, le pauvre peut gagner 20 % et risque de perdre 17 % quand le plus riche peut gagner 17 % et risque de perdre 20 %. (dans l'article, la base de calcul est la richesse du plus pauvre, avec l'idée que la personne ne dépensera pas plus de 17% de sa richesse).
Si on fait un grand nombre d’itérations du processus, on arrive à une situation d’oligarchie. Après quelques centaines de milliers d’échange, on n’a plus que quelques personnes très riches et le reste est ruiné. L’auteur indique qui si on continue, on arrive à un seul oligarque.

Ma démarche

Ça vaut toujours le coût d’aller faire les calculs et les simulations soi-même. Généralement, je ne prends pas le temps, mais là je pouvais. Donc je me suis lancé dans une simulation très simple. Le code n'est pas optimisé, est purement procédural… mais ce n'était pas le but.
J’ai d’abord repris les paramètres de l’article puis joué avec ces paramètres avec les constantes du début de code.

#!/usr/bin/python3
# -*- coding: utf8 -*-

from random import *
NB_PEOPLE=500
AMOUNT_START=2000
NB_LOOP=5000000
RATE=0.2
DRATE=0.05
i=0
people=[]


# first setting NB_PEOPLE people with the same amount of money (AMOUNT_START)
while i<NB_PEOPLE :
    people.append(AMOUNT_START)
    i+=1

# second part, transactions to be done NB_LOOP times
i=0
a=0
b=0
base=0
while i<NB_LOOP :
    #select two random people
    a=randint(0, (NB_PEOPLE-1))
    b=randint(0, (NB_PEOPLE-1))

    #compare the capital of both and select the base for the calculation for the next step (capital of the poorest)
    if people[a]<people[b]:
        base=people[a]
    else:
        base=people[b]
    #select direction of the transaction randomly
    c=random()
    if c>0.5 : # a pays b
        if people[a]==people[b]: # no advantage for the poorest as no poorest
            people[a]=people[a]-base*RATE
            people[b]=people[b]+base*RATE
        elif people[a]>people[b]: # poorest receive the money so add DRATE in the transfer
            people[a]=people[a]-base*(RATE+DRATE)
            people[b]=people[b]+base*(RATE+DRATE)
        else : # poorest pay the money so deduct DRATE in the transfer
            people[a]=people[a]-base*(RATE-DRATE)
            people[b]=people[b]+base*(RATE-DRATE)

    else: # b pays a
        if people[a]==people[b]: # no advantage for the poorest as no poorest
            people[a]=people[a]+base*RATE
            people[b]=people[b]-base*RATE
        elif people[a]>people[b]: # Reachest receive the money so deduct DRATE in the transfer
            people[a]=people[a]-base*(RATE-DRATE)
            people[b]=people[b]+base*(RATE-DRATE)
        else : # Richest pay the money so add DRATE in the transfer
            people[a]=people[a]-base*(RATE+DRATE)
            people[b]=people[b]+base*(RATE+DRATE)

    i+=1


# final, show the resluts   
i=0
final="Last tranfert base : " + str(base) +". "
while i<NB_PEOPLE :
    final+=str(int(people[i]))
    final+=", "
    i+=1

print(final)

Ensuite j'ai modifié le code pour rendre la simulation plus réaliste en évitant que les transactions se fasse toujours sur un pourcentage de la richesse du plus pauvre, mais j'ai maintenu ce pourcentage de la richesse du plus pauvre comme plafond, considérant qu'en général on essaye de ne pas se ruiné directement…

J'ai donc juste modifié le calcul de la variable base en ajoutant

c=randint(0,100)
base=base*c/100 # so the caculation base is only a random proportion of the max base.

Résultats et conclusion:

Étonnamment, toutes ces modifications, ne changent fondamentalement rien au résultat final, on a un oligarque. On peut jouer sur l'écart entre le gain et la perte du plus pauvre et plus riche (mes 17% et 20% du début) sans plus impacter le résultat final.

Ça fait réfléchir sur l'importance de la redistribution active des richesses, car le système par pur commerce ne semble pas produire le but recherché…

  • # Quel est le rapport avec la réalité ?

    Posté par  . Évalué à 5.

    Je n'ai rien compris : en quoi consiste les "échanges" ? Pour moi dans un échange il y a un vendeur et un acheteur qui se mettent d'accord sur un prix. Je ne vois pas ce que viennent faire des probabilités là-dedans, ni pourquoi il y a des "si" dans la description dans la section "Le propos".

    • [^] # Re: Quel est le rapport avec la réalité ?

      Posté par  (site web personnel) . Évalué à 7.

      Il semble que je n'ai pas été très bon pour expliquer cet article. Probablement que j'étais trop concentré sur les conséquences.

      En fait, l'article tente de modéliser les écarts de richesses rencontré dans différents pays à différentes époques. Pour cela il commence avec un modèle très simple (celui décrit plus haut).
      Il n'y a que des acheteurs et des vendeurs. Les rôles sont tirés au hasard. Malgré les systèmes tentant de rééquilibrer la situation (le plus riche paye plus ou gagne moins) la conséquence reste la même. Le système reste déséquilibré et conduit à l'oligarchie. Ce qui empêche un système économique simulé de tomber dans ce piège est la redistribution (un paramètre que je n'ai pas traité ici, je ne voulais pas parler de tout l'article, mais du fait que si dans un système économique on ne considère que des échanges marchant, alors on tombe vers une oligarchie.)

      Les "Si" évoqués sont juste des tentative de favoriser les plus pauvres, mais elles ne marchent pas.

      • [^] # Re: Quel est le rapport avec la réalité ?

        Posté par  . Évalué à 7.

        Ok, je comprends mieux, mais je ne vois toujours pas le rapport avec la réalité. Cela me fait penser au modèle météo de Lorenz qui a créé la légende de l'effet papillon. Il a été prouvé depuis que l'atmosphère n'est pas sensible aux petites variations des conditions initiales, et donc que l'effet papillon n'existe pas dans l'atmosphère même s'il existe dans le modèle de Lorenz. Voir l'article de Raoul Robert dans Pour la Science de mai 2001.
        Ici on a un modèle très simple qui a un comportement contre-intuitif et on transfère sans preuve ce comportement à l'économie réelle. Je pense qu'on prend le problème à l'envers. Le modèle ne prouve pas que la redistribution ne fonctionne pas. La vraie question est de savoir si la redistribution fonctionne. Dans ce cas on a une preuve que le modèle est faux. Mais si elle ne fonctionne pas, on n'a pas pour autant une preuve que le modèle est correct.

        • [^] # Re: Quel est le rapport avec la réalité ?

          Posté par  (site web personnel) . Évalué à 10.

          « Le modèle ne prouve pas que la redistribution ne fonctionne pas. »

          Il me semble que ce qu'il s'agisse de montrer dans l'article serait plutôt la chose suivante :

          Même l'emploi d'un biais favorisant les pauvres ne permet pas qu'un modèle d'échanges purement stochastiques ne produise pas des inégalités de fortune comparables à ceux observés dans la réalité.

          Ensuite chacun est libre d'interpréter.
          Par exemple, il est bien connu que les échanges ont tendance à favoriser le maintien des différences de richesse (modèle décrit par Aristote, amplement commenté par P. Jorion dans sa critique du dogme naïf de l'offre et de la demande). On en déduira que les échanges commerciaux risquent fort d'amplifier l'effet observé dans le jeu de l'article.
          Ou encore, certaines élites attribuent leur différence de fortune avec le prolétariat par des performances exceptionnelles (aristocratie). Le jeu remet cette auto-justification en perspective en montrant que le pur hasard conduit au même résultat.
          Ou comme vous le faites : ce n'est qu'un jeu, ça ne montre rien.
          Ou bien : pour compenser la tendance concentrationnaire des échanges, les politiques publiques devraient inclure des mécanismes sérieux de redistribution.
          Et cætera.

          « IRAFURORBREVISESTANIMUMREGEQUINISIPARETIMPERAT » — Odes — Horace

        • [^] # Re: Quel est le rapport avec la réalité ?

          Posté par  (site web personnel) . Évalué à 7.

          En fait, dans la suite, le modèle améliorer de 3 autres facteurs est ce qui permet de simuler au mieux la réalité. Les 3 autres facteurs étant: un coefficient de redistribution, un facteur d'avantage pour les plus riches, un facteur de prêts. Avec juste cela ils parviennent à coller de très prêt aux courbes de répartitions des richesse (écart de l'ordre de 0.3%) ce qui en fait les simulations les plus performantes alors qu'il y a très peu de paramètre.

          La simulation initiale, sans les trois autres paramètres, donc celle de mon code, montre justement que si on considère le système économique comme n'étant qu'une succession d'échanges, alors on arrive dans un système instable conduisant à un oligopole. Le papier indique donc que comme dans la réalité on est pas dans un système instable, c'est donc que le système économique n'est pas un simple système d'échange. Ils ajoutent alors un système de redistribution et avec juste ce paramètre complémentaire, ils parviennent à un système stable. Mais ils doivent ajouter les deux autres paramètres pour obtenir une simulation qui colle très bien à la réalité.

        • [^] # Re: Quel est le rapport avec la réalité ?

          Posté par  . Évalué à 4.

          Je dois avouer que je connais peu les mathématiques derrières les modèles météorologiques, donc j'espère ne pas dire trop de conneries, mais j'ai quand même quelques interrogations. L'article de Raoul Robert mentionne que le comportement à long terme n'est pas si chaotique parce que après un certain temps, les solutions des équations météorologiques se séparent en quelques structures qui se comportent de manière simple (en jargon mathématique, c'est ce qu'on appelle une résolution en solitons). Soit, mais ça laisse ouvert les questions de savoir quelles structures vont apparaître pour chaque donnée initiale (problèmes mentionné dans l'article), ainsi que du moment où la solution «change» d'un comportement chaotique à un comportement «somme de trucs simples», et de ce qu'il se passe avant ce moment. Peut-être que cet article est plus pertinent pour dire que le climat à long terme est plus facilement prévisible que la météo à court terme ? Bref, comme je l'ai dit, je ne suis pas spécialiste, donc il se peut que j'aie mélangé des choses.

          Mais il y a deux autres problèmes plus gros. En premier : s'il ne faut pas calquer «l'effet papillon» comme un dogme sur tous les systèmes un peu compliqués, il ne faut pas non plus croire que tous les systèmes ont un comportement statistique simple. Il existe des systèmes, même de dimension infinie, qui sont instables. On peut avoir des perturbations qui changent de fréquences, créant ainsi de l'instabilité . Un des exemples où on peut démontrer ça est l'équation de Szego cubique (équation sans grande pertinence physique, mais bon), et un exemple plus pertinent physiquement sont certains systèmes de magnétohydrodynamique, même s'il me semble qu'on n'a pas de démonstration mathématique. Mais bon, pour nuancer et couronner le tout, je ne suis pas sûr que le genre d'instabilité que je mentionne exclue une résolution en soliton pour autant. Bref, c'est compliqué, et attention à ne pas plaquer une méthode adaptée à un problème sur un autre problème.

          En parlant de plaquer une méthode adaptée à un problème sur un autre problème, voici le plus gros problème. Tu dis «Je pense qu'on prend le problème à l'envers. Le modèle ne prouve pas que la redistribution ne fonctionne pas.» Tu sembles préférer partir de données expérimentales et complètement subordonner les modèles à ces données ; peut-être as-tu en tête la méthode hypothetico-déductive ? Si c'est le cas, c'est un problème. En effet, si cette approche est plus ou moins celle de certaines sciences expérimentales, ce n'est pas le cas de toutes les sciences. Par exemple, pendant longtemps, la théorie de l'évolution ne suivait pas ce modèle sans que ça pose problème. C'est encore moins le cas des sciences sociales. Les sciences sociales vont plus facilement faire appel à l'abduction. En ce sens, l'utilité d'un modèle ne se limite pas à «tel modèle colle aux données expérimentales», mais peut aussi servir à dire «tel phénomène est cohérent avec telles hypothèses» sans que ce soit une démonstration définitive que le modèle est «juste». Il me semble que c'est la deuxième approche qui est utilisée par l'auteur du journal ainsi que par l'article auquel il se réfère (et si on y réfléchit, même en sciences expérimentales, de nombreux modèles apparaissent de cette manière). Bien sûr, la seule observation qu'un modèle reproduit un phénomène n'est pas suffisant pour «démontrer» quoique ce soit, mais en conjonctions avec d'autres arguments, ça peut appuyer une thèse. Pour un peu plus de détails sur ce genre d'apsects, je peux recommander les vidéos «le raisonnement sociologique» sur Youtube de Gregoire Simpson (partie 1 partie 2 partie 3) (oui, je sais, Youtu*e, pas libre, toussa toussa; je fais ce que je peux) (une autre (parenthèse)).

          • [^] # Re: Quel est le rapport avec la réalité ?

            Posté par  . Évalué à 4.

            peut-être as-tu en tête la méthode hypothetico-déductive ? Si c'est le cas, c'est un problème. En effet, si cette approche est plus ou moins celle de certaines sciences expérimentales, ce n'est pas le cas de toutes les sciences.

            Ah bon, parce qu'il existe d'autres sciences que les sciences expérimentales ? ;-) [mode troll off]

            Dans les sciences expérimentales et en mathématique on utilise tout le temps l'abduction pour élaborer des hypothèses pas trop stupides. Ensuite il faut prouver. Un modèle numérique peut être un bon outil pour construire des hypothèses.

            • [^] # Re: Quel est le rapport avec la réalité ?

              Posté par  . Évalué à 6. Dernière modification le 20 juillet 2021 à 16:17.

              Je vois que nous sommes d'accord là dessus. La différence avec les sciences sociales, il me semble, est que l'abduction ne sert pas seulement en science sociale à construire des hypothèses, mais sert aussi d'argument (parmi d'autres) en faveur d'une théorie. Il est de toute façon difficile d'obtenir des données en quantité et qualité suffisante pour prouver par une méthode hypothetico déductive certaines théories en sciences sociales, en particulier si on essaye d'aller plus loin que des statistiques qui surplombent tout et qui ne permettent pas de comprendre les cas particuliers. Plutôt que de laisser tomber les bras*, on utilise alors d'autres méthodes, plus adaptées aux questions et problématiques de chaque discipline.

              Bref, je voulais juste mettre en garde sur le fait que toutes les sciences n'ont pas la même épistémologie, et que beaucoup de personnes (y compris scientifiques) plaque une épistémologie d'un certain domaine sur un autre. Pour l'anecdote, j'ai vu mon lot de mathématiciens (puisque je suis moi-même mathématicien) qui ne comprennent pas ça et qui pensent aller révolutionner des domaines scientifiques, alors que tout ce qu'ils arrivent à faire, c'est démontrer (certes rigoureusement selon les standards de preuve en mathématiques) des propriétés très basiques sur des modèles qu'ils ont sortis de leur euh, de nulle part, ce qui ne répond à aucune question du domaine dans lequel ils ont débarqué avec leur gros sabot.

              *Je crois avoir mélangé plusieurs pinceaux d'expressions.

            • [^] # Re: Quel est le rapport avec la réalité ?

              Posté par  (Mastodon) . Évalué à 3.

              Ensuite il faut prouver.

              Aurélien Barrau te dirais que en physique tu ne prouves jamais rien :)

              En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

        • [^] # Re: Quel est le rapport avec la réalité ?

          Posté par  . Évalué à 7.

          Ici on a un modèle très simple qui a un comportement contre-intuitif et on transfère sans preuve ce comportement à l'économie réelle.

          C’est bizarrement formulé, une analyse phénoménologique de l’économie réelle montre bien une concentration des richesses, et ce que fait l’article c’est proposer un modèle simple et suffisant pour expliquer le phénomène. L’aspect contre-intuitif, c’est juste que même en déséquilibrant les échanges à la faveur des plus pauvres, les richesses continuent à se concentrer chez ceux qui ont commencé plus riches. Encore une fois, une analyse empirique de l’économie réelle ne contredit pas ce point non plus.

  • # Il y a un problème dans ton code…

    Posté par  . Évalué à 7.

        else: # b pays a
            if people[a]==people[b]: # no advantage for the poorest as no poorest
                people[a]=people[a]+base*RATE
                people[b]=people[b]-base*RATE
            elif people[a]>people[b]: # Reachest receive the money so deduct DRATE in the transfer
                people[a]=people[a]-base*(RATE-DRATE)
                people[b]=people[b]+base*(RATE-DRATE)
            else : # Richest pay the money so add DRATE in the transfer
                people[a]=people[a]-base*(RATE+DRATE)
                people[b]=people[b]+base*(RATE+DRATE)

    Dans les deux dernières branches, ton commentaire dit b pay a mais c'est a qui paye b.

    Si tu corriges ce problème, l'effet contre-intuitif disparait.

    • [^] # Re: Il y a un problème dans ton code…

      Posté par  (site web personnel) . Évalué à 3.

      En effet il semble qu'il y est un gros bug. Je regarde ça demain.

      Merci

      • [^] # Re: Il y a un problème dans ton code…

        Posté par  (site web personnel) . Évalué à 6.

        En fait tu n'as pas besoin de faire une branche. Comme tu tires A et B au hasard, tu peux dire a priori que c'est toujours A qui paie B. Ça n'apporte rien de choisir à pile ou face si c'est A qui paie B ou vice-versa.

    • [^] # Re: Il y a un problème dans ton code…

      Posté par  (site web personnel) . Évalué à 4.

      J'ai modifier le code pour qu'on ai bien b qui paye a… Merci pour l'avoir signalé. Comme le résultat allait dans le sens de l'article, je ne m'en étais pas rendu compte.

      Par contre, tu me signales que le résultat contre intuitif disparait. Je n'ai pas ce résultat. Il est ralenti, mais existe toujours. Il faut un plus grand nombre de boucle.

      J'ai modifier ma sortie pour rappeler les paramètres. Voici des résultats obtenus avec 50k et 5000k loops

      nous@Desktop:~/Documents/Code$ ./transaction_oligarchie-both_direction-poor_favorable-random_amount.py
      Parametres : NB_PEOPLE: 100, NB_LOOP: 50000, RATE: 0.2, DRATE: 0.01. 28, 265, 292, 1389, 3175, 334, 5623, 4379, 713, 3, 1813, 399, 142, 5994, 600, 1030, 0, 14, 20155, 196, 1650, 282, 3260, 355, 1006, 158, 85, 874, 0, 96, 234, 10, 396, 277, 4, 2, 3016, 15, 675, 3237, 5, 2069, 31, 4814, 288, 42, 7794, 22, 1836, 18171, 4580, 431, 354, 9, 7082, 49, 6277, 45, 5, 2123, 2510, 5542, 67, 1905, 4068, 11501, 7716, 14, 213, 979, 629, 2, 2, 5, 8962, 62, 75, 2392, 19, 181, 0, 288, 1682, 70, 40, 3, 34, 4325, 176, 12836, 21, 13, 122, 162, 329, 2253, 432, 730, 11342, 47,
      nous@Desktop:~/Documents/Code$ ./transaction_oligarchie-both_direction-poor_favorable-random_amount.py
      Parametres : NB_PEOPLE: 100, NB_LOOP: 5000000, RATE: 0.2, DRATE: 0.01. 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5904, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15915, 0, 0, 177840, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

      • [^] # Re: Il y a un problème dans ton code…

        Posté par  . Évalué à 4.

        Par contre, tu me signales que le résultat contre intuitif disparait. Je n'ai pas ce résultat. Il est ralenti, mais existe toujours. Il faut un plus grand nombre de boucle.

        Effectivement, je n’ai pas pensé à augmenter le nombre d’itérations.

  • # Simplification, mais moins fun

    Posté par  . Évalué à 8.

    Si je ne me trompe pas, le problème peut se simplifier à un achat suivi d'une vente avec un plus riche: (1+20%) * (1-17%) = 0.996, qui est inférieur à 1. Donc voilà, avec ces taux, si tu échanges avec plus riche que toi, tu perds. En répétant l'opération plein de fois, on termine par transférer tout vers le plus riche. Mais c'est moins fun que de faire la simulation… ;)

    • [^] # Re: Simplification, mais moins fun

      Posté par  (site web personnel) . Évalué à 8.

      C'est en effet le point de départ, et c'est encore plus évident quand on simplifie encore plus en mettant le même taux T dans tous les cas.
      (1+T)(1-T)=1-T² < 1 pour T non nul…

      Mais c'est intéressant de faire visualiser. Je suis maintenant prof de math (changement de carrière). Pour bien faire rentrer les choses, un bon exemple est souvant utile.
      Donc là, je tiens un bon exemple.

      • [^] # Re: Simplification, mais moins fun

        Posté par  (site web personnel) . Évalué à -2. Dernière modification le 21 juillet 2021 à 06:02.

        Prof de math ? Ça existe encore ? Je croyais que désormais on n’enseignait plus que les lettres :-( ; et encore, seulement un tout petit sous ensemble.

        « IRAFURORBREVISESTANIMUMREGEQUINISIPARETIMPERAT » — Odes — Horace

      • [^] # Re: Simplification, mais moins fun

        Posté par  (site web personnel) . Évalué à 2.

        Je suis maintenant prof de math (changement de carrière).

        Ah c'est marrant moi aussi je fais ça à partir de septembre. Qu'est-ce qui t'a fait changer?

        • [^] # Re: Simplification, mais moins fun

          Posté par  (site web personnel) . Évalué à 5.

          L'age d'abord, j'ai déjà fait deux carrière, et si je voulais en faire une troisième il était tant de s'y mettre.
          Pour le sujet (prof, la matière étant pour moi moins importante), c'est venu un peu en réaction de mon métier précédant. J'avais monté une boite d'audit sociale intervenant un peu partout dans le monde. L'audit social c'est d'aller dans les usines vérifier que les gens sont payés, que l'usine n'est pas dangereuse, qu'il n'y a pas d'enfants… Le secteur d'activité parait séduisant dans le sens que tu peux avoir un réel impact d'amélioration dans les conditions de travail dans le monde. Mais c'est un domaine tellement corrompu et pourris que quand tu cherches à faire ton travail honnêtement tu passes pour un ovni. Donc j'ai voulu qlq chose sur une plus petite échelle, mais pour laquelle tu sais exactement ce que tu fais.

          Avec le recul (juste un an), c'était un bon choix. Pour qlques élèves tu sens que tu fais une réelle différence.

          • [^] # Re: Simplification, mais moins fun

            Posté par  (site web personnel) . Évalué à 4.

            L'age d'abord, j'ai déjà fait deux carrière, et si je voulais en faire une troisième il était tant de s'y mettre.

            Ah ah pareil… deux ou trois de mon côté :-)

            Pour le sujet […], c'est venu un peu en réaction de mon métier précédant.

            Après avoir fait de la recherche en maths j'ai du travailler dans l'industrie (précarité des chercheurs) et de fil en aiguille amélioré ma situation… pour me rendre compte que je ne fais pas des choses très stimulantes ou ambitieuses intellectuellement, que je suis (à la fois trop et pas assez) payé à faire un travail dont la contribution au bien commun est loin d'être évidente, etc. Donc au final enseigner me semble une perspective plus réjouissante! :-)

  • # Python

    Posté par  . Évalué à 6.

    Sans aucune animosité, je me permet simplement quelques remarques sur le style du code:

    # -*- coding: utf8 -*-

    C’est un vieux truc qui n’a plus aucun intérêt.

    from random import *

    C’est en général pas conseillé d’importer *, il vaut mieux soit importer le module et utiliser module.fonction soit importer les fonctions dont tu as besoin.

    people=[]
    
    # first setting NB_PEOPLE people with the same amount of money (AMOUNT_START)
    while i<NB_PEOPLE :
        people.append(AMOUNT_START)
        i+=1

    L’initialisation de la variable people peut être réduite à people = [AMOUNT_START] * NB_PEOPLE

    i=0
    a=0
    b=0
    base=0

    Tu n’as pas besoin d’initialiser toutes tes variables globalement en Python.

    while i<NB_LOOP :

    En général en Python on utilise des boucles for plutôt que while : for _ in range(0, NB_LOOP + 1): (tu n’utilise pas la variable i, autant ne pas lui donner de nom)

    if c>0.5 : # a pays b

    Il me semble que les deux blocks font la même chose, avec les signes inversés, du coup une fonction pays(a, b, base) où tu inverse a et b et qui renvoie la nouvelle valeur de a et b devrait faire l’affaire non ?

    Genre people[a], people[b] = pays(a, b, base) ou people[b], people[a] = pays(b, a, base).

    while i<NB_PEOPLE :

    Idem que précédemment, mais en plus tu peux directement itérer sur people for person in people:, mais le mieux c’est surtout d’utiliser str.join() : final = ", ".join(str(p) for p in people).

    final="Last tranfert base : " + str(base) +". "

    On utilise plutôt les f-string, str.format() ou les %-string plutôt que la concaténation pour faire ça en général : print(f"Last tranfert base : {base}. {', '.join(str(p) for p in people)}")


    Après avoir appliquer ça (et utilisé black pour le formatage), ça donne quelque chose comme ça :

    #!/usr/bin/python3
    
    from random import random, randint
    
    NB_PEOPLE = 500
    AMOUNT_START = 2000
    NB_LOOP = 5000000
    RATE = 0.2
    DRATE = 0.05
    
    
    def pays(a, b, base):
        # no advantage for the poorest as no poorest
        if a == b:
            a = a - base * RATE
            b = b + base * RATE
        # poorest receive the money so add DRATE in the transfer
        elif a > b:
            a = a - base * (RATE + DRATE)
            b = b + base * (RATE + DRATE)
        # poorest pay the money so deduct DRATE in the transfer
        else:
            a = a - base * (RATE - DRATE)
            b = b + base * (RATE - DRATE)
    
        return a, b
    
    
    people = [AMOUNT_START] * NB_PEOPLE
    
    for _ in range(0, NB_LOOP + 1):
        # select two random people
        a = randint(0, NB_PEOPLE - 1)
        b = randint(0, NB_PEOPLE - 1)
    
        # compare the capital of both and select the base for the calculation for  the next
        # step (capital of the poorest)
        if people[a] < people[b]:
            base = people[a]
        else:
            base = people[b]
    
        # a pays b
        if random() > 0.5:
            people[a], people[b] = pays(a, b, base)
        # b pays a
        else:
            people[b], people[a] = pays(b, a, base)
    
    # final, show the resluts
    print(f"Last tranfert base : {base}. {', '.join(str(p) for p in people)}")

    Cela dit, ma fonction pays() doit pas être bonne, j’obtiens des résultats qui m’ont l’air bien différent de ton code.

    • [^] # Re: Python

      Posté par  . Évalué à 3.

      Cela dit, ma fonction pays() doit pas être bonne, j’obtiens des résultats qui m’ont l’air bien différent de ton code.

      Déjà parce que comme le souligne Moonz<, il y a une erreur dans le code original, et aussi, parce que j’ai fais une typo en passant les index et non pas les valeurs :

      --- a 2021-07-20 14:12:27.830368254 -0400
      +++ b 2021-07-20 14:12:17.222132326 -0400
      @@ -42,10 +42,10 @@
      
           # a pays b
           if random() > 0.5:
      -        people[a], people[b] = pays(a, b, base)
      +        people[a], people[b] = pays(people[a], people[b], base)
           # b pays a
           else:
      -        people[b], people[a] = pays(b, a, base)
      +        people[b], people[a] = pays(people[b], people[a], base)
      
       # final, show the resluts
       print(f"Last tranfert base : {base}. {', '.join(str(p) for p in people)}")
      • [^] # Re: Python

        Posté par  . Évalué à 7.

        Il me semble que tu n'as pas besoin de choisir aléatoirement le sens du payement et donc de dupliquer ton code. Par exemple, décide que c'est toujours 'a' qui paye 'b'. Ta sélection aléatoire de 'a' et 'b' doit être suffisante.

    • [^] # Re: Python

      Posté par  (site web personnel, Mastodon) . Évalué à 1.

      # -*- coding: utf8 -*-
      

      C’est un vieux truc qui n’a plus aucun intérêt.

      Vieux comment ? Et pourquoi plus aucun intérêt ?

      Tu n’as pas besoin d’initialiser toutes tes variables globalement en Python.

      C'est pourtant plus propre, je trouve, de déclarer et initialiser toutes les variables qu'on va utiliser.

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: Python

        Posté par  . Évalué à 1.

        Ce n'est pas un truc spécifique à emacs pour indiquer l'encodage des fichiers?

        https://www.gnu.org/software/emacs/manual/html_node/emacs/Specify-Coding.html

        J'ai toujours considéré que python (et les autre outils) ignoraient totalement ces commentaires spéciaux.

        Il me semble qu'un emacs moderne devrait déjà être en UTF-8 par défaut (sauf si spécifié autrement dans la config d'emacs).

        • [^] # Re: Python

          Posté par  (site web personnel, Mastodon) . Évalué à 1.

          Oui, c'est un format de commentaire destiné à Emacs et similaires.
          Et comme c'est du commentaire (enfin ça se met normalement en commentaire au début) ça n'a aucun effet sur l'interpréteur ; raison pour laquelle je n'ai pas compris la remarque.

          Que l'éditeur soit en UTF-8 par défaut est une chose, mais ça va toujours mieux en l'indiquant ; les uns et les autres pouvant avoir des réglages/configurations qui ne sont pas celles par défaut… Ça sert aussi aux humains qui vont vouloir éditer le fichier.

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

          • [^] # Re: Python

            Posté par  . Évalué à 3.

            Et comme c'est du commentaire (enfin ça se met normalement en commentaire au début) ça n'a aucun effet sur l'interpréteur ; raison pour laquelle je n'ai pas compris la remarque.

            Justement, si, ça a un effet sur l’interpréteur. Et comme utf-8 est le défaut, ça n’a aucun intérêt.

            C’est juste un de ces vieux cargo cultes qu’on se traine, tout le monde fait comme ça, je vais faire pareil.

            • [^] # Re: Python

              Posté par  (site web personnel, Mastodon) . Évalué à 3.

              Merci pour cette PEP dont je n'étais pas au courant : quel sacré bricolage…
              Pour l'effet sur l'interpréteur il est dit que

                5. Encoding comments which don't work:

                  B. Encoding comment not on line 1 or 2:

              C'est probablement pour ça que je ne risquais pas d'en constater l'effet, sachant que j'ai toujours mis des modelines en début de tous mes scripts.

              D'après la PEP utf-8 n'est pas ce qui est attendu par défaut (avant la v3 ?) et j'ai déjà hérité de bases de code py qui ont d'autres encodages. Donc pas si cargo culte…

              “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: Python

        Posté par  . Évalué à 3.

        Vieux comment ?

        13 ans

        Et pourquoi plus aucun intérêt ?

        Parce que l’encodage par défaut en Python est UTF-8.

        C'est pourtant plus propre, je trouve, de déclarer et initialiser toutes les variables qu'on va utiliser.

        Plus propre de déclarer des variables globales qui n’ont d’intérêt que dans un scope précis ?

        • [^] # Re: Python

          Posté par  (site web personnel, Mastodon) . Évalué à 1. Dernière modification le 21 juillet 2021 à 07:34.

          Inutile donc en Py3 si on utilise l'encodage par défaut (et qui dit par défaut dit qu'il y a d'autres choix possibles) Mais je comprends mieux ce que tu veux dire, et merci encore pour la PEP.

          Il est plus propre, je précise à mon goût (peut-être du à l'habitude/héritage d'autres langages avant de toucher à celui-ci), de déclarer les variables tout court. Aussi bien les globales que les locales (dans un scope précis pour moi c'est du local normalement)

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

    • [^] # Re: Python

      Posté par  . Évalué à 3. Dernière modification le 21 juillet 2021 à 00:05.

          # compare the capital of both and select the base for the calculation for  the next
          # step (capital of the poorest)
          if people[a] < people[b]:
              base = people[a]
          else:
              base = people[b]

      ou simplement

      base = min(people[a], people[b])

      et je pense que ça peut se passer de commentaire.
      On peut aussi alléger la lecture avec des écritures comme :

      Ce qui donne :

      a -= base * (RATE + DRATE)
      b += base * (RATE + DRATE)

      Et, comme je lis assiduement linuxfr, je change le shebang.

      Enfin comme propose SChauveau pas besoin du random() et pas besoin du +1 pour le nombre d'itérations

      #!/usr/bin/env python3
      
      from random import randint
      from statistics import mean,  median, pstdev
      
      NB_PEOPLE = 500
      AMOUNT_START = 2000
      NB_LOOP = 5000000
      RATE = 0.2
      DRATE = 0.05
      
      def pays(customer, seller base):
          # no advantage for the poorest as no poorest
          if seller == customer:
              customer -= base * RATE
              seller   += base * RATE
          # poorest receive the money so add DRATE in the transfer
          elif seller > customer:
              customer -= base * (RATE + DRATE)
              seller   += base * (RATE + DRATE)
          # poorest pay the money so deduct DRATE in the transfer
          else:
              customer -= base * (RATE - DRATE)
              seller   += base * (RATE - DRATE)
      
          return seller, customer
      
      
      people = [AMOUNT_START] * NB_PEOPLE
      
      for _ in range(0, NB_LOOP):
          # select two random people
          a = randint(0, NB_PEOPLE - 1)
          b = randint(0, NB_PEOPLE - 1)
      
          base = min(people[a], people[b])
      
          people[a], people[b] = pays(people[a], people[b], base)
      
      # final, show the results
      people = sorted([int(p) for p in people])
      print(f"Last tranfert base: {base}.")
      print(f"people: {', '.join(str(p) for p in people)}")
      print(f"avg: {mean(people)} | median: {median(people)} | pstdev: {pstdev(people)}")

      Je me suis permis :

      • d'arrondir les montants à la fin pour la lisibilité
      • de trier les données pour la lisibilité encore
      • d'ajouter un calcul de moyenne, de médiane et d'écart-type

      Perso j'observe une explosion des inégalités avec les itérations (mais ça reste au jugé, je n'ai pas fais des masses d'exécutions). Je me demande s'il s'agissait d'appauvrissement en relatif ou absolue. Ici la planche à billet tourne à fond les ballons (il y a 50 fois plus d'argent qu'à l'état initial), mais la progression des patrimoines entre les riches et les pauvres est très… contrastée ?

      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: Python

        Posté par  . Évalué à 1.

        Je ne suis pas très python mais ne devrait t'il pas y avoir une virgule après seller dans le def pays?

        • [^] # Re: Python

          Posté par  . Évalué à 2.

          Si, si tout à fait.

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: Python

        Posté par  . Évalué à 1.

        Il ne devrait pas y avoir d'appauvrissement car la même somme est ajoutée et retranchée à chaque étape. Le bug se produit dans l'affectation people[a], people[b] = pays(people[a], people[b], base) quand a==b. Dans ce cas spécial, tu affectes 2 valeurs différentes dans la même variable et une seule est prise en compte. Dans ce cas précis, c'est la deuxième, donc la valeur de customer qui est utilisée et, comme c'est la plus petite, cela réduit donc le montant total. Il faut donc t'assurer que a et b sont différents.

        Je remarque aussi que tu as inversé seller et customer dans le return de pays.

        • [^] # Re: Python

          Posté par  . Évalué à 2.

          Je remarque aussi que tu as inversé seller et customer dans le return de pays.

          L'ordre n'a pas de sens particulier, ça ne change rien.

          Pour le reste je regarde demain.

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: Python

        Posté par  . Évalué à 1.

        Même après avoir corrigé le cas a==b, le montant final devrait être très légèrement différent. Rien de grave. C'est du aux erreurs d'arrondis sur les calculs en virgule flottantes.

      • [^] # Re: Python

        Posté par  . Évalué à 3.

        On peut aussi ajouter des annotations de types, d’ailleurs on devrait tout le temps le faire.

        C’est le truc qui m’embête le plus avec Python, qu’il soit faiblement/dynamiquement typé et que même quand tu annotes, l’interpréteur s’en fout.

      • [^] # Re: Python

        Posté par  (site web personnel) . Évalué à 2.

        . Ici la planche à billet tourne à fond les ballons (il y a 50 fois plus d'argent qu'à l'état initial)

        C'est louche que la masse monétaire augmente puisque la simulation n'introduit jamais d'argent supplémentaire. Tu es sûr de ça?

        • [^] # Re: Python

          Posté par  . Évalué à 2.

          Alors je suis certain que le code crée de la monnaie. C'est simple tu fais la somme des patrimoine avant et après. Hier je me suis dis que c'était le signe d'un bug et puis je me suis dis que ça venait du DRATE, mais il était tard hier soir…

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

          • [^] # Re: Python

            Posté par  (site web personnel) . Évalué à 2. Dernière modification le 21 juillet 2021 à 09:47.

            Pourtant si je regarde ce que vaut customer + seller à l'entrée de la fonction pays et à la sortie, cette quantité est invariante.

            Pour en avoir le cœur net j'ai écrit une alternative à pays qui met cela bien en évidence:

            def pays(customer, seller, base):
                amount = 0
                # no advantage for the poorest as no poorest
                if seller == customer:
                    amount = base * RATE
                # poorest receive the money so add DRATE in the transfer
                elif seller > customer:
                    amount = base * (RATE + DRATE)
                # poorest pay the money so deduct DRATE in the transfer
                else:
                    amount = base * (RATE - DRATE)
                return (seller + amount, customer - amount)
            

            et ajouté des print dans le programme pour tracer:

            for _ in range(0, NB_LOOP):
                # select two random people
                a = randint(0, NB_PEOPLE - 1)
                b = randint(0, NB_PEOPLE - 1)
            

            base = min(people[a], people[b])
            ``` invariant1 = people[a] + people[b]
            print(f"1st people[a], people[b] {people[a]}, {people[b]}")
            u, v = pays2(people[a], people[b], base)
            print(f"u,v ={u}, {v}")
            people[a], people[b] = u, v
            print(f"2nd people[a], people[b] {people[a]}, {people[b]}")
            invariant2 = people[a] + people[b]
            assert(invariant1 == invariant2)
            ~~~

            et ça me fait un truc du genre:

            1st people[a], people[b] 2000, 2000
            u,v =2400.0, 1600.0
            2nd people[a], people[b] 2400.0, 1600.0
            1st people[a], people[b] 1600.0, 2000
            u,v =2400.0, 1200.0
            2nd people[a], people[b] 2400.0, 1200.0
            1st people[a], people[b] 2000, 2000
            u,v =2400.0, 1600.0
            2nd people[a], people[b] 2400.0, 1600.0
            1st people[a], people[b] 2400.0, 1200.0
            u,v =1380.0, 2220.0
            2nd people[a], people[b] 1380.0, 2220.0
            1st people[a], people[b] 2000, 2000
            u,v =2400.0, 1600.0
            2nd people[a], people[b] 2400.0, 1600.0
            1st people[a], people[b] 2400.0, 2400.0
            u,v =2880.0, 1920.0
            2nd people[a], people[b] 2880.0, 1920.0
            1st people[a], people[b] 2000, 2880.0
            u,v =3380.0, 1500.0
            2nd people[a], people[b] 3380.0, 1500.0
            1st people[a], people[b] 1920.0, 2400.0
            u,v =2880.0, 1440.0
            2nd people[a], people[b] 2880.0, 1440.0
            1st people[a], people[b] 1500.0, 1600.0
            u,v =1975.0, 1125.0
            2nd people[a], people[b] 1975.0, 1125.0
            1st people[a], people[b] 1600.0, 3380.0
            u,v =3780.0, 1200.0
            2nd people[a], people[b] 3780.0, 1200.0
            1st people[a], people[b] 2000, 1440.0
            u,v =1656.0, 1784.0
            2nd people[a], people[b] 1656.0, 1784.0
            1st people[a], people[b] 2000, 1200.0
            u,v =1380.0, 1820.0
            2nd people[a], people[b] 1380.0, 1820.0
            1st people[a], people[b] 1380.0, 1380.0
            u,v =1656.0, 1104.0
            2nd people[a], people[b] 1104.0, 1104.0
            Traceback (most recent call last):
              File "/Users/michael/Desktop/example.py", line 70, in <module>
                assert(invariant1 == invariant2)
            

            On voit que l'affectation de deux variables en même temps genre marche de façon bizarre dans la dernière trace: même si u et v ont les bonnes valeurs (on a 1656 + 1104 == 1380 + 1380) tout se casse la gueule quand on fait l'affectation à people[a], people[b]. Que se passe-t-il?

            PS: Mon commentaire fait foirer markdown de façon aussi magistrale qu'inattendue!

          • [^] # Re: Python

            Posté par  (site web personnel) . Évalué à 2.

            Ah je l'ai: le problème est si a == b où à la fin de l'itération people[a] == people[b] vaut aléatoirement l'une des deux valeurs renvoyée par pays alors que rien ne devrait se passer.

    • [^] # Re: Python

      Posté par  (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 21 juillet 2021 à 01:08.

      Il me semble que les deux blocks font la même chose, avec les signes inversés, du coup une fonction pays(a, b, base) où tu inverse a et b et qui renvoie la nouvelle valeur de a et b devrait faire l’affaire non ?

      Genre people[a], people[b] = pays(a, b, base) ou people[b], people[a] = pays(b, a, base).

      C'est tentant mais attention aux types de variables avec lesquels on fait ce swap avec du multiple assignment (dans ces moments t'as envie d'étrangler tous les gens qui t'ont dit que ce langage est simple etc.)
      https://stackoverflow.com/questions/68152730/understand-python-swapping-why-is-a-b-b-a-not-always-equivalent-to-b-a-a
      https://stackoverflow.com/questions/51950193/python-a-b-b-a-implementation-how-is-it-different-from-c-swap-function

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

    • [^] # Re: Python

      Posté par  (site web personnel) . Évalué à 3.

      J'abonde dans tous ces commentaires, mais aussi dans le fait que mon seul but était d'avoir un code facile et logiquement aisé à suivre.

      Dans les commentaires, il y a par exemple le fait que le choix aléatoire de a et b suffit à créer de l'aléatoire dans l'ordre de la transaction. C'est tout à fait vrai, mais alors, on n'explicite plus le fait qu'on choisit aléatoirement et les acteurs et l'ordre. C'est vrai mais sous entendu.

      Même chose, j'aime expliciter mes variables. C'est pas nécessaire, mais finalement je trouve que ça clarifie la lecture du code.

      Pour finir, il y a des sujets pour lesquels il n'y a aucune explication. J'ai juste l'habitude de faire des while, mais une boucle for aurait été très bien…

      Merci pour les commentaires c'est toujours bon d'avoir des rappels. Je ne code pas suffisamment souvent pour ne pas reprendre mes mauvaises habitudes (mes premiers codes on été en basic sur un ZX81…)

      • [^] # Re: Python

        Posté par  . Évalué à 3. Dernière modification le 21 juillet 2021 à 05:18.

        J'abonde dans tous ces commentaires, mais aussi dans le fait que mon seul but était d'avoir un code facile et logiquement aisé à suivre.

        Ok, là je vais rajouter un peu d’animosité : c’est raté.

        Dans les commentaires, il y a par exemple le fait que le choix aléatoire de a et b suffit à créer de l'aléatoire dans l'ordre de la transaction. C'est tout à fait vrai, mais alors, on n'explicite plus le fait qu'on choisit aléatoirement et les acteurs et l'ordre. C'est vrai mais sous entendu.

        Ajoute un commentaire dans ton code qui explique pourquoi tu fais les choses de tel ou tel manière (plutôt que de décrire ce que le code fait).

        Même chose, j'aime expliciter mes variables. C'est pas nécessaire, mais finalement je trouve que ça clarifie la lecture du code.

        Tu utilises plusieurs fois la variable i, tes variable d’index s’appellent a et b, ton tableau de capitaux s’appelle people.


        Après, c’est juste des conseils pour faire du Python plus « standard », ça peut t’aider si tu souhaites collaborer avec d’autres personnes, mais tu fais bien ce que tu veux.

        • [^] # Re: Python

          Posté par  (site web personnel) . Évalué à 5.

          J'aurais du préciser, logique à suivre quand on lit l'article initial, que je ne vous avais pas fournis.

          Je prends donc les tords sur moi.

          Dans tous les cas, merci pour tous ces commentaires.

  • # Common Lisp

    Posté par  (site web personnel) . Évalué à 6. Dernière modification le 21 juillet 2021 à 02:56.

    Comme je n'ai pas envie de dormir et que ce que tu dis est très intéressant j'ai écrit ton programme en Common Lisp – utilisable abec sbcl par exemple. Cela ressemble à ça:

    (defparameter *population-size* 500
      "The number of persons in our modelisation.")
    
    (defparameter *initial-fortune* 2000
      "The initial fortune of each person in the modelisation.")
    
    (defparameter *transaction-rate* 20/100
      "The proportion of their fortune a person will engage in a transaction.")
    
    (defparameter *regulatory-advantage* 5/100
      "The correction introduces by the regulator to alleviate the poor.")
    
    
    (defun fortune-simulation (number-of-iterations)
      "Run the Boghosian model for NUMBER-OF-ITERATIONS."
      (declare (optimize (speed 3)))
      (labels
          ((make-initial-state ()
         (make-array *population-size* :initial-element (float *initial-fortune*)))
           (transaction (state)
         (let* ((payer
              (random *population-size*))
            (receiver
              (random *population-size*))
            (fortune-payer
              (aref state payer))
            (fortune-receiver
              (aref state receiver))
            (transaction-base
              (min fortune-payer fortune-receiver))
            (transaction-rate
              (float
               (if (> fortune-payer fortune-receiver)
                   (+ *transaction-rate* *regulatory-advantage*)
                   *transaction-rate*)))
            (transaction-amount
              (* transaction-base transaction-rate)))
           (decf (aref state payer) transaction-amount)
           (incf (aref state receiver) transaction-amount))))
        (let ((state (make-initial-state)))
          (dotimes (_ number-of-iterations state) (transaction state)))))

    La fonction calcule l'état final du modèle après tant et tant d'itérations. Cet état est juste le vecteur des fortunes. (Pourquoi ne pas donner 1 EUR à tout le monde au début au lieu de 2000 ? :-) )

    Pour interpréter les résultats plus facilement j'ai aussi écrit la fonction suivante

    (defun fortune-repartition (fortune-vector &rest query)
      "Analyse fortune repartition according to request QUERY.
    
    The QUERY is a list of percentages to break down the fortune repartition. The answer
    is a list whose elements have the form
    
      (P N AMOUNT)
    
    which reads like
    
      “In FORTUNE-VECTOR the N-th first people own together AMOUNT of money,
       which is more than P of the total amount of money.”
    "
      (let*
          ((total-fortune
         (* *population-size* *initial-fortune*))
           (decreasing-fortune
         (sort fortune-vector #'>=))
           (detailed-query
         (mapcar #'(lambda (p) (cons (float p) (float (* p total-fortune))))
             (sort query #'<=))))
        (loop with current-query = detailed-query
          with answer = nil
          for i from 1 upto *population-size*
          for ax = (aref decreasing-fortune 0) then (+ ax (aref decreasing-fortune (1- i)))
          when (null current-query)
          do (return answer)
          when (>= ax (cdr (first current-query)))
          do (progn
               (push (list (car (first current-query)) i ax) answer)
               (setf current-query (rest current-query)))
          finally (return answer))))

    Au final avec les nombres que tu utilises mais seulement 100000 simulations, on trouve:

    CL-USER> (fortune-repartition (fortune-simulation 1000000) 1/2 3/4 90/100 95/100)
    ((0.95 53 951680.8) (0.9 38 901602.1) (0.75 20 756305.9) (0.5 9 527378.4))
    

    Ce qui signifie que les 53 personnes les plus riches(sur les 500 de départ) possèdent à elles seules plus de 95% de la fortune totale du système, et ainsi de suite jusqu'au terme final qui dit que les 9 personnes possèdent plus de la moitié des richesses totales.

    Si on annule *regulatory-advantage* on voit cet effet s'accentuer:

    CL-USER> (let ((*regulatory-advantage* 0))
           (fortune-repartition (fortune-simulation 1000000) 1/2 3/4 90/100 95/100))
    ((0.95 18 955952.6) (0.9 14 916244.6) (0.75 8 763214.2) (0.5 4 537323.9))
    

    et les seules 4 personnes les plus riches concentrent 50% des richesses totales.

    • [^] # Re: Common Lisp

      Posté par  (site web personnel) . Évalué à 6.

      Assisterait-on là à la naissance d'un nouveau TapTempo :-) ?

      Pour nous émanciper des géants du numérique : Zelbinium !

    • [^] # Re: Common Lisp

      Posté par  . Évalué à 3.

      Ce qui signifie que les 53 personnes les plus riches(sur les 500 de départ) possèdent à elles seules plus de 95% de la fortune totale du système, et ainsi de suite jusqu'au terme final qui dit que les 9 personnes possèdent plus de la moitié des richesses totales.

      C'était le genre de chose que je voulais mettre en évidence avec le calcul de l'écart-type, mais c'est pas très parlant comme mesure.

      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: Common Lisp

        Posté par  (site web personnel) . Évalué à 3.

        En faisant un exemple avec une plus grande population (1 million) et un grand nombre de transaction (10000 par acteur, soit 10000 millions ici) on arrive à

        CL-USER> (let ((*regulatory-advantage* 5/100)
                   (*population-size* 1000000))
        (fortune-repartition (fortune-simulation (* *population-size* 10000))
                     1/2 3/4 90/100 95/100))
        ((0.95 34643 1.9000037e9) (0.9 23940 1.8000128e9) (0.75 11779 1.5000092e9)
         (0.5 4583 1.0000814e9))
        

        ce qui fait que les 34643 personnes les plus riches détiennent à elles seules plus de 95% du patrimoine total!

  • # solde négatif ?

    Posté par  . Évalué à 2.

    Il y a un truc qui m'échappe, je ne vois rien dans le code qui empêche quelqu'un de dépenser plus qu'il n'a, et donc un solde de devenir négatif. Logiquement une transaction devrait être annulée si l'acheteur n'a pas de quoi payer.

    • [^] # Re: solde négatif ?

      Posté par  . Évalué à 4. Dernière modification le 21 juillet 2021 à 12:19.

      Le montant transféré est une fraction de la richesse du plus pauvre des deux participants. Le solde ne peut donc pas passer en négatif.

  • # Code ?

    Posté par  . Évalué à 2.

    Merci pour cet intéressant article. Est-ce que l'un de vous pourrait publier la dernière version du code ?

    • [^] # Re: Code ?

      Posté par  (site web personnel) . Évalué à 3.

      Il me semble que c'est celui de ce commentaire : https://linuxfr.org/users/fdf/journaux/un-article-de-pour-la-science-m-ayant-amener-a-coder-pour-une-petite-verification-perso#comment-1860111, mis à part le problème de virgule signalé au commentaire qui suit.

      Voici la version corrigée :

      #!/usr/bin/env python3
      
      from random import randint
      from statistics import mean,  median, pstdev
      
      NB_PEOPLE = 500
      AMOUNT_START = 2000
      NB_LOOP = 5000000
      RATE = 0.2
      DRATE = 0.05
      
      def pays(customer, seller, base):
          # no advantage for the poorest as no poorest
          if seller == customer:
              customer -= base * RATE
              seller   += base * RATE
          # poorest receive the money so add DRATE in the transfer
          elif seller > customer:
              customer -= base * (RATE + DRATE)
              seller   += base * (RATE + DRATE)
          # poorest pay the money so deduct DRATE in the transfer
          else:
              customer -= base * (RATE - DRATE)
              seller   += base * (RATE - DRATE)
      
          return seller, customer
      
      
      people = [AMOUNT_START] * NB_PEOPLE
      
      for _ in range(0, NB_LOOP):
          # select two random people
          a = randint(0, NB_PEOPLE - 1)
          b = randint(0, NB_PEOPLE - 1)
      
          base = min(people[a], people[b])
      
          people[a], people[b] = pays(people[a], people[b], base)
      
      # final, show the results
      people = sorted([int(p) for p in people])
      print(f"Last tranfert base: {base}.")
      print(f"people: {', '.join(str(p) for p in people)}")
      print(f"avg: {mean(people)} | median: {median(people)} | pstdev: {pstdev(people)}")

      On peut la tester ici : https://replit.com/@AtlasTK/LFR124926-cli-python (ça prend du temps avant que le résultat ne s'affiche).

      Et la version avec une GUI, pour pouvoir modifier les paramètres sans avoir à modifier le code source : https://replit.com/@AtlasTK/LFR124926-python.

      Pour nous émanciper des géants du numérique : Zelbinium !

      • [^] # Re: Code ?

        Posté par  . Évalué à 1.

        Super, merci beaucoup !

        • [^] # Re: Code ?

          Posté par  . Évalué à 2.

          Non! Ce code est incorrect car il ne traite pas correctement le cas a==b.
          Dans ce cas particulier, la ligne people[a], people[b] = pays(people[a], people[b], base essaye d'affecter 2 valeurs différentes au même élément de tableau. C'est impossible et cela fausse le résultat final.

          Une correction simple consiste à remplacer la ligne

          b = randint(0, NB_PEOPLE - 1)
          

          par

          b = a
          while a==b :
              b = randint(0, NB_PEOPLE - 1)
          
          • [^] # Re: Code ?

            Posté par  (site web personnel) . Évalué à 2. Dernière modification le 23 juillet 2021 à 08:07.

            J'avais corrigé la version GUI, mais j'avais oublié de reporter la correction dans la version CLI. Comme la simulation prend pas mal de temps avec les paramètres par défaut, j'ai rajouté l'affichage de la progression.

            Au final, ça donne :

            #!/usr/bin/env python3
            
            from random import randint
            from statistics import mean,  median, pstdev
            import time
            
            NB_PEOPLE = 500
            AMOUNT_START = 2000
            NB_LOOP = 5000000
            RATE = 0.2
            DRATE = 0.05
            
            def pays(customer, seller, base):
                # no advantage for the poorest as no poorest
                if seller == customer:
                    customer -= base * RATE
                    seller   += base * RATE
                # poorest receive the money so add DRATE in the transfer
                elif seller > customer:
                    customer -= base * (RATE + DRATE)
                    seller   += base * (RATE + DRATE)
                # poorest pay the money so deduct DRATE in the transfer
                else:
                    customer -= base * (RATE - DRATE)
                    seller   += base * (RATE - DRATE)
            
                return customer, seller
            
            
            people = [AMOUNT_START] * NB_PEOPLE
            
            timestamp = time.time_ns()
            
            for _ in range(0, NB_LOOP):
                # select two random people
                while True:
                  a = randint(0, NB_PEOPLE - 1)
                  b = randint(0, NB_PEOPLE - 1)
            
                  if a != b:
                    break
            
                base = min(people[a], people[b])
            
                people[a], people[b] = pays(people[a], people[b], base)
            
                if time.time_ns() - timestamp > 1500000000:
                    print(f"{round(100*_/NB_LOOP)}%", end='\r')
                    timestamp = time.time_ns()
            
            print(f"{round(100*_/NB_LOOP)}%", end='\r')
            
            
            # final, show the results
            people = sorted([int(p) for p in people])
            print(f"Last tranfert base: {base}.")
            print(f"people: {', '.join(str(p) for p in people)}")
            print(f"avg: {mean(people)} | median: {median(people)} | pstdev: {pstdev(people)}")

            Pour essayer en ligne :

            Pour nous émanciper des géants du numérique : Zelbinium !

            • [^] # Re: Code ?

              Posté par  . Évalué à 1.

              OK, merci beaucoup pour cette version corrigée :-)

            • [^] # Re: Code ?

              Posté par  . Évalué à 3.

                  while True:
                    a = randint(0, NB_PEOPLE - 1)
                    b = randint(0, NB_PEOPLE - 1)
              
                    if a != b:
                      break

              Ça vaut le coup de relancer uniquement le b pour limiter le nombre de nombres aléatoires à générer.

              https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

            • [^] # Re: Code ?

              Posté par  (Mastodon) . Évalué à 3.

              # poorest receive the money so add DRATE in the transfer
                  elif seller > customer:
              

              Ça serait pas < au lieu de > ?
              Parce que je lis le code et je vois si le vendeur est plus riche que l'acheteur, c'est à dire si le plus riche reçoit de l'argent, alors on augmente le taux.
              Soit exactement l'inverse de l'effet recherché non ?

              De façon amusante, si je remplace le < par un >, mon résultat à 500k itération est nettement moins balaise, le plus riche est à 9011, le plus pauvre à 42, et une richesse médiane à 1522.

              Mais même à 5 millions d'itérations aucune oligarchie ne se dégage vraiment : de 60 à 8685, et une richesse médiane à 1580, on semble avoir un vrai équilibre.
              Mais avec les chiffres fournis on a que le pauvre vend à 1,25 et achète à 0,85 : 1.25*0,85 = 1,0625 > 1 on n'est plus dans les conditions oligarchiques.

              • Yth.
              • [^] # Re: Code ?

                Posté par  . Évalué à 2.

                Je n'avais pas vu ton commentaire, mais c'est que je me disais : avec les valeurs choisis (25 % et 15 %) le pauvre a interet à jouer ! il va s'enrichir, tandis qu'avec les montant initiaux (20% et 17 %) il s'appauvrissait.
                Du coup l'algo proposé ne devrait pas mener à une oligarchie.

  • # L'article en question

    Posté par  . Évalué à 4.

    • [^] # Re: L'article en question

      Posté par  . Évalué à 1.

      J'ai lu l'article, très intéressant…

      Un point que je ne comprends pas : pourquoi le gain est de 20% et la perte de 17% ? Pourquoi pas 20% pour les deux ?

      • [^] # Re: L'article en question

        Posté par  (Mastodon) . Évalué à 4. Dernière modification le 23 juillet 2021 à 11:22.

        À vue de nez parce que la perte moyenne est plus faible :

        (1+0,2)*(1-0,2)=0,96 mais (1+0,2)*(1-0,17)=0,996.

        Donc on a un écart très petit quand on achète et vend une fois, ça fait un « meilleur » équilibrage.
        Et là où c'est intéressant, c'est qu'on a beau viser un meilleur équilibrage, le résultat est le même : oligarchie.

        • Yth.
  • # Mais où sont passés les 17% et 20%

    Posté par  . Évalué à 1. Dernière modification le 23 juillet 2021 à 15:50.

    Je dois être idiot, mais je ne vois pas bien le rapport entre les algos donnés (que je n'ai fait que regarder vite) et l'énoncé.
    Le riche doit donner 20 % et le pauvre 17 %, mais je ne vois nulle part ces pourcentages (mais plutôt, peut-être 20.5 % et 19.5 %)
    Quelqu'un peut m'expliquer ?

    • [^] # Re: Mais où sont passés les 17% et 20%

      Posté par  . Évalué à 1. Dernière modification le 23 juillet 2021 à 15:55.

      correction : 25 % et 15 % (et pas 20.5 et 19.5)

    • [^] # Re: Mais où sont passés les 17% et 20%

      Posté par  (site web personnel) . Évalué à 2.

      L'article choisit des taux arbitraires, basé sur un exemple encore plus lointain (un cas de casino) pour montrer petit à petit que même si on prend des valeurs qui semblent à première vue favorable pour le plus pauvre, en réalité ça ne l'est pas.

      Donc, dans l'algo ici, rien n'empêche de changer les taux. Et en fait j'ai testé avec plein de valeurs différentes et en général au moins 5 millions d'itérations pour qu'on voit l'effet de manière clair.

      • [^] # Re: Mais où sont passés les 17% et 20%

        Posté par  . Évalué à 1.

        OK, je comprends mieux, merci pour l'explication…

      • [^] # Re: Mais où sont passés les 17% et 20%

        Posté par  . Évalué à 2.

        Pour moi Les 20% et 17% sont choisis parce : (1+0.2)x(1-0.17) est très proche de 1 mais en dessous : 0.996
        ce qui amène l'appauvrissement inexorable du pauvre -> oligarchie
        avec les données que tu prends dans ton exemple (RATE=0.2, DRATE=0.05) on obtient 25% et 15%, en refaisant le calcul (1+0.25)x(1-0.15) on obtient 1.06 bien au dessus de 1 le pauvre a tendance à s'enrichir il n'y aura pas d'oligarchie.
        D'ailleurs avec le script "corrigé", c'est bien ce que je constate (voir ci dessous l'algo que j'utilise).

        je trouve qu'avec rate et drate c'est difficile à voir si on est en dessus ou en dessous de cette barre fatidique des "1" où tout bascule.


        L'algo (pour le cas 20%, 17 % : oligarchie assez rapide.)

        #!/usr/bin/env python3
        
        from random import randint
        from statistics import mean,  median, pstdev
        import time
        
        NB_PEOPLE = 15
        AMOUNT_START = 10000
        NB_LOOP = 5000000
        RATE = 0.185
        DRATE = 0.015
        
        def pays(customer, seller, base):
             # no advantage for the poorest as no poorest
            if seller == customer:
                customer -= base * RATE
                seller   += base * RATE
            # poorest receive the money so add DRATE in the transfer
            elif seller < customer:
                customer -= base * (RATE + DRATE)
                seller   += base * (RATE + DRATE)
            # poorest pay the money so deduct DRATE in the transfer
            else:
                customer -= base * (RATE - DRATE)
                seller   += base * (RATE - DRATE)
            return customer, seller
        
        
        people = [AMOUNT_START] * NB_PEOPLE
        timestamp = time.time()
        
        for _ in range(0, NB_LOOP):
            # select two random people
            a = randint(0, NB_PEOPLE - 1)
            b = a
            while a == b:
              b = randint(0, NB_PEOPLE - 1)
        
            base = min(people[a], people[b])
            people[a], people[b] = pays(people[a], people[b], base)
        
            if time.time() - timestamp >= 0.5:
                print(f"people: {', '.join(str(round(p,2)) for p in people)}                                 ",end='\r')
                timestamp = time.time()
        
        # final, show the results
        print(f"people: {', '.join(str(round(p,2)) for p in people)}")
        print(f"Last tranfert base: {round(base)}")
        print(f"avg: {mean(people)} | median: {round(median(people))} | pstdev: {round(pstdev(people))}")

        avec ta proposition

        RATE = 0.2
        DRATE = 0.005
        

        pas d'oligarchie

  • # Commentaire supprimé

    Posté par  . Évalué à 1. Dernière modification le 24 juillet 2021 à 23:19.

    Ce commentaire a été supprimé par l’équipe de modération.

  • # Version pour Octave / MATLAB

    Posté par  . Évalué à 3. Dernière modification le 27 juillet 2021 à 20:48.

    Voici une version pour Octave / MATLAB. À la fin, on affiche le pourcentage de la richesse totale détenu par les 1% les plus riches…

    J'ai regardé les temps de calcul (pour Python avec le code publié plus haut) :

    Python => 10.5 s
    Octave 5.2.0 => 320 s
    MATLAB 2020a => 1 s

    Je suis quand même étonné de la mauvaise performance d'Octave, même si je sais qu'il s'en sort mal quand il y a des boucles…

    -------- Fichier richesse.m ----------
    
    % 27/07/2021
    % Modèle de marché libre du type "vide grenier"
    % Cf. article de Pour la Science par B. Boghosian
    
    
    close all;
    clear all;
    
    tic;
    
    
        %% Paramètres
    
        % Nombre de transactions
        nb_transactions = 5000000;
    
    
        % Pourcentage de la richesse du plus pauvre gagné par le plus pauvre à chaque transaction gagnante
        pct_gain_pauvre = 20;
    
    
        % Pourcentage de la richesse du plus pauvre gagné par le plus riche à chaque transaction gagnante
        pct_gain_riche = 17;
    
    
        % Nombre d'agents
        nb_agents = 1000;
    
    
        % Richesse initiale (identique pour tous les agents)
        richesse_ini = 1000;
    
    
        % Seed des générateurs aléatoires
        rand('state', 35291);
    
    
        %% Simulation
    
    
        % Initialisation
        Richesse = richesse_ini * ones(1, nb_agents);
    
    
        % Boucle sur les transactions
        for nt = 1:nb_transactions
    
            if (mod(nt, 100000) == 0)
                fprintf(1, 'Transaction %d\n', nt);
            end
    
    
            %% Tirages aléatoires
    
            % Tire aléatoirement deux agents distincts
            [A, B] = randi2(nb_agents);
    
            % Tire à pile ou face si A perd ou gagne
            if (rand < 0.5)
                A_perd = true;
            else
                A_perd = false;
            end
    
    
            %% Transaction
    
            % A perd
            if A_perd
    
                % A et B ont la même richesse
                if (Richesse(A) == Richesse(B))
    
                    % Même gain (personne n'est avantagé)
                    gain = pct_gain_pauvre/100 * Richesse(A);
    
                    Richesse(B) = Richesse(B) + gain;
                    Richesse(A) = Richesse(A) - gain;
    
                % A pauvre, B riche (A est avantagé)
                elseif (Richesse(A) < Richesse(B))
    
                    gain = pct_gain_riche/100 * Richesse(A);
                    Richesse(B) = Richesse(B) + gain;
                    Richesse(A) = Richesse(A) - gain;
    
                % A riche, B pauvre (B est avantagé)
                else
    
                    gain = pct_gain_pauvre/100 * Richesse(B);
                    Richesse(B) = Richesse(B) + gain;
                    Richesse(A) = Richesse(A) - gain;
    
                end
    
            % A gagne
            else
    
                % A et B ont la même richesse
                if (Richesse(A) == Richesse(B))
    
                    % Même gain (personne n'est avantagé)
                    gain = pct_gain_pauvre/100 * Richesse(A);
                    Richesse(A) = Richesse(A) + gain;
                    Richesse(B) = Richesse(B) - gain;
    
                % A pauvre, B riche (A est avantagé)
                elseif (Richesse(A) < Richesse(B))
    
                    gain = pct_gain_pauvre/100 * Richesse(A);
                    Richesse(A) = Richesse(A) + gain;
                    Richesse(B) = Richesse(B) - gain;
    
                % A riche, B pauvre (B est avantagé)
                else
    
                    gain = pct_gain_riche/100 * Richesse(B);
                    Richesse(A) = Richesse(A) + gain;
                    Richesse(B) = Richesse(B) - gain;
    
                end
    
            end
    
        end
    
    
        %% Pourcentage de la richesse totale détenue par les 1 % les plus riches
        R = quantile(Richesse, 0.99);
        k = find(Richesse >= R);
        pct = sum(Richesse(k)) / sum(Richesse);
    
        fprintf(1, '\nLes 1 %% les plus riches détiennent %.1f %% de la richesse totale\n\n', pct * 100);
    
    
        toc;
    
    
        --------- Fichier randi2.m --------
    
        % Tire aléatoirement deux entiers distincts entre 1 et N
        function [m, n] = randi2(N)
    
            m = floor(1 + rand * N);
    
            n = m;
            while n == m
                n = floor(1 + rand * N);    
            end
    
        end
    

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.