 Okay, hello everybody. So it's the second day of CLA and we are happy to listen to Marta Pepin about statistical analysis of non-deterministic for joint processes. So thank you. Good morning or good afternoon whenever you are. Thank you for having me. My name is Marta Pepin as you said. I'm a PhD student at Sorbonne University in Paris. I work with Antoine Génie and Patrick Pichanski, which you may know. And today I'm going to talk about concurrency and as you said, statistical analysis of non-deterministic for joint processes. So before I go into that, I'd like to give you some insight about concurrency. Okay, sorry for that. So concurrency is a programming paradigm in which you have several processes and they may compute stuff independently, they may communicate, they may fork new processes, they may wait for other processes to stop before doing something else. But in the end, they're all going to be sharing one computation unit. So think one CPU. So that, sorry, I'm having trouble with my computer. So yeah, so that in the end, you will have a scheduler that takes the smallest component of your programs that takes the atomic actions, we say, and that put them in a linear order on the computation unit. Such that this order is correct with respect to the logic of the program, but there may be a lot of different valid ordering. So that's scheduling and that's what concurrency is about, studying these kind of processes. And so in this setup, when you want to check that the program is correct, then you have to check that all the possible scheduling are correct, right? And that's the fact of considering all possible schedulings. That's typically a combinatorial problem. So, and what happens in reality is that there are in fact a huge, there are typically a huge number of possible schedulings and we call that a combinatorial explosion or people in concurrency prefer to call it state explosion, but the same thing. So that in general, this is impractical to test all the possible scheduling and people have to resort to other tricks to verify their programs. And so what can we do to help them? Well, we can look at two different problems. First, we can try to quantify this explosion. So that's a counting problem. We take a program and we'd like to measure the size of its state space. We would like to count the number of possible schedulings. And also we can sample executions among all the possible executions. And that's helpful because that's actually what people do for, I mean, not everyone, but that's when some people do to check that the program are correct. They take a sample of possible schedulings and they check these particular schedulings. And we can help by providing efficient and uniform samplers, which, which is important because uniformity means a good coverage. And so what do you call a correct schedule? What do you call it correct? And that's, that's not the topic of the of the talk here. I assume that you have some notion of correctness. Okay, maybe you just want to run some tests, check that some invariant as are verified. You have your own notion of correctness and you need to have a scheduling to test this. So I'm going to provide you with the property on your program. And what I give you is what I'm going to give you is a sample of possible schedulings and you're going to check that all the scheduling satisfy your property. And if that's the case, then you're happy and you're confident that your program is correct. So I'm providing the tool for people to verify the program. I don't specify what verifying needs. That's okay. Any other question? So I'm going to keep going. Right. So the first issue we face when we want to do this is this result from right wheel and winter, which states that the number of linear extensions of a partial order is a man. Sorry, I mean counting them is a sharply complete problem. So first, remember it's really like a contract program. When you have a contract program, you have a bunch of actions and constraints between them. You say that action A must be run before it changes. So this is typically a partial order. And counting in your extensions corresponds exactly to counting schedulings. So we're talking about the same thing and shall be complete means that it's as difficult as counting the number of solution in sets. So it's strictly more difficult that than checking that a set instance is satisfiable. So it's more difficult than an empty complete program. So we are definitely not going to try to solve this. I don't do the general case. This is what this means. But fortunately for us in real life program have some kind of structures in general, but they're written by human we think in a structured way. So we are going to enforce some const, some, sorry, some constraints on the program some structure and look and do from there. And that is exactly what these people have been doing. They've been doing this for the last few years. So the idea is to study the quantitative aspects, quantitative and algorithm aspects of different programs. And the idea is to take some core components of currency, which I will describe in moments, the essential features, and look at program that only have these features and no more. So in comment or your interpretation of these features, we can extract some results using the two set of combinatorics. Okay, so that's the big picture. And that's where my PhD fits in. So the talk with the divided into sections. First, I will introduce a class program programs that has the most basic features you would like to have new programs, I guess. In the second section, I will talk to you about the algorithmic aspects algorithmic solutions to program problems I mentioned before. I mentioned before. So, I told you about essential features of currency. Well, the most essential one is these. Can you see my pointer. Yes, thanks. Yes. So the most important one is this parallel composition operator. So when I say I have two programs p and q and I compose them in parallel. I mean that they will run independently. So an execution or scheduling of what I'm using the two words but then I mean the same thing. So this is actually parallel q that can be any interleaving of a scheduling of p and scheduling of q. Okay, so that's what this means they run independently anything can happen. On the other hand, you may want to have some kind of synchronization and you may want to say, I run a program p, my way, I wait for you to terminate. And then I will run program q and this is sequential composition. The execution of p sequence q is the is an execution of p followed by an execution of q. And if you have only these two constructions, you ended with something we called for drawing for drawing type of parallelism. This is typically what you have if you're using a Unix. No, sorry. Yes, POSIX threads, I think, without loops and and anything like that. Maybe the core of the core of the program. But that's not enough to write interesting programs. I'm going to introduce two other operators. First, the choice operator that models branching that may happen at runtime. So think of an if, if some condition rank p otherwise rank q and you can't guess the condition before running the program. So this is why we call this non-deterministic choice. And naturally an execution of p choice q is either an execution of p or an execution of q. So that's not too difficult. And another useful construction is the loop. So we are going to choose a way behaved form of loop, which we denote q star and I say that an execution of q star is any sequence of executions of q. So it may do nothing, or it may run q once or twice or three times, etc. And, and, and that's pretty much it. I mean, if I summarize, I my four combinators are here. I also add two base cases. So I will have atomic actions. That's the smallest component of your programs and they can be split in two. And I will have the empty program, which does nothing. And also I'm going to assume that across my program, every atomic action is distinct. So I'm working on the control flow of programs rather than actually what they're computing. So from my point of view, two different actions are not distinguished. Sorry, they are distinguishable. So I know that they're different. So I'm assuming all the actions are distinct. Okay. So that was for syntax. And I gave you more or less informally here, the semantics of the program. Now I'm going to reformulate the semantics in terms of commentarial interpretation. So it's going to be a little more formal. And for each program, P, I'm going to describe a combinatorial class bracket P specification that describes all the possible scheduling of the program, all the possible ways it can be executed on a mutation unit. So the base cases, this is easy. Memt program does nothing. So it has only one possible ways of executing. And it has size zero because it does nothing. So that's more by the epsilon class. On the other hand, An atomic action, it does one thing. It can do only one thing. It has also one execution, but the blanks one because it does one action. I'm counting atomic action here. The next constructors are more interesting. So in the case of P sequence Q, as I told you an execution of P sequence Q is an execution of P followed by an execution of Q. So any pair is possible. And this is, this is exactly what a partition product is. So if I want to count them, I'm going to count a partition product. On the other hand, for the parallel composition, I also have to take account of the interleaving. So the trick here is to use the label product from labels, combinatorial classes. So this, this means take a scheduling of P, take a scheduling of Q and choose an interleaving. And at this point, I'd like to point out that it's, it's rather unusual to have these two operators in the same grammar because this belongs to the unlabeled world of combinatorics and this belongs more to the label world. You can make them work in the same specification, but that's, that's unusual, I guess. I keep going with my two last constructions. So I had the choice. So in a program P choice Q, an execution is either an execution of P or an execution of Q. So if I, if I have to count them, I'm going to use the district union here. And in the case of the loop, as I told you, an execution of P star is a sequence of executions of P. So I know this using the sequence construction. So at that point I'm done, but I've made a mistake. And you can use body. The thing is that the set of executions of the program can contain the empty execution. And in that case, I have two issues here. My union is not disjoint because it's almost destroying, but the empty execution can belong to both specs. And also the sequences is not well defined because I can have an object of zero here. So that's fixable. The trick is to consider the non empty executions of people skew. So this is what they find this in union. And also I can say that an execution of P star of loop of P is sequence of non empty executions. I just removed the empty executions from the process. Okay, so that was a little trick, a little trap. But now I'm done. And I have specified in an automatic way the executions of the program. And I'm happy because now that I have this current variable specification, I can use a lot of tools from combinatorics. And that's going to help me solve my two questions that I have before that is counting the number of executions of a program and something among them. I'm going to jump to the next part of my talk. The first algorithm I would like to present is the algorithm for counting execution. So I have a program, and I would like to know the number of executions of length n. So with n atomic actions inside. Okay. I already described you this transformation from program to its specification. And I have in my tool set the symbolic method that helps me jump from the spec to a generating function and then from there, I can extract the number of executions by extracting a coefficient so all of this works well. And I think all of you know what the symbolic method is. For the sake of completeness, I recalled, I expressed explicitly the translation from program to generating function here. So that's natural. There are a few things to be mentioned here. First of all, I would like to implement this on my computer, which is finite. So I'm going to not going to work with generating function, but rather than truncated generating function. So I stopped at degree n. Also note the the substructions here. They are here to handle the little trap we had before. Remember that this union is not destroyed and stuff like that. So you may have noticed this construction here, which is really hard. We call this the color products. And it's the expression in terms of ordinary generating function of the label product because remember we have these. We have at the same time, a label constructions and label constructions. So I have to choose between ordinary generating function and exponential generating function. So now I have to express the label product in terms of ordinary generating function. And actually it's not that hard. The trick is to do a conversion. So first thing you convert the PNQ generating function into exponential ones. That's the purpose of the Borel transform. Okay, that you can do in linear time on your computer. Then you do your product, a normal product, and you go back to the label world with the Laplace transform, which is the converse of the Borel transform, and which also can be implemented in linear time. So that's not very difficult. But again, this is unusual. And I claim that this algorithm performs in P times MN multiplication where P is the size of the program and MN is the cost of one polynomial multiplication, what polynomial has the real end. And the reason behind this is that at each, for each constructor in my program, I do only one operation. Here I do one multiplication plus some linear information that are cheap. Now I do just one multiplication here. I do an addition which is cheap. And this inversion here can in fact be implemented in at the cost of one multiplication. The trick is to use Newton iteration. If you know about this, if not, take a look at the paper from Pivoto, Salvi and Soria, which is great and explains it at length. But basically the bottom line is you can implement this at the cost of one multiplication, in a multi-cost of one multiplication. So in the end I have this complexity. And also I'm multiplying big integers, which have about annual gain bits, because of the interleaving which bring binomial coefficients and factorial and stuff like that. So the big complexity in the end is like that. For each constructor in the program, I do one multiplication over integers of annual gain bits. So this is a polynomial of degree a little more than two in N. Okay, so that's for the counting problem. And I'm going to jump to random sampling. So that's a little bit more involved. So again, I'm going to use my specification and I want to go to a uniform execution of given length of my program. And I'm going to, again, I'm going to use a well-known technique from common lyrics. This time it's called the recursive method. That's been described by a pleasure. It's not meant in that content. But this time, rather than showing you the algorithm, I'm going to explain it on one example. I think it's more easy to understand this way. So I'm going to take a look at this program, this more program, and I want to sample a uniform execution of length three. So here I'm going to write what rule of the recursive method I'm using and below I have a scratch pad. So first thing. I have this parallel composition here. It turns out that the formula for computing the number of executions of this guy is given by this. So this is what you obtain because of the Borrel and Laplacian form. So basically it's like a Cartesian product, but you have the binomial coefficient that take account of the schedule of the interleaving of executions. And if I want to take a uniform execution of this composition here, I need an execution of length K of this program, an execution of links and minus K of this other program, and the shuffling, the interleaving. But how do I choose K? Well, I must choose it according to this distribution in order to remain uniform. And this is because of this formula. So I take one term and I evaluate it by PM. So let's apply it in this program. K equals to zero is not possible because this program has no links three addition. Similarly, K equals to three is not possible either because, again, this program has no addition of links zero. I must choose between K cost one and Gables two. Okay, in that case, K cost one implies that I must choose a length one execution here. There are two of them. And there are two executions of links two on the right, hence the two, and this binomial coefficient accounts for the interleaving of one execution with two executions. K equals to two is similar. I have four executions on the left. I have one next one execution on the right plus the interleaving. In the end, if I compute this sum, I get that I must choose K cost one with probability one half and K cost two with probability one half two. So say I choose K cost one. So now I have to sample the length one execution on the left and the links to execution on the right. Okay, let's focus on the left first. So I have a loop. The thing is with the loop that it behaves like this P star from the point of view of something P star is the same as zero plus P sequence P star. So I can rewrite my program. And also I can remove the zero because I'm looking for a non empty execution here. So it's not possible to go on the left. And now I have a sequence composition. So again, there is a rule in the recursive method to end all the sequence. So this is more common. This is a Cartesian product. So I must choose the size K of the left components. According to this distribution, this is the same as before, but I have no binomial coefficients this time. And here I observe that the only possibility K cost one because there is no length zero execution here. So I must choose an indication of links one here and an execution of links zero here. And since there is only one institution of links zero I can just remove this term. I'm left with this. Finally, I'm going to show you the rule for the distant union, which is the simplest of all. You compute the number of possible links one execution in the left. There is only one. There is only one number of links. One execution on the right. There is also one. So I toss a coin and say I choose a and now I'm done as they say that that's the atom I must choose the other. So now if I go back up, I still have to end all the right term. I'm not going to bother you with that. I have a choice. And then I will have a sequence and that's that's easy because there is only one choice here and I ended up with this. And in the end, I still have to do my shuffling my interleaving. And there are three possibilities here that's going to happen in that term and say I choose this one and I'm done. So that's the random sampling procedure. And you will notice that here I chose this this term here when I did my random sampling of the variable. Okay, I should this term and this term here. And I had to sample one limitation. So if you compute one over two times one over two times one over this binary coefficient, you will find one over 24. And this is exactly the number of links three execution in my program. So I indeed have achieved uniformity. Well on that example. What thing they have left. Okay, that should be fine. So I didn't tell you precisely how to compute these cave variables. There is one natural way of doing this. That's to sample uniform integer between zero and the sum of all the possibilities. So you compute the partial sum and you stop as soon as you get to X, as you are above X and the term you are at at this moment. That's the case. Okay, and that's a way to sample a case with this distribution. It turns out that there isn't a better way. That being found by Flagellé Simervan in Femkinson and studied in depth by Amarinero. The Flagellé algorithm in the idea is rather than summing these terms, rather than taking the partial sum in that order, you go, you take the first term, then the last, then the second term, then the term before the last term, etc. So you sum a bit like this. And by doing so, I don't think I have enough time to explain it, but it turns out that your algorithm is going to get faster than if you do this solution. But the reason, in one sentence, the reason that the worst case here is when you take K to be equals to N over two, and give you some kind of divide and compare scheme. And then your recursive calls, you have N over two arguments and it converge faster. So I'm going to use two results there. The first result is that using the Bustro-Fedonic algorithm, I can achieve an again complexity for my random sampling procedure. And Amarinero also have another result that states that on the recursion-free case, you can achieve linearity. And that's our case because remember our specifications. They don't have their recursion-free. I don't refer to the specification to P when I specify P. So in theory, I could use this result, but actually there is a trap here because the constant hidden in front of this N, it depends on the size of the drama. So I must handle that carefully and you must refine these results and turn that the constant is proportional to the depth of the drama. So that's equivalent to say it's proportional to the height of my program. If you think of my program as, if you think of it as a tree, as a syntactic tree, then the height, the height of this tree, the number of nested constructors, that's your parameter here. So I can put these two results together and I get this complexity for my random sampling procedure. So it's rather fast. It's much cheaper than counting the number of execution. Of course, I need to have done the counting before, but then it's cheap. And I'm reaching the end of my time so that's good because I'm done. So I think that if I managed to get you, keep you with me all the time, I think that the getaway of this, the takeover of this talk is that specifications can be used as first class objects. What I mean by that is that we usually, when you look at when we look at a problem, we usually choose, I mean, fix a specification, then we work from there. And in this case, what we had to do is derive automatically in a systematic way, derive one specification for one instance of the problem and then work from there. And I, I think it's not used a lot, this kind of techniques and it can be really useful. I wanted to highlight that. And from there, there are still a lot of work to do. Well, first we put generalize the model because as I mentioned, there is the most general problem which is intractable and there is this one which we proved to be tractable and there are plenty of things in between. Interesting to investigate that. Also, I didn't talk about any analytic properties of my problem. So that's also something that could be investigated. And finally, the whole purpose of this work is to check, is to test programs and to answer to Sege's question actually. So, yeah, a nice next step would be to actually use these techniques to do something like random testing or something called statistical model checking or whatever application we can have. So, I'm done. Thank you very much for your attention. And if you have any questions, I think we have five minutes. And I don't know if you can mute yourself or you just don't have questions. I would have a question if I may. Yeah, so I would ask, how far can you go actually with the, with the target size of those schedules you're generating. I'm carrying the recursive method and say multiple. Yeah, I think we reached something like, I should have prepared this question. I should have anticipated. And check we went until just roughly speaking. Yes, I don't need the exact figure. Something like 10,000 something like that. 10,000 of atomic actions. Oh, there's quite a lot. Yeah, that's, that's okay. And just confirm. Oh, no, I, sorry, sorry, I've been lying. That's more like 5,000. Sorry. Yeah, 5,000 within a minute in a minute in about a minute you can a little more than a minute you can compute the number of executions for a program with 5,000 notes and about 5,000 executions. So, sorry, additional size about 5,000 three to five hours. So that's the other of the minute and then if you want to sampling. That's, that's quite fast. It's, it measures in milliseconds, the sampling part. But really that the expensive part is counting. Oh yeah, another, yeah. An extension could be to apply something called approximate sampling. I don't remember who did this but if you count using floating points arithmetic and if you carefully handle your flow to, if you carefully quantify, sorry, the approximation error. So maybe you can achieve more efficient sampling. Oh, sorry, more efficient counting at the cost of some approximations and then go a little bit further, but I didn't do that. Okay, thank you very much. It's a naive question, but I really liked like how you, you modeled all that stuff and how did nicely fits but I was wondering if you want that if you're now looking at applications like how big is use a program in reality you could you would you would expect to test with something like that or you would work on how big would that model be like, you know, if I don't know if it's like, it's a bit naive question but I kind of, you know, what would you expect. I mean, actually I wouldn't expect programs to be too big. I think about 1000 executions is fine but I didn't really investigate this a lot. I know that people doing exhaustive testing, they look at a very, very small programs because of the state explosion. Okay, and the other people who have big programs they do random testing but in a very naive way and far from being uniform. And I don't okay. Okay, so connecting with the previous question it's like okay every 5000 you can actually do something. I think that again I would have to try very nice. I think I'm going to elaborate on this. I think that the problem would not be the size of the problem but whether the features. I mean, I don't know if a real life problem can fit. Yes. Yeah, if you can, of course, but that's that's the model you chose to do and that's a start and that's not, but that's very nice. Thank you. I have a question about the symbolic on the recursive methods, which are systematic. Do you use tools to to handle them to help you to know how do you apply it by hand. I implemented all of it, except for you. Sorry, I lost focus. Yes. I implemented all of this by hand. I mean, yeah, I implemented all of this in C. So not using maple or stuff like that. The only tool I used was a library for doing the polynomial notifications, because I, I mean, a polynomial notification is whole topic. And they are very fine tuned algorithms to do that. So I use the library for that, except for this, all of this is implemented by myself. Oh, yeah, and I didn't mention this, but this is available on my on my. I can give a link in the. Just to inform that there is a current effort by Florence ever to implement the tools of combinatorics inside cock. Yeah, which is a proof assistant. Yeah. And he has a great experience in it on the. So the idea that you input the equations. In terms of combinatorial classes on you get the generating functions on the counting. It's, it's a starting project only works around for the moment so we cannot expect for for a result soon but they are already tools in the time is over for the talk. Okay, is there another question. Yes, you can ask it. She can unmute your microphone. So junior asked how likely is it to implement your word product as a Boltzmann sample. What the problem that's a very interesting question. The problem is on this slide actually. It's everything except for for this line can be implemented is easy to implement as a book and sampler. But since we have this mix of labeled and enable constructions. That's a really tricky thing to do and there has been an attempt to do this actually. I did this. I think I was so yeah and that's maybe I can find the reference and give it to you in the discord thing. Yes, it's in the thesis of the house. Even if the exclusion part will be difficult to have with the both. Oh, the, the exclusion is not really an issue because you just remove one term in some cases. You could. But in both in sampling you don't have a particular term that you could remove, I guess. Yeah, but I mean for the junior part. Yeah, but in this particular case, you can rewrite your grammar so that you don't have exclusion. It's just convenient. But since I'm just moving one epsilon sometimes I can just rewrite the grammar to handle this. That's feasible. The problem is really in the mix of these two operators and they tried to implement this. They tried to do this in the most important context. But at the cost of an expensive generation procedure at some point you have to do something that is similar to the recursive method. Then I think it's unlikely to be more efficient than a more traditional approach like this. I didn't run the tool because it seems I think there is a maple implementation, but I don't have access to it. They admit it's not very efficient. So I guess it's difficult. We can stop the recording for this.