Reading time: 10 min

12 /4/2019

Generation of Random Circular Aggregates for Mesoscale Modelling

Concrete is a four phase material comprising of cement, sand, aggregate and voids. In order to better represent the complexity of concrete material, a mesoscale modelling approach is normally considered which attempts to capture the dominant material structure whilst ignoring finer details. For example, there will be a limitation to the minimum particle size and packing of particles considered in the model due to computational reasons.

To begin considering heterogeneous concrete models for simulation, you would normally start with circles (2D) or spheres (3D) to represent the aggregate before moving on to ellipse and polygon type aggregates. In order to accurately represent concrete as a mesoscale model, the gradation of particles/sieve distribution needs to be considered and implemented in the generation algorithm. The sieve distribution would either come from your experiment or adapted from literature.

Size distribution of aggregates and pores

The distribution of particles or gradation is often determined by the fuller curve [1].


Where P(d) is the cumulative percentage passing a sieve with aperture diameter d, dmax is the maximum size of aggregates and n is a constant n=0.45-0.70, respectively.

The area representation of particles A_agg in each sieve size [d_i, d_i+1}] is [2]

Where d_max, d_min is the maximum and minimum size of aggregates, P_agg is the area fraction of all aggregates with respect to the total area of concrete sample A.

The aggregates generally occupy 60-80% in volume for concrete [3]. For normal strength concrete, coarse aggregates usually represent 40-50% of concrete volume [1]. In this article, the approach by [3] is adopted. Aggregates larger than 2.36mm are modelled and smaller fine particles with cement is treated as mortar. Pore size range between 2-4mm diameter is considered here with a 2% total pore area. The sieve size distribution adapted from [3] is given in Table 1 and illustrated in Figure 1.

Table 1. Four segment gradation of aggregate size distribution (Wang et al, 2015)

Sieve Size (mm) Total Percentage Passing (%)
19.00 100
12.70 97
9.50 61
4.75 10
2.36 1.4

Aggregate and pore generation

The common aggregate/pore generation procedure is to generate and place the aggregates and pores in a repeated manner until the target area is fully packed. I will explain the detailed procedure for circular aggregates alongside python code for implementation.

    • Define input parameters such as circular aggregate gradation diameter ranges, pore size range, total coarse aggregate area, aggregate area for each gradation and total concrete area.

height = 0.1 #m
length = 0.1
d0 = 0.002
d1 = 0.004
pore_content = 0.02 #%
minbound = 0.005
    • Generate a pore with a random x and y coordinate and random pore size from the pore size range.
def generateCirclePores(length, height, d0, d1, pore_content, minbound):
	#length and height is the dimension of the generation area
	#d0 is min pore diameter, d1 is max pore diameter
	#pore content is pore % of total area
	#minbound is the distance away from side boundaries
	poreArea = pore_content*length*height
	totalPoreArea = 0
	poreXvalues = []
	poreYvalues = []
	poreRvalues = []
	while totalPoreArea <= poreArea:
		r = (d0+random.random()*(d1-d0))/2
		boundary = minbound + r
		x = boundary + random.random()*(length-2*boundary)
		y = boundary + random.random()*(height-2*boundary)
    • Check if the pore generated is (1) Inside the concrete area (2) Does not overlap or intersect with existing pores by checking the distance between the pore centres and the sum of the two radii. If generated pore satisfies conditions, append pore parameters into an array.
if len(poreXvalues) == 0:
			poreXvalues.append(x)
			poreYvalues.append(y)
			poreRvalues.append(r)
			totalPoreArea = totalPoreArea + np.pi*r**2
		else: #check conditions
			distance = np.asarray([(x-x1)**2+(y-y1)**2  for x1,y1 in zip(poreXvalues,poreYvalues)])
			minDistance = np.asarray([(2*(r+r1))**2 for r1 in poreRvalues])
			if np.any(distance < minDistance):
				continue
			else:
				poreXvalues.append(x)
				poreYvalues.append(y)
				poreRvalues.append(r)
				totalPoreArea = totalPoreArea + np.pi*r**2
	print("Total pore area is {} and error is {}%".format(totalPoreArea,(totalPoreArea-poreArea)*100/poreArea))
	return poreXvalues, poreYvalues, poreRvalues

 

    • Loop through steps 3 and 4 until the area of the pore remaining to be generated exceeds 2% of the total area.
[poreX, poreY, poreR] = generateCirclesPores(length, height, d0, d1, pore_content, minbound)
    • Generate an aggregate with random x, y coordinate and starting with the largest random aggregate size from the gradation segment.
aggVol = 0.3 #%
sieveMax = [0.019, 0.0127,0.0095,0.00475] #m
sieveMin = [0.0127,0.0095,0.00475,0.00236] #m
cumPass = [1, 0.97, 0.61, 0.10, 0.014]
itz = 0.0025
def generateCirclesWithPores(length, height, minbound, poreX,poreY,poreR, aggVol, sieveMin, sieveMax, cumPass, itz):
    #length and height is the dimension of the generation area
    #minbound is the distance away from side boundaries
    #aggVol is volume/area of coarse aggregate in decimal i.e 0.5
    #sieveMin is the min sieve sizes ordered from largest to smallest in list format
    #sieveMax is the max sieve sizes ordered from largest to smallest in list format
    #cumPass is the passing percentage in decimal for each sieve size from largest to smallest in list format
    #itz is interfacial transition zone thickness in metres
    area = length*height
    Pmax = cumPass[0]
    Pmin = cumPass[-1]
    aggArea = [(cumPass[i]-cumPass[i+1])/(Pmax-Pmin)*aggVol*area for i in range(len(cumPass)) if i!=len(cumPass)-1]
    #initalise lists
    aggX = []
    aggY = []
    aggR = []
    aggRitz = []
    for sMin, sMax, gradeArea in zip(sieveMin, sieveMax, aggArea):
        totalAggArea = 0
        breakCount = 0
        while totalAggArea < gradeArea: if breakCount >2**20:
                break
            r = (sMin+random.random()*(sMax-sMin))/2
            r_itz = r+itz;
            boundary = minbound + r_itz
            x = boundary + random.random()*(length-2*boundary)
            y = boundary + random.random()*(height-2*boundary)

 

    • Check if the aggregate generated is (1) Inside the concrete area (2) Does not overlap/intersect with existing pores or aggregates (3) Has a minimum distance away from the boundaries (4)Has a minimum distance between aggregates and pores. When all conditions are satisfied, the aggregate parameters are appended to an array.

#check not overlapping with pores
            distance = np.asarray([(x-x1)**2+(y-y1)**2  for x1,y1 in zip(poreX,poreY)])
            minDistance = np.asarray([(r_itz+r1)**2 for r1 in poreR])
            if np.any(distance < minDistance):
                breakCount = breakCount +1
                continue
            if len(aggX) ==0:
                aggX.append(x)
                aggY.append(y)
                aggR.append(r)
                aggRitz.append(r_itz)
                totalAggArea = totalAggArea + np.pi*r**2
            else:
                #check not overlapping with existing aggregates
                distance = np.asarray([(x-x1)**2+(y-y1)**2  for x1,y1 in zip(aggX,aggY)])
                minDistance = np.asarray([(r_itz+r1)**2 for r1 in aggR])
                if np.any(distance < minDistance):
                    breakCount = breakCount +1
                    continue
                else:
                    aggX.append(x)
                    aggY.append(y)
                    aggR.append(r)
                    aggRitz.append(r_itz)
                    totalAggArea = totalAggArea + np.pi*r**2
        print("Reached agg size: {}".format(sMax))
        print("Agg Area: {}, error is {}% ".format(totalAggArea, (totalAggArea-gradeArea)*100/gradeArea))
    return aggX, aggY, aggR, aggRitz

 

    • Loop through step 5 and 6 until the aggregate area for the largest aggregate gradation exceeds the gradation area limit. Once exceeded, the generation of aggregate size will move on to the next gradation size and steps 5 and 6 are repeated with a different area limit.

[aggX, aggY, aggR, aggRitz] = generateCirclesWithPores(length,height,minbound,poreX,poreY,poreR,aggVol,sieveMin, sieveMax, cumPass, itz)

 

To visual the generation of circular aggregates, the list of stored aggregate/pore values will be extracted and plotted in a chart.


def plotCircles(X,Y,R,xrange,yrange):
    fig, ax = plt.subplots()
    ax.set_xlim((0, xrange))
    ax.set_ylim((0, yrange))
    for x,y,r in zip(X,Y,R):
        circles = plt.Circle([x,y],r,color='red', fill=False)
        ax.add_artist(circles)
	plt.show()

plotCircles(poreX,poreY,poreR,length,height)
plotCircles(aggX,aggY,aggR,length,height)

So there you have it, I have demonstrated a brute force algorithm for circular aggregate generation. I hope this introductory overview provides you with the foundation to conduct more advanced mesoscale modelling. Of course, there is a still a crucial aspect that is missing, which is conducting mesoscale modelling through finite element software. This aspect of mesoscale modelling is something I will discuss later.

References

[1] Wriggers, P., Moftah, S.O., 2006, Mesoscale models for concrete: homogenisation and damage behaviour, Finite Element Analysis and Design, 42 (7), 623-636

[2] Wang, Z., Kwan, A., Chan, H., 1999, Mesoscopic study of concrete I: generation of random aggregate structure and finite element mesh, Computers & Structures, 70 (5), 533-544

[3] Wang, X.F., Yang, Z.J., Yates, J.R., Jivkov, A.P., Zhang, Ch., 2015, Monte carlo simulations of mesoscale fracture modelling of concrete with random aggregates and pores, Construction and Building Materials, 75, 35-45

Hello.

Submit a form to get to touch