lightsolver-lib 0.6.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightsolver-lib
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Tools for developers using LightSolver's Cloud Platform
5
5
  Author-email: Matan Arkin <matan@lightsolver.com>
6
6
  Project-URL: Homepage, https://lightsolver.com
@@ -16,7 +16,7 @@ __all__ = ['calc_ising_energies',
16
16
  'calc_ising_energy_from_states',
17
17
  'XYModelParams',
18
18
  'coupling_matrix_xy',
19
- 'embed_coupmat',
20
- 'analyze_sol_XY',
21
- 'generate_animation',
19
+ 'embed_coupmat',
20
+ 'analyze_sol_XY',
21
+ 'generate_animation',
22
22
  ]
@@ -1,4 +1,6 @@
1
1
  import numpy
2
+ import plotly.graph_objects as go
3
+ import plotly.express as px
2
4
 
3
5
  def calc_ising_energies(problem_mat_ising: numpy.ndarray, states_ising: numpy.ndarray) -> numpy.ndarray:
4
6
  """
@@ -124,8 +126,8 @@ def best_energy_search_xy(state: numpy.ndarray, probmat_ising: numpy.ndarray) ->
124
126
  - `best_energy` - Energy of the best state
125
127
  """
126
128
  is_single_state = len(state.shape) == 1
127
- # if is_single_state:
128
- # state = state.reshape(-1, 1)
129
+ if is_single_state:
130
+ state = state.reshape(-1, 1)
129
131
 
130
132
  n_lasers = state.shape[0]
131
133
  n_states = 1 if is_single_state else state.shape[1]
@@ -257,21 +259,18 @@ class XYModelParams():
257
259
  ## Parameters for converting an Ising model to the XY model.
258
260
 
259
261
  ### Members:
260
- - `alphaR` - Reference lasers self coupling strength - default: 0.7
261
- - `alphaI` - Vortex lasers self coupling strength - default: 0.7
262
+ - `minSelfCoup` - Vortex lasers minimal allowed self coupling strength - default: 0.5
262
263
  - `coupAmp` - Coupling amplitude, default: 0.3
263
264
  - `exFieldCoup` - Coupling of external field, default : 0.3
264
265
  """
265
- def __init__(self, alphaR = 0.7, alphaI = 0.7, coupAmp = 0.3, exFieldCoup = 0.3):
266
- self.alphaI = alphaI
267
- self.alphaR = alphaR
266
+ def __init__(self, minSelfCoup = 0.7, coupAmp = 0.3, exFieldCoup = 0.3):
267
+ self.minSelfCoup = minSelfCoup
268
268
  self.coupAmp = coupAmp
269
269
  self.exFieldCoup = exFieldCoup
270
270
 
271
271
  def __eq__(self, other: object) -> bool:
272
272
  return isinstance(other, XYModelParams) and \
273
- self.alphaI == other.alphaI and \
274
- self.alphaR == other.alphaR and \
273
+ self.minSelfCoup == other.minSelfCoup and \
275
274
  self.coupAmp == other.coupAmp and \
276
275
  self.exFieldCoup == other.exFieldCoup
277
276
 
@@ -289,18 +288,243 @@ def coupling_matrix_xy(problem_matrix:numpy.ndarray, modelParamsXY:XYModelParams
289
288
  - `coupling_matrix` - The generated coupling matrix for the XY model
290
289
  """
291
290
  N = problem_matrix.shape[0]
292
- max_sum_rows = numpy.max(numpy.sum(numpy.abs(problem_matrix), axis=1))
293
-
294
- if external_field:
295
- coupling_matrix = numpy.zeros((N + 1, N + 1), dtype=numpy.float32)
296
- coupling_matrix[1:, 1:] = - (problem_matrix / max_sum_rows) * modelParamsXY.coupAmp
297
- numpy.fill_diagonal(coupling_matrix[1:, 1:], modelParamsXY.alphaI)
298
- coupling_matrix[1:, 0] = - modelParamsXY.exFieldCoup * (problem_matrix.diagonal() / max_sum_rows)
299
- coupling_matrix[0, 0] = modelParamsXY.alphaR
291
+ problem_matrix[numpy.diag_indices(N)] = 0.5 * numpy.diag(problem_matrix)
292
+ max_sum_rows = numpy.max(numpy.sum(numpy.abs(problem_matrix),axis=1))
293
+ row_sum = 1
300
294
 
295
+ if external_field:
296
+ external_field_half = numpy.diag(problem_matrix)
297
+ sum_ex_field_half = numpy.sum(numpy.abs(external_field_half))
298
+ M = max(sum_ex_field_half, max_sum_rows) / (1 - modelParamsXY.minSelfCoup)
299
+
300
+ coupling_matrix = numpy.zeros((N + 1, N + 1),dtype=numpy.float32)
301
+ coupling_matrix[1:,1:] = -(problem_matrix / M) * modelParamsXY.coupAmp
302
+ coupling_matrix[1:, 0] = -modelParamsXY.exFieldCoup * (external_field_half / M)
303
+ coupling_matrix[0, 1:] = -modelParamsXY.exFieldCoup * (external_field_half / M)
304
+ coupling_matrix[numpy.diag_indices(N + 1)] = row_sum - numpy.sum(numpy.abs(coupling_matrix), axis=1)
301
305
  else:
302
- coupling_matrix = numpy.zeros((N, N), dtype=numpy.float32)
303
- coupling_matrix = - (problem_matrix / max_sum_rows) * modelParamsXY.coupAmp
304
- numpy.fill_diagonal(coupling_matrix, modelParamsXY.alphaI)
306
+ M = max_sum_rows / (1 - modelParamsXY.minSelfCoup)
307
+
308
+ coupling_matrix = numpy.zeros((N, N), dtype=numpy.float32)
309
+ coupling_matrix = -(problem_matrix / M) * modelParamsXY.coupAmp
310
+ coupling_matrix[numpy.diag_indices(N)] = row_sum - numpy.sum(numpy.abs(coupling_matrix), axis=1)
311
+
312
+ assert (numpy.diag(coupling_matrix) >= modelParamsXY.minSelfCoup).all()
313
+ assert (numpy.sum(numpy.abs(coupling_matrix), axis=1) <= 1).all()
314
+
315
+ return coupling_matrix
316
+
317
+
318
+ def embed_coupmat(coupling_matrix, indices=None, total_laser_num=15):
319
+ '''
320
+ ## Embed a coupling matrix into a larger matrix of size total_laser_num x total_laser_num.
321
+
322
+ ### Parameters:
323
+ - coupling_matrix: (N x N) complex matrix to embed
324
+ - indices: list of indices at which to embed the matrix. If None, embed from index 0.
325
+ - total_laser_num: total size of the output matrix (default 15)
326
+
327
+ ### Returns:
328
+ - embedded: (total_laser_num x total_laser_num) matrix with the coupling matrix embedded
329
+ '''
330
+ N = coupling_matrix.shape[0]
331
+
332
+ embedded = 0.001 * numpy.eye(total_laser_num).astype(numpy.complex64) # small coupling for the rest of the lasers
333
+ # (value will remain for those not included in the problem)
334
+
335
+ if indices is None:
336
+ embedded[:N, :N] = coupling_matrix
337
+ else:
338
+ if len(indices) == 1:
339
+ start = indices[0]
340
+ embedded[start:start+N, start:start+N] = coupling_matrix
341
+ else:
342
+ indices = numpy.array(indices)
343
+ embedded[numpy.ix_(indices, indices)] = coupling_matrix
344
+
345
+ return embedded
346
+
347
+
348
+ def analyze_sol_XY(problem_matrix, phases_diffs):
349
+ '''
350
+ ## Function to analyze the solution from LPU and return binary state and its energy.
351
+ ## It recives the problem matrix and the phase difference solution and returns binary solution according to XY mapping.
352
+ ### Parameters:
353
+ - `problem_matrix` - The problem matrix (Ising model)
354
+ - `phases_diffs` - The phase differences solution from the LPU
355
+ ### Returns:
356
+ - `min_energy` - The minimum energy found
357
+ - `best_state` - The binary state corresponding to the minimum energy
358
+ '''
359
+ #
360
+ phases = (numpy.cumsum(phases_diffs, axis=1)) % (2 * numpy.pi)
361
+ N = phases.shape[1]
362
+
363
+ # Initialize:
364
+ energy = numpy.zeros(phases.shape[0])
365
+ x = numpy.zeros(shape=phases.shape)
366
+ correction = numpy.linspace(0, 0.1, N) # optional error sizes
367
+ correction = numpy.append(correction, -1*correction) # error may go both ways
368
+
369
+ def _to_scalar(value):
370
+ # best_energy_search_xy may return a scalar-like ndarray; normalize it.
371
+ return float(numpy.asarray(value).reshape(-1)[0])
372
+
373
+ for i in range(phases.shape[0]) :
305
374
 
306
- return coupling_matrix
375
+ min_bin_state, eng_new = best_energy_search_xy(numpy.exp(1J * phases[i, :]), numpy.real(problem_matrix))
376
+ min_bin_energy = _to_scalar(eng_new) if numpy.asarray(eng_new).size == 1 else eng_new
377
+
378
+ energies = [numpy.Inf]
379
+
380
+ for corr in correction:
381
+ correction_array = corr * numpy.arange(phases[i, :].shape[0])
382
+ new_state = phases[i, :] + correction_array
383
+ sol_new =(new_state[:])
384
+ laser_sol_new, eng_new = best_energy_search_xy(numpy.exp(1J * sol_new), numpy.real(problem_matrix))
385
+ eng_new_value = _to_scalar(eng_new) if numpy.asarray(eng_new).size == 1 else eng_new
386
+ if eng_new_value < min_bin_energy:
387
+ min_bin_state = laser_sol_new
388
+ min_bin_energy = eng_new_value
389
+ energies.append(eng_new_value)
390
+
391
+ corrected = numpy.argmin(energies)
392
+ x[i, :] = numpy.asarray(min_bin_state).reshape(-1)[:N]
393
+ energy[i] = numpy.min(energies)
394
+
395
+ min_ind = numpy.argmin(energy)
396
+
397
+ return numpy.min(energy), x[min_ind,:]
398
+
399
+
400
+ def generate_animation(outWave, save=False):
401
+ '''
402
+ ## Generate an animated polar plot using Plotly to visualize the evolution of laser states.
403
+ ### Parameters:
404
+ - `outWave` - A 2D numpy array where each row represents the state of lasers at a specific time/frame.
405
+ - `save` - Boolean flag to save the animation as an HTML file (default is False).
406
+ ### Returns:
407
+ - None (displays the animation and optionally saves it).
408
+ '''
409
+ # Number of frames in the animation
410
+ num_frames = outWave.shape[0]
411
+ color_scale = px.colors.qualitative.Set1[:outWave.shape[1]]
412
+
413
+ fig = go.Figure()
414
+
415
+ # Create text for the polar plot
416
+ N = outWave.shape[1]
417
+ text = [str(int(a)) for a in numpy.linspace(1, N, num=N, endpoint=True)]
418
+
419
+ # Create data for the initial frame
420
+ theta = numpy.angle(outWave[0, :]) * 180 / numpy.pi
421
+ radius = numpy.abs(outWave[0, :])
422
+ initial_frame_data = [go.Scatterpolar(r=radius, theta=theta, mode='markers+text', marker=dict(size=10, color=color_scale), text=text, textposition='top center', showlegend=False),
423
+ ]
424
+
425
+ # Create the layout
426
+ layout = go.Layout(
427
+ showlegend=False,
428
+ polar=dict(radialaxis=dict(visible=True)),
429
+ )
430
+
431
+ # Create the figure with the initial frame
432
+ fig.add_trace(initial_frame_data[0]) # Add the initial trace
433
+ fig.update_layout(layout)
434
+ fig.update_xaxes(title_text="iteration")
435
+ fig.update_layout(showlegend=True)
436
+
437
+ # Define animation frames
438
+ animation_frames = []
439
+
440
+ for i in range(1, num_frames):
441
+ theta = numpy.angle(outWave[i, :]) * 180 / numpy.pi
442
+ r_values = numpy.abs(outWave[i, :])
443
+ frame_data = [go.Scatterpolar(r=r_values, theta=theta, mode='markers+text', marker=dict(size=10, color=color_scale), name='polar plot', text=text, textposition='top center', showlegend=False),
444
+ ]
445
+ animation_frames.append(go.Frame(data=frame_data, name=f"frame_{i}"))
446
+
447
+ # Add frames to the figure
448
+ fig.frames = animation_frames
449
+
450
+ # Define animation options
451
+ animation_opts = dict(
452
+ frame=dict(duration=500, redraw=True),
453
+ fromcurrent=True
454
+ )
455
+
456
+ # Add play button
457
+ fig.update_layout(
458
+ updatemenus=[
459
+ {
460
+ 'buttons': [
461
+ {
462
+ 'args': [None, {'frame': {'duration': 500, 'redraw': True}, 'fromcurrent': True}],
463
+ 'label': 'Play',
464
+ 'method': 'animate'
465
+ },
466
+ {
467
+ 'args': [[None], {'frame': {'duration': 0, 'redraw': True}, 'mode': 'immediate', 'transition': {'duration': 0}}],
468
+ 'label': 'Pause',
469
+ 'method': 'animate'
470
+ }
471
+ ],
472
+ 'direction': 'left',
473
+ 'pad': {'r': 10, 't': 87},
474
+ 'showactive': False,
475
+ 'type': 'buttons',
476
+ 'x': 0.1,
477
+ 'xanchor': 'right',
478
+ 'y': 0.2,
479
+ 'yanchor': 'top'
480
+ }
481
+ ],
482
+ template= "seaborn" # "plotly_dark"
483
+ )
484
+
485
+ # Define slider steps
486
+ steps = []
487
+ for i in range(num_frames):
488
+ step = dict(
489
+ method="animate",
490
+ args=[
491
+ [f"frame_{i}"],
492
+ dict(
493
+ mode="immediate",
494
+ frame=dict(duration=300, redraw=True),
495
+ transition=dict(duration=0)
496
+ ),
497
+ ],
498
+ label=str(i)
499
+ )
500
+ steps.append(step)
501
+
502
+ # Add slider to layout
503
+ fig.update_layout(
504
+ sliders=[dict(
505
+ active=0,
506
+ currentvalue={"prefix": "Frame: "},
507
+ pad={"t": 50},
508
+ steps=steps
509
+ )]
510
+ )
511
+
512
+ fig.update_layout(
513
+ polar=dict(
514
+ radialaxis=dict(visible=True)
515
+ ),
516
+ xaxis=dict(domain=[0.65, 1]),
517
+ yaxis=dict(domain=[0.65, 1]),
518
+ yaxis2=dict(domain=[0, 0.35], anchor='x', overlaying='y', side='right')
519
+ )
520
+
521
+ fig.update_layout(
522
+ autosize=False,
523
+ width=1200,
524
+ height=800,
525
+ )
526
+ # Display & save the figure
527
+ fig.show()
528
+ # Save the figure as an HTML file
529
+ if save:
530
+ fig.write_html('polar_animation.html')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightsolver-lib
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Tools for developers using LightSolver's Cloud Platform
5
5
  Author-email: Matan Arkin <matan@lightsolver.com>
6
6
  Project-URL: Homepage, https://lightsolver.com
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lightsolver-lib"
7
- version = "0.6.0"
7
+ version = "0.8.0"
8
8
  description = "Tools for developers using LightSolver's Cloud Platform"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes