naev 0.12.6
safelanes.c
Go to the documentation of this file.
1/*
2 * See Licensing and Copyright notice in naev.h
3 */
12#include <math.h>
13
14#if HAVE_SUITESPARSE_CHOLMOD_H
15#include <suitesparse/cholmod.h>
16#else /* HAVE_SUITESPARSE_CHOLMOD_H */
17#include <cholmod.h>
18#endif /* HAVE_SUITESPARSE_CHOLMOD_H */
19
20#if HAVE_OPENBLAS_CBLAS_H
21#include <openblas/cblas.h>
22#elif HAVE_CBLAS_OPENBLAS_H
23#include <cblas_openblas.h>
24#elif HAVE_CBLAS_HYPHEN_OPENBLAS_H
25#include <cblas-openblas.h>
26#elif HAVE_ACCELERATE_ACCELERATE_H
27#include <Accelerate/Accelerate.h>
28#elif HAVE_CBLAS_H
29#include <cblas.h>
30#elif HAVE_F77BLAS_H
31#include <f77blas.h>
32#define I_LOVE_FORTRAN 1
33#elif HAVE_OPENBLAS_F77BLAS_H
34#include <openblas/f77blas.h>
35#define I_LOVE_FORTRAN 1
36#endif
37
38#include "SDL_timer.h"
39
40#include "naev.h"
42
43#include "safelanes.h"
44
45#include "array.h"
46#include "conf.h"
47#include "log.h"
48#include "union_find.h"
49
50/*
51 * Global parameters.
52 */
53static const double ALPHA = 9.;
54static const double LAMBDA = 2e10;
55static const double JUMP_CONDUCTIVITY =
56 0.001;
57static const double MIN_ANGLE =
58 M_PI / 18.;
59enum {
61 -1,
63 0,
65 +1,
66 SORTED = 1,
67 PACKED = 1,
69};
70
71/*
72 * Types.
73 */
79typedef enum VertexType_ { VERTEX_SPOB, VERTEX_JUMP } VertexType;
80
82typedef struct Vertex_ {
83 int system;
85 int index;
86} Vertex;
87
89typedef int Edge[2];
90
92typedef struct Faction_ {
93 int id;
96 double lane_base_cost;
97} Faction;
98
100typedef uint32_t FactionMask;
101static const FactionMask MASK_0 = 0, MASK_1 = 1;
102
103/*
104 * Global state.
105 */
106static cholmod_common C;
114static Edge
116static int *sys_to_first_edge;
118static Faction *
120static int *lane_faction;
124static double *
127static int *tmp_spob_indices;
131static double
134static int
137static UnionFind
140static cholmod_triplet
143static cholmod_sparse *QtQ;
144static cholmod_dense
146static cholmod_dense
148static cholmod_dense **PPl;
150static double *cmp_key_ref;
153 0;
154
155/*
156 * Prototypes.
157 */
158static int safelanes_buildOneTurn( int iters_done );
159static int safelanes_activateByGradient( const cholmod_dense *Lambda_tilde,
160 int iters_done );
161static void safelanes_initStacks( void );
162static void safelanes_initStacks_edge( void );
163static void safelanes_initStacks_faction( void );
164static void safelanes_initStacks_vertex( void );
165static void safelanes_initStacks_anchor( void );
166static void safelanes_initOptimizer( void );
167static void safelanes_destroyOptimizer( void );
168static void safelanes_destroyStacks( void );
169static void safelanes_destroyTmp( void );
170static void safelanes_initStiff( void );
171static double safelanes_initialConductivity( int ei );
172static void safelanes_updateConductivity( int ei_activated );
173static void safelanes_initQtQ( void );
174static void safelanes_initFTilde( void );
175static void safelanes_initPPl( void );
176static int safelanes_triangleTooFlat( const vec2 *m, const vec2 *n,
177 const vec2 *p, double lmn );
178static int vertex_faction( int vi );
179static const vec2 *vertex_pos( int vi );
180static inline int FACTION_ID_TO_INDEX( int id );
181static inline FactionMask MASK_ANY_FACTION();
182static inline FactionMask MASK_ONE_FACTION( int id );
183static inline FactionMask MASK_COMPROMISE( int id1, int id2 );
184static int cmp_key( const void *p1, const void *p2 );
185static inline void triplet_entry( cholmod_triplet *m, int i, int j, double v );
186static cholmod_dense *safelanes_sliceByPresence( const cholmod_dense *m,
187 const double *sysPresence );
188static cholmod_dense *ncholmod_ddmult( cholmod_dense *A, int transA,
189 cholmod_dense *B );
190static double safelanes_row_dot_row( cholmod_dense *A, cholmod_dense *B, int i,
191 int j );
192
196static inline void array_push_back_edge( Edge **a, int v0, int v1 )
197{
198 int *vs = array_grow( a );
199 vs[0] = v0;
200 vs[1] = v1;
201}
202
206void safelanes_init( void )
207{
208 cholmod_start( &C );
209 /* Ideally we would want to recalculate here, but since we load the first
210 * save and try to use unidiffs there, we instead defer the safe lane
211 * computation to only if necessary after loading save unidiffs. */
212 /* safelanes_recalculate(); */
213}
214
219{
222 cholmod_finish( &C );
223}
224
233SafeLane *safelanes_get( int faction, int standing, const StarSystem *system )
234{
236
237 for ( int i = sys_to_first_edge[system->id];
238 i < sys_to_first_edge[1 + system->id]; i++ ) {
239 SafeLane *l;
240 const Vertex *v[2];
241 int lf = lane_faction[i];
242
243 /* No lane on edge. */
244 if ( lf <= 0 )
245 continue;
246
247 /* Filter by standing. */
248 if ( faction >= 0 ) {
249 if ( standing == 0 ) {
250 /* Only match exact faction. */
251 if ( lf != faction )
252 continue;
253 } else {
254 /* Try to do more advanced matching. */
255 int fe = areEnemies( faction, lf );
256 int fa = areAllies( faction, lf );
257 int skip = 1;
258 if ( ( standing & SAFELANES_FRIENDLY ) && fa )
259 skip = 0;
260 if ( ( standing & SAFELANES_NEUTRAL ) && !fe )
261 skip = 0;
262 if ( ( standing & SAFELANES_HOSTILE ) && fe )
263 skip = 0;
264 if ( skip )
265 continue;
266 }
267 }
268
269 for ( int j = 0; j < 2; j++ )
270 v[j] = &vertex_stack[edge_stack[i][j]];
271
272 l = &array_grow( &out );
273 l->faction = lane_faction[i];
274 for ( int j = 0; j < 2; j++ ) {
275 switch ( v[j]->type ) {
276 case VERTEX_SPOB:
277 l->point_type[j] = SAFELANE_LOC_SPOB;
278 l->point_id[j] = system->spobs[v[j]->index]->id;
279 break;
280 case VERTEX_JUMP:
281 l->point_type[j] = SAFELANE_LOC_DEST_SYS;
282 l->point_id[j] = system->jumps[v[j]->index].targetid;
283 break;
284 default:
285 WARN( _( "Safe-lane vertex type is invalid." ) );
286 }
287 }
288 }
289 return out;
290}
291
297{
298#if DEBUGGING
299 Uint32 time = SDL_GetTicks();
300#endif /* DEBUGGING */
301
302 /* Don't recompute on exit. */
303 if ( naev_isQuit() )
304 return;
305
308 for ( int iters_done = 0; safelanes_buildOneTurn( iters_done ) > 0;
309 iters_done++ )
310 ;
312 /* Stacks remain available for queries. */
313#if DEBUGGING
314 if ( conf.devmode )
315 DEBUG( n_( "Charted safe lanes for %d object in %.3f s",
316 "Charted safe lanes for %d objects in %.3f s",
318 array_size( vertex_stack ), ( SDL_GetTicks() - time ) / 1000. );
319#endif /* DEBUGGING */
320
322}
323
328{
330}
331
343
347static void safelanes_destroyOptimizer( void )
348{
349 for ( int i = 0; i < array_size( PPl ); i++ )
350 cholmod_free_dense( &PPl[i], &C );
351 array_free( PPl );
352 PPl = NULL;
353 cholmod_free_dense( &utilde,
354 &C ); /* CAUTION: if we instead save it, ensure it's
355 updated after the final activateByGradient. */
356 cholmod_free_dense( &ftilde, &C );
357 cholmod_free_sparse( &QtQ, &C );
358 cholmod_free_triplet( &stiff, &C );
359}
360
365static int safelanes_buildOneTurn( int iters_done )
366{
367 cholmod_sparse *stiff_s;
368 cholmod_factor *stiff_f;
369 cholmod_dense *_QtQutilde, *Lambda_tilde, *Y_workspace, *E_workspace;
370 int turns_next_time;
371 double zero[] = { 0, 0 }, neg_1[] = { -1, 0 };
372
373 Y_workspace = E_workspace = Lambda_tilde = NULL;
374 stiff_s = cholmod_triplet_to_sparse( stiff, 0, &C );
375 stiff_f = cholmod_analyze( stiff_s, &C );
376 cholmod_factorize( stiff_s, stiff_f, &C );
377 cholmod_solve2( CHOLMOD_A, stiff_f, ftilde, NULL, &utilde, NULL,
378 &Y_workspace, &E_workspace, &C );
379 _QtQutilde = cholmod_zeros( utilde->nrow, utilde->ncol, CHOLMOD_REAL, &C );
380 cholmod_sdmult( QtQ, 0, neg_1, zero, utilde, _QtQutilde, &C );
381 cholmod_solve2( CHOLMOD_A, stiff_f, _QtQutilde, NULL, &Lambda_tilde, NULL,
382 &Y_workspace, &E_workspace, &C );
383 cholmod_free_dense( &_QtQutilde, &C );
384 cholmod_free_dense( &Y_workspace, &C );
385 cholmod_free_dense( &E_workspace, &C );
386 cholmod_free_factor( &stiff_f, &C );
387 cholmod_free_sparse( &stiff_s, &C );
388 turns_next_time = safelanes_activateByGradient( Lambda_tilde, iters_done );
389 cholmod_free_dense( &Lambda_tilde, &C );
390
391 return turns_next_time;
392}
393
397static void safelanes_initStacks( void )
398{
400 safelanes_initStacks_faction(); /* Dependency for vertex. */
401 safelanes_initStacks_vertex(); /* Dependency for edge. */
404}
405
410{
411 const StarSystem *systems_stack = system_getAll();
412
418 for ( int system = 0; system < array_size( systems_stack ); system++ ) {
419 const StarSystem *sys = &systems_stack[system];
420 if ( sys_isFlag( sys, SYSTEM_NOLANES ) ) {
422 continue;
423 }
424
425 for ( int i = 0; i < array_size( sys->spobs ); i++ ) {
426 const Spob *p = sys->spobs[i];
427 if ( spob_isFlag( p, SPOB_NOLANES ) )
428 continue;
429 if ( p->presence.base != 0. || p->presence.bonus != 0. ) {
430 Vertex v = { .system = system, .type = VERTEX_SPOB, .index = i };
433 }
434 }
435
436 for ( int i = 0; i < array_size( sys->jumps ); i++ ) {
437 const JumpPoint *jp = &sys->jumps[i];
438 if ( jp_isFlag( jp, JP_HIDDEN | JP_EXITONLY | JP_NOLANES ) )
439 continue;
440 Vertex v = { .system = system, .type = VERTEX_JUMP, .index = i };
442 if ( jp->targetid < system && jp->returnJump != NULL )
443 for ( int j = sys_to_first_vertex[jp->targetid];
444 j < sys_to_first_vertex[1 + jp->targetid]; j++ )
445 if ( vertex_stack[j].type == VERTEX_JUMP &&
446 jp->returnJump ==
447 &jp->target->jumps[vertex_stack[j].index] ) {
449 array_size( vertex_stack ) - 1, j );
450 break;
451 }
452 }
453
455 }
456 // array_shrink( &vertex_stack );
457 // array_shrink( &sys_to_first_vertex );
458
459 vertex_fmask = calloc( array_size( vertex_stack ), sizeof( FactionMask ) );
460}
461
466static void safelanes_initStacks_edge( void )
467{
468 const StarSystem *systems_stack = system_getAll();
469
474 tmp_edge_conduct = array_create( double );
475 for ( int system = 0; system < array_size( systems_stack ); system++ ) {
476 for ( int i = sys_to_first_vertex[system];
477 i < sys_to_first_vertex[1 + system]; i++ ) {
478 const vec2 *pi = vertex_pos( i );
479 for ( int j = sys_to_first_vertex[system]; j < i; j++ ) {
480 const vec2 *pj = vertex_pos( j );
481 double lij = vec2_dist( pi, pj );
482 int has_approx_midpoint = 0;
483 for ( int k = sys_to_first_vertex[system];
484 k < sys_to_first_vertex[1 + system]; k++ )
485 if ( k != i && k != j &&
487 lij ) ) {
488 has_approx_midpoint = 1;
489 break;
490 }
491 if ( has_approx_midpoint )
492 continue; /* The edge from i to j is disallowed in favor of a
493 path through k. */
496 &lane_fmask,
499 }
500 }
502 }
505
508 memset( lane_faction, 0,
509 array_size( lane_faction ) * sizeof( lane_faction[0] ) );
510}
511
516{
517 int *faction_all;
518 const StarSystem *systems_stack;
519
521 faction_all = faction_getAllVisible();
522 for ( int fi = 0; fi < array_size( faction_all ); fi++ ) {
523 int f = faction_all[fi];
524 Faction rec = { .id = f,
525 .lane_length_per_presence =
527 .lane_base_cost = faction_lane_base_cost( f ) };
528 if ( rec.lane_length_per_presence > 0. )
530 }
531 array_free( faction_all );
533 assert( "FactionMask size is sufficient" &&
534 (size_t)array_size( faction_stack ) <= 8 * sizeof( FactionMask ) );
535
538 for ( int fi = 0; fi < array_size( faction_stack ); fi++ ) {
542 for ( int s = 0; s < array_size( systems_stack ); s++ ) {
543 const StarSystem *sys = &systems_stack[s];
544 double budget = system_getPresence( sys, faction_stack[fi].id );
545 array_push_back( &presence_budget[fi], budget );
546 }
547 }
548}
549
557{
558 int *anchor_systems;
559 int nsys = array_size( sys_to_first_vertex ) - 1;
560 unionfind_init( &tmp_sys_uf, nsys );
561 for ( int i = 0; i < array_size( tmp_jump_edges ); i++ )
563 vertex_stack[tmp_jump_edges[i][1]].system );
564 anchor_systems = unionfind_findall( &tmp_sys_uf );
565 tmp_anchor_vertices = array_create_size( int, array_size( anchor_systems ) );
566
567 /* Add an anchor vertex per system, but only if there actually is a vertex in
568 * the system. */
569 for ( int i = 0; i < array_size( anchor_systems ); i++ )
570 if ( sys_to_first_vertex[anchor_systems[i]] <
571 sys_to_first_vertex[1 + anchor_systems[i]] )
573 sys_to_first_vertex[anchor_systems[i]] );
574 array_free( anchor_systems );
575}
576
580static void safelanes_destroyStacks( void )
581{
584 vertex_stack = NULL;
585 free( vertex_fmask );
586 vertex_fmask = NULL;
588 sys_to_first_vertex = NULL;
590 edge_stack = NULL;
592 sys_to_first_edge = NULL;
593 for ( int i = 0; i < array_size( presence_budget ); i++ )
596 presence_budget = NULL;
598 faction_stack = NULL;
600 lane_faction = NULL;
602 lane_fmask = NULL;
603}
604
620
624static void safelanes_initStiff( void )
625{
626 int nnz, v;
627 double max_conductivity;
628
629 cholmod_free_triplet( &stiff, &C );
631 nnz = 3 * ( array_size( edge_stack ) + array_size( tmp_jump_edges ) ) +
633 stiff = cholmod_allocate_triplet(
634 v, v, nnz, STORAGE_MODE_UPPER_TRIANGULAR_PART, CHOLMOD_REAL, &C );
635 /* Populate triplets: internal edges (ii ij jj), implicit jump connections
636 * (ditto), anchor conditions. */
637 for ( int i = 0; i < array_size( edge_stack ); i++ ) {
638 triplet_entry( stiff, edge_stack[i][0], edge_stack[i][0],
639 +tmp_edge_conduct[i] );
640 triplet_entry( stiff, edge_stack[i][0], edge_stack[i][1],
641 -tmp_edge_conduct[i] );
642 triplet_entry( stiff, edge_stack[i][1], edge_stack[i][1],
643 +tmp_edge_conduct[i] );
644 }
645 for ( int i = 0; i < array_size( tmp_jump_edges ); i++ ) {
646 triplet_entry( stiff, tmp_jump_edges[i][0], tmp_jump_edges[i][0],
648 triplet_entry( stiff, tmp_jump_edges[i][0], tmp_jump_edges[i][1],
650 triplet_entry( stiff, tmp_jump_edges[i][1], tmp_jump_edges[i][1],
652 }
653 /* Add a Robin boundary condition, using the max conductivity (after
654 * activation) for spectral reasons. */
655 max_conductivity = JUMP_CONDUCTIVITY / ( 1 + ALPHA );
656 for ( int i = 0; i < array_size( edge_stack ); i++ )
657 max_conductivity = MAX( max_conductivity, tmp_edge_conduct[i] );
658 max_conductivity = MAX(
660 ( 1 + ALPHA ) *
661 max_conductivity ); /* Activation scales entries by 1+ALPHA later. */
662 for ( int i = 0; i < array_size( tmp_anchor_vertices ); i++ )
663 triplet_entry( stiff, tmp_anchor_vertices[i], tmp_anchor_vertices[i],
664 max_conductivity );
665#if DEBUGGING
666 assert( stiff->nnz == stiff->nzmax );
667 assert( cholmod_check_triplet( stiff, &C ) );
668#endif /* DEBUGGING */
669}
670
677static double safelanes_initialConductivity( int ei )
678{
679 double *sv = stiff->x;
680 return lane_faction[ei] ? sv[3 * ei] / ( 1 + ALPHA ) : sv[3 * ei];
681}
682
687static void safelanes_updateConductivity( int ei_activated )
688{
689 double *sv = stiff->x;
690 for ( int i = 3 * ei_activated; i < 3 * ( ei_activated + 1 ); i++ )
691 sv[i] *= 1 + ALPHA;
692}
693
697static void safelanes_initQtQ( void )
698{
699 cholmod_sparse *Q;
700
701 cholmod_free_sparse( &QtQ, &C );
702 /* Form Q, the edge-vertex projection where (Dirac notation) Q |edge> =
703 * |edge[0]> - |edge[1]>. It has a +1 and -1 per column. */
704 Q = cholmod_allocate_sparse( array_size( vertex_stack ),
707 STORAGE_MODE_UNSYMMETRIC, CHOLMOD_REAL, &C );
708 ( (int *)Q->p )[0] = 0;
709 for ( int i = 0; i < array_size( edge_stack ); i++ ) {
710 ( (int *)Q->p )[i + 1] = 2 * ( i + 1 );
711 ( (int *)Q->i )[2 * i + 0] = edge_stack[i][0];
712 ( (int *)Q->i )[2 * i + 1] = edge_stack[i][1];
713 ( (double *)Q->x )[2 * i + 0] = +1;
714 ( (double *)Q->x )[2 * i + 1] = -1;
715 }
716#if DEBUGGING
717 assert( cholmod_check_sparse( Q, &C ) );
718#endif /* DEBUGGING */
719 QtQ = cholmod_aat( Q, NULL, 0, MODE_NUMERICAL, &C );
720 cholmod_free_sparse( &Q, &C );
721}
722
726static void safelanes_initFTilde( void )
727{
728 cholmod_sparse *eye =
729 cholmod_speye( array_size( vertex_stack ), array_size( vertex_stack ),
730 CHOLMOD_REAL, &C );
731 cholmod_sparse *sp =
732 cholmod_submatrix( eye, NULL, -1, tmp_spob_indices,
734 cholmod_free_dense( &ftilde, &C );
735 ftilde = cholmod_sparse_to_dense( sp, &C );
736 cholmod_free_sparse( &sp, &C );
737 cholmod_free_sparse( &eye, &C );
738}
739
743static void safelanes_initPPl( void )
744{
745 int *component;
746 int np = array_size( tmp_spob_indices );
747
748 for ( int fi = 0; fi < array_size( PPl ); fi++ )
749 cholmod_free_dense( &PPl[fi], &C );
750 array_free( PPl );
751 PPl = array_create_size( cholmod_dense *, array_size( faction_stack ) );
752 for ( int fi = 0; fi < array_size( faction_stack ); fi++ )
753 array_push_back( &PPl, cholmod_zeros( np, np, CHOLMOD_REAL, &C ) );
754
755 /* Form P, the pair-vertex projection where (Dirac notation) P |pair(i,j)> =
756 * |i> - |j>. It has a +1 and -1 per column. */
757 /* At least, pretend we did. We want (PD)(PD)*, where D is a diagonal matrix
758 * whose pair(i,j) are these presence sums: */
759
760 component = calloc( np, sizeof( int ) );
761 for ( int i = 0; i < np; i++ )
762 component[i] = unionfind_find( &tmp_sys_uf,
763 vertex_stack[tmp_spob_indices[i]].system );
764
765 for ( int i = 0; i < np; i++ ) {
766 double *Di;
767 int sys = vertex_stack[tmp_spob_indices[i]].system;
768 Spob *pnt =
769 system_getIndex( sys )->spobs[vertex_stack[tmp_spob_indices[i]].index];
770 double pres =
771 pnt->presence.base +
772 pnt->presence.bonus; /* TODO distinguish between base and bonus? */
773 int fi = FACTION_ID_TO_INDEX( pnt->presence.faction );
774 if ( fi < 0 )
775 continue;
776 Di = PPl[fi]->x;
777 for ( int j = 0; j < i; j++ )
778 if ( component[i] == component[j] )
779 Di[np * i + j] += pres;
780 for ( int j = i + 1; j < np; j++ )
781 if ( component[i] == component[j] )
782 Di[np * j + i] += pres;
783 }
784
785 /* At this point, PPl[fi]->x[np*i+j] holds the pair(i,j) entry of D. */
786 for ( int fi = 0; fi < array_size( faction_stack ); fi++ )
787 for ( int i = 0; i < np; i++ )
788 for ( int j = 0; j < i; j++ ) {
789 double d = ( (double *)PPl[fi]->x )[np * i + j];
790 d *= d;
791 ( (double *)PPl[fi]->x )[np * i + j] = -d;
792 ( (double *)PPl[fi]->x )[np * j + i] = -d;
793 ( (double *)PPl[fi]->x )[np * i + i] += d;
794 ( (double *)PPl[fi]->x )[np * j + j] += d;
795 }
796
797 free( component );
798}
799
805static int safelanes_activateByGradient( const cholmod_dense *Lambda_tilde,
806 int iters_done )
807{
808 int *facind_opts, *edgeind_opts, turns_next_time;
809 double *facind_vals, Linv;
810 cholmod_dense **lal;
812 size_t *lal_bases, lal_base;
814
815 lal = calloc( array_size( faction_stack ), sizeof( cholmod_dense *) );
816 lal_bases = calloc( array_size( faction_stack ), sizeof( size_t ) );
817 edgeind_opts = array_create( int );
818 facind_opts = array_create_size( int, array_size( faction_stack ) );
819 facind_vals = array_create_size( double, array_size( faction_stack ) );
820 for ( int fi = 0; fi < array_size( faction_stack ); fi++ ) {
821 array_push_back( &facind_opts, fi );
822 array_push_back( &facind_vals, 0 );
823 }
824 turns_next_time = 0;
825
826 for ( int si = 0; si < array_size( sys_to_first_vertex ) - 1; si++ ) {
827 /* Factions with most presence here choose first. */
828 const StarSystem *sys = system_getIndex( si );
829 for ( int fi = 0; fi < array_size( faction_stack ); fi++ )
830 facind_vals[fi] = -system_getPresence(
831 sys, faction_stack[fi]
832 .id ); /* FIXME: Is this better, or presence_budget? */
833 cmp_key_ref = facind_vals;
834 qsort( facind_opts, array_size( faction_stack ), sizeof( int ), cmp_key );
835
836 for ( int fii = 0; fii < array_size( faction_stack ); fii++ ) {
837 int ei_best;
838 double cost_best, cost_cheapest_other;
839 int fi = facind_opts[fii];
840 size_t sys_base = sys_to_first_vertex[si];
841 double score_best = 0.; /* Negative scores get ignored. */
842
843 /* Get the base index to use for this system. Save the value we expect
844 * to be the next iteration's base index. The current system's rows are
845 * in the lal[fi] matrix if there's presence at the time we slice it.
846 * What we know is whether there's presence *now*. We can use that as a
847 * proxy and fix lal_bases[fi] if we deplete this presence before
848 * constructing lal[fi]. This is tricky, so there are assertions below,
849 * which can warn us if we fuck this up. */
850 lal_base = lal_bases[fi];
851 if ( presence_budget[fi][si] <= 0. )
852 continue;
853 /* We "should" find these DoF's interesting if/when we slice, and will
854 * unless we deplete this presence first. */
855 lal_bases[fi] += sys_to_first_vertex[1 + si] - sys_to_first_vertex[si];
856
857 array_resize( &edgeind_opts, 0 );
858 for ( int ei = sys_to_first_edge[si]; ei < sys_to_first_edge[1 + si];
859 ei++ ) {
860 int sis = edge_stack[ei][0];
861 int sjs = edge_stack[ei][1];
862 int disconnecting = iters_done &&
863 !( vertex_fmask[sis] & ( MASK_1 << fi ) ) &&
864 !( vertex_fmask[sjs] & ( MASK_1 << fi ) );
865 double cost = 1. / safelanes_initialConductivity( ei ) /
866 faction_stack[fi].lane_length_per_presence +
867 faction_stack[fi].lane_base_cost;
868 if ( !lane_faction[ei] && !disconnecting &&
869 presence_budget[fi][si] >= cost &&
870 ( lane_fmask[ei] & ( MASK_1 << fi ) ) )
871 array_push_back( &edgeind_opts, ei );
872 }
873
874 if ( array_size( edgeind_opts ) == 0 ) {
875 presence_budget[fi][si] =
876 0.; /* Nothing to build here! Tell ourselves to stop trying. */
877 if ( lal[fi] == NULL )
878 lal_bases[fi] -=
880 continue;
881 }
882
883 ei_best = edgeind_opts[0];
884 cost_best = 1. / safelanes_initialConductivity( ei_best ) /
885 faction_stack[fi].lane_length_per_presence +
886 faction_stack[fi].lane_base_cost;
887 cost_cheapest_other = +HUGE_VAL;
888 if ( array_size( edgeind_opts ) > 0 ) {
889 /* There's an actual choice. Search for the best option. Lower is
890 * better. */
891 for ( int eii = 0; eii < array_size( edgeind_opts ); eii++ ) {
892 int ei = edgeind_opts[eii];
893 int sis = edge_stack[ei][0];
894 int sjs = edge_stack[ei][1];
895 double score = 0.;
896 double cost;
897
898 if ( lal[fi] == NULL ) { /* Is it time to evaluate the
899 lazily-calculated matrix? */
900 cholmod_dense *lamt = safelanes_sliceByPresence(
901 Lambda_tilde, presence_budget[fi] );
902 lal[fi] = ncholmod_ddmult( lamt, 0, PPl[fi] );
903 cholmod_free_dense( &lamt, &C );
904 }
905
906 /* Evaluate (LUTll[0,0] + LUTll[1,1] - LUTll[0,1] - LUTll[1,0]),
907 */
908 /* where LUTll = np.dot( lal[[sis,sjs],:] ,
909 * utilde[[sis,sjs],:].T ) */
910 score += safelanes_row_dot_row( utilde, lal[fi], sis,
911 sis - sys_base + lal_base );
912 score += safelanes_row_dot_row( utilde, lal[fi], sjs,
913 sjs - sys_base + lal_base );
914 score -= safelanes_row_dot_row( utilde, lal[fi], sjs,
915 sis - sys_base + lal_base );
916 score -= safelanes_row_dot_row( utilde, lal[fi], sis,
917 sjs - sys_base + lal_base );
919 score *= ALPHA * Linv * Linv;
920 score += LAMBDA;
921
922 cost = 1. / safelanes_initialConductivity( ei ) /
923 faction_stack[fi].lane_length_per_presence +
924 faction_stack[fi].lane_base_cost;
925 if ( score < score_best ) {
926 ei_best = ei;
927 score_best = score;
928 cost_cheapest_other = MIN( cost_cheapest_other, cost_best );
929 cost_best = cost;
930 } else
931 cost_cheapest_other = MIN( cost_cheapest_other, cost );
932 }
933 }
934
935 /* Ignore positive scores. */
936 if ( score_best >= 0. )
937 continue;
938
939 /* Add the lane. */
940 presence_budget[fi][si] -= cost_best;
941 if ( presence_budget[fi][si] >= cost_cheapest_other )
942 turns_next_time++;
943 else {
944 presence_budget[fi][si] =
945 0.; /* Nothing more to do here; tell ourselves. */
946 if ( lal[fi] == NULL )
947 lal_bases[fi] -=
949 }
951 vertex_fmask[edge_stack[ei_best][0]] |= ( MASK_1 << fi );
952 vertex_fmask[edge_stack[ei_best][1]] |= ( MASK_1 << fi );
953 lane_faction[ei_best] = faction_stack[fi].id;
954 }
955 }
956
957#if DEBUGGING
958 for ( int fi = 0; fi < array_size( faction_stack ); fi++ )
959 if ( lal[fi] != NULL )
960 assert( "Correctly tracked row offsets between the 'lal' and 'utilde' "
961 "matrices" &&
962 lal[fi]->nrow == lal_bases[fi] );
963#endif /* DEBUGGING */
964
965 for ( int fi = 0; fi < array_size( faction_stack ); fi++ )
966 cholmod_free_dense( &lal[fi], &C );
967 free( lal );
968 free( lal_bases );
969 array_free( edgeind_opts );
970 array_free( facind_vals );
971 array_free( facind_opts );
972
973 return turns_next_time;
974}
975
978static int cmp_key( const void *p1, const void *p2 )
979{
980 double d = cmp_key_ref[*(int *)p1] - cmp_key_ref[*(int *)p2];
981 return SIGN( d );
982}
983
988static int safelanes_triangleTooFlat( const vec2 *m, const vec2 *n,
989 const vec2 *p, double lmn )
990{
991 const double MAX_COSINE = cos( MIN_ANGLE );
992 double lnp = vec2_dist( n, p );
993 double lmp = vec2_dist( m, p );
994 double dpn = ( ( n->x - m->x ) * ( n->x - p->x ) +
995 ( n->y - m->y ) * ( n->y - p->y ) ) /
996 ( lmn * lnp );
997 double dpm = ( ( m->x - n->x ) * ( m->x - p->x ) +
998 ( m->y - n->y ) * ( m->y - p->y ) ) /
999 ( lmn * lmp );
1000 return ( dpn > MAX_COSINE && lnp < lmn ) ||
1001 ( dpm > MAX_COSINE && lmp < lmn );
1002}
1003
1008static int vertex_faction( int vi )
1009{
1010 const StarSystem *sys = system_getIndex( vertex_stack[vi].system );
1011 switch ( vertex_stack[vi].type ) {
1012 case VERTEX_SPOB:
1013 return sys->spobs[vertex_stack[vi].index]->presence.faction;
1014 case VERTEX_JUMP:
1015 return -1;
1016 default:
1017 WARN( _( "Safe-lane vertex type is invalid." ) );
1018 return -1;
1019 }
1020}
1021
1026static const vec2 *vertex_pos( int vi )
1027{
1028 const StarSystem *sys = system_getIndex( vertex_stack[vi].system );
1029 switch ( vertex_stack[vi].type ) {
1030 case VERTEX_SPOB:
1031 return &sys->spobs[vertex_stack[vi].index]->pos;
1032 case VERTEX_JUMP:
1033 return &sys->jumps[vertex_stack[vi].index].pos;
1034 default:
1035 WARN( _( "Safe-lane vertex type is invalid." ) );
1036 return NULL;
1037 }
1038}
1039
1042static inline int FACTION_ID_TO_INDEX( int id )
1043{
1044 for ( int i = 0;
1045 i < array_size( faction_stack ) && faction_stack[i].id <= id; i++ )
1046 if ( faction_stack[i].id == id )
1047 return i;
1048 return -1;
1049}
1050
1053{
1054 return ~MASK_0;
1055}
1056
1059static inline FactionMask MASK_ONE_FACTION( int id )
1060{
1061 int ind = FACTION_ID_TO_INDEX( id );
1062 return ind > 0 ? ( MASK_1 ) << ind : MASK_ANY_FACTION();
1063}
1064
1067static inline FactionMask MASK_COMPROMISE( int id1, int id2 )
1068{
1069 FactionMask m1 = MASK_ONE_FACTION( id1 ), m2 = MASK_ONE_FACTION( id2 );
1070 return ( m1 & m2 )
1071 ? ( m1 & m2 )
1072 : ( m1 |
1073 m2 ); /* Any/Any -> any, Any/f -> just f, f1/f2 -> either. */
1074}
1075
1076static inline void triplet_entry( cholmod_triplet *m, int i, int j, double x )
1077{
1078 ( (int *)m->i )[m->nnz] = i;
1079 ( (int *)m->j )[m->nnz] = j;
1080 ( (double *)m->x )[m->nnz] = x;
1081 m->nnz++;
1082}
1083
1088static cholmod_dense *safelanes_sliceByPresence( const cholmod_dense *m,
1089 const double *sysPresence )
1090{
1091 size_t nr, nc, in_r, out_r;
1092 cholmod_dense *out;
1093
1094 nr = 0;
1095 nc = m->ncol;
1096 for ( int si = 0; si < array_size( sys_to_first_vertex ) - 1; si++ )
1097 if ( sysPresence[si] > 0 )
1098 nr += sys_to_first_vertex[1 + si] - sys_to_first_vertex[si];
1099
1100 out = cholmod_allocate_dense( nr, nc, nr, CHOLMOD_REAL, &C );
1101
1102 in_r = out_r = 0;
1103 for ( int si = 0; si < array_size( sys_to_first_vertex ) - 1; si++ ) {
1104 int sz = sys_to_first_vertex[1 + si] - sys_to_first_vertex[si];
1105 if ( sysPresence[si] > 0 ) {
1106 for ( size_t c = 0; c < nc; c++ )
1107 memcpy( &( (double *)out->x )[c * out->d + out_r],
1108 &( (double *)m->x )[c * m->d + in_r],
1109 sz * sizeof( double ) );
1110 out_r += sz;
1111 }
1112 in_r += sz;
1113 }
1114 return out;
1115}
1116
1118static cholmod_dense *ncholmod_ddmult( cholmod_dense *A, int transA,
1119 cholmod_dense *B )
1120{
1121#if I_LOVE_FORTRAN
1122 blasint M = transA ? A->ncol : A->nrow, K = transA ? A->nrow : A->ncol,
1123 N = B->ncol, lda = A->d, ldb = B->d, ldc = M;
1124 assert( K == (blasint)B->nrow );
1125 cholmod_dense *out = cholmod_allocate_dense( M, N, M, CHOLMOD_REAL, &C );
1126 double alpha = 1., beta = 0.;
1127 BLASFUNC( dgemm )
1128 ( transA ? "T" : "N", "N", &M, &N, &K, &alpha, A->x, &lda, B->x, &ldb, &beta,
1129 out->x, &ldc );
1130#else /* I_LOVE_FORTRAN */
1131 size_t M = transA ? A->ncol : A->nrow, K = transA ? A->nrow : A->ncol,
1132 N = B->ncol;
1133 assert( K == B->nrow );
1134 cholmod_dense *out = cholmod_allocate_dense( M, N, M, CHOLMOD_REAL, &C );
1135 cblas_dgemm( CblasColMajor, transA ? CblasTrans : CblasNoTrans, CblasNoTrans,
1136 M, N, K, 1, A->x, A->d, B->x, B->d, 0, out->x, out->d );
1137#endif /* I_LOVE_FORTRAN */
1138 return out;
1139}
1140
1143static double safelanes_row_dot_row( cholmod_dense *A, cholmod_dense *B, int i,
1144 int j )
1145{
1146 assert( A->ncol == B->ncol );
1147#if I_LOVE_FORTRAN
1148 blasint N = A->ncol, incA = A->d, incB = B->d;
1149 return BLASFUNC( ddot )( &N, (double *)A->x + i, &incA, (double *)B->x + j,
1150 &incB );
1151#else /* I_LOVE_FORTRAN */
1152 return cblas_ddot( A->ncol, (double *)A->x + i, A->d, (double *)B->x + j,
1153 B->d );
1154#endif /* I_LOVE_FORTRAN */
1155}
Provides macros to work with dynamic arrays.
#define array_free(ptr_array)
Frees memory allocated and sets array to NULL.
Definition array.h:170
#define array_resize(ptr_array, new_size)
Resizes the array to accomodate new_size elements.
Definition array.h:113
#define array_create_size(basic_type, capacity)
Creates a new dynamic array of ‘basic_type’ with an initial capacity.
Definition array.h:102
static ALWAYS_INLINE int array_size(const void *array)
Returns number of elements in the array.
Definition array.h:179
#define array_grow(ptr_array)
Increases the number of elements by one and returns the last element.
Definition array.h:122
#define array_shrink(ptr_array)
Shrinks memory to fit only ‘size’ elements.
Definition array.h:160
#define array_push_back(ptr_array, element)
Adds a new element at the end of the array.
Definition array.h:134
#define array_create(basic_type)
Creates a new dynamic array of ‘basic_type’.
Definition array.h:93
StarSystem * systems_stack
Definition space.c:94
static Faction * faction_stack
Definition faction.c:129
int areEnemies(int a, int b)
Checks whether two factions are enemies.
Definition faction.c:1450
double faction_lane_base_cost(int f)
Gets the faction's weight for patrolled safe-lane construction;.
Definition faction.c:464
double faction_lane_length_per_presence(int f)
Gets the faction's weight for patrolled safe-lane construction (0 means they don't build lanes).
Definition faction.c:452
int * faction_getAllVisible(void)
Returns all non-invisible faction IDs in an array (array.h).
Definition faction.c:231
int areAllies(int a, int b)
Checks whether two factions are allies or not.
Definition faction.c:1476
int naev_isQuit(void)
Get if Naev is trying to quit.
Definition naev.c:153
Header file with generic functions and naev-specifics.
#define MIN(x, y)
Definition naev.h:39
#define SIGN(x)
Definition naev.h:48
#define MAX(x, y)
Definition naev.h:37
static const double c[]
Definition rng.c:256
static const double d[]
Definition rng.c:263
static int safelanes_calculated_once
Definition safelanes.c:152
static FactionMask MASK_COMPROMISE(int id1, int id2)
A mask with appropriate lane-building rights given one faction ID owning each endpoint.
Definition safelanes.c:1067
static FactionMask * vertex_fmask
Definition safelanes.c:110
static void array_push_back_edge(Edge **a, int v0, int v1)
Like array_push_back( a, Edge{v0, v1} ), but achievable in C. :-P.
Definition safelanes.c:196
static FactionMask * lane_fmask
Definition safelanes.c:122
static const double MIN_ANGLE
Definition safelanes.c:57
static void safelanes_initStiff(void)
Sets up the stiffness matrix.
Definition safelanes.c:624
static void safelanes_destroyTmp(void)
Tears down the local faction/object stacks.
Definition safelanes.c:608
static cholmod_dense * ftilde
Definition safelanes.c:145
static int safelanes_activateByGradient(const cholmod_dense *Lambda_tilde, int iters_done)
Per-system, per-faction, activates the affordable lane with best (grad phi)/L.
Definition safelanes.c:805
static void safelanes_updateConductivity(int ei_activated)
Updates the stiffness matrix to account for the given edge being activated.
Definition safelanes.c:687
static void safelanes_destroyStacks(void)
Tears down the local faction/object stacks.
Definition safelanes.c:580
@ STORAGE_MODE_UPPER_TRIANGULAR_PART
Definition safelanes.c:64
@ PACKED
Definition safelanes.c:67
@ SORTED
Definition safelanes.c:66
@ MODE_NUMERICAL
Definition safelanes.c:68
@ STORAGE_MODE_LOWER_TRIANGULAR_PART
Definition safelanes.c:60
@ STORAGE_MODE_UNSYMMETRIC
Definition safelanes.c:62
int safelanes_calculated(void)
Whether or not the safe lanes have been calculated at least once.
Definition safelanes.c:327
void safelanes_destroy(void)
Shuts down the safelanes system.
Definition safelanes.c:218
static Edge * edge_stack
Definition safelanes.c:115
static void safelanes_initStacks(void)
Sets up the local faction/object stacks.
Definition safelanes.c:397
static void safelanes_initStacks_anchor(void)
Identifies anchor points: The universe graph (with in-system and 2-way-jump edges) could have many co...
Definition safelanes.c:556
static void safelanes_initPPl(void)
Sets up the PPl matrices appearing in the gradient formula.
Definition safelanes.c:743
void safelanes_init(void)
Initializes the safelanes system.
Definition safelanes.c:206
static int vertex_faction(int vi)
Return the vertex's owning faction (ID, not faction_stack index), or -1 if not applicable.
Definition safelanes.c:1008
static const vec2 * vertex_pos(int vi)
Return the vertex's coordinates within its system (by reference since our vec2's are fat).
Definition safelanes.c:1026
static FactionMask MASK_ANY_FACTION()
Return a mask matching any faction.
Definition safelanes.c:1052
static double safelanes_initialConductivity(int ei)
Returns the initial conductivity value (1/length) for edge ei. The live value is stored in the stiffn...
Definition safelanes.c:677
static cholmod_dense ** PPl
Definition safelanes.c:148
static void safelanes_initStacks_edge(void)
Sets up the local stacks with entry per edge. Faction stack must be set up.
Definition safelanes.c:466
static UnionFind tmp_sys_uf
Definition safelanes.c:138
static double safelanes_row_dot_row(cholmod_dense *A, cholmod_dense *B, int i, int j)
Return the i,j entry of A*B', or equivalently the dot product of row i of A with row j of B.
Definition safelanes.c:1143
static int cmp_key(const void *p1, const void *p2)
It's a qsort comparator. Set the cmp_key_ref pointer prior to use, or else.
Definition safelanes.c:978
static cholmod_triplet * stiff
Definition safelanes.c:141
static int safelanes_buildOneTurn(int iters_done)
Run a round of optimization. Return how many builds (upper bound) have to happen next turn.
Definition safelanes.c:365
static const double LAMBDA
Definition safelanes.c:54
uint32_t FactionMask
A set of lane-building factions, represented as a bitfield.
Definition safelanes.c:100
static int * lane_faction
Definition safelanes.c:120
static double ** presence_budget
Definition safelanes.c:125
static cholmod_dense * safelanes_sliceByPresence(const cholmod_dense *m, const double *sysPresence)
Construct the matrix-slice of m, selecting those rows where the corresponding presence value is posit...
Definition safelanes.c:1088
static const double JUMP_CONDUCTIVITY
Definition safelanes.c:55
void safelanes_recalculate(void)
Update the safe lane locations in response to the universe changing (e.g., diff applied).
Definition safelanes.c:296
static int * sys_to_first_edge
Definition safelanes.c:116
static void safelanes_initStacks_faction(void)
Sets up the local stacks with entry per faction.
Definition safelanes.c:515
static cholmod_common C
Definition safelanes.c:106
static void safelanes_initOptimizer(void)
Initializes resources used by lane optimization.
Definition safelanes.c:335
static void safelanes_initStacks_vertex(void)
Sets up the local stacks with entry per vertex (or per jump).
Definition safelanes.c:409
static cholmod_sparse * QtQ
Definition safelanes.c:143
static void safelanes_initFTilde(void)
Sets up the fluxes matrix f~.
Definition safelanes.c:726
SafeLane * safelanes_get(int faction, int standing, const StarSystem *system)
Gets a set of safelanes for a faction and system.
Definition safelanes.c:233
static cholmod_dense * ncholmod_ddmult(cholmod_dense *A, int transA, cholmod_dense *B)
Dense times dense matrix. Return A*B, or A'*B if transA is true.
Definition safelanes.c:1118
static int * sys_to_first_vertex
Definition safelanes.c:112
static FactionMask MASK_ONE_FACTION(int id)
A mask giving this faction (NOT faction_stack index) exclusive rights to build, if it's a lane-buildi...
Definition safelanes.c:1059
static double * cmp_key_ref
Definition safelanes.c:150
static int safelanes_triangleTooFlat(const vec2 *m, const vec2 *n, const vec2 *p, double lmn)
Return true if this triangle is so flat that lanes from point m to point n aren't allowed.
Definition safelanes.c:988
static double * tmp_edge_conduct
Definition safelanes.c:132
int Edge[2]
An edge is a pair of vertex indices.
Definition safelanes.c:89
static int * tmp_anchor_vertices
Definition safelanes.c:135
static Vertex * vertex_stack
Definition safelanes.c:108
static void safelanes_destroyOptimizer(void)
Frees resources used by lane optimization.
Definition safelanes.c:347
static Edge * tmp_jump_edges
Definition safelanes.c:129
static int * tmp_spob_indices
Definition safelanes.c:127
static cholmod_dense * utilde
Definition safelanes.c:147
VertexType
Object type: like SafeLaneLocType, but with Naev stack indexing in mind.
Definition safelanes.c:79
static void safelanes_initQtQ(void)
Sets up the (Q*)Q matrix.
Definition safelanes.c:697
static int FACTION_ID_TO_INDEX(int id)
Return the faction_stack index corresponding to a faction ID, or -1.
Definition safelanes.c:1042
StarSystem * system_getIndex(int id)
Get the system by its index.
Definition space.c:1038
StarSystem * system_getAll(void)
Gets an array (array.h) of all star systems.
Definition space.c:925
double system_getPresence(const StarSystem *sys, int faction)
Get the presence of a faction in a system.
Definition space.c:4537
Description of a lane-building faction.
Definition faction.c:64
double lane_base_cost
Definition faction.c:112
double lane_length_per_presence
Definition faction.c:110
int id
Definition safelanes.c:93
Describes a safe lane, patrolled by a faction, within a system.
Definition safelanes.h:29
int point_id[2]
Definition safelanes.h:32
int faction
Definition safelanes.h:30
SafeLaneLocType point_type[2]
Definition safelanes.h:31
double bonus
Definition space.h:81
double base
Definition space.h:80
int faction
Definition space.h:79
Represents a Space Object (SPOB), including and not limited to planets, stations, wormholes,...
Definition space.h:102
SpobPresence presence
Definition space.h:121
Disjoint set forest on {0, .., n-1}.
Definition union_find.h:7
Reference to a spob or jump point.
Definition safelanes.c:82
int index
Definition safelanes.c:85
int system
Definition safelanes.c:83
VertexType type
Definition safelanes.c:84
Represents a 2d vector.
Definition vec2.h:45
double y
Definition vec2.h:47
double x
Definition vec2.h:46
int * unionfind_findall(UnionFind *uf)
Returns a designated representative of each subset in an array (array.h).
Definition union_find.c:57
int unionfind_find(UnionFind *uf, int x)
Finds the designated representative of the subset containing x.
Definition union_find.c:48
void unionfind_init(UnionFind *uf, int n)
Creates a UnionFind structure on {0, ..., n}.
Definition union_find.c:14
void unionfind_union(UnionFind *uf, int x, int y)
Declares x and y to be in the same subset.
Definition union_find.c:34
void unionfind_free(UnionFind *uf)
Frees resources associated with uf.
Definition union_find.c:25